#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>
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);
#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>
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());
" }\n"
" }\n"
" ],\n"
+" \"directory\": \"\",\n"
+" \"realm\": \"\",\n"
" \"type\": \"basic\",\n"
" \"user-context\": {\n"
" \"comment\": \"basic HTTP authentication\"\n"
#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>
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());
" }\n"
" }\n"
" ],\n"
+" \"directory\": \"\",\n"
+" \"realm\": \"\",\n"
" \"type\": \"basic\",\n"
" \"user-context\": {\n"
" \"comment\": \"basic HTTP authentication\"\n"
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
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
boost::shared_ptr<CommandMgrImpl> impl_;
};
-}; // end of isc::config namespace
-}; // end of isc namespace
-
+} // end of isc::config namespace
+} // end of isc namespace
#endif
--- /dev/null
+// 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
--- /dev/null
+// 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
--- /dev/null
+// 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
--- /dev/null
+// 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
--- /dev/null
+// 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
--- /dev/null
+// 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
--- /dev/null
+// 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
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) {
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);
return (getD2CfgContext()->getControlSocketInfo());
}
-const isc::data::ConstElementPtr
+isc::config::HttpCommandConfigPtr
D2CfgMgr::getHttpControlSocketInfo() {
return (getD2CfgContext()->getHttpControlSocketInfo());
}
-// 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
#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>
}
/// @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;
}
/// @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_;
/// @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.
///
" 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,
" 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,
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);
#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>
/// @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;
}
/// @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_;