Skip to content

Conversation

@julianbrost
Copy link
Contributor

@julianbrost julianbrost commented Sep 23, 2025

Use case: Allow settings headers like Strict-Transport-Security if one likes. How this headers would benefit the Icinga 2 API is questionable, but there are security scanners that see HTTPS and complain about the missing header, so this gives an easy way to make them happy (with this probably being the only benefit).

Implementation detail: I've added the loop after the existing line to set the Server: header, which allows overwriting the compiled-in default. Something like this was requested in #10466, this this PR allows to do this as well. Note however that this won't hide the Icinga 2 version completely, as already explained in #10466 (comment).

Tests

General functionality

Adding the following configuration to the ApiListener object defined in /etc/icinga2/features-available/api.conf:

http_response_headers["Strict-Transport-Security"] = "max-age=42"

Results in the following HTTP response:

$ curl -ki https://localhost:5665/
HTTP/1.1 401 Unauthorized
Server: Icinga/v2.15.0-193-g93df206c3
Strict-Transport-Security: max-age=42
WWW-Authenticate: Basic realm="Icinga 2"
Connection: close
Content-Type: text/html
Content-Length: 58

<h1>Unauthorized. Please check your user credentials.</h1>

Config validation (allowed cases)

While I'd love to be proven wrong with what's allowed in HTTP headers, in particular their names, curl, Firefox, and Chromium are all happy to parse responses with abominations of headers like from the test cases:

http_response_headers["X-Empty"] = ""                                               
http_response_headers["Content-Security-Policy"] = "default-src 'self'; img-src 'self' example.com"
http_response_headers["Strict-Transport-Security"] = "max-age=31536000"
http_response_headers["lowercase-is-fine-too"] = "spaces are allowed"
http_response_headers["everything-from-the-spec-!#$%&'*+-.^_`|~0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"] = "tabs\tare\tallowed"
http_response_headers["-this-seems-to-be-allowed-too-"] = "non-ascii-is-allowed äöü"
http_response_headers["~http~is~weird~"] = "yes, indeed"

curl

$ curl -ki https://localhost:5665/
HTTP/1.1 401 Unauthorized
Server: Icinga/v2.15.0-193-g93df206c3
-this-seems-to-be-allowed-too-: non-ascii-is-allowed äöü
Content-Security-Policy: default-src 'self'; img-src 'self' example.com
Strict-Transport-Security: max-age=31536000
X-Empty: 
everything-from-the-spec-!#$%&'*+-.^_`|~0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ: tabs	are	allowed
lowercase-is-fine-too: spaces are allowed
~http~is~weird~: yes, indeed
WWW-Authenticate: Basic realm="Icinga 2"
Connection: close
Content-Type: text/html
Content-Length: 58

<h1>Unauthorized. Please check your user credentials.</h1>

Firefox

image

Chromium

image

Config validation (forbidden cases)

Invalid headers are rejected as they should be:

[2025-11-28 11:31:45 +0100] critical/config: Error: Validation failed for object 'api' of type 'ApiListener'; Attribute 'http_response_headers' -> '': Header name is invalid.
Location: in /data/etc/icinga2/features-enabled/api.conf: 16:2-16:36
/data/etc/icinga2/features-enabled/api.conf(14):  #cipher_list = "TLS_AES_256_GCM_SHA384"
/data/etc/icinga2/features-enabled/api.conf(15): 
/data/etc/icinga2/features-enabled/api.conf(16):  http_response_headers[""] = "empty"
                                                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/data/etc/icinga2/features-enabled/api.conf(17):  #http_response_headers["X-newline"] = "hello\nworld"
/data/etc/icinga2/features-enabled/api.conf(18):  #http_response_headers["X-dict"] = {foo=42}
[2025-11-28 11:31:45 +0100] critical/config: 1 error
[2025-11-28 11:32:02 +0100] critical/config: Error: Validation failed for object 'api' of type 'ApiListener'; Attribute 'http_response_headers' -> 'X-newline': Header value is invalid.
Location: in /data/etc/icinga2/features-enabled/api.conf: 17:2-17:52
/data/etc/icinga2/features-enabled/api.conf(15): 
/data/etc/icinga2/features-enabled/api.conf(16):  #http_response_headers[""] = "empty"
/data/etc/icinga2/features-enabled/api.conf(17):  http_response_headers["X-newline"] = "hello\nworld"
                                                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/data/etc/icinga2/features-enabled/api.conf(18):  #http_response_headers["X-dict"] = {foo=42}
/data/etc/icinga2/features-enabled/api.conf(19):  #http_response_headers["X-list"] = [1,3,3,7]
[2025-11-28 11:32:02 +0100] critical/config: 1 error
[2025-11-28 11:32:24 +0100] critical/config: Error: Validation failed for object 'api' of type 'ApiListener'; Attribute 'http_response_headers' -> 'X-dict': Header value must be a string.
Location: in /data/etc/icinga2/features-enabled/api.conf: 18:2-18:43
/data/etc/icinga2/features-enabled/api.conf(16):  #http_response_headers[""] = "empty"
/data/etc/icinga2/features-enabled/api.conf(17):  #http_response_headers["X-newline"] = "hello\nworld"
/data/etc/icinga2/features-enabled/api.conf(18):  http_response_headers["X-dict"] = {foo=42}
                                                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/data/etc/icinga2/features-enabled/api.conf(19):  #http_response_headers["X-list"] = [1,3,3,7]
/data/etc/icinga2/features-enabled/api.conf(20):  #http_response_headers["X-func"] = {{ }}
[2025-11-28 11:32:24 +0100] critical/config: 1 error
[2025-11-28 11:32:41 +0100] critical/config: Error: Validation failed for object 'api' of type 'ApiListener'; Attribute 'http_response_headers' -> 'X-list': Header value must be a string.
Location: in /data/etc/icinga2/features-enabled/api.conf: 19:2-19:44
/data/etc/icinga2/features-enabled/api.conf(17):  #http_response_headers["X-newline"] = "hello\nworld"
/data/etc/icinga2/features-enabled/api.conf(18):  #http_response_headers["X-dict"] = {foo=42}
/data/etc/icinga2/features-enabled/api.conf(19):  http_response_headers["X-list"] = [1,3,3,7]
                                                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/data/etc/icinga2/features-enabled/api.conf(20):  #http_response_headers["X-func"] = {{ }}
/data/etc/icinga2/features-enabled/api.conf(21): 
[2025-11-28 11:32:41 +0100] critical/config: 1 error
[2025-11-28 11:32:59 +0100] critical/config: Error: Validation failed for object 'api' of type 'ApiListener'; Attribute 'http_response_headers' -> 'X-func': Header value must be a string.
Location: in /data/etc/icinga2/features-enabled/api.conf: 20:2-20:40
/data/etc/icinga2/features-enabled/api.conf(18):  #http_response_headers["X-dict"] = {foo=42}
/data/etc/icinga2/features-enabled/api.conf(19):  #http_response_headers["X-list"] = [1,3,3,7]
/data/etc/icinga2/features-enabled/api.conf(20):  http_response_headers["X-func"] = {{ }}
                                                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/data/etc/icinga2/features-enabled/api.conf(21): 
/data/etc/icinga2/features-enabled/api.conf(22): 
[2025-11-28 11:32:59 +0100] critical/config: 1 error

ref/IP/61034
ref/IP/61442
refs #10466

@julianbrost julianbrost added enhancement New feature or request area/api REST API labels Sep 23, 2025
@cla-bot cla-bot bot added the cla/signed label Sep 23, 2025
@julianbrost
Copy link
Contributor Author

Help wanted: who wants to dig through some standards to find a reasonable allow list for characters in header names and values?

@Al2Klimov
Copy link
Member

@julianbrost julianbrost force-pushed the http-response-extra-headers branch from d0de564 to 93df206 Compare November 28, 2025 10:17
@julianbrost julianbrost requested a review from yhabteab November 28, 2025 10:35
@julianbrost julianbrost added this to the 2.16.0 milestone Nov 28, 2025
@julianbrost julianbrost force-pushed the http-response-extra-headers branch 2 times, most recently from 4f5f295 to ac7a8db Compare November 28, 2025 14:37
@julianbrost julianbrost requested a review from yhabteab November 28, 2025 14:52
Copy link
Member

@yhabteab yhabteab left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks fine to me aprt from the below nit-picking.

Use case: Allow settings headers like Strict-Transport-Security if one likes.
How this headers would benefit the Icinga 2 API is questionable, but there are
security scanners that see HTTPS and complain about it, so this gives an easy
way to make them happy (with this probably being the only benefit).
@julianbrost julianbrost force-pushed the http-response-extra-headers branch from ac7a8db to 985db97 Compare November 28, 2025 15:19
@julianbrost julianbrost requested a review from yhabteab November 28, 2025 15:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/api REST API cla/signed enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants