summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--doc/sphinx/arm/agent.rst9
-rw-r--r--doc/sphinx/arm/congestion-handling.rst2
-rw-r--r--doc/sphinx/arm/ctrl-channel.rst5
-rw-r--r--doc/sphinx/arm/dhcp4-srv.rst9
-rw-r--r--doc/sphinx/arm/dhcp6-srv.rst9
-rw-r--r--doc/sphinx/arm/hooks-lease-query.rst2
-rw-r--r--doc/sphinx/arm/quickstart.rst2
-rw-r--r--src/bin/agent/ca_response_creator.cc35
-rw-r--r--src/bin/agent/tests/ca_response_creator_unittests.cc162
9 files changed, 232 insertions, 3 deletions
diff --git a/doc/sphinx/arm/agent.rst b/doc/sphinx/arm/agent.rst
index f69696d1af..4862d46a92 100644
--- a/doc/sphinx/arm/agent.rst
+++ b/doc/sphinx/arm/agent.rst
@@ -56,6 +56,12 @@ The following example demonstrates the basic CA configuration.
"Control-agent": {
"http-host": "10.20.30.40",
"http-port": 8000,
+ "http-headers": [
+ {
+ "name": "Strict-Transport-Security",
+ "value": "max-age=31536000"
+ }
+ ],
"trust-anchor": "/path/to/the/ca-cert.pem",
"cert-file": "/path/to/the/agent-cert.pem",
"key-file": "/path/to/the/agent-key.pem",
@@ -114,6 +120,9 @@ different from the HA peer URLs, which are strictly
for internal HA traffic between the peers. User commands should
still be sent via the CA.
+Since Kea 1.7.5 the ``http-headers`` parameter specifies a list of
+extra HTTP headers to add to HTTP responses.
+
The ``trust-anchor``, ``cert-file``, ``key-file``, and ``cert-required``
parameters specify the TLS setup for HTTP, i.e. HTTPS. If these parameters
are not specified, HTTP is used. The TLS/HTTPS support in Kea is
diff --git a/doc/sphinx/arm/congestion-handling.rst b/doc/sphinx/arm/congestion-handling.rst
index 68b7913036..0e54ebca0f 100644
--- a/doc/sphinx/arm/congestion-handling.rst
+++ b/doc/sphinx/arm/congestion-handling.rst
@@ -22,7 +22,7 @@ Congestion typically occurs when there is a network event that causes overly lar
numbers of clients to simultaneously need leases, such as recovery after
a network outage. In a well-planned deployment, the number and capacity of servers is
matched to the maximum expected client load. If the load is routinely too
-heavy, then the deployment needs to be re-evaluated.
+heavy, then the deployment needs to be re-evaluated.
The goal of congestion handling is to help servers mitigate the peak in
traffic by fulfilling as many of the most relevant requests as possible
diff --git a/doc/sphinx/arm/ctrl-channel.rst b/doc/sphinx/arm/ctrl-channel.rst
index 3079069287..2a1c2f6f4b 100644
--- a/doc/sphinx/arm/ctrl-channel.rst
+++ b/doc/sphinx/arm/ctrl-channel.rst
@@ -207,6 +207,11 @@ depends on the specific command.
}
}
+.. note::
+
+ Since Kea 2.7.5 it is possible to specify extra HTTP headers which
+ are added to HTTP responses.
+
.. _ctrl-channel-control-agent-command-response-format:
Control Agent Command Response Format
diff --git a/doc/sphinx/arm/dhcp4-srv.rst b/doc/sphinx/arm/dhcp4-srv.rst
index bbb428e081..50283a2408 100644
--- a/doc/sphinx/arm/dhcp4-srv.rst
+++ b/doc/sphinx/arm/dhcp4-srv.rst
@@ -7777,6 +7777,9 @@ TLS is required). The ``socket-address`` (default ``127.0.0.1``) and
``socket-port`` (default 8000) specify an IP address and port to which
the HTTP service will be bound.
+Since Kea 1.7.5 the ``http-headers`` parameter specifies a list of
+extra HTTP headers to add to HTTP responses.
+
The ``trust-anchor``, ``cert-file``, ``key-file``, and ``cert-required``
parameters specify the TLS setup for HTTP, i.e. HTTPS. If these parameters
are not specified, HTTP is used. The TLS/HTTPS support in Kea is
@@ -7832,6 +7835,12 @@ to detect configuration errors as soon as possible.
"socket-type": "https",
"socket-address": "10.20.30.40",
"socket-port": 8004,
+ "http-headers": [
+ {
+ "name": "Strict-Transport-Security",
+ "value": "max-age=31536000"
+ }
+ ],
"trust-anchor": "/path/to/the/ca-cert.pem",
"cert-file": "/path/to/the/agent-cert.pem",
"key-file": "/path/to/the/agent-key.pem",
diff --git a/doc/sphinx/arm/dhcp6-srv.rst b/doc/sphinx/arm/dhcp6-srv.rst
index 4ea40f4321..14c89c70eb 100644
--- a/doc/sphinx/arm/dhcp6-srv.rst
+++ b/doc/sphinx/arm/dhcp6-srv.rst
@@ -7591,6 +7591,9 @@ TLS is required). The ``socket-address`` (default ``::1``) and
``socket-port`` (default 8000) specify an IP address and port to which
the HTTP service will be bound.
+Since Kea 1.7.5 the ``http-headers`` parameter specifies a list of
+extra HTTP headers to add to HTTP responses.
+
The ``trust-anchor``, ``cert-file``, ``key-file``, and ``cert-required``
parameters specify the TLS setup for HTTP, i.e. HTTPS. If these parameters
are not specified, HTTP is used. The TLS/HTTPS support in Kea is
@@ -7646,6 +7649,12 @@ to detect configuration errors as soon as possible.
"socket-type": "https",
"socket-address": "2010:30:40::50",
"socket-port": 8005,
+ "http-headers": [
+ {
+ "name": "Strict-Transport-Security",
+ "value": "max-age=31536000"
+ }
+ ],
"trust-anchor": "/path/to/the/ca-cert.pem",
"cert-file": "/path/to/the/agent-cert.pem",
"key-file": "/path/to/the/agent-key.pem",
diff --git a/doc/sphinx/arm/hooks-lease-query.rst b/doc/sphinx/arm/hooks-lease-query.rst
index ff8f223236..c7ddbe0023 100644
--- a/doc/sphinx/arm/hooks-lease-query.rst
+++ b/doc/sphinx/arm/hooks-lease-query.rst
@@ -649,7 +649,7 @@ and includes either the count of leases updated or the nature of the failure:
This ``extended-info6-upgrade`` command must be called when:
-- the database schema was upgraded from 2.4.1 or older version.
+- the database schema was upgraded from 2.4.1 or older version.
``extended-info4-upgrade`` can be used when upgrading from 2.3.8 or older version.
- Bulk Leasequery was not enabled; tables are maintained only when v6 BLQ is
diff --git a/doc/sphinx/arm/quickstart.rst b/doc/sphinx/arm/quickstart.rst
index 9ece8adf34..6cabbce77e 100644
--- a/doc/sphinx/arm/quickstart.rst
+++ b/doc/sphinx/arm/quickstart.rst
@@ -17,7 +17,7 @@ Quick Start Guide Using tarball
:ref:`build-requirements` for details.
2. Download the Kea source tarball from
- `the main isc.org downloads page <https://www.isc.org/download/>`__,
+ `the main isc.org downloads page <https://www.isc.org/download/>`__,
`the ISC downloads site <https://downloads.isc.org/isc/kea/>`__, or
`the ISC Cloudsmith page <https://cloudsmith.io/~isc/packages/?q=format%3Araw>`__.
diff --git a/src/bin/agent/ca_response_creator.cc b/src/bin/agent/ca_response_creator.cc
index ffe1d5d3ef..ddc010acbf 100644
--- a/src/bin/agent/ca_response_creator.cc
+++ b/src/bin/agent/ca_response_creator.cc
@@ -81,12 +81,30 @@ createStockHttpResponseInternal(const HttpRequestPtr& request,
}
// This will generate the response holding JSON content.
HttpResponsePtr response(new HttpResponseJson(http_version, status_code));
+ // See the comment below.
+ boost::shared_ptr<CtrlAgentController> controller =
+ boost::dynamic_pointer_cast<CtrlAgentController>(CtrlAgentController::instance());
+ if (controller) {
+ CtrlAgentProcessPtr process = controller->getCtrlAgentProcess();
+ if (process) {
+ CtrlAgentCfgMgrPtr cfgmgr = process->getCtrlAgentCfgMgr();
+ if (cfgmgr) {
+ CtrlAgentCfgContextPtr ctx = cfgmgr->getCtrlAgentCfgContext();
+ if (ctx) {
+ copyHttpHeaders(ctx->getHttpHeaders(), *response);
+ }
+ }
+ }
+ }
return (response);
}
HttpResponsePtr
CtrlAgentResponseCreator::
createDynamicHttpResponse(HttpRequestPtr request) {
+ // Extra headers.
+ CfgHttpHeaders headers;
+
// First check authentication.
HttpResponseJsonPtr http_response;
@@ -106,6 +124,7 @@ createDynamicHttpResponse(HttpRequestPtr request) {
if (cfgmgr) {
ctx = cfgmgr->getCtrlAgentCfgContext();
if (ctx) {
+ headers = ctx->getHttpHeaders();
const HttpAuthConfigPtr& auth = ctx->getAuthConfig();
if (auth) {
// Check authentication.
@@ -116,6 +135,13 @@ createDynamicHttpResponse(HttpRequestPtr request) {
}
}
+ // Pass extra headers to the hook.
+ bool auth_failed = false;
+ if (http_response) {
+ auth_failed = true;
+ copyHttpHeaders(headers, *http_response);
+ }
+
// Callout point for "http_auth".
bool reset_handle = false;
if (HooksManager::calloutsPresent(Hooks.hook_index_http_auth_)) {
@@ -142,6 +168,15 @@ createDynamicHttpResponse(HttpRequestPtr request) {
// The basic HTTP authentication check or a callout failed and
// left a response.
if (http_response) {
+ // Avoid to copy extra headers twice even this should not be required.
+ if (!auth_failed && !headers.empty()) {
+ copyHttpHeaders(headers, *http_response);
+ if (http_response->isFinalized()) {
+ // Argh! The response was already finalized.
+ http_response->reset();
+ http_response->finalize();
+ }
+ }
return (http_response);
}
diff --git a/src/bin/agent/tests/ca_response_creator_unittests.cc b/src/bin/agent/tests/ca_response_creator_unittests.cc
index df394deb9d..e45c2f363b 100644
--- a/src/bin/agent/tests/ca_response_creator_unittests.cc
+++ b/src/bin/agent/tests/ca_response_creator_unittests.cc
@@ -152,6 +152,16 @@ public:
return (ctx);
}
+ /// @brief Convert header vector to header map.
+ static std::map<std::string, std::string>
+ headers2map(std::vector<HttpHeaderContext> headers) {
+ std::map<std::string, std::string> result;
+ for (const auto& header : headers) {
+ result[header.name_] = header.value_;
+ }
+ return (result);
+ }
+
/// @brief Instance of the response creator.
CtrlAgentResponseCreator response_creator_;
@@ -191,6 +201,31 @@ TEST_F(CtrlAgentResponseCreatorTest, createStockHttpResponseCorrectVersion) {
testStockResponse(HttpStatusCode::NO_CONTENT, "HTTP/1.1 204 No Content");
}
+// Test that the server responds with extra headers for an error response.
+TEST_F(CtrlAgentResponseCreatorTest, createStockHttpResponseHeaders) {
+ CtrlAgentCfgContextPtr ctx = getCtrlAgentCfgContext();
+ ASSERT_TRUE(ctx);
+ // Add a STS header.
+ CfgHttpHeader hsts("Strict-Transport-Security", "max-age=31536000");
+ CfgHttpHeaders headers;
+ headers.push_back(hsts);
+ // Add a random header.
+ CfgHttpHeader foobar("Foo", "bar");
+ headers.push_back(foobar);
+ ctx->setHttpHeaders(headers);
+ // Set request.
+ request_->context()->http_version_major_ = 1;
+ request_->context()->http_version_minor_ = 1;
+ const HttpStatusCode& status_code = HttpStatusCode::NO_CONTENT;
+ HttpResponsePtr response;
+ response = response_creator_.createStockHttpResponse(request_, status_code);
+ ASSERT_TRUE(response);
+ // Check that the two extra headers are in the response.
+ auto got = headers2map(response->context()->headers_);
+ EXPECT_EQ("max-age=31536000", got["Strict-Transport-Security"]);
+ EXPECT_EQ("bar", got["Foo"]);
+}
+
// Test successful server response when the client specifies valid command.
TEST_F(CtrlAgentResponseCreatorTest, createDynamicHttpResponse) {
setBasicContext(request_);
@@ -219,6 +254,39 @@ TEST_F(CtrlAgentResponseCreatorTest, createDynamicHttpResponse) {
std::string::npos);
}
+// Test that the server responds with extra headers for a command response.
+TEST_F(CtrlAgentResponseCreatorTest, createDynamicHttpResponseHeaders) {
+ CtrlAgentCfgContextPtr ctx = getCtrlAgentCfgContext();
+ ASSERT_TRUE(ctx);
+ // Add a STS header.
+ CfgHttpHeader hsts("Strict-Transport-Security", "max-age=31536000");
+ CfgHttpHeaders headers;
+ headers.push_back(hsts);
+ // Add a random header.
+ CfgHttpHeader foobar("Foo", "bar");
+ headers.push_back(foobar);
+ ctx->setHttpHeaders(headers);
+
+ // From previous test.
+ setBasicContext(request_);
+
+ // Body: "foo" command has been registered in the test fixture constructor.
+ request_->context()->body_ = "{ \"command\": \"foo\" }";
+
+ // All requests must be finalized before they can be processed.
+ ASSERT_NO_THROW(request_->finalize());
+
+ // Create response from the request.
+ HttpResponsePtr response;
+ ASSERT_NO_THROW(response = response_creator_.createHttpResponse(request_));
+ ASSERT_TRUE(response);
+
+ // Check that the two extra headers are in the response.
+ auto got = headers2map(response->context()->headers_);
+ EXPECT_EQ("max-age=31536000", got["Strict-Transport-Security"]);
+ EXPECT_EQ("bar", got["Foo"]);
+}
+
// This test verifies that Internal Server Error is returned when invalid C++
// request type is used. This is considered an error in the server logic.
TEST_F(CtrlAgentResponseCreatorTest, createDynamicHttpResponseInvalidType) {
@@ -282,6 +350,44 @@ TEST_F(CtrlAgentResponseCreatorTest, noAuth) {
EXPECT_TRUE(response_json->toString().find(expected) != std::string::npos);
}
+// Test that the server responds with extra headers for no auth response.
+TEST_F(CtrlAgentResponseCreatorTest, noAuthHeader) {
+ setBasicContext(request_);
+
+ // Body: "list-commands" is natively supported by the command manager.
+ request_->context()->body_ = "{ \"command\": \"list-commands\" }";
+
+ // All requests must be finalized before they can be processed.
+ ASSERT_NO_THROW(request_->finalize());
+
+ // Require authentication.
+ CtrlAgentCfgContextPtr ctx = getCtrlAgentCfgContext();
+ ASSERT_TRUE(ctx);
+ BasicHttpAuthConfigPtr auth(new BasicHttpAuthConfig());
+ ASSERT_NO_THROW(ctx->setAuthConfig(auth));
+ auth->setRealm("ISC.ORG");
+ auth->add("foo", "", "bar", "");
+
+ // Add a STS header.
+ CfgHttpHeader hsts("Strict-Transport-Security", "max-age=31536000");
+ CfgHttpHeaders headers;
+ headers.push_back(hsts);
+ // Add a random header.
+ CfgHttpHeader foobar("Foo", "bar");
+ headers.push_back(foobar);
+ ctx->setHttpHeaders(headers);
+
+ HttpResponsePtr response;
+ ASSERT_NO_THROW(response = response_creator_.createHttpResponse(request_));
+ EXPECT_TRUE(request_->getBasicAuth().empty());
+ ASSERT_TRUE(response);
+
+ // Check that the two extra headers are in the response.
+ auto got = headers2map(response->context()->headers_);
+ EXPECT_EQ("max-age=31536000", got["Strict-Transport-Security"]);
+ EXPECT_EQ("bar", got["Foo"]);
+}
+
// Test successful server response when the client is authenticated.
TEST_F(CtrlAgentResponseCreatorTest, basicAuth) {
setBasicContext(request_);
@@ -383,6 +489,62 @@ TEST_F(CtrlAgentResponseCreatorTest, hookNoAuth) {
EXPECT_TRUE(response_json->toString().find(expected) != std::string::npos);
}
+// This test verifies that the server responds with extra headers for no
+// auth response provided by the hool.
+TEST_F(CtrlAgentResponseCreatorTest, hookNoAuthHeaders) {
+ setBasicContext(request_);
+
+ // Body: "list-commands" is natively supported by the command manager.
+ // We add a random value in the extra entry: see next unit test
+ // for an explanation about how it is used.
+ auto r32 = isc::cryptolink::random(4);
+ ASSERT_EQ(4, r32.size());
+ int extra = r32[0];
+ extra = (extra << 8) | r32[1];
+ extra = (extra << 8) | r32[2];
+ extra = (extra << 8) | r32[3];
+ request_->context()->body_ = "{ \"command\": \"list-commands\", ";
+ request_->context()->body_ += "\"extra\": " + std::to_string(extra) + " }";
+
+ // All requests must be finalized before they can be processed.
+ ASSERT_NO_THROW(request_->finalize());
+
+ // Setup hook.
+ CtrlAgentCfgContextPtr ctx = getCtrlAgentCfgContext();
+ ASSERT_TRUE(ctx);
+ HooksConfig& hooks_cfg = ctx->getHooksConfig();
+ std::string auth_cfg = "{ \"config\": {\n"
+ "\"type\": \"basic\",\n"
+ "\"realm\": \"ISC.ORG\",\n"
+ "\"clients\": [{\n"
+ " \"user\": \"foo\",\n"
+ " \"password\": \"bar\"\n"
+ " }]}}";
+ ConstElementPtr auth_json;
+ ASSERT_NO_THROW(auth_json = Element::fromJSON(auth_cfg));
+ hooks_cfg.add(std::string(BASIC_AUTH_LIBRARY), auth_json);
+ ASSERT_NO_THROW(hooks_cfg.loadLibraries(false));
+
+ // Add a STS header.
+ CfgHttpHeader hsts("Strict-Transport-Security", "max-age=31536000");
+ CfgHttpHeaders headers;
+ headers.push_back(hsts);
+ // Add a random header.
+ CfgHttpHeader foobar("Foo", "bar");
+ headers.push_back(foobar);
+ ctx->setHttpHeaders(headers);
+
+ HttpResponsePtr response;
+ ASSERT_NO_THROW(response = response_creator_.createHttpResponse(request_));
+ EXPECT_TRUE(request_->getBasicAuth().empty());
+ ASSERT_TRUE(response);
+
+ // Check that the two extra headers are in the response.
+ auto got = headers2map(response->context()->headers_);
+ EXPECT_EQ("max-age=31536000", got["Strict-Transport-Security"]);
+ EXPECT_EQ("bar", got["Foo"]);
+}
+
// Test successful server response when the client is authenticated.
TEST_F(CtrlAgentResponseCreatorTest, hookBasicAuth) {
setBasicContext(request_);