"http-host": "127.0.0.1",
"http-port": 8000,
+ "basic-authentication-realm": "kea-control-agent",
+
+ // In basoc HTTP authentication
+ "basic-authentications":
+ [
+ {
+ "comment": "admin is authorized",
+ "user": "admin",
+ "password": "1234"
+ }
+ ],
// In control socket
"control-sockets":
// Another mandatory parameter is the HTTP port.
"http-port": 8000,
+ // An optional parameter is the basic HTTP authentication realm.
+ // Its default is "kea-control-agent".
+ "basic-authentication-realm": "kea-control-agent",
+
+ // This list specifies the user ids and passwords to use for
+ // basic HTTP authentication. If empty or not present any client
+ // is authorized.
+ "basic-authentications":
+ [
+ // This specifies an authorized client.
+ {
+ "comment": "admin is authorized",
+
+ // The user id must not be empty or contain the ':' character.
+ // It is a mandatory parameter.
+ "user": "admin",
+
+ // If password is not specified an empty password is used.
+ "password": "1234"
+ }
+ ],
+
// This map specifies where control channel of each server is configured
// to listen on. See 'control-socket' object in the respective
// servers. At this time the only supported socket type is "unix".
-// Copyright (C) 2016-2019 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-2020 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
namespace agent {
CtrlAgentCfgContext::CtrlAgentCfgContext()
- :http_host_(""), http_port_(0) {
+ : http_host_(""), http_port_(0), basic_auth_realm_("") {
}
CtrlAgentCfgContext::CtrlAgentCfgContext(const CtrlAgentCfgContext& orig)
// Then print the control-sockets
s << ctx->getControlSocketInfoSummary();
+ // @todo: add something if authentication is required
+
// Finally, print the hook libraries names
const isc::hooks::HookLibsCollection libs = ctx->getHooksConfig().get();
s << ", " << libs.size() << " lib(s):";
ca->set("http-host", Element::create(http_host_));
// Set http-port
ca->set("http-port", Element::create(static_cast<int64_t>(http_port_)));
+ // Set basic-authentication-realm
+ ca->set("basic-authentication-realm", Element::create(basic_auth_realm_));
// Set hooks-libraries
ca->set("hooks-libraries", hooks_config_.toElement());
// Set control-sockets
control_sockets->set(si->first, socket);
}
ca->set("control-sockets", control_sockets);
+ // @todo: Set authentication.
// Set Control-agent
ElementPtr result = Element::createMap();
result->set("Control-agent", ca);
-// Copyright (C) 2016-2018 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-2020 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
return (http_port_);
}
+ /// @brief Sets basic-authentication-realm parameter
+ ///
+ /// @param real Basic HTTP authentication realm
+ void setBasicAuthRealm(const std::string& realm) {
+ basic_auth_realm_ = realm;
+ }
+
+ /// @brief Returns basic-authentication-realm parameter
+ ///
+ /// @return Basic HTTP authentication realm.
+ std::string getBasicAuthRealm() const {
+ return (basic_auth_realm_);
+ }
+
/// @brief Returns non-const reference to configured hooks libraries.
///
/// @return non-const reference to configured hooks libraries.
/// TCP port the CA should listen on.
uint16_t http_port_;
+ /// Basic HTTP authentication realm.
+ std::string basic_auth_realm_;
+
/// @brief Configured hooks libraries.
isc::hooks::HooksConfig hooks_config_;
};
///
/// These are global Control Agent parameters.
const SimpleDefaults AgentSimpleParser::AGENT_DEFAULTS = {
- { "http-host", Element::string, "127.0.0.1"},
- { "http-port", Element::integer, "8000"}
+ { "http-host", Element::string, "127.0.0.1" },
+ { "http-port", Element::integer, "8000" },
+ { "basic-authentication-realm", Element::string, "kea-control-agent" }
};
/// @brief This table defines default values for control sockets.
// Let's get the HTTP parameters first.
ctx->setHttpHost(SimpleParser::getString(config, "http-host"));
ctx->setHttpPort(SimpleParser::getIntType<uint16_t>(config, "http-port"));
+ ctx->setBasicAuthRealm(SimpleParser::getString(config,
+ "basic-authentication-realm"));
// Control sockets are second.
ConstElementPtr ctrl_sockets = config->get("control-sockets");
}
}
+ // Basic HTTP authentications are third.
+
// User context can be done at anytime.
ConstElementPtr user_context = config->get("user-context");
if (user_context) {
libkea_http_la_SOURCES += response_json.cc response_json.h
libkea_http_la_SOURCES += url.cc url.h
libkea_http_la_SOURCES += basic_auth.cc basic_auth.h
+libkea_http_la_SOURCES += basic_auth_config.cc basic_auth_config.h
libkea_http_la_CXXFLAGS = $(AM_CXXFLAGS)
libkea_http_la_CPPFLAGS = $(AM_CPPFLAGS)
# Specify the headers for copying into the installation directory tree.
libkea_http_includedir = $(pkgincludedir)/http
libkea_http_include_HEADERS = \
+ basic_auth.h \
+ basic_auth_config.h \
client.h \
connection.h \
connection_pool.h \
response.h \
response_context.h \
response_creator.h \
+ response_creator_auth.h \
response_creator_factory.h \
response_json.h \
response_parser.h \
--- /dev/null
+// Copyright (C) 2020 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 <http/basic_auth_config.h>
+
+using namespace isc;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace std;
+
+namespace isc {
+namespace http {
+
+BasicHttpAuthClient::BasicHttpAuthClient(const std::string& user,
+ const std::string& password,
+ const isc::data::ConstElementPtr& user_context)
+ : user_(user), password_(password) {
+ if (user_context) {
+ setContext(user_context);
+ }
+}
+
+ElementPtr
+BasicHttpAuthClient::toElement() const {
+ ElementPtr result = Element::createMap();
+
+ // Set user-context
+ contextToElement(result);
+
+ // Set user
+ result->set("user", Element::create(user_));
+
+ // Set password
+ result->set("password", Element::create(password_));
+
+ return (result);
+}
+
+void
+BasicHttpAuthConfig::add(const std::string& user,
+ const std::string& password,
+ const ConstElementPtr& user_context) {
+ BasicHttpAuth basic_auth(user, password);
+ list_.push_back(BasicHttpAuthClient(user, password, user_context));
+ map_[basic_auth.getCredential()] = user;
+}
+
+void
+BasicHttpAuthConfig::clear() {
+ list_.clear();
+ map_.clear();
+}
+
+ElementPtr
+BasicHttpAuthConfig::toElement() const {
+ ElementPtr result = Element::createList();
+
+ for (auto client : list_) {
+ result->add(client.toElement());
+ }
+
+ return (result);
+}
+
+void
+BasicHttpAuthConfig::parse(const ConstElementPtr& config) {
+ if (!config) {
+ return;
+ }
+ if (config->getType() != Element::list) {
+ isc_throw(DhcpConfigError, "basic-authentications must be a list ("
+ << config->getPosition() << ")");
+ }
+ for (auto client : config->listValue()) {
+ if (client->getType() != Element::map) {
+ isc_throw(DhcpConfigError, "basic-authentications items must be "
+ << "maps (" << client->getPosition() << ")");
+ }
+
+ // user
+ ConstElementPtr user_cfg = client->get("user");
+ if (!user_cfg) {
+ isc_throw(DhcpConfigError, "user is required in "
+ << "basic-authentications items ("
+ << client->getPosition() << ")");
+ }
+ if (user_cfg->getType() != Element::string) {
+ isc_throw(DhcpConfigError, "user must be a string ("
+ << user_cfg->getPosition() << ")");
+ }
+ string user = user_cfg->stringValue();
+ if (user.empty()) {
+ isc_throw(DhcpConfigError, "user must be not be empty ("
+ << user_cfg->getPosition() << ")");
+ }
+ if (user.find(':') != string::npos) {
+ isc_throw(DhcpConfigError, "user must not contain a ':': '"
+ << user << "' (" << user_cfg->getPosition() << ")");
+ }
+
+ // password
+ string password;
+ ConstElementPtr password_cfg = client->get("password");
+ if (password_cfg) {
+ if (password_cfg->getType() != Element::string) {
+ isc_throw(DhcpConfigError, "password must be a string ("
+ << password_cfg->getPosition() << ")");
+ }
+ password = password_cfg->stringValue();
+ }
+
+ // user context
+ ConstElementPtr user_context = client->get("user-context");
+ if (user_context) {
+ if (user_context->getType() != Element::map) {
+ isc_throw(DhcpConfigError, "user-context must be a map ("
+ << user_context->getPosition() << ")");
+ }
+ }
+
+ // add it.
+ try {
+ add(user, password, user_context);
+ } catch (const std::exception& ex) {
+ isc_throw(DhcpConfigError, ex.what() << " ("
+ << client->getPosition() << ")");
+ }
+ }
+}
+
+} // end of namespace isc::http
+} // end of namespace isc
--- /dev/null
+// Copyright (C) 2020 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_BASIC_AUTH_CONFIG_H
+#define HTTP_BASIC_AUTH_CONFIG_H
+
+#include <cc/cfg_to_element.h>
+#include <cc/data.h>
+#include <cc/simple_parser.h>
+#include <cc/user_context.h>
+#include <http/basic_auth.h>
+#include <list>
+#include <unordered_map>
+
+namespace isc {
+namespace http {
+
+/// @brief Type of basic HTTP authentication credential and user id map.
+typedef std::unordered_map<std::string, std::string> BasicHttpAuthMap;
+
+/// @brief Basic HTTP authentication client configuration.
+class BasicHttpAuthClient : public isc::data::UserContext,
+ public isc::data::CfgToElement {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param user User id
+ /// @param password Password
+ /// @param user_context Optional user context
+ BasicHttpAuthClient(const std::string& user,
+ const std::string& password,
+ const isc::data::ConstElementPtr& user_context);
+
+ /// @brief Returns the user id.
+ const std::string& getUser() const {
+ return (user_);
+ }
+
+ /// @brief Returns the password.
+ const std::string& getPassword() const {
+ return (password_);
+ }
+
+ /// @brief Unparses basic HTTP authentication client configuration.
+ ///
+ /// @return A pointer to unparsed client configuration.
+ virtual isc::data::ElementPtr toElement() const;
+
+private:
+
+ /// @brief The user id.
+ std::string user_;
+
+ /// @brief The password.
+ std::string password_;
+};
+
+/// @brief Type of basic HTTP authentication client configuration list.
+typedef std::list<BasicHttpAuthClient> BasicHttpAuthClientList;
+
+/// @brief Basic HTTP authentication configuration.
+class BasicHttpAuthConfig : public isc::data::CfgToElement {
+public:
+
+ /// @brief Add a client configuration.
+ ///
+ /// @param user User id
+ /// @param password Password
+ /// @param user_context Optional user context
+ /// @throw BadValue if the user id contains the ':' character.
+ void add(const std::string& user,
+ const std::string& password,
+ const isc::data::ConstElementPtr& user_context = isc::data::ConstElementPtr());
+
+ /// @brief Clear configuration.
+ void clear();
+
+ /// @brief Returns the list of client configuration.
+ ///
+ /// @return List of basic HTTP authentication client configuration.
+ const BasicHttpAuthClientList& getClientList() const {
+ return (list_);
+ }
+
+ /// @brief Returns the credential and user id map.
+ ///
+ /// @return The basic HTTP authentication credential and user id map.
+ const BasicHttpAuthMap& getCredentialMap() const {
+ return (map_);
+ }
+
+ /// @brief Parses basic HTTP authentication configuration.
+ ///
+ /// @param config Element holding the basic HTTP authentication
+ /// configuration to be parsed.
+ /// @throw DhcpConfigError when the configuration is invalid.
+ void parse(const isc::data::ConstElementPtr& config);
+
+ /// @brief Unparses basic HTTP authentication configuration.
+ ///
+ /// @return A pointer to unparsed basic HTTP authentication configuration.
+ virtual isc::data::ElementPtr toElement() const;
+
+private:
+
+ /// @brief The list of basic HTTP authentication client configuration.
+ BasicHttpAuthClientList list_;
+
+ /// @brief The basic HTTP authentication credential and user id map.
+ BasicHttpAuthMap map_;
+};
+
+} // end of namespace isc::http
+} // end of namespace isc
+
+#endif // endif HTTP_BASIC_AUTH_CONFIG_H
#ifndef HTTP_RESPONSE_CREATOR_AUTH_H
#define HTTP_RESPONSE_CREATOR_AUTH_H
+#include <http/basic_auth_config.h>
#include <http/response_creator.h>
#include <string.h>
#include <unordered_map>
namespace isc {
namespace http {
-/// @brief Type of basic HTTP authentication credential and user id map.
-typedef std::unordered_map<std::string, std::string> BasicHttpAuthMap;
-
/// @brief Validate basic HTTP authentication.
///
/// @param creator The HTTP response creator.
TESTS += libhttp_unittests
libhttp_unittests_SOURCES = basic_auth_unittests.cc
+libhttp_unittests_SOURCES += basic_auth_config_unittests.cc
libhttp_unittests_SOURCES += connection_pool_unittests.cc
libhttp_unittests_SOURCES += date_time_unittests.cc
libhttp_unittests_SOURCES += http_header_unittests.cc
--- /dev/null
+// Copyright (C) 2020 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 <http/basic_auth_config.h>
+#include <testutils/gtest_utils.h>
+#include <testutils/test_to_element.h>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::http;
+using namespace isc::test;
+using namespace std;
+
+namespace {
+
+// Test that basic auth client works as expected.
+TEST(BasicHttpAuthClientTest, basic) {
+ // Create a client.
+ ConstElementPtr ctx = Element::fromJSON("{ \"foo\": \"bar\" }");
+ BasicHttpAuthClient client("foo", "bar", ctx);
+
+ // Check it.
+ EXPECT_EQ("foo", client.getUser());
+ EXPECT_EQ("bar", client.getPassword());
+ EXPECT_TRUE(ctx->equals(*client.getContext()));
+
+ // Check toElement.
+ ElementPtr expected = Element::createMap();
+ expected->set("user", Element::create(string("foo")));
+ expected->set("password", Element::create(string("bar")));
+ expected->set("user-context", ctx);
+ runToElementTest<BasicHttpAuthClient>(expected, client);
+}
+
+// Test that basic auth configuration works as expected.
+TEST(BasicHttpAuthConfigTest, basic) {
+ // Create a configuration.
+ BasicHttpAuthConfig config;
+
+ // Initial configuration is empty.
+ EXPECT_TRUE(config.getClientList().empty());
+ EXPECT_TRUE(config.getCredentialMap().empty());
+
+ // Add rejects user id with embedded ':'.
+ EXPECT_THROW(config.add("foo:", "bar"), BadValue);
+
+ // Add a client.
+ ConstElementPtr ctx = Element::fromJSON("{ \"foo\": \"bar\" }");
+ EXPECT_NO_THROW(config.add("foo", "bar", ctx));
+
+ // Check the client.
+ ASSERT_EQ(1, config.getClientList().size());
+ const BasicHttpAuthClient& client = config.getClientList().front();
+ EXPECT_EQ("foo", client.getUser());
+ EXPECT_EQ("bar", client.getPassword());
+ EXPECT_TRUE(ctx->equals(*client.getContext()));
+
+ // Check the credential.
+ ASSERT_NE(0, config.getCredentialMap().count("Zm9vOmJhcg=="));
+ string user;
+ EXPECT_NO_THROW(user = config.getCredentialMap().at("Zm9vOmJhcg=="));
+ EXPECT_EQ("foo", user);
+
+ // Check toElement.
+ ElementPtr expected = Element::createList();
+ ElementPtr elem = Element::createMap();
+ elem->set("user", Element::create(string("foo")));
+ elem->set("password", Element::create(string("bar")));
+ elem->set("user-context", ctx);
+ expected->add(elem);
+ runToElementTest<BasicHttpAuthConfig>(expected, config);
+
+ // Add a second client and test it.
+ EXPECT_NO_THROW(config.add("test", "123\xa3"));
+ ASSERT_EQ(2, config.getClientList().size());
+ EXPECT_EQ("foo", config.getClientList().front().getUser());
+ EXPECT_EQ("test", config.getClientList().back().getUser());
+ ASSERT_NE(0, config.getCredentialMap().count("dGVzdDoxMjPCow=="));
+
+ // Check clear.
+ config.clear();
+ expected = Element::createList();
+ runToElementTest<BasicHttpAuthConfig>(expected, config);
+
+ // Add clients again.
+ EXPECT_NO_THROW(config.add("test", "123\xa3"));
+ EXPECT_NO_THROW(config.add("foo", "bar", ctx));
+
+ // Check that toElement keeps add order.
+ ElementPtr elem0 = Element::createMap();
+ elem0->set("user", Element::create(string("test")));
+ elem0->set("password", Element::create(string("123\xa3")));
+ expected->add(elem0);
+ expected->add(elem);
+ runToElementTest<BasicHttpAuthConfig>(expected, config);
+}
+
+// Test that basic auth configuration parses.
+TEST(BasicHttpAuthConfigTest, parse) {
+ BasicHttpAuthConfig config;
+ ElementPtr cfg;
+
+ // No config is accepted.
+ EXPECT_NO_THROW(config.parse(cfg));
+ EXPECT_TRUE(config.getClientList().empty());
+ EXPECT_TRUE(config.getCredentialMap().empty());
+ runToElementTest<BasicHttpAuthConfig>(Element::createList(), config);
+
+ // The config must be a list.
+ cfg = Element::createMap();
+ EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+ "basic-authentications must be a list (:0:0)");
+
+ // The client config must be a map.
+ cfg = Element::createList();
+ ElementPtr client_cfg = Element::createList();
+ cfg->add(client_cfg);
+ EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+ "basic-authentications items must be maps (:0:0)");
+
+ // The user parameter is mandatory in client config.
+ client_cfg = Element::createMap();
+ cfg = Element::createList();
+ cfg->add(client_cfg);
+ EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+ "user is required in basic-authentications items (:0:0)");
+
+ // The user parameter must be a string.
+ ElementPtr user_cfg = Element::create(1);
+ client_cfg = Element::createMap();
+ client_cfg->set("user", user_cfg);
+ cfg = Element::createList();
+ cfg->add(client_cfg);
+ EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+ "user must be a string (:0:0)");
+
+ // The user parameter must not be empty.
+ user_cfg = Element::create(string(""));
+ client_cfg = Element::createMap();
+ client_cfg->set("user", user_cfg);
+ cfg = Element::createList();
+ cfg->add(client_cfg);
+ EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+ "user must be not be empty (:0:0)");
+
+ // The user parameter must not contain ':'.
+ user_cfg = Element::create(string("foo:bar"));
+ client_cfg = Element::createMap();
+ client_cfg->set("user", user_cfg);
+ cfg = Element::createList();
+ cfg->add(client_cfg);
+ EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+ "user must not contain a ':': 'foo:bar' (:0:0)");
+
+ // Password is not required.
+ user_cfg = Element::create(string("foo"));
+ client_cfg = Element::createMap();
+ client_cfg->set("user", user_cfg);
+ cfg = Element::createList();
+ cfg->add(client_cfg);
+ EXPECT_NO_THROW(config.parse(cfg));
+ ASSERT_EQ(1, config.getClientList().size());
+ EXPECT_EQ("", config.getClientList().front().getPassword());
+ config.clear();
+
+ // The password parameter must be a string.
+ ElementPtr password_cfg = Element::create(1);
+ client_cfg = Element::createMap();
+ client_cfg->set("user", user_cfg);
+ client_cfg->set("password", password_cfg);
+ cfg = Element::createList();
+ cfg->add(client_cfg);
+ EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+ "password must be a string (:0:0)");
+
+ // Empty password is accepted.
+ password_cfg = Element::create(string(""));
+ client_cfg = Element::createMap();
+ client_cfg->set("user", user_cfg);
+ client_cfg->set("password", password_cfg);
+ cfg = Element::createList();
+ cfg->add(client_cfg);
+ EXPECT_NO_THROW(config.parse(cfg));
+ ASSERT_EQ(1, config.getClientList().size());
+ EXPECT_EQ("", config.getClientList().front().getPassword());
+ config.clear();
+
+ // User context must be a map.
+ password_cfg = Element::create(string("bar"));
+ ElementPtr ctx = Element::createList();
+ client_cfg = Element::createMap();
+ client_cfg->set("user", user_cfg);
+ client_cfg->set("password", password_cfg);
+ client_cfg->set("user-context", ctx);
+ cfg = Element::createList();
+ cfg->add(client_cfg);
+ EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError,
+ "user-context must be a map (:0:0)");
+
+ // Check a working not empty config.
+ ctx = Element::fromJSON("{ \"foo\": \"bar\" }");
+ client_cfg = Element::createMap();
+ client_cfg->set("user", user_cfg);
+ client_cfg->set("password", password_cfg);
+ client_cfg->set("user-context", ctx);
+ cfg = Element::createList();
+ cfg->add(client_cfg);
+ EXPECT_NO_THROW(config.parse(cfg));
+ runToElementTest<BasicHttpAuthConfig>(cfg, config);
+}
+
+} // end of anonymous namespace