From: Francis Dupont Date: Sat, 2 Apr 2022 16:09:18 +0000 (+0200) Subject: [#1263] Record access parameters in HTTP request X-Git-Tag: Kea-2.1.5~65 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b66618fabe963922d3e470ae107bfa075f1ac607;p=thirdparty%2Fkea.git [#1263] Record access parameters in HTTP request --- diff --git a/src/bin/agent/ca_response_creator.cc b/src/bin/agent/ca_response_creator.cc index 9bfdec3ef5..93021dbe45 100644 --- a/src/bin/agent/ca_response_creator.cc +++ b/src/bin/agent/ca_response_creator.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2017-2020 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2017-2022 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/src/bin/agent/tests/ca_response_creator_unittests.cc b/src/bin/agent/tests/ca_response_creator_unittests.cc index 42274bf790..3fd5d32416 100644 --- a/src/bin/agent/tests/ca_response_creator_unittests.cc +++ b/src/bin/agent/tests/ca_response_creator_unittests.cc @@ -58,6 +58,7 @@ public: ADD_FAILURE() << "CtrlAgentResponseCreator::createNewHttpRequest" " returns NULL!"; } + HttpRequest::recordBasicAuth = true; // Initialize process and cfgmgr. try { initProcess(); @@ -71,6 +72,7 @@ public: /// /// Removes registered commands from the command manager. virtual ~CtrlAgentResponseCreatorTest() { + HttpRequest::recordBasicAuth = false; CtrlAgentCommandMgr::instance().deregisterAll(); HooksManager::prepareUnloadLibraries(); static_cast(HooksManager::unloadLibraries()); @@ -264,6 +266,7 @@ TEST_F(CtrlAgentResponseCreatorTest, noAuth) { HttpResponsePtr response; ASSERT_NO_THROW(response = response_creator_.createHttpResponse(request_)); + EXPECT_TRUE(request_->getBasicAuth().empty()); ASSERT_TRUE(response); // Response must be convertible to HttpResponseJsonPtr. @@ -306,6 +309,7 @@ TEST_F(CtrlAgentResponseCreatorTest, basicAuth) { HttpResponsePtr response; ASSERT_NO_THROW(response = response_creator_.createHttpResponse(request_)); + EXPECT_EQ("foo", request_->getBasicAuth()); ASSERT_TRUE(response); // Response must be convertible to HttpResponseJsonPtr. @@ -359,6 +363,7 @@ TEST_F(CtrlAgentResponseCreatorTest, hookNoAuth) { HttpResponsePtr response; ASSERT_NO_THROW(response = response_creator_.createHttpResponse(request_)); + EXPECT_TRUE(request_->getBasicAuth().empty()); ASSERT_TRUE(response); // Request should have no extra. @@ -433,6 +438,7 @@ TEST_F(CtrlAgentResponseCreatorTest, hookBasicAuth) { HttpResponsePtr response; ASSERT_NO_THROW(response = response_creator_.createHttpResponse(request_)); + EXPECT_EQ("foo", request_->getBasicAuth()); ASSERT_TRUE(response); // Request should have no extra. diff --git a/src/lib/http/basic_auth_config.cc b/src/lib/http/basic_auth_config.cc index 571b24f042..c7d911ec5a 100644 --- a/src/lib/http/basic_auth_config.cc +++ b/src/lib/http/basic_auth_config.cc @@ -366,6 +366,9 @@ BasicHttpAuthConfig::checkAuth(const HttpResponseCreator& creator, if (it != credentials.end()) { LOG_INFO(auth_logger, HTTP_CLIENT_REQUEST_AUTHORIZED) .arg(it->second); + if (HttpRequest::recordBasicAuth) { + request->setBasicAuth(it->second); + } authentic = true; } else { LOG_INFO(auth_logger, HTTP_CLIENT_REQUEST_NOT_AUTHORIZED); diff --git a/src/lib/http/connection.cc b/src/lib/http/connection.cc index ba238ac431..aa307929bf 100644 --- a/src/lib/http/connection.cc +++ b/src/lib/http/connection.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2017-2021 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2017-2022 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -92,6 +92,37 @@ HttpConnection::~HttpConnection() { close(); } +void +HttpConnection::recordParameters(const HttpRequestPtr& request) const { + if (!request) { + // Should never happen. + return; + } + + // Record the remote address. + request->setRemote(getRemoteEndpointAddressAsText()); + + // Record TLS parameters. + if (!tls_socket_) { + return; + } + + // The connection uses HTTPS aka HTTP over TLS. + request->setTls(true); + + // Record the first commonName of the subjectName of the client + // certificate when wanted. + if (HttpRequest::recordSubject) { + request->setSubject(tls_socket_->getTlsStream().getSubject()); + } + + // Record the first commonName of the issuerName of the client + // certificate when wanted. + if (HttpRequest::recordIssuer) { + request->setIssuer(tls_socket_->getTlsStream().getIssuer()); + } +} + void HttpConnection::shutdownCallback(const boost::system::error_code&) { tls_socket_->close(); @@ -215,6 +246,7 @@ HttpConnection::doRead(TransactionPtr transaction) { // new request. if (!transaction) { transaction = Transaction::create(response_creator_); + recordParameters(transaction->getRequest()); } // Create instance of the callback. It is safe to pass the local instance diff --git a/src/lib/http/connection.h b/src/lib/http/connection.h index 64cbd6fe43..70109de660 100644 --- a/src/lib/http/connection.h +++ b/src/lib/http/connection.h @@ -1,4 +1,4 @@ -// Copyright (C) 2017-2021 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2017-2022 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -267,9 +267,13 @@ public: /// @brief Closes the socket. void close(); - /// @brief Asynchronously performs TLS handshake. + /// @brief Records connection parameters into the HTTP request. /// + /// @param request Pointer to the HTTP request. + void recordParameters(const HttpRequestPtr& request) const; + /// @brief Asynchronously performs TLS handshake. + /// /// When the handshake is performed successfully or skipped because TLS /// was not enabled, the asynchronous read from the socket is started. void doHandshake(); diff --git a/src/lib/http/request.cc b/src/lib/http/request.cc index 15f7b614e6..391b22113a 100644 --- a/src/lib/http/request.cc +++ b/src/lib/http/request.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2016-2020 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2016-2022 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -21,10 +21,18 @@ const std::string crlf = "\r\n"; namespace isc { namespace http { +bool HttpRequest::recordSubject = false; + +bool HttpRequest::recordIssuer = false; + +bool HttpRequest::recordBasicAuth = false; + HttpRequest::HttpRequest() : HttpMessage(INBOUND), required_methods_(), method_(Method::HTTP_METHOD_UNKNOWN), - context_(new HttpRequestContext()) { + context_(new HttpRequestContext()), + remote_(""), tls_(false), subject_(""), issuer_(""), + basic_auth_(""), custom_("") { } HttpRequest::HttpRequest(const Method& method, @@ -34,7 +42,9 @@ HttpRequest::HttpRequest(const Method& method, const BasicHttpAuthPtr& basic_auth) : HttpMessage(OUTBOUND), required_methods_(), method_(Method::HTTP_METHOD_UNKNOWN), - context_(new HttpRequestContext()) { + context_(new HttpRequestContext()), + remote_(""), tls_(false), subject_(""), issuer_(""), + basic_auth_(""), custom_("") { context()->method_ = methodToString(method); context()->uri_ = uri; context()->http_version_major_ = version.major_; diff --git a/src/lib/http/request.h b/src/lib/http/request.h index b5ce5ac55c..dfd2c29c05 100644 --- a/src/lib/http/request.h +++ b/src/lib/http/request.h @@ -1,4 +1,4 @@ -// Copyright (C) 2016-2020 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2016-2022 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -45,6 +45,14 @@ typedef boost::shared_ptr HttpRequestPtr; /// includes body holding a JSON structure and provides methods to parse the /// JSON body. /// +/// Objects of this class is used to record some parameters for access control: +/// - the remote address +/// - the use of TLS +/// - the first commonName of the SubjectName of the client certificate +/// - the first commonName of the IssuerName of the client certificate +/// - the user ID of the basic HTTP authentication +/// - a custom value +/// /// Callouts are associated to the request. class HttpRequest : public HttpMessage, public hooks::CalloutHandleAssociate { public: @@ -150,6 +158,104 @@ public: /// otherwise. bool isPersistent() const; + /// Access control parameters: get/set methods. + + /// @brief Returns recorded remote address. + /// + /// @return recorded remote address. + std::string getRemote() const { + return (remote_); + } + + /// @brief Set (record) remote address. + /// + /// @param remote Remote end-point address in textual form. + void setRemote(const std::string& remote) { + remote_ = remote; + } + + /// @brief Returns recorded TLS usage. + /// + /// @return recorded TLS usage. + bool getTls() const { + return (tls_); + } + + /// @brief Set (record) TLS usage. + /// + /// @param tls TLS usage. + void setTls(bool tls) { + tls_ = tls; + } + + /// @brief Returns recorded subject name. + /// + /// @return recorded subject name. + std::string getSubject() const { + return (subject_); + } + + /// @brief Set (record) subjet name. + /// + /// @param subjet Subject name. + void setSubject(const std::string& subject) { + subject_ = subject; + } + + /// @brief Returns recorded issuer name. + /// + /// @return recorded issuer name. + std::string getIssuer() const { + return (issuer_); + } + + /// @brief Set (record) issuer name. + /// + /// @param issuer Issuer name. + void setIssuer(const std::string& issuer) { + issuer_ = issuer; + } + + /// @brief Returns recorded basic auth. + /// + /// @return recorded basic auth. + std::string getBasicAuth() const { + return (basic_auth_); + } + + /// @brief Set (record) basic auth. + /// + /// @param basic_auth Basic auth. + void setBasicAuth(const std::string& basic_auth) { + basic_auth_ = basic_auth; + } + + /// @brief Returns recorded custom name. + /// + /// @return recorded custom name. + std::string getCustom() const { + return (custom_); + } + + /// @brief Set (record) custom name. + /// + /// @param custom Custom name. + void setCustom(const std::string& custom) { + custom_ = custom; + } + + /// Access control parameters: want to record flags. + /// Remote address and TLS usage are always recorded. + + /// @brief Record subjet name. + static bool recordSubject; + + /// @brief Record issuer name. + static bool recordIssuer; + + /// @brief Record basic auth. + static bool recordBasicAuth; + protected: /// @brief Converts HTTP method specified in textual format to @ref Method. @@ -179,6 +285,24 @@ protected: /// @brief Pointer to the @ref HttpRequestContext holding parsed /// data. HttpRequestContextPtr context_; + + /// @brief Remote address. + std::string remote_; + + /// @brief TLS usage. + bool tls_; + + /// @brief Subject name. + std::string subject_; + + /// @brief Issuer name. + std::string issuer_; + + /// @brief Basic auth. + std::string basic_auth_; + + /// @brief Custom name. + std::string custom_; }; } diff --git a/src/lib/http/tests/request_unittests.cc b/src/lib/http/tests/request_unittests.cc index 0bc25b322b..168c47f1c1 100644 --- a/src/lib/http/tests/request_unittests.cc +++ b/src/lib/http/tests/request_unittests.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2016-2020 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2016-2022 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -389,4 +389,31 @@ TEST_F(HttpRequestTest, basicAuth) { EXPECT_EQ(value, "Basic " + basic_auth->getCredential()); } +/// This test verifies that access parameters are handled as expected. +TEST_F(HttpRequestTest, parameters) { + setContextBasics("GET", "/isc/org", HttpVersion(1, 1)); + ASSERT_NO_THROW(request_->create()); + + EXPECT_TRUE(request_->getRemote().empty()); + EXPECT_FALSE(request_->getTls()); + EXPECT_TRUE(request_->getSubject().empty()); + EXPECT_TRUE(request_->getIssuer().empty()); + EXPECT_TRUE(request_->getBasicAuth().empty()); + EXPECT_TRUE(request_->getCustom().empty()); + + request_->setRemote("my-remote"); + request_->setTls(true); + request_->setSubject("my-subject"); + request_->setIssuer("my-issuer"); + request_->setBasicAuth("foo"); + request_->setCustom("bar"); + + EXPECT_EQ("my-remote", request_->getRemote()); + EXPECT_TRUE(request_->getTls()); + EXPECT_EQ("my-subject", request_->getSubject()); + EXPECT_EQ("my-issuer", request_->getIssuer()); + EXPECT_EQ("foo", request_->getBasicAuth()); + EXPECT_EQ("bar", request_->getCustom()); +} + } diff --git a/src/lib/http/tests/response_creator_unittests.cc b/src/lib/http/tests/response_creator_unittests.cc index 3514859dc1..6886d1d77d 100644 --- a/src/lib/http/tests/response_creator_unittests.cc +++ b/src/lib/http/tests/response_creator_unittests.cc @@ -156,6 +156,7 @@ TEST_F(HttpResponseCreatorAuthTest, noAuth) { request->context()->method_ = "GET"; request->context()->uri_ = "/foo"; ASSERT_NO_THROW(request->finalize()); + HttpRequest::recordBasicAuth = true; HttpResponsePtr response; TestHttpResponseCreatorPtr creator(new TestHttpResponseCreator());; @@ -170,6 +171,7 @@ TEST_F(HttpResponseCreatorAuthTest, noAuth) { "{ \"result\": 401, \"text\": \"Unauthorized\" }", response->toString()); + EXPECT_TRUE(request->getBasicAuth().empty()); addString("HTTP_CLIENT_REQUEST_NO_AUTH_HEADER received HTTP request " "without required authentication header"); EXPECT_TRUE(checkFile()); @@ -195,6 +197,7 @@ TEST_F(HttpResponseCreatorAuthTest, authTooShort) { HttpHeaderContext auth("Authorization", "Basic ="); request->context()->headers_.push_back(auth); ASSERT_NO_THROW(request->finalize()); + HttpRequest::recordBasicAuth = true; HttpResponsePtr response; TestHttpResponseCreatorPtr creator(new TestHttpResponseCreator());; @@ -209,6 +212,7 @@ TEST_F(HttpResponseCreatorAuthTest, authTooShort) { "{ \"result\": 401, \"text\": \"Unauthorized\" }", response->toString()); + EXPECT_TRUE(request->getBasicAuth().empty()); addString("HTTP_CLIENT_REQUEST_BAD_AUTH_HEADER received HTTP request " "with malformed authentication header: " "header content is too short"); @@ -235,6 +239,7 @@ TEST_F(HttpResponseCreatorAuthTest, badScheme) { HttpHeaderContext auth("Authorization", "Basis dGVzdDoxMjPCow=="); request->context()->headers_.push_back(auth); ASSERT_NO_THROW(request->finalize()); + HttpRequest::recordBasicAuth = true; HttpResponsePtr response; TestHttpResponseCreatorPtr creator(new TestHttpResponseCreator());; @@ -249,6 +254,7 @@ TEST_F(HttpResponseCreatorAuthTest, badScheme) { "{ \"result\": 401, \"text\": \"Unauthorized\" }", response->toString()); + EXPECT_TRUE(request->getBasicAuth().empty()); addString("HTTP_CLIENT_REQUEST_BAD_AUTH_HEADER received HTTP request " "with malformed authentication header: " "not basic authentication"); @@ -276,6 +282,7 @@ TEST_F(HttpResponseCreatorAuthTest, notMatching) { HttpHeaderContext auth("Authorization", "Basic dGvZdDoxMjPcOw=="); request->context()->headers_.push_back(auth); ASSERT_NO_THROW(request->finalize()); + HttpRequest::recordBasicAuth = true; HttpResponsePtr response; TestHttpResponseCreatorPtr creator(new TestHttpResponseCreator());; @@ -290,6 +297,7 @@ TEST_F(HttpResponseCreatorAuthTest, notMatching) { "{ \"result\": 401, \"text\": \"Unauthorized\" }", response->toString()); + EXPECT_TRUE(request->getBasicAuth().empty()); addString("HTTP_CLIENT_REQUEST_NOT_AUTHORIZED received HTTP request " "with not matching authentication header"); EXPECT_TRUE(checkFile()); @@ -315,15 +323,18 @@ TEST_F(HttpResponseCreatorAuthTest, matching) { HttpHeaderContext auth("Authorization", "Basic dGVzdDoxMjPCow=="); request->context()->headers_.push_back(auth); ASSERT_NO_THROW(request->finalize()); + HttpRequest::recordBasicAuth = true; HttpResponsePtr response; TestHttpResponseCreatorPtr creator(new TestHttpResponseCreator());; ASSERT_NO_THROW(response = auth_config->checkAuth(*creator, request)); EXPECT_FALSE(response); + EXPECT_EQ("test", request->getBasicAuth()); addString("HTTP_CLIENT_REQUEST_AUTHORIZED received HTTP request " "authorized for 'test'"); EXPECT_TRUE(checkFile()); + HttpRequest::recordBasicAuth = false; } } diff --git a/src/lib/http/tests/tls_client_unittests.cc b/src/lib/http/tests/tls_client_unittests.cc index 4a64898bfd..c9a239e7d1 100644 --- a/src/lib/http/tests/tls_client_unittests.cc +++ b/src/lib/http/tests/tls_client_unittests.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2017-2021 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2017-2022 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -138,6 +138,15 @@ private: /// @return Pointer to the generated HTTP OK response with no content. virtual HttpResponsePtr createDynamicHttpResponse(HttpRequestPtr request) { + // Check access parameters. + if (HttpRequest::recordSubject) { + EXPECT_TRUE(request->getTls()); + EXPECT_EQ("kea-client", request->getSubject()); + } + if (HttpRequest::recordIssuer) { + EXPECT_TRUE(request->getTls()); + EXPECT_EQ("kea-ca", request->getIssuer()); + } // Request must always be JSON. PostHttpRequestJsonPtr request_json = boost::dynamic_pointer_cast(request); @@ -308,6 +317,8 @@ public: listener3_->stop(); io_service_.poll(); MultiThreadingMgr::instance().setMode(false); + HttpRequest::recordSubject = false; + HttpRequest::recordIssuer = false; } /// @brief Creates HTTP request with JSON body. @@ -513,6 +524,10 @@ public: } })); + // Record subjet and issuer: they will be check during response creation. + HttpRequest::recordSubject = true; + HttpRequest::recordIssuer = true; + // Actually trigger the requests. ASSERT_NO_THROW(runIOService());