Search code examples
python-requestsicinga2

Backslashes are not correctly encoded


I am at wits end.

My scenario is that I am using python requests to interact with Icinga2 API and I am trying to schedule a downtime. So I know how that is supposed to works and it works most of time. But unfortunately I am totally out of luck when the Icinga2 service I try to set a downtime to has name with a backslash in it.

My test environment:

  • Icinga2 2.9.0
  • Python 3.6.8 / Python 3.8.11
  • requests 2.27.0

Prerequisite: Create a host in Icinga. Create a service in Icinga with a "\"-character.

Python Code for Reproduction:

import requests
session = requests.Session()
session.hooks = {
             "response": lambda r, *args, **kwargs: r.raise_for_status()
         }

session.headers.update(
             {
                 "Accept": "application/json",
                 "Content-Type": "application/json",
                 "cache-control": "no-cache",
             }
         )

session.auth = requests.auth.HTTPBasicAuth("user","password")
url = "https://icinga2-server.com:5665/v1/actions/schedule-downtime"

body = {
   'comment': 'Downtime',
    'author': ('icingaadmin',),
    'start_time': 1605196800.0,
    'filter': 'host.name==\"HOSTNAME\" && service.name==\"SERVICE\\NAME\"',
    'end_time': 1605286800.0,
    'fixed': True,
    'type': 'Service'}

session.post(url, json=body, verify=False)

Result:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/root/.pyenv/versions/3.8.11/lib/python3.8/site-packages/requests/sessions.py", line 590, in post
    return self.request('POST', url, data=data, json=json, **kwargs)
  File "/root/.pyenv/versions/3.8.11/lib/python3.8/site-packages/requests/sessions.py", line 542, in request
    resp = self.send(prep, **send_kwargs)
  File "/root/.pyenv/versions/3.8.11/lib/python3.8/site-packages/requests/sessions.py", line 662, in send
    r = dispatch_hook('response', hooks, r, **kwargs)
  File "/root/.pyenv/versions/3.8.11/lib/python3.8/site-packages/requests/hooks.py", line 31, in dispatch_hook
    _hook_data = hook(hook_data, **kwargs)
  File "<stdin>", line 2, in <lambda>
  File "/root/.pyenv/versions/3.8.11/lib/python3.8/site-packages/requests/models.py", line 953, in raise_for_status
    raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 404 Client Error: Not Found for url: https://icinga2-server.com:5665/v1/actions/schedule-downtime

I know very well that this error message indicates that Icinga2 could not find/match a service. But executing the command via curl is clearly working for me and I get a proper scheduled downtime!

CURL Request:

curl -k -s -u user:password -H 'Accept: application/json'  -X POST 'https://icinga2-server.com:5665/v1/actions/schedule-downtime'  -d '{"comment": "Downtime", "author": ["icingaadmin"], "start_time": 1605196800.0, "filter": "host.name==\"MSSQLSERVER\" && service.name==\"MSSQLSERVER\\\\INSTANCE2\"", "end_time": 1605286800.0, "fixed": true, "type": "Service"}'

CURL Answer (SUCCESS):

{"results":[{"code":200.0,"legacy_id":8.0,"name":"MSSQLSERVER!MSSQLSERVER\\INSTANCE2!137c9ef9-3150-4e57-ba0b-a22ddc6611d4","status":"Successfully scheduled downtime 'MSSQLSERVER!MSSQLSERVER\\INSTANCE2!137c9ef9-3150-4e57-ba0b-a22ddc6611d4' for object 'MSSQLSERVER!MSSQLSERVER\\INSTANCE2'."}]}

Alternate approaches that did not help:

session.post(url, data=json.dumps(body), verify=False)

string_body = json.dumps(body)
session.post(url, data=string_body, verify=False)


Solution

  • You can try adding an r to the front of the string after the filter key. Also, I think this closed parenthesis is unnecessary.

    Python treats backslashes in strings as special characters, adding an 'r' before the string make it not treat them as special characters, and instead as backslashes 'filter': r'host.name=="HOSTNAME" && service.name=="SERVICE\NAME"'),

    https://www.journaldev.com/23598/python-raw-string

    body1 = {
       'comment': 'Downtime',
        'author': ('icingaadmin',),
        'start_time': 1605196800.0,
        'filter': 'host.name==\"HOSTNAME\" && service.name==\"SERVICE\\NAME\"',
        'end_time': 1605286800.0,
        'fixed': True,
        'type': 'Service'}
    
    body2 = {
       'comment': 'Downtime',
        'author': ('icingaadmin',),
        'start_time': 1605196800.0,
        'filter': r'host.name==\"HOSTNAME\" && service.name==\"SERVICE\\NAME\"',
        'end_time': 1605286800.0,
        'fixed': True,
        'type': 'Service'}
    
    body1==body2 -> False
    
    body1['filter'] = 'host.name=="HOSTNAME" && service.name=="SERVICE\\NAME"'
    
    body2['filter'] = 'host.name==\\"HOSTNAME\\" && service.name==\\"SERVICE\\\\NAME\\"'