2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-08-31 05:55:28 +00:00

[#1304] Added config/parse

This commit is contained in:
Francis Dupont
2020-07-12 14:58:32 +02:00
parent b7f239885d
commit 0fc1cd514d
11 changed files with 546 additions and 8 deletions

View File

@@ -10,6 +10,17 @@
"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":

View File

@@ -11,6 +11,28 @@
// 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".

View File

@@ -1,4 +1,4 @@
// 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
@@ -21,7 +21,7 @@ namespace isc {
namespace agent {
CtrlAgentCfgContext::CtrlAgentCfgContext()
:http_host_(""), http_port_(0) {
: http_host_(""), http_port_(0), basic_auth_realm_("") {
}
CtrlAgentCfgContext::CtrlAgentCfgContext(const CtrlAgentCfgContext& orig)
@@ -50,6 +50,8 @@ CtrlAgentCfgMgr::getConfigSummary(const uint32_t /*selection*/) {
// 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):";
@@ -152,6 +154,8 @@ CtrlAgentCfgContext::toElement() const {
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
@@ -161,6 +165,7 @@ CtrlAgentCfgContext::toElement() const {
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);

View File

@@ -1,4 +1,4 @@
// 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
@@ -98,6 +98,20 @@ public:
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.
@@ -147,6 +161,9 @@ private:
/// 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_;
};

View File

@@ -36,8 +36,9 @@ namespace agent {
///
/// 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.
@@ -88,6 +89,8 @@ AgentSimpleParser::parse(const CtrlAgentCfgContextPtr& ctx,
// 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");
@@ -98,6 +101,8 @@ AgentSimpleParser::parse(const CtrlAgentCfgContextPtr& ctx,
}
}
// Basic HTTP authentications are third.
// User context can be done at anytime.
ConstElementPtr user_context = config->get("user-context");
if (user_context) {

View File

@@ -40,6 +40,7 @@ libkea_http_la_SOURCES += response_creator_factory.h
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)
@@ -90,6 +91,8 @@ endif
# 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 \
@@ -112,6 +115,7 @@ libkea_http_include_HEADERS = \
response.h \
response_context.h \
response_creator.h \
response_creator_auth.h \
response_creator_factory.h \
response_json.h \
response_parser.h \

View File

@@ -0,0 +1,137 @@
// 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

View File

@@ -0,0 +1,120 @@
// 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

View File

@@ -7,6 +7,7 @@
#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>
@@ -14,9 +15,6 @@
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.

View File

@@ -21,6 +21,7 @@ if HAVE_GTEST
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

View File

@@ -0,0 +1,218 @@
// 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