]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#3477] Introduced a new config class
authorFrancis Dupont <fdupont@isc.org>
Fri, 5 Jul 2024 17:19:46 +0000 (19:19 +0200)
committerFrancis Dupont <fdupont@isc.org>
Thu, 1 Aug 2024 07:23:53 +0000 (09:23 +0200)
20 files changed:
src/bin/d2/tests/d2_cfg_mgr_unittests.cc
src/bin/dhcp4/tests/config_parser_unittest.cc
src/bin/dhcp4/tests/get_config_unittest.cc
src/bin/dhcp6/tests/config_parser_unittest.cc
src/bin/dhcp6/tests/get_config_unittest.cc
src/lib/config/Makefile.am
src/lib/config/command_mgr.h
src/lib/config/http_command_config.cc [new file with mode: 0644]
src/lib/config/http_command_config.h [new file with mode: 0644]
src/lib/config/http_command_mgr.cc [new file with mode: 0644]
src/lib/config/http_command_mgr.h [new file with mode: 0644]
src/lib/config/http_command_response_creator.cc [new file with mode: 0644]
src/lib/config/http_command_response_creator.h [new file with mode: 0644]
src/lib/config/http_command_response_creator_factory.h [new file with mode: 0644]
src/lib/d2srv/d2_cfg_mgr.cc
src/lib/d2srv/d2_cfg_mgr.h
src/lib/d2srv/d2_simple_parser.cc
src/lib/dhcpsrv/parsers/dhcp_parsers.cc
src/lib/dhcpsrv/srv_config.cc
src/lib/dhcpsrv/srv_config.h

index 96e423e688f787d8e019b1f0e7a0d3e036221b87..fbe14a21427053a42a77ff8bc0f249acba3ecacd 100644 (file)
@@ -6,6 +6,7 @@
 
 #include <config.h>
 
+#include <config/http_command_config.h>
 #include <d2/parser_context.h>
 #include <d2/tests/parser_unittest.h>
 #include <d2/tests/test_callout_libraries.h>
@@ -1039,7 +1040,11 @@ TEST_F(D2CfgMgrTest, comments) {
     EXPECT_EQ("\"Indirect comment\"", ctx_socket->get("comment")->str());
 
     // There is a HTTP control socket with authentication.
-    socket = d2_context->getHttpControlSocketInfo();
+    config::HttpCommandConfigPtr http_socket =
+        d2_context->getHttpControlSocketInfo();
+    ASSERT_TRUE(http_socket);
+    /// @todo use the configuration object.
+    socket = http_socket->toElement();
     ASSERT_TRUE(socket);
     ctx_socket = socket->get("user-context");
     ASSERT_TRUE(ctx_socket);
index 2ddcc5e198df3d140e0c8619a5b3ff8a3a9ac077..56d5788a268abc67ec933a38d7a7234847728cb4 100644 (file)
@@ -10,6 +10,7 @@
 #include <gtest/gtest.h>
 
 #include <cc/command_interpreter.h>
+#include <config/http_command_config.h>
 #include <dhcp4/dhcp4_srv.h>
 #include <dhcp4/ctrl_dhcp4_srv.h>
 #include <dhcp4/json_config_parser.h>
@@ -6815,7 +6816,11 @@ TEST_F(Dhcp4ParserTest, comments) {
     EXPECT_EQ("\"Indirect comment\"", ctx_socket->get("comment")->str());
 
     // There is a HTTP control socket with authentication.
-    socket = CfgMgr::instance().getStagingCfg()->getHttpControlSocketInfo();
+    HttpCommandConfigPtr http_socket =
+        CfgMgr::instance().getStagingCfg()->getHttpControlSocketInfo();
+    ASSERT_TRUE(http_socket);
+    /// @todo use the configuration object.
+    socket = http_socket->toElement();
     ASSERT_TRUE(socket);
     ASSERT_TRUE(socket->get("socket-type"));
     EXPECT_EQ("\"http\"", socket->get("socket-type")->str());
index 72e7279e111e921173bf1976294a6dd45553cb32..6eb05fc787b561a63a96d4f143c77dff25702fe1 100644 (file)
@@ -10939,6 +10939,8 @@ const char* UNPARSED_CONFIGS[] = {
 "                            }\n"
 "                        }\n"
 "                    ],\n"
+"                    \"directory\": \"\",\n"
+"                    \"realm\": \"\",\n"
 "                    \"type\": \"basic\",\n"
 "                    \"user-context\": {\n"
 "                        \"comment\": \"basic HTTP authentication\"\n"
index 8aefc7d070f4187707bd599872dc1fac7fac4fa8..5121b423ed7ef06cd3111698afe4870acc7919e4 100644 (file)
@@ -7,6 +7,7 @@
 #include <config.h>
 
 #include <cc/command_interpreter.h>
+#include <config/http_command_config.h>
 #include <dhcp/docsis3_option_defs.h>
 #include <dhcp/libdhcp++.h>
 #include <dhcp/option6_ia.h>
@@ -7398,7 +7399,11 @@ TEST_F(Dhcp6ParserTest, comments) {
     EXPECT_EQ("\"Indirect comment\"", ctx_socket->get("comment")->str());
 
     // There is a HTTP control socket with authentication.
-    socket = CfgMgr::instance().getStagingCfg()->getHttpControlSocketInfo();
+    HttpCommandConfigPtr http_socket =
+        CfgMgr::instance().getStagingCfg()->getHttpControlSocketInfo();
+    ASSERT_TRUE(http_socket);
+    /// @todo use the configuration object.
+    socket = http_socket->toElement();
     ASSERT_TRUE(socket);
     ASSERT_TRUE(socket->get("socket-type"));
     EXPECT_EQ("\"http\"", socket->get("socket-type")->str());
index 2bfd3f10d7cb93b14de02fcf772180eb400056a1..ea94dad6182fe2b07a559d1ab9a595197edc105b 100644 (file)
@@ -9638,6 +9638,8 @@ const char* UNPARSED_CONFIGS[] = {
 "                            }\n"
 "                        }\n"
 "                    ],\n"
+"                    \"directory\": \"\",\n"
+"                    \"realm\": \"\",\n"
 "                    \"type\": \"basic\",\n"
 "                    \"user-context\": {\n"
 "                        \"comment\": \"basic HTTP authentication\"\n"
index a04bf92c6a8f5c3eaea2b0289df742340568bd62..72e603e56321dfa7eca653383976826252ce771a 100644 (file)
@@ -17,6 +17,11 @@ libkea_cfgclient_la_SOURCES += timeouts.h
 libkea_cfgclient_la_SOURCES += cmd_http_listener.cc cmd_http_listener.h
 libkea_cfgclient_la_SOURCES += cmd_response_creator.cc cmd_response_creator.h
 libkea_cfgclient_la_SOURCES += cmd_response_creator_factory.h
+libkea_cfgclient_la_SOURCES += http_command_config.cc http_command_config.h
+libkea_cfgclient_la_SOURCES += http_command_mgr.cc http_command_mgr.h
+libkea_cfgclient_la_SOURCES += http_command_response_creator_factory.h
+libkea_cfgclient_la_SOURCES += http_command_response_creator.cc
+libkea_cfgclient_la_SOURCES += http_command_response_creator.h
 
 libkea_cfgclient_la_LIBADD  = $(top_builddir)/src/lib/http/libkea-http.la
 libkea_cfgclient_la_LIBADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
@@ -84,5 +89,9 @@ libkea_cfgclient_include_HEADERS = \
        config_log.h \
        config_messages.h \
        hooked_command_mgr.h \
+       http_command_config.h \
+       http_command_mgr.h \
+       http_command_response_creator.h \
+       http_command_response_creator_factory.h \
        timeouts.h
 
index f4ba6c1c05fb584844abfdce6d85e44b38619fc9..d1eee35c10e2ac090ba8ef9a47f1463176d6d3bb 100644 (file)
@@ -88,7 +88,6 @@ private:
     boost::shared_ptr<CommandMgrImpl> impl_;
 };
 
-}; // end of isc::config namespace
-}; // end of isc namespace
-
+} // end of isc::config namespace
+} // end of isc namespace
 #endif
diff --git a/src/lib/config/http_command_config.cc b/src/lib/config/http_command_config.cc
new file mode 100644 (file)
index 0000000..00b9a04
--- /dev/null
@@ -0,0 +1,232 @@
+// Copyright (C) 2015-2024 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
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <cc/dhcp_config_error.h>
+#include <config/http_command_config.h>
+#include <http/basic_auth_config.h>
+#include <limits>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::config;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::http;
+using namespace std;
+
+namespace isc {
+namespace config {
+
+IOAddress HttpCommandConfig::DefaultSocketAddress = IOAddress("127.0.0.1");
+
+uint16_t HttpCommandConfig::DefaultSocketPort = 8000;
+
+string HttpCommandConfig::DefaultAuthenticationRealm = "";
+
+HttpCommandConfig::HttpCommandConfig(ConstElementPtr config)
+    : socket_type_("http"), socket_address_(DefaultSocketAddress),
+      socket_port_(DefaultSocketPort), auth_config_(),
+      trust_anchor_(""), cert_file_(""), key_file_(""), cert_required_(true),
+      emulate_agent_response_(true) {
+    // Get socket type.
+    ConstElementPtr socket_type = config->get("socket-type");
+    if (socket_type) {
+        if (socket_type->getType() != Element::string) {
+            isc_throw(DhcpConfigError,
+                      "invalid type specified for parameter 'socket_type' ("
+                      << socket_type->getPosition() << ")");
+        }
+        socket_type_ = socket_type->stringValue();
+        if ((socket_type_ != "http") && (socket_type_ != "https")) {
+            isc_throw(DhcpConfigError, "unsupported 'socket-type': '"
+                      << socket_type_ << "' not 'http' or 'https'");
+        }
+    }
+
+    // Get socket address.
+    ConstElementPtr socket_name = config->get("socket-name");
+    ConstElementPtr socket_address = config->get("socket-address");
+    if (socket_name) {
+        // socket-name is an alias of socket-address.
+        if (socket_address) {
+            isc_throw(DhcpConfigError,
+                      "specify both 'socket-name' and 'socket-address' "
+                      "is forbidden");
+        }
+        socket_address = socket_name;
+    }
+    if (socket_address) {
+        if (socket_address->getType() != Element::string) {
+            isc_throw(DhcpConfigError,
+                      "invalid type specified for parameter 'socket-"
+                      << (socket_name ? "name" : "address") << "' ("
+                      << socket_address->getPosition() << ")");
+        }
+        try {
+            socket_address_ = IOAddress(socket_address->stringValue());
+        } catch (const std::exception& ex) {
+            isc_throw(DhcpConfigError, "failed to convert '"
+                      << socket_address->stringValue()
+                      << "' to address: " << ex.what()
+                      << " (" << socket_address->getPosition() << ")");
+        }
+    }
+
+    // Get socket port.
+    ConstElementPtr socket_port = config->get("socket-port");
+    if (socket_port) {
+        if (socket_port->getType() != Element::integer) {
+            isc_throw(DhcpConfigError,
+                      "invalid type specified for parameter 'socket-port' ("
+                      << socket_port->getPosition() << ")");
+        }
+        int64_t value = socket_port->intValue();
+        if ((value < numeric_limits<uint16_t>::min()) ||
+            (value > numeric_limits<uint16_t>::max())) {
+            isc_throw(DhcpConfigError,
+                      "out of range value (" << value
+                      << ") specified for parameter 'socket-port' {"
+                      << socket_port->getPosition() << ")");
+        }
+    }
+
+    // Get HTTP authentication.
+    ConstElementPtr auth_config = config->get("authentication");
+    if (auth_config) {
+        ElementPtr mutable_auth_config =
+            boost::const_pointer_cast<Element>(auth_config);
+        if (auth_config->getType() != Element::map) {
+            isc_throw(DhcpConfigError,
+                      "invalid type specified for parameter 'authentication' ("
+                      << auth_config->getPosition() << ")");
+        }
+        // Default type is basic.
+        ConstElementPtr type = auth_config->get("type");
+        if (!type) {
+            mutable_auth_config->set("type", Element::create(string("basic")));
+        }
+        // Set default realm when not present.
+        ConstElementPtr realm = auth_config->get("realm");
+        if (!realm) {
+            mutable_auth_config->set("realm",
+                Element::create(DefaultAuthenticationRealm));
+        }
+
+        BasicHttpAuthConfigPtr auth(new BasicHttpAuthConfig());
+        auth->parse(auth_config);
+        auth_config_ = auth;
+    }
+
+    // Get trust anchor.
+    ConstElementPtr trust_anchor = config->get("trust-anchor");
+    if (trust_anchor) {
+        if (socket_port->getType() != Element::string) {
+            isc_throw(DhcpConfigError,
+                      "invalid type specified for parameter 'trust-anchor' ("
+                      << trust_anchor->getPosition() << ")");
+        }
+        trust_anchor_ = trust_anchor->stringValue();
+    }
+
+    // Get cert file.
+    ConstElementPtr cert_file = config->get("cert-file");
+    if (cert_file) {
+        if (socket_port->getType() != Element::string) {
+            isc_throw(DhcpConfigError,
+                      "invalid type specified for parameter 'cert-file' ("
+                      << cert_file->getPosition() << ")");
+        }
+        cert_file_ = cert_file->stringValue();
+    }
+
+    // Get key file.
+    ConstElementPtr key_file = config->get("key-file");
+    if (key_file) {
+        if (socket_port->getType() != Element::string) {
+            isc_throw(DhcpConfigError,
+                      "invalid type specified for parameter 'key-file' ("
+                      << key_file->getPosition() << ")");
+        }
+        key_file_ = key_file->stringValue();
+    }
+
+    // Get cert required.
+    ConstElementPtr cert_required = config->get("cert-required");
+    if (cert_required) {
+        if (socket_port->getType() != Element::boolean) {
+            isc_throw(DhcpConfigError,
+                      "invalid type specified for parameter 'cert-required' ("
+                      << cert_required->getPosition() << ")");
+        }
+        cert_required_ = cert_required->boolValue();
+    }
+
+    // Check the TLS settup.
+    checkTlsSetup(socket_type_ == "https");
+
+    // Get user context.
+    ConstElementPtr user_context = config->get("user-context");
+    if (user_context) {
+        setContext(user_context);
+    }
+}
+
+void
+HttpCommandConfig::checkTlsSetup(bool require_tls) const {
+    bool have_ca = !trust_anchor_.empty();
+    bool have_cert = !cert_file_.empty();
+    bool have_key = !key_file_.empty();
+    if (!have_ca && !have_cert && !have_key) {
+        if (require_tls) {
+            isc_throw(ConfigError, "no TLS setup for a HTTPS control socket");
+        }
+        return;
+    }
+    // TLS is used: all 3 parameters are required.
+    if (!have_ca) {
+        isc_throw(ConfigError, "trust-anchor parameter is missing or empty:"
+                  " all or none of TLS parameters must be set");
+    }
+    if (!have_cert) {
+        isc_throw(ConfigError, "cert-file parameter is missing or empty:"
+                  " all or none of TLS parameters must be set");
+    }
+    if (!have_key) {
+        isc_throw(ConfigError, "key-file parameter is missing or empty:"
+                  " all or none of TLS parameters must be set");
+    }
+}
+
+ElementPtr
+HttpCommandConfig::toElement() const {
+    ElementPtr result = Element::createMap();
+    // Set user-context.
+    contextToElement(result);
+    // Set socket type.
+    result->set("socket-type", Element::create(socket_type_));
+    // Set socket address.
+    result->set("socket-address", Element::create(socket_address_.toText()));
+    // Set socket port.
+    result->set("socket-port",
+                Element::create(static_cast<uint32_t>(socket_port_)));
+    /// Set authentication.
+    if (auth_config_) {
+        result->set("authentication", auth_config_->toElement());
+    }
+    // Set TLS setup when enabled.
+    if (!trust_anchor_.empty()) {
+        result->set("trust-anchor", Element::create(trust_anchor_));
+        result->set("cert-file", Element::create(cert_file_));
+        result->set("key-file", Element::create(key_file_));
+        result->set("cert-required", Element::create(cert_required_));
+    }
+    return (result);
+}
+
+} // end of isc::config
+} // end of isc
diff --git a/src/lib/config/http_command_config.h b/src/lib/config/http_command_config.h
new file mode 100644 (file)
index 0000000..cfab1f6
--- /dev/null
@@ -0,0 +1,216 @@
+// Copyright (C) 2024 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
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_COMMAND_CONFIG_H
+#define HTTP_COMMAND_CONFIG_H
+
+#include <asiolink/io_address.h>
+#include <cc/cfg_to_element.h>
+#include <cc/user_context.h>
+#include <http/auth_config.h>
+
+namespace isc {
+namespace config {
+
+/// @brief HTTP command config aka HTTP control socket info class.
+class HttpCommandConfig : public isc::data::UserContext,
+                          public isc::data::CfgToElement {
+public:
+    /// @brief Constructor.
+    ///
+    /// @param config Pointer to the configuration to parse.
+    HttpCommandConfig(isc::data::ConstElementPtr config);
+
+    /// @brief Virtual destructor.
+    ~HttpCommandConfig() = default;
+
+    /// @brief Returns socket type.
+    ///
+    /// @return The socket type ("http" or "https").
+    std::string getSocketType() const {
+        return (socket_type_);
+    }
+
+    /// @brief Sets socket type.
+    ///
+    /// @param socket_type The new socket type (should be "http" or "https").
+    void setSocketType(const std::string& socket_type) {
+        socket_type_ = socket_type;
+    }
+
+    /// @brief Returns socket address.
+    ///
+    /// @return IP address where the server's HTTP service is available.
+    isc::asiolink::IOAddress getSocketAddress() const {
+        return (socket_address_);
+    }
+
+    /// @brief Sets socket address.
+    ///
+    /// @param socket_address The new socket address.
+    void setSocketAddress(const isc::asiolink::IOAddress& socket_address) {
+        socket_address_ = socket_address;
+    }
+
+    /// @brief Returns socket port.
+    uint16_t getSocketPort() const {
+        return (socket_port_);
+    }
+
+    /// @brief Sets socket port.
+    ///
+    /// @param socket_port The new socket port.
+    void setSocketPort(const uint16_t socket_port) {
+        socket_port_ = socket_port;
+    }
+
+    /// @nrief Returns HTTP authentication configuration.
+    ///
+    /// @note Only the basic HTTP authentication is supported.
+    ///
+    /// @return HTTP authentication configuration.
+    const isc::http::HttpAuthConfigPtr& getAuthConfig() const {
+        return (auth_config_);
+    }
+
+    /// @brief Sets HTTP authentication configuration.
+    ///
+    /// @note Only the basic HTTP authentication is supported.
+    ///
+    /// @param auth_config HTTP authentication configuration.
+    void setAuthConfig(const isc::http::HttpAuthConfigPtr& auth_config) {
+        auth_config_ = auth_config;
+    }
+
+    /// @brief Returns trust-anchor TLS parameter.
+    ///
+    /// @return Trust anchor aka Certificate Authority.
+    std::string getTrustAnchor() const {
+        return (trust_anchor_);
+    }
+
+    /// @brief Sets trust-anchor TLS parameter.
+    ///
+    /// @param ca Trust anchor aka Certificate Authority (can be a file name
+    /// or a directory path).
+    void setTrustAnchor(const std::string& ca) {
+        trust_anchor_ = ca;
+    }
+
+    /// @brief Returns cert-file TLS parameter.
+    ///
+    /// @return Server certificate file name.
+    std::string getCertFile() const {
+        return (cert_file_);
+    }
+
+    /// @brief Sets cert-file TLS parameter.
+    ///
+    /// @param cert Server certificate file name.
+    void setCertFile(const std::string& cert) {
+        cert_file_ = cert;
+    }
+
+    /// @brief Returns key-file TLS parameter.
+    ///
+    /// @return Server private key file name.
+    std::string getKeyFile() const {
+        return (key_file_);
+    }
+
+    /// @brief Sets key-file TLS parameter.
+    ///
+    /// @param key Server private key file name.
+    void setKeyFile(const std::string& key) {
+        key_file_ = key;
+    }
+
+    /// @brief Returns cert-required TLS parameter.
+    ///
+    /// @return True when client certificates are required, false when they
+    /// are optional, the default is to require them (true).
+    bool getCertRequired() const {
+        return (cert_required_);
+    }
+
+    /// @brief Sets cert-required TLS parameter.
+    ///
+    /// @param required Client certificates are required when true
+    /// (the default) or optional when false.
+    void setCertRequired(bool required) {
+        cert_required_ = required;
+    }
+
+    /// @brief Returns emulate agent response flag.
+    ///
+    /// @return True when responses for normal ommand outcomes are
+    /// guaranteed to be wrapped in an Element::list. This emulates
+    /// how kea-ctrl-agent forms responses. Defaults to true.
+    bool getEmulateAgentResponse() const {
+        return (emulate_agent_response_);
+    }
+
+    /// @brief Sets emulate agent response flag.
+    ///
+    /// @param emulate_agent_response The new value of the emulation flag.
+    void setEmulateAgentResponse(const bool emulate_agent_response) {
+        emulate_agent_response_ = emulate_agent_response;
+    }
+
+    /// @brief Unparse a configuration object.
+    ///
+    /// @return A pointer to configuration.
+    virtual isc::data::ElementPtr toElement() const;
+
+    /// @brief Default socket address (127.0.0.1).
+    static isc::asiolink::IOAddress DefaultSocketAddress;
+
+    /// @brief Default socket port.
+    static uint16_t DefaultSocketPort;
+
+    /// @brief Default HTTP authentication realm.
+    static std::string DefaultAuthenticationRealm;
+
+private:
+    /// @brief Check TLS configuration.
+    ///
+    /// @param require_tls When true TLS is required.
+    void checkTlsSetup(bool require_tls) const;
+
+    /// @brief Socket type ("http" or "https").
+    std::string socket_type_;
+
+    /// @brief Socket address.
+    isc::asiolink::IOAddress socket_address_;
+
+    /// @brief Socket port.
+    uint16_t socket_port_;
+
+    /// @brief HTTP authentication configuration.
+    isc::http::HttpAuthConfigPtr auth_config_;
+
+    /// @brief Trust anchor aka Certificate Authority.
+    std::string trust_anchor_;
+
+    /// @brief Server certificate file name.
+    std::string cert_file_;
+
+    /// @brief Server private key file name.
+    std::string key_file_;
+
+    /// @brief Require client certificates flag.
+    bool cert_required_;
+
+    /// @brief Emulation flag.
+    bool emulate_agent_response_;
+};
+
+/// @brief Pointer to a HttpCommandConfig object.
+typedef boost::shared_ptr<HttpCommandConfig> HttpCommandConfigPtr;
+
+} // end of isc::config namespace
+} // end of isc namespace
+#endif
diff --git a/src/lib/config/http_command_mgr.cc b/src/lib/config/http_command_mgr.cc
new file mode 100644 (file)
index 0000000..9f280bd
--- /dev/null
@@ -0,0 +1,29 @@
+// Copyright (C) 2024 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
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/io_service.h>
+#include <config/command_mgr.h>
+#include <cc/data.h>
+#include <cc/command_interpreter.h>
+#include <cc/json_feed.h>
+#include <dhcp/iface_mgr.h>
+#include <config/config_log.h>
+#include <config/timeouts.h>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::config;
+using namespace isc::data;
+namespace ph = std::placeholders;
+
+namespace isc {
+namespace config {
+
+} // end of isc::config
+} // end of isc
diff --git a/src/lib/config/http_command_mgr.h b/src/lib/config/http_command_mgr.h
new file mode 100644 (file)
index 0000000..80f8b2f
--- /dev/null
@@ -0,0 +1,22 @@
+// Copyright (C) 2024 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
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_COMMAND_MGR_H
+#define HTTP_COMMAND_MGR_H
+
+#include <asiolink/io_service.h>
+#include <cc/data.h>
+#include <config/hooked_command_mgr.h>
+#include <exceptions/exceptions.h>
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace config {
+
+} // end of isc::config namespace
+} // end of isc namespace
+#endif
diff --git a/src/lib/config/http_command_response_creator.cc b/src/lib/config/http_command_response_creator.cc
new file mode 100644 (file)
index 0000000..8cd7190
--- /dev/null
@@ -0,0 +1,198 @@
+// Copyright (C) 2021-2024 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
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <config/http_command_response_creator.h>
+#include <config/command_mgr.h>
+#include <config/config_log.h>
+#include <hooks/callout_handle.h>
+#include <hooks/hooks_log.h>
+#include <hooks/hooks_manager.h>
+#include <http/post_request_json.h>
+#include <http/response_json.h>
+#include <boost/pointer_cast.hpp>
+#include <iostream>
+
+using namespace isc::config;
+using namespace isc::data;
+using namespace isc::hooks;
+using namespace isc::http;
+using namespace std;
+
+namespace {
+
+/// Structure that holds registered hook indexes.
+struct HttpCommandHooks {
+    int hook_index_http_auth_;     ///< index of "http_auth" hook point.
+    int hook_index_http_response_; ///< index of "http_response" hook point.
+
+    /// Constructor that registers hook points.
+    HttpCommandHooks() {
+        hook_index_http_auth_  = HooksManager::registerHook("http_auth");
+        hook_index_http_response_ = HooksManager::registerHook("http_response");
+    }
+};
+
+} // end of anonymous namespace.
+
+// Declare a Hooks object. As this is outside any function or method, it
+// will be instantiated (and the constructor run) when the module is loaded.
+// As a result, the hook indexes will be defined before any method in this
+// module is called.
+HttpCommandHooks Hooks;
+
+namespace isc {
+namespace config {
+
+HttpRequestPtr
+HttpCommandResponseCreator::createNewHttpRequest() const {
+    return (HttpRequestPtr(new PostHttpRequestJson()));
+}
+
+HttpResponsePtr
+HttpCommandResponseCreator::
+createStockHttpResponse(const HttpRequestPtr& request,
+                        const HttpStatusCode& status_code) const {
+    HttpResponsePtr response = createStockHttpResponseInternal(request, status_code);
+    response->finalize();
+    return (response);
+}
+
+HttpResponsePtr
+HttpCommandResponseCreator::
+createStockHttpResponseInternal(const HttpRequestPtr& request,
+                                const HttpStatusCode& status_code) const {
+    // The request hasn't been finalized so the request object
+    // doesn't contain any information about the HTTP version number
+    // used. But, the context should have this data (assuming the
+    // HTTP version is parsed OK).
+    HttpVersion http_version(request->context()->http_version_major_,
+                             request->context()->http_version_minor_);
+    // We only accept HTTP version 1.0 or 1.1. If other version number is found
+    // we fall back to HTTP/1.0.
+    if ((http_version < HttpVersion(1, 0)) || (HttpVersion(1, 1) < http_version)) {
+        http_version.major_ = 1;
+        http_version.minor_ = 0;
+    }
+    // This will generate the response holding JSON content.
+    HttpResponsePtr response(new HttpResponseJson(http_version, status_code));
+    return (response);
+}
+
+HttpResponsePtr
+HttpCommandResponseCreator::createDynamicHttpResponse(HttpRequestPtr request) {
+    HttpResponseJsonPtr http_response;
+
+    // Check the basic HTTP authentication.
+    if (config_) {
+        const HttpAuthConfigPtr& auth = config_->getAuthConfig();
+        if (auth) {
+            http_response = auth->checkAuth(*this, request);
+        }
+    }
+
+    // Callout point for "http_auth".
+    bool reset_handle = false;
+    if (HooksManager::calloutsPresent(Hooks.hook_index_http_auth_)) {
+        // Get callout handle.
+        CalloutHandlePtr callout_handle = request->getCalloutHandle();
+        ScopedCalloutHandleState callout_handle_state(callout_handle);
+
+        // Pass arguments.
+        callout_handle->setArgument("request", request);
+        callout_handle->setArgument("response", http_response);
+
+        // Call callouts.
+        HooksManager::callCallouts(Hooks.hook_index_http_auth_,
+                                   *callout_handle);
+        callout_handle->getArgument("request", request);
+        callout_handle->getArgument("response", http_response);
+
+        // Status other than continue means 'please reset the handle'.
+        if (callout_handle->getStatus() != CalloutHandle::NEXT_STEP_CONTINUE) {
+            reset_handle = true;
+        }
+    }
+
+    // The basic HTTP authentication check or a callout failed and
+    // left a response.
+    if (http_response) {
+        return (http_response);
+    }
+
+    // Reset the handle when a hook asks for.
+    if (reset_handle) {
+        request->resetCalloutHandle();
+    }
+
+    // The request is always non-null, because this is verified by the
+    // createHttpResponse method. Let's try to convert it to the
+    // PostHttpRequestJson type as this is the type generated by the
+    // createNewHttpRequest. If the conversion result is null it means that
+    // the caller did not use createNewHttpRequest method to create this
+    // instance. This is considered an error in the server logic.
+    PostHttpRequestJsonPtr request_json =
+        boost::dynamic_pointer_cast<PostHttpRequestJson>(request);
+    if (!request_json) {
+        // Notify the client that we have a problem with our server.
+        return (createStockHttpResponse(request, HttpStatusCode::INTERNAL_SERVER_ERROR));
+    }
+
+    // We have already checked that the request is finalized so the call
+    // to getBodyAsJson must not trigger an exception.
+    ConstElementPtr command = request_json->getBodyAsJson();
+
+    // Process command doesn't generate exceptions but can possibly return
+    // null response, if the handler is not implemented properly. This is
+    // again an internal server issue.
+    ConstElementPtr response = config::CommandMgr::instance().processCommand(command);
+
+    if (!response) {
+        // Notify the client that we have a problem with our server.
+        return (createStockHttpResponse(request, HttpStatusCode::INTERNAL_SERVER_ERROR));
+    }
+
+    // Normal Responses coming from the Kea Control Agent must always be wrapped in
+    // a list as they may contain responses from multiple daemons.
+    // If we're emulating that for backward compatibility, then we need to wrap
+    // the answer in a list if it isn't in one already.
+    if ((!config_ || config_->getEmulateAgentResponse()) &&
+        (response->getType() != Element::list)) {
+        ElementPtr response_list = Element::createList();
+        response_list->add(boost::const_pointer_cast<Element>(response));
+        response = response_list;
+    }
+
+    // The response is OK, so let's create new HTTP response with the status OK.
+    http_response = boost::dynamic_pointer_cast<
+        HttpResponseJson>(createStockHttpResponseInternal(request, HttpStatusCode::OK));
+    http_response->setBodyAsJson(response);
+    http_response->finalize();
+
+    // Callout point for "http_response".
+    if (HooksManager::calloutsPresent(Hooks.hook_index_http_response_)) {
+        // Get callout handle.
+        CalloutHandlePtr callout_handle = request->getCalloutHandle();
+        ScopedCalloutHandleState callout_handle_state(callout_handle);
+
+        // Pass arguments.
+        callout_handle->setArgument("request", request);
+        callout_handle->setArgument("response", http_response);
+
+        // Call callouts.
+        HooksManager::callCallouts(Hooks.hook_index_http_response_,
+                                   *callout_handle);
+        callout_handle->getArgument("response", http_response);
+
+        // Ignore status as the HTTP response is used instead.
+    }
+
+    return (http_response);
+}
+
+} // end of namespace isc::config
+} // end of namespace isc
diff --git a/src/lib/config/http_command_response_creator.h b/src/lib/config/http_command_response_creator.h
new file mode 100644 (file)
index 0000000..8f72665
--- /dev/null
@@ -0,0 +1,103 @@
+// Copyright (C) 2021-2024 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
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_COMMAND_RESPONSE_CREATOR_H
+#define HTTP_COMMAND_RESPONSE_CREATOR_H
+
+#include <config/http_command_config.h>
+#include <http/response_creator.h>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace config {
+
+/// @brief Concrete implementation of the HTTP response creator used
+/// for HTTP control socket.
+///
+/// See the documentation of the @ref isc::http::HttpResponseCreator for
+/// the basic information how HTTP response creators are utilized by
+/// the libkea-http library to generate HTTP responses.
+///
+/// This creator expects that received requests are encapsulated in the
+/// @ref isc::http::PostHttpRequestJson objects. The generated responses
+/// are encapsulated in the HttpResponseJson objects.
+///
+/// This class uses @ref CommandMgr singleton to process commands
+/// conveyed in the HTTP body. The JSON responses returned by the manager
+/// are placed in the body of the generated HTTP responses.
+class HttpCommandResponseCreator : public http::HttpResponseCreator {
+public:
+
+    /// @brief Constructor
+    ///
+    /// @param config The HTTP control socket config.
+    HttpCommandResponseCreator(HttpCommandConfigPtr config) : config_(config) {
+    }
+
+    /// @brief virtual destructor.
+    virtual ~HttpCommandResponseCreator() = default;
+
+    /// @brief Create a new request.
+    ///
+    /// This method creates a bare instance of the @ref
+    /// isc::http::PostHttpRequestJson.
+    ///
+    /// @return Pointer to the new instance of the @ref
+    /// isc::http::PostHttpRequestJson.
+    virtual http::HttpRequestPtr createNewHttpRequest() const;
+
+    /// @brief Creates stock HTTP response.
+    ///
+    /// @param request Pointer to an object representing HTTP request.
+    /// @param status_code Status code of the response.
+    /// @return Pointer to an @ref isc::http::HttpResponseJson object
+    /// representing stock HTTP response.
+    virtual http::HttpResponsePtr
+    createStockHttpResponse(const http::HttpRequestPtr& request,
+                            const http::HttpStatusCode& status_code) const;
+
+    /// @brief Returns HTTP control socket config.
+    HttpCommandConfigPtr getHttpCommandConfig() const {
+        return (config_);
+    }
+
+private:
+
+    /// @brief Creates un-finalized stock HTTP response.
+    ///
+    /// The un-finalized response is the response that can't be sent over the
+    /// wire until @c finalize() is called, which commits the contents of the
+    /// message body.
+    ///
+    /// @param request Pointer to an object representing HTTP request.
+    /// @param status_code Status code of the response.
+    /// @return Pointer to an @ref isc::http::HttpResponseJson object
+    /// representing stock HTTP response.
+    http::HttpResponsePtr
+    createStockHttpResponseInternal(const http::HttpRequestPtr& request,
+                                    const http::HttpStatusCode& status_code) const;
+
+    /// @brief Creates implementation specific HTTP response.
+    ///
+    /// @param request Pointer to an object representing HTTP request.
+    /// @return Pointer to an object representing HTTP response.
+    virtual http::HttpResponsePtr
+    createDynamicHttpResponse(http::HttpRequestPtr request);
+
+    /// @brief Returns HTTP control socket config.
+    ///
+    /// Used for HTTP authentication and CA emulation.
+    HttpCommandConfigPtr config_;
+};
+
+/// @brief Pointer to the @ref HttpCommandResponseCreator.
+typedef boost::shared_ptr<HttpCommandResponseCreator>
+    HttpCommandResponseCreatorPtr;
+
+} // end of namespace isc::config
+} // end of namespace isc
+
+#endif
diff --git a/src/lib/config/http_command_response_creator_factory.h b/src/lib/config/http_command_response_creator_factory.h
new file mode 100644 (file)
index 0000000..be8c134
--- /dev/null
@@ -0,0 +1,62 @@
+// Copyright (C) 2021-2024 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
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef HTTP_COMMAND_RESPONSE_CREATOR_FACTORY_H
+#define HTTP_COMMAND_RESPONSE_CREATOR_FACTORY_H
+
+#include <http/response_creator_factory.h>
+#include <config/http_command_config.h>
+#include <config/http_command_response_creator.h>
+
+namespace isc {
+namespace config {
+
+/// @brief HTTP response creator factory for HTTP control socket.
+///
+/// @param emulate_agent_response if true results for normal command
+/// outcomes are wrapped in Element::list.  This emulates responses
+/// generated by kea-ctrl-agent.  The value is passed into the
+/// HttpCommandResponseCreator when created. Defaults to true.
+///
+/// See the documentation of the @ref isc::http::HttpResponseCreatorFactory
+/// for the details how the response factory object is used by the
+/// @ref isc::http::HttpListener.
+///
+/// This class always returns the same instance of the
+/// @ref HttpCommandResponseCreator which @ref isc::http::HttpListener and
+/// @ref isc::http::HttpConnection classes use to generate HTTP response
+/// messages which comply with the formats required by the Control Agent.
+class HttpCommandResponseCreatorFactory : public http::HttpResponseCreatorFactory {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// Creates sole instance of the @ref HttpCommandResponseCreator object
+    /// returned by the @ref HttpCommandResponseCreatorFactory::create.
+    ///
+    /// @param config The HTTP control socket config.
+    HttpCommandResponseCreatorFactory(HttpCommandConfigPtr config)
+        : sole_creator_(new HttpCommandResponseCreator(config)) {
+    }
+
+    /// @brief Returns an instance of the @ref HttpCommandResponseCreator which
+    /// is used by HTTP server to generate responses to commands.
+    ///
+    /// @return Pointer to the @ref HttpCommandResponseCreator object.
+    virtual http::HttpResponseCreatorPtr create() const {
+        return (sole_creator_);
+    }
+
+private:
+
+    /// @brief Instance of the @ref HttpCommandResponseCreator returned.
+    http::HttpResponseCreatorPtr sole_creator_;
+};
+
+} // end of namespace isc::config
+} // end of namespace isc
+
+#endif
index eea8ff2237f4fd46eb2a3497dc14f0440b5934b2..1138b176b9275c7ec63d12ce7d8b8d7891bca6d7 100644 (file)
@@ -36,7 +36,7 @@ D2CfgContext::D2CfgContext()
       reverse_mgr_(new DdnsDomainListMgr("reverse-ddns")),
       keys_(new TSIGKeyInfoMap()),
       unix_control_socket_(ConstElementPtr()),
-      http_control_socket_(ConstElementPtr()) {
+      http_control_socket_(HttpCommandConfigPtr()) {
 }
 
 D2CfgContext::D2CfgContext(const D2CfgContext& rhs) : ConfigBase(rhs) {
@@ -106,8 +106,8 @@ D2CfgContext::toElement() const {
     if (!isNull(unix_control_socket_)) {
         control_sockets->add(UserContext::toElement(unix_control_socket_));
     }
-    if (!isNull(http_control_socket_)) {
-        control_sockets->add(UserContext::toElement(http_control_socket_));
+    if (http_control_socket_) {
+        control_sockets->add(http_control_socket_->toElement());
     }
     if (!control_sockets->empty()) {
         d2->set("control-sockets", control_sockets);
@@ -247,7 +247,7 @@ D2CfgMgr::getControlSocketInfo() {
     return (getD2CfgContext()->getControlSocketInfo());
 }
 
-const isc::data::ConstElementPtr
+isc::config::HttpCommandConfigPtr
 D2CfgMgr::getHttpControlSocketInfo() {
     return (getD2CfgContext()->getHttpControlSocketInfo());
 }
index 841684e7250bd75813c7cbefc0a266fc0af3356a..49838e03cd928c6f9ceff89a7bb1233a42b9cd55 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2021 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2024 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
@@ -9,6 +9,7 @@
 
 #include <asiolink/io_service.h>
 #include <cc/data.h>
+#include <config/http_command_config.h>
 #include <exceptions/exceptions.h>
 #include <d2srv/d2_config.h>
 #include <hooks/hooks_config.h>
@@ -104,14 +105,14 @@ public:
     }
 
     /// @brief Returns information about HTTP/HTTPS control socket
-    /// @return pointer to the Element that holds control-socket map
-    const isc::data::ConstElementPtr getHttpControlSocketInfo() const {
+    /// @return pointer to the HTTP/HTTPS control socket config
+    isc::config::HttpCommandConfigPtr getHttpControlSocketInfo() const {
         return (http_control_socket_);
     }
 
     /// @brief Sets information about the HTTP/HTTPS control socket
-    /// @param control_socket Element that holds control-socket map
-    void setHttpControlSocketInfo(const isc::data::ConstElementPtr& control_socket) {
+    /// @param control_socket HTTP/HTTPScontrol socket config
+    void setHttpControlSocketInfo(const isc::config::HttpCommandConfigPtr& control_socket) {
         http_control_socket_ = control_socket;
     }
 
@@ -157,8 +158,8 @@ private:
     /// @brief Pointer to the UNIX control-socket information.
     isc::data::ConstElementPtr unix_control_socket_;
 
-    /// @brief Pointer to the HTTP/HTTPS control-socket information.
-    isc::data::ConstElementPtr http_control_socket_;
+    /// @brief Pointer to the HTTP/HTTPS control socket configuration
+    isc::config::HttpCommandConfigPtr http_control_socket_;
 
     /// @brief Configured hooks libraries.
     isc::hooks::HooksConfig hooks_config_;
@@ -307,8 +308,8 @@ public:
 
     /// @brief Convenience method fetches information about
     /// HTTP/HTTPS control socket from context
-    /// @return pointer to the Element that holds control-socket map
-    const isc::data::ConstElementPtr getHttpControlSocketInfo();
+    /// @return pointer to the HTTP/HTTPS control socket config
+    isc::config::HttpCommandConfigPtr getHttpControlSocketInfo();
 
     /// @brief Returns configuration summary in the textual format.
     ///
index 4a6ae88d0406c1c4ba3a86981543e6d77d250edf..aa6573a076d72af733aa48abe348e89b0168d210 100644 (file)
@@ -309,7 +309,9 @@ void D2SimpleParser::parse(const D2CfgContextPtr& ctx,
                               " already configured");
                 }
                 seen_http = true;
-                ctx->setHttpControlSocketInfo(socket);
+                using namespace isc::config;
+                HttpCommandConfigPtr http_config(new HttpCommandConfig(socket));
+                ctx->setHttpControlSocketInfo(http_config);
             } else {
                 // Sanity check: not supposed to fail.
                 isc_throw(D2CfgError,
index 56e17509fcdf9c046247ec2b81ffa95fb3824427..eee5ab92eb42e2003d0eb02d36c19fde097cae37 100644 (file)
@@ -113,7 +113,9 @@ void ControlSocketsParser::parse(SrvConfig& srv_cfg, ConstElementPtr value) {
                           " already configured");
             }
             seen_http = true;
-            srv_cfg.setHttpControlSocketInfo(socket);
+            using namespace isc::config;
+            HttpCommandConfigPtr http_config(new HttpCommandConfig(socket));
+            srv_cfg.setHttpControlSocketInfo(http_config);
         } else {
             // Sanity check: not supposed to fail.
             isc_throw(DhcpConfigError,
index 57d6f1617cba05064abf61d7acf74652b7c92ee9..b6cc3a0fd0d3b5f9595fd13f6398cb5d536b9e7d 100644 (file)
@@ -898,8 +898,8 @@ SrvConfig::toElement() const {
     if (!isNull(unix_control_socket_)) {
         control_sockets->add(UserContext::toElement(unix_control_socket_));
     }
-    if (!isNull(http_control_socket_)) {
-        control_sockets->add(UserContext::toElement(http_control_socket_));
+    if (http_control_socket_) {
+        control_sockets->add(http_control_socket_->toElement());
     }
     if (!control_sockets->empty()) {
         dhcp->set("control-sockets", control_sockets);
index 6384b9601a810cad4e82ceea472b64317daa0791..50195a7bdb6fb100263eaf903f52814f402947d7 100644 (file)
@@ -30,6 +30,7 @@
 #include <cc/data.h>
 #include <cc/user_context.h>
 #include <cc/simple_parser.h>
+#include <config/http_command_config.h>
 #include <util/optional.h>
 #include <util/str.h>
 
@@ -546,15 +547,15 @@ public:
 
     /// @brief Returns information about HTTP/HTTPS control socket
     ///
-    /// @return pointer to the Element that holds control-socket map
-    const isc::data::ConstElementPtr getHttpControlSocketInfo() const {
+    /// @return pointer to the HTTP/HTTPS control socket config
+    isc::config::HttpCommandConfigPtr getHttpControlSocketInfo() const {
         return (http_control_socket_);
     }
 
     /// @brief Sets information about the HTTP/HTTPS control socket
     ///
-    /// @param control_socket Element that holds control-socket map
-    void setHttpControlSocketInfo(const isc::data::ConstElementPtr& control_socket) {
+    /// @param control_socket HTTP/HTTPScontrol socket config
+    void setHttpControlSocketInfo(const isc::config::HttpCommandConfigPtr& control_socket) {
         http_control_socket_ = control_socket;
     }
 
@@ -1193,8 +1194,8 @@ private:
     /// @brief Pointer to the UNIX control-socket information
     isc::data::ConstElementPtr unix_control_socket_;
 
-    /// @brief Pointer to the HTTP control-socket information
-    isc::data::ConstElementPtr http_control_socket_;
+    /// @brief Pointer to the HTTP/HTTPS control socket configuration
+    isc::config::HttpCommandConfigPtr http_control_socket_;
 
     /// @brief Pointer to the dhcp-queue-control information
     isc::data::ConstElementPtr dhcp_queue_control_;