From 985db970bbafac8b7f2bfc4b94441cbd49f202b1 Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Tue, 16 Sep 2025 15:17:28 +0200 Subject: [PATCH] Allow to set extra headers in HTTP responses 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). --- lib/remote/apilistener.cpp | 26 ++++++++++++++++++++++++++ lib/remote/apilistener.hpp | 1 + lib/remote/apilistener.ti | 1 + lib/remote/httpserverconnection.cpp | 10 ++++++++++ 4 files changed, 38 insertions(+) diff --git a/lib/remote/apilistener.cpp b/lib/remote/apilistener.cpp index 59e537f76..774e9efb1 100644 --- a/lib/remote/apilistener.cpp +++ b/lib/remote/apilistener.cpp @@ -8,6 +8,7 @@ #include "remote/apifunction.hpp" #include "remote/configpackageutility.hpp" #include "remote/configobjectutility.hpp" +#include "remote/httputility.hpp" #include "base/atomic-file.hpp" #include "base/convert.hpp" #include "base/defer.hpp" @@ -2008,6 +2009,31 @@ void ApiListener::ValidateTlsHandshakeTimeout(const Lazy& lvalue, const BOOST_THROW_EXCEPTION(ValidationError(this, { "tls_handshake_timeout" }, "Value must be greater than 0.")); } +void ApiListener::ValidateHttpResponseHeaders(const Lazy& lvalue, const ValidationUtils& utils) +{ + ObjectImpl::ValidateHttpResponseHeaders(lvalue, utils); + + if (Dictionary::Ptr headers = lvalue(); headers) { + ObjectLock lock(headers); + for (auto& [name, value] : headers) { + if (!HttpUtility::IsValidHeaderName(name.GetData())) { + BOOST_THROW_EXCEPTION(ValidationError(this, { "http_response_headers", name }, + "Header name is invalid.")); + } + + if (!value.IsString()) { + BOOST_THROW_EXCEPTION(ValidationError(this, { "http_response_headers", name }, + "Header value must be a string.")); + } + + if (!HttpUtility::IsValidHeaderValue(value.Get().GetData())) { + BOOST_THROW_EXCEPTION(ValidationError(this, { "http_response_headers", name }, + "Header value is invalid.")); + } + } + } +} + bool ApiListener::IsHACluster() { Zone::Ptr zone = Zone::GetLocalZone(); diff --git a/lib/remote/apilistener.hpp b/lib/remote/apilistener.hpp index 866af7614..7b98db964 100644 --- a/lib/remote/apilistener.hpp +++ b/lib/remote/apilistener.hpp @@ -170,6 +170,7 @@ public: protected: void ValidateTlsProtocolmin(const Lazy& lvalue, const ValidationUtils& utils) override; void ValidateTlsHandshakeTimeout(const Lazy& lvalue, const ValidationUtils& utils) override; + void ValidateHttpResponseHeaders(const Lazy& lvalue, const ValidationUtils& utils) override; private: Shared::Ptr m_SSLContext; diff --git a/lib/remote/apilistener.ti b/lib/remote/apilistener.ti index 55ec749c5..8c9fdab40 100644 --- a/lib/remote/apilistener.ti +++ b/lib/remote/apilistener.ti @@ -55,6 +55,7 @@ class ApiListener : ConfigObject [config, deprecated] String access_control_allow_headers; [config, deprecated] String access_control_allow_methods; + [config] Dictionary::Ptr http_response_headers; [state, no_user_modify] Timestamp log_message_timestamp; diff --git a/lib/remote/httpserverconnection.cpp b/lib/remote/httpserverconnection.cpp index 39fa2d79d..6ae147452 100644 --- a/lib/remote/httpserverconnection.cpp +++ b/lib/remote/httpserverconnection.cpp @@ -466,6 +466,16 @@ void HttpServerConnection::ProcessMessages(boost::asio::yield_context yc) request.Parser().body_limit(-1); response.set(http::field::server, l_ServerHeader); + if (auto listener (ApiListener::GetInstance()); listener) { + if (Dictionary::Ptr headers = listener->GetHttpResponseHeaders(); headers) { + ObjectLock lock(headers); + for (auto& [header, value] : headers) { + if (value.IsString()) { + response.set(header, value.Get()); + } + } + } + } if (!EnsureValidHeaders(buf, request, response, m_ShuttingDown, yc)) { break;