"name": "server3",
"url": "http://192.168.56.99:8000/",
"role": "backup",
+ "basic-auth-user": "foo",
+ "basic-auth-password": "bar",
"auto-failover": false
}]
}]
this example, there is one backup server which receives lease updates
from the active servers.
+Since Kea version 1.7.10 the basic HTTP authentication is available
+to protect the Kea control agent against local attackers.
+
These are the parameters specified for each of the peers within this
list:
the control channel. Other servers use this URL to send control
commands to that server.
+- ``basic-auth-user`` - specifies the user id for basic HTTP
+ authentication. If not specified or specified to the empty string
+ no authentication header will be added to HTTP transactions.
+ Must not contain the colon (:) character.
+
+- ``basic-auth-password`` - specifies the password for basic HTTP
+ authentication. Ignored when the user id is not specified or empty.
+ The password is optional: if not specified an empty password is used.
+
- ``role`` - denotes the role of the server in the HA setup. The
following roles are supported in the load-balancing configuration:
``primary``, ``secondary``, and ``backup``. There must be exactly one
is appropriate for the received query. In other words, if the query
would normally be processed by ``server2`` but this server is not
available, ``server1`` will allocate the lease from the pool of
-"192.0.3.200 - 192.0.3.250".
+"192.0.3.200 - 192.0.3.250". The Kea control agent in front of the
+``server3`` requires basic HTTP authentication and authorizes the
+user id "foo" with the password "bar".
.. _ha-load-balancing-advanced-config:
}, {
"name": "server3",
"url": "http://192.168.56.99:8000/",
+ "basic-auth-user": "foo",
+ "basic-auth-password": "bar",
"role": "backup",
"auto-failover": false
}]
}, {
"name": "server3",
"url": "http://192.168.56.99:8000/",
+ "basic-auth-user": "foo",
+ "basic-auth-password": "bar",
"role": "backup"
}]
}]
}
}
+Since Kea version 1.7.10 the basic HTTP authentication is supported.
+
.. _ha-maintenance:
Controlled Shutdown and Maintenance of DHCP servers
#include <ha_service_states.h>
#include <sstream>
+using namespace isc::http;
using namespace isc::util;
namespace isc {
namespace ha {
HAConfig::PeerConfig::PeerConfig()
- : name_(), url_(""), role_(STANDBY), auto_failover_(false) {
+ : name_(), url_(""), role_(STANDBY), auto_failover_(false),
+ basic_auth_() {
}
void
return ("");
}
+void
+HAConfig::PeerConfig::addBasicAuthHttpHeader(PostHttpRequestJsonPtr request) const {
+ const BasicHttpAuthPtr& auth = getBasicAuth();
+ if (!request || !auth) {
+ return;
+ }
+ request->context()->headers_.push_back(BasicAuthHttpHeaderContext(*auth));
+}
+
HAConfig::StateConfig::StateConfig(const int state)
: state_(state), pausing_(STATE_PAUSE_NEVER) {
}
#define HA_CONFIG_H
#include <exceptions/exceptions.h>
+#include <http/basic_auth.h>
+#include <http/post_request_json.h>
#include <http/url.h>
#include <util/state_model.h>
#include <boost/shared_ptr.hpp>
auto_failover_ = auto_failover;
}
- private:
+ /// @brief Returns non-const basic HTTP authentication.
+ http::BasicHttpAuthPtr& getBasicAuth() {
+ return (basic_auth_);
+ }
+
+ /// @brief Returns const basic HTTP authentication.
+ const http::BasicHttpAuthPtr& getBasicAuth() const {
+ return (basic_auth_);
+ }
+
+ /// @brief Adds a basic HTTP authentication header to a request.
+ void addBasicAuthHttpHeader(http::PostHttpRequestJsonPtr request) const;
- std::string name_; ///< Server name.
- http::Url url_; ///< Server URL.
- Role role_; ///< Server role.
- bool auto_failover_; ///< Auto failover state.
+ private:
+ std::string name_; ///< Server name.
+ http::Url url_; ///< Server URL.
+ Role role_; ///< Server role.
+ bool auto_failover_; ///< Auto failover state.
+ http::BasicHttpAuthPtr basic_auth_; ///< Basic HTTP authentication.
};
/// @brief Pointer to the server's configuration.
// Auto failover configuration.
cfg->setAutoFailover(getBoolean(*p, "auto-failover"));
+
+ // Basic HTTP authentication password.
+ std::string password;
+ if ((*p)->contains("basic-auth-password")) {
+ password = getString((*p), "basic-auth-password");
+ }
+
+ // Basic HTTP authentication user.
+ if ((*p)->contains("basic-auth-user")) {
+ std::string user = getString((*p), "basic-auth-user");
+ BasicHttpAuthPtr& auth = cfg->getBasicAuth();
+ try {
+ if (!user.empty()) {
+ // Validate the user id value.
+ auth.reset(new BasicHttpAuth(user, password));
+ }
+ } catch (const std::exception& ex) {
+ isc_throw(dhcp::DhcpConfigError, ex.what() << " in peer '"
+ << cfg->getName() << "'");
+ }
+ }
}
// Per state configuration is optional.
PostHttpRequestJsonPtr request = boost::make_shared<PostHttpRequestJson>
(HttpRequest::Method::HTTP_POST, "/", HttpVersion::HTTP_11(),
HostHttpHeader(config->getUrl().getHostname()));
+ config->addBasicAuthHttpHeader(request);
request->setBodyAsJson(command);
request->finalize();
PostHttpRequestJsonPtr request = boost::make_shared<PostHttpRequestJson>
(HttpRequest::Method::HTTP_POST, "/", HttpVersion::HTTP_11(),
HostHttpHeader(partner_config->getUrl().getHostname()));
+ partner_config->addBasicAuthHttpHeader(request);
request->setBodyAsJson(CommandCreator::createHeartbeat(server_type_));
request->finalize();
(HttpRequest::Method::HTTP_POST, "/", HttpVersion::HTTP_11(),
HostHttpHeader(remote_config->getUrl().getHostname()));
+ remote_config->addBasicAuthHttpHeader(request);
request->setBodyAsJson(CommandCreator::createDHCPDisable(max_period,
server_type_));
request->finalize();
PostHttpRequestJsonPtr request = boost::make_shared<PostHttpRequestJson>
(HttpRequest::Method::HTTP_POST, "/", HttpVersion::HTTP_11(),
HostHttpHeader(remote_config->getUrl().getHostname()));
+ remote_config->addBasicAuthHttpHeader(request);
request->setBodyAsJson(CommandCreator::createDHCPEnable(server_type_));
request->finalize();
PostHttpRequestJsonPtr request = boost::make_shared<PostHttpRequestJson>
(HttpRequest::Method::HTTP_POST, "/", HttpVersion::HTTP_11(),
HostHttpHeader(partner_config->getUrl().getHostname()));
+ partner_config->addBasicAuthHttpHeader(request);
if (server_type_ == HAServerType::DHCPv4) {
request->setBodyAsJson(CommandCreator::createLease4GetPage(
boost::dynamic_pointer_cast<Lease4>(last_lease), config_->getSyncPageLimit()));
PostHttpRequestJsonPtr request = boost::make_shared<PostHttpRequestJson>
(HttpRequest::Method::HTTP_POST, "/", HttpVersion::HTTP_11(),
HostHttpHeader(remote_config->getUrl().getHostname()));
+ remote_config->addBasicAuthHttpHeader(request);
request->setBodyAsJson(CommandCreator::createMaintenanceNotify(false, server_type_));
request->finalize();
PostHttpRequestJsonPtr request = boost::make_shared<PostHttpRequestJson>
(HttpRequest::Method::HTTP_POST, "/", HttpVersion::HTTP_11(),
HostHttpHeader(remote_config->getUrl().getHostname()));
+ remote_config->addBasicAuthHttpHeader(request);
request->setBodyAsJson(CommandCreator::createMaintenanceNotify(true, server_type_));
request->finalize();
" \"name\": \"server1\","
" \"url\": \"http://127.0.0.1:8080/\","
" \"role\": \"primary\","
+ " \"basic-auth-password\": \"1234\","
" \"auto-failover\": false"
" },"
" {"
" \"name\": \"server2\","
" \"url\": \"http://127.0.0.1:8081/\","
+ " \"basic-auth-user\": \"\","
" \"role\": \"secondary\""
" },"
" {"
" \"name\": \"server3\","
" \"url\": \"http://127.0.0.1:8082/\","
+ " \"basic-auth-user\": \"foo\","
+ " \"basic-auth-password\": \"bar\","
" \"role\": \"backup\","
" \"auto-failover\": false"
" }"
EXPECT_EQ(cfg->getLogLabel(), "server1 (http://127.0.0.1:8080/)");
EXPECT_EQ(HAConfig::PeerConfig::PRIMARY, cfg->getRole());
EXPECT_FALSE(cfg->isAutoFailover());
+ EXPECT_FALSE(cfg->getBasicAuth());
cfg = impl->getConfig()->getPeerConfig("server2");
ASSERT_TRUE(cfg);
EXPECT_EQ(cfg->getLogLabel(), "server2 (http://127.0.0.1:8081/)");
EXPECT_EQ(HAConfig::PeerConfig::SECONDARY, cfg->getRole());
EXPECT_TRUE(cfg->isAutoFailover());
+ EXPECT_FALSE(cfg->getBasicAuth());
cfg = impl->getConfig()->getPeerConfig("server3");
ASSERT_TRUE(cfg);
EXPECT_EQ(cfg->getLogLabel(), "server3 (http://127.0.0.1:8082/)");
EXPECT_EQ(HAConfig::PeerConfig::BACKUP, cfg->getRole());
EXPECT_FALSE(cfg->isAutoFailover());
+ ASSERT_TRUE(cfg->getBasicAuth());
+ EXPECT_EQ("foo:bar", cfg->getBasicAuth()->getSecret());
// Verify that per-state configuration is correct.x
" {"
" \"name\": \"server1\","
" \"url\": \"http://127.0.0.1:8080/\","
+ " \"basic-auth-user\": \"admin\","
" \"role\": \"primary\","
" \"auto-failover\": false"
" },"
EXPECT_EQ("http://127.0.0.1:8080/", cfg->getUrl().toText());
EXPECT_EQ(HAConfig::PeerConfig::PRIMARY, cfg->getRole());
EXPECT_FALSE(cfg->isAutoFailover());
+ ASSERT_TRUE(cfg->getBasicAuth());
+ EXPECT_EQ("admin:", cfg->getBasicAuth()->getSecret());
cfg = impl->getConfig()->getPeerConfig("server2");
ASSERT_TRUE(cfg);
EXPECT_EQ("http://127.0.0.1:8081/", cfg->getUrl().toText());
EXPECT_EQ(HAConfig::PeerConfig::STANDBY, cfg->getRole());
EXPECT_TRUE(cfg->isAutoFailover());
+ EXPECT_FALSE(cfg->getBasicAuth());
cfg = impl->getConfig()->getPeerConfig("server3");
ASSERT_TRUE(cfg);
EXPECT_EQ("http://127.0.0.1:8082/", cfg->getUrl().toText());
EXPECT_EQ(HAConfig::PeerConfig::BACKUP, cfg->getRole());
EXPECT_FALSE(cfg->isAutoFailover());
+ EXPECT_FALSE(cfg->getBasicAuth());
HAConfig::StateConfigPtr state_cfg;
ASSERT_NO_THROW(state_cfg = impl->getConfig()->getStateMachineConfig()->
" {"
" \"name\": \"server3\","
" \"url\": \"http://127.0.0.1:8082/\","
+ " \"basic-auth-user\": \"test\","
+ " \"basic-auth-password\": \"123\\u00a3\","
" \"role\": \"backup\""
" }"
" ]"
EXPECT_EQ("server1", cfg->getName());
EXPECT_EQ("http://127.0.0.1:8080/", cfg->getUrl().toText());
EXPECT_EQ(HAConfig::PeerConfig::PRIMARY, cfg->getRole());
+ EXPECT_FALSE(cfg->getBasicAuth());
cfg = impl->getConfig()->getPeerConfig("server2");
ASSERT_TRUE(cfg);
EXPECT_EQ("server2", cfg->getName());
EXPECT_EQ("http://127.0.0.1:8081/", cfg->getUrl().toText());
EXPECT_EQ(HAConfig::PeerConfig::BACKUP, cfg->getRole());
+ EXPECT_FALSE(cfg->getBasicAuth());
cfg = impl->getConfig()->getPeerConfig("server3");
ASSERT_TRUE(cfg);
EXPECT_EQ("server3", cfg->getName());
EXPECT_EQ("http://127.0.0.1:8082/", cfg->getUrl().toText());
EXPECT_EQ(HAConfig::PeerConfig::BACKUP, cfg->getRole());
+ ASSERT_TRUE(cfg->getBasicAuth());
+ EXPECT_EQ("dGVzdDoxMjPCow==", cfg->getBasicAuth()->getCredential());
}
// This server name must not be empty.
"primary server required in the passive backup configuration");
}
+// Test that empty name id is forbidden for basic HTTP authentication.
+TEST_F(HAConfigTest, invalidUser) {
+ testInvalidConfig(
+ "["
+ " {"
+ " \"this-server-name\": \"server1\","
+ " \"mode\": \"load-balancing\","
+ " \"peers\": ["
+ " {"
+ " \"name\": \"server1\","
+ " \"url\": \"http://127.0.0.1:8080/\","
+ " \"role\": \"primary\","
+ " \"auto-failover\": false"
+ " },"
+ " {"
+ " \"name\": \"server2\","
+ " \"url\": \":http//127.0.0.1:8080/\","
+ " \"basic-auth-user\": \"foo:bar\","
+ " \"role\": \"secondary\","
+ " \"auto-failover\": true"
+ " }"
+ " ]"
+ " }"
+ "]",
+ "user 'foo:bar' must not contain a ':' in peer 'server2'");
+}
+
// Test that conversion of the role names works correctly.
TEST_F(HAConfigTest, stringToRole) {