]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#1263] Record access parameters in HTTP request
authorFrancis Dupont <fdupont@isc.org>
Sat, 2 Apr 2022 16:09:18 +0000 (18:09 +0200)
committerRazvan Becheriu <razvan@isc.org>
Sun, 17 Apr 2022 14:52:29 +0000 (17:52 +0300)
src/bin/agent/ca_response_creator.cc
src/bin/agent/tests/ca_response_creator_unittests.cc
src/lib/http/basic_auth_config.cc
src/lib/http/connection.cc
src/lib/http/connection.h
src/lib/http/request.cc
src/lib/http/request.h
src/lib/http/tests/request_unittests.cc
src/lib/http/tests/response_creator_unittests.cc
src/lib/http/tests/tls_client_unittests.cc

index 9bfdec3ef5ac44be47db8f729277617f1ac3a93b..93021dbe453336f1fff14cf581eb63b128ebe569 100644 (file)
@@ -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
index 42274bf790cd4cd59a6985826e27aa9de1331db6..3fd5d3241626d08c242adaee1bba7eecfca2f401 100644 (file)
@@ -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<void>(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.
index 571b24f042f3eb773e3489dae14989637a0a1220..c7d911ec5acaf05f16f566bd4c47f29c2348dc1a 100644 (file)
@@ -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);
index ba238ac4310639df17b8310b482a7a3d4aa12cb3..aa307929bf9d4c80b27323cd5c5750e755ee9f2c 100644 (file)
@@ -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
index 64cbd6fe43d5986e79d459ee96a631954a1c2d66..70109de6605233c3bfbe58711d577e7fdc86ae64 100644 (file)
@@ -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();
index 15f7b614e6f24666463195f141565b9ffb767f98..391b22113af6a262d0a2d1f41b3171658b4375a8 100644 (file)
@@ -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_;
index b5ce5ac55ca43bbaab1deafec050764cef3eff05..dfd2c29c05d35bfc08f20be0a5e4f9855e55a1b8 100644 (file)
@@ -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<HttpRequest> 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_;
 };
 
 }
index 0bc25b322b0469cff46984b16e9471a54a729694..168c47f1c15330a81c9690fc731dfaf45bd99e3a 100644 (file)
@@ -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());
+}
+
 }
index 3514859dc122724dd1998e9491adac48f4a6ef4a..6886d1d77daf16cb9b1555d4ed69029939aef37b 100644 (file)
@@ -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;
 }
 
 }
index 4a64898bfd5d6a863a24da5a33377ea9d78545ae..c9a239e7d14fed8f7912742bb6e2233ca9db4e88 100644 (file)
@@ -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<PostHttpRequestJson>(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());