2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-08-30 21:45:37 +00:00

[#3694] apply tls settings on reload

This commit is contained in:
Razvan Becheriu
2025-01-08 20:53:31 +02:00
parent 6fcbddce6f
commit 1334b83c8a
18 changed files with 1932 additions and 84 deletions

View File

@@ -55,7 +55,7 @@ address and port over a TLS channel.
% CTRL_AGENT_HTTP_SERVICE_REUSED reused HTTP service bound to address %1:%2
This informational message indicates that the server has reused existing
HTTPS service on the specified address and port.
HTTP service on the specified address and port.
% CTRL_AGENT_HTTP_SERVICE_STARTED HTTP service bound to address %1:%2
This informational message indicates that the server has started HTTP service

View File

@@ -139,14 +139,31 @@ CtrlAgentProcess::configure(isc::data::ConstElementPtr config_set,
// Search for the specific connection and reuse the existing one if found.
auto it = sockets_.find(std::make_pair(server_address, server_port));
if (it != sockets_.end()) {
auto listener = getHttpListener();
auto listener = it->second->listener_;
if (listener) {
// Reconfig keeping the same address and port.
if (listener->getTlsContext()) {
LOG_INFO(agent_logger, CTRL_AGENT_HTTPS_SERVICE_REUSED)
.arg(server_address.toText())
.arg(server_port);
} else {
if (ctx->getTrustAnchor().empty()) {
// Can not switch from HTTPS to HTTP
LOG_INFO(agent_logger, CTRL_AGENT_HTTPS_SERVICE_REUSED)
.arg(server_address.toText())
.arg(server_port);
} else {
// Apply TLS settings each time.
TlsContextPtr tls_context;
TlsContext::configure(tls_context,
TlsRole::SERVER,
ctx->getTrustAnchor(),
ctx->getCertFile(),
ctx->getKeyFile(),
ctx->getCertRequired());
// Overwrite the authentication setup and the http headers in the response creator config.
it->second->config_->setAuthConfig(ctx->getAuthConfig());
it->second->config_->setHttpHeaders(ctx->getHttpHeaders());
getIOService()->post([listener, tls_context]() { listener->setTlsContext(tls_context); });
}
} else if (!ctx->getTrustAnchor().empty()) {
// Can not switch from HTTP to HTTPS
LOG_INFO(agent_logger, CTRL_AGENT_HTTP_SERVICE_REUSED)
.arg(server_address.toText())
.arg(server_port);

View File

@@ -30,6 +30,7 @@ AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/bin/agent/tests\"
AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
AM_CPPFLAGS += -DCFG_EXAMPLES=\"$(abs_top_srcdir)/doc/examples/agent\"
AM_CPPFLAGS += -DSYNTAX_FILE=\"$(abs_srcdir)/../agent_parser.yy\"
AM_CPPFLAGS += -DTEST_CA_DIR=\"$(abs_top_srcdir)/src/lib/asiolink/testutils/ca\"
AM_CXXFLAGS = $(KEA_CXXFLAGS)

View File

@@ -21,6 +21,7 @@
#include <unistd.h>
using namespace isc::asiolink;
using namespace isc::asiolink::test;
using namespace isc::agent;
using namespace isc::data;
@@ -402,7 +403,7 @@ TEST_F(CtrlAgentControllerTest, unsuccessfulConfigUpdate) {
// Tests that it is possible to update the configuration in such a way that the
// listener configuration remains the same. The server should continue using the
// listener instance it has been using prior to the reconfiguration.
TEST_F(CtrlAgentControllerTest, noListenerChange) {
TEST_F(CtrlAgentControllerTest, noListenerChangeHttp) {
// This configuration should be used to override the initial configuration.
const char* second_config =
"{"
@@ -420,6 +421,8 @@ TEST_F(CtrlAgentControllerTest, noListenerChange) {
" }"
"}";
const HttpListener* listener_ptr = 0;
// This check callback is called before the shutdown.
auto check_callback = [&] {
CtrlAgentProcessPtr process = getCtrlAgentProcess();
@@ -428,6 +431,8 @@ TEST_F(CtrlAgentControllerTest, noListenerChange) {
// Check that the HTTP listener still exists after reconfiguration.
ConstHttpListenerPtr listener = process->getHttpListener();
ASSERT_TRUE(listener);
ASSERT_EQ(listener_ptr, listener.get());
ASSERT_FALSE(listener->getTlsContext());
EXPECT_TRUE(process->isListening());
EXPECT_EQ("127.0.0.1", listener->getLocalAddress().toText());
@@ -439,6 +444,16 @@ TEST_F(CtrlAgentControllerTest, noListenerChange) {
// Schedule SIGHUP signal to trigger reconfiguration.
TimedSignal sighup(getIOService(), SIGHUP, 200);
IntervalTimer timer(getIOService());
timer.setup([&] {
CtrlAgentProcessPtr process = getCtrlAgentProcess();
ASSERT_TRUE(process);
ConstHttpListenerPtr listener = process->getHttpListener();
ASSERT_TRUE(listener);
listener_ptr = listener.get();
ASSERT_FALSE(listener->getTlsContext());
}, 50, IntervalTimer::ONE_SHOT);
// Start the server.
time_duration elapsed_time;
runWithConfig(valid_agent_config, 500,
@@ -463,6 +478,292 @@ TEST_F(CtrlAgentControllerTest, noListenerChange) {
EXPECT_FALSE(process->isListening());
}
// Tests that it is possible to update the configuration in such a way that the
// listener configuration remains the same. The server should continue using the
// listener instance it has been using prior to the reconfiguration.
TEST_F(CtrlAgentControllerTest, noListenerChangeHttps) {
// This configuration should be used to override the initial configuration.
string ca_dir(string(TEST_CA_DIR));
ostringstream agent_st;
agent_st << "{"
<< " \"http-host\": \"127.0.0.1\","
<< " \"http-port\": 8081,"
<< " \"trust-anchor\": \"" << ca_dir << "/kea-ca.crt\", \n"
<< " \"cert-file\": \"" << ca_dir << "/kea-server.crt\", \n"
<< " \"key-file\": \"" << ca_dir << "/kea-server.key\", \n"
<< " \"control-sockets\": {"
<< " \"dhcp4\": {"
<< " \"socket-type\": \"unix\","
<< " \"socket-name\": \"/first/dhcp4/socket\""
<< " },"
<< " \"dhcp6\": {"
<< " \"socket-type\": \"unix\","
<< " \"socket-name\": \"/first/dhcp6/socket\""
<< " }"
<< " }"
<< "}";
ostringstream second_config_st;
second_config_st << "{"
<< " \"http-host\": \"127.0.0.1\","
<< " \"http-port\": 8081,"
<< " \"trust-anchor\": \"" << ca_dir << "/kea-ca.crt\", \n"
<< " \"cert-file\": \"" << ca_dir << "/kea-server.crt\", \n"
<< " \"key-file\": \"" << ca_dir << "/kea-server.key\", \n"
<< " \"control-sockets\": {"
<< " \"dhcp4\": {"
<< " \"socket-type\": \"unix\","
<< " \"socket-name\": \"/second/dhcp4/socket\""
<< " },"
<< " \"dhcp6\": {"
<< " \"socket-type\": \"unix\","
<< " \"socket-name\": \"/second/dhcp6/socket\""
<< " }"
<< " }"
<< "}";
const HttpListener* listener_ptr = 0;
TlsContext* context = 0;
// This check callback is called before the shutdown.
auto check_callback = [&] {
CtrlAgentProcessPtr process = getCtrlAgentProcess();
ASSERT_TRUE(process);
// Check that the HTTP listener still exists after reconfiguration.
ConstHttpListenerPtr listener = process->getHttpListener();
ASSERT_TRUE(listener);
ASSERT_EQ(listener_ptr, listener.get());
ASSERT_TRUE(listener->getTlsContext());
// The TLS settings have been applied
ASSERT_NE(context, listener->getTlsContext().get());
EXPECT_TRUE(process->isListening());
EXPECT_EQ("127.0.0.1", listener->getLocalAddress().toText());
EXPECT_EQ(8081, listener->getLocalPort());
};
// Schedule reconfiguration.
scheduleTimedWrite(second_config_st.str(), 100);
// Schedule SIGHUP signal to trigger reconfiguration.
TimedSignal sighup(getIOService(), SIGHUP, 200);
IntervalTimer timer(getIOService());
timer.setup([&] {
CtrlAgentProcessPtr process = getCtrlAgentProcess();
ASSERT_TRUE(process);
ConstHttpListenerPtr listener = process->getHttpListener();
ASSERT_TRUE(listener);
listener_ptr = listener.get();
ASSERT_TRUE(listener->getTlsContext());
context = listener->getTlsContext().get();
}, 50, IntervalTimer::ONE_SHOT);
// Start the server.
time_duration elapsed_time;
runWithConfig(agent_st.str(), 500,
static_cast<const TestCallback&>(check_callback),
elapsed_time);
CtrlAgentCfgContextPtr ctx = getCtrlAgentCfgContext();
ASSERT_TRUE(ctx);
// The server should use a correct listener configuration.
EXPECT_EQ("127.0.0.1", ctx->getHttpHost());
EXPECT_EQ(8081, ctx->getHttpPort());
// The forwarding configuration should have been updated.
testUnixSocketInfo("dhcp4", "/second/dhcp4/socket");
testUnixSocketInfo("dhcp6", "/second/dhcp6/socket");
CtrlAgentProcessPtr process = getCtrlAgentProcess();
ASSERT_TRUE(process);
ConstHttpListenerPtr listener = process->getHttpListener();
ASSERT_FALSE(listener);
EXPECT_FALSE(process->isListening());
}
// Verify that the reload will reuse listener
TEST_F(CtrlAgentControllerTest, ignoreHttpToHttpsSwitch) {
string ca_dir(string(TEST_CA_DIR));
// This configuration should be used to override the initial configuration.
ostringstream second_config_st;
second_config_st << "{"
<< " \"http-host\": \"127.0.0.1\","
<< " \"http-port\": 8081,"
<< " \"trust-anchor\": \"" << ca_dir << "/kea-ca.crt\", \n"
<< " \"cert-file\": \"" << ca_dir << "/kea-server.crt\", \n"
<< " \"key-file\": \"" << ca_dir << "/kea-server.key\", \n"
<< " \"control-sockets\": {"
<< " \"dhcp4\": {"
<< " \"socket-type\": \"unix\","
<< " \"socket-name\": \"/second/dhcp4/socket\""
<< " },"
<< " \"dhcp6\": {"
<< " \"socket-type\": \"unix\","
<< " \"socket-name\": \"/second/dhcp6/socket\""
<< " }"
<< " }"
<< "}";
const HttpListener* listener_ptr = 0;
// This check callback is called before the shutdown.
auto check_callback = [&] {
CtrlAgentProcessPtr process = getCtrlAgentProcess();
ASSERT_TRUE(process);
// Check that the HTTP listener still exists after reconfiguration.
ConstHttpListenerPtr listener = process->getHttpListener();
ASSERT_TRUE(listener);
ASSERT_EQ(listener_ptr, listener.get());
ASSERT_FALSE(listener->getTlsContext());
EXPECT_TRUE(process->isListening());
EXPECT_EQ("127.0.0.1", listener->getLocalAddress().toText());
EXPECT_EQ(8081, listener->getLocalPort());
};
// Schedule reconfiguration.
scheduleTimedWrite(second_config_st.str(), 100);
// Schedule SIGHUP signal to trigger reconfiguration.
TimedSignal sighup(getIOService(), SIGHUP, 200);
IntervalTimer timer(getIOService());
timer.setup([&] {
CtrlAgentProcessPtr process = getCtrlAgentProcess();
ASSERT_TRUE(process);
ConstHttpListenerPtr listener = process->getHttpListener();
ASSERT_TRUE(listener);
listener_ptr = listener.get();
ASSERT_FALSE(listener->getTlsContext());
}, 50, IntervalTimer::ONE_SHOT);
// Start the server.
time_duration elapsed_time;
runWithConfig(valid_agent_config, 500,
static_cast<const TestCallback&>(check_callback),
elapsed_time);
CtrlAgentCfgContextPtr ctx = getCtrlAgentCfgContext();
ASSERT_TRUE(ctx);
// The server should use a correct listener configuration.
EXPECT_EQ("127.0.0.1", ctx->getHttpHost());
EXPECT_EQ(8081, ctx->getHttpPort());
// The forwarding configuration should have been updated.
testUnixSocketInfo("dhcp4", "/second/dhcp4/socket");
testUnixSocketInfo("dhcp6", "/second/dhcp6/socket");
CtrlAgentProcessPtr process = getCtrlAgentProcess();
ASSERT_TRUE(process);
ConstHttpListenerPtr listener = process->getHttpListener();
ASSERT_FALSE(listener);
EXPECT_FALSE(process->isListening());
}
// Verify that the reload will reuse listener
TEST_F(CtrlAgentControllerTest, ignoreHttpsToHttpSwitch) {
string ca_dir(string(TEST_CA_DIR));
ostringstream agent_st;
agent_st << "{"
<< " \"http-host\": \"127.0.0.1\","
<< " \"http-port\": 8081,"
<< " \"trust-anchor\": \"" << ca_dir << "/kea-ca.crt\", \n"
<< " \"cert-file\": \"" << ca_dir << "/kea-server.crt\", \n"
<< " \"key-file\": \"" << ca_dir << "/kea-server.key\", \n"
<< " \"control-sockets\": {"
<< " \"dhcp4\": {"
<< " \"socket-type\": \"unix\","
<< " \"socket-name\": \"/first/dhcp4/socket\""
<< " },"
<< " \"dhcp6\": {"
<< " \"socket-type\": \"unix\","
<< " \"socket-name\": \"/first/dhcp6/socket\""
<< " }"
<< " }"
<< "}";
// This configuration should be used to override the initial configuration.
ostringstream second_config_st;
second_config_st << "{"
<< " \"http-host\": \"127.0.0.1\","
<< " \"http-port\": 8081,"
<< " \"control-sockets\": {"
<< " \"dhcp4\": {"
<< " \"socket-type\": \"unix\","
<< " \"socket-name\": \"/second/dhcp4/socket\""
<< " },"
<< " \"dhcp6\": {"
<< " \"socket-type\": \"unix\","
<< " \"socket-name\": \"/second/dhcp6/socket\""
<< " }"
<< " }"
<< "}";
const HttpListener* listener_ptr = 0;
TlsContext* context = 0;
// This check callback is called before the shutdown.
auto check_callback = [&] {
CtrlAgentProcessPtr process = getCtrlAgentProcess();
ASSERT_TRUE(process);
// Check that the HTTP listener still exists after reconfiguration.
ConstHttpListenerPtr listener = process->getHttpListener();
ASSERT_TRUE(listener);
ASSERT_EQ(listener_ptr, listener.get());
ASSERT_TRUE(listener->getTlsContext());
// The TLS settings have not changed
ASSERT_EQ(context, listener->getTlsContext().get());
EXPECT_TRUE(process->isListening());
EXPECT_EQ("127.0.0.1", listener->getLocalAddress().toText());
EXPECT_EQ(8081, listener->getLocalPort());
};
// Schedule reconfiguration.
scheduleTimedWrite(second_config_st.str(), 100);
// Schedule SIGHUP signal to trigger reconfiguration.
TimedSignal sighup(getIOService(), SIGHUP, 200);
IntervalTimer timer(getIOService());
timer.setup([&] {
CtrlAgentProcessPtr process = getCtrlAgentProcess();
ASSERT_TRUE(process);
ConstHttpListenerPtr listener = process->getHttpListener();
ASSERT_TRUE(listener);
listener_ptr = listener.get();
ASSERT_TRUE(listener->getTlsContext());
context = listener->getTlsContext().get();
}, 50, IntervalTimer::ONE_SHOT);
// Start the server.
time_duration elapsed_time;
runWithConfig(agent_st.str(), 500,
static_cast<const TestCallback&>(check_callback),
elapsed_time);
CtrlAgentCfgContextPtr ctx = getCtrlAgentCfgContext();
ASSERT_TRUE(ctx);
// The server should use a correct listener configuration.
EXPECT_EQ("127.0.0.1", ctx->getHttpHost());
EXPECT_EQ(8081, ctx->getHttpPort());
// The forwarding configuration should have been updated.
testUnixSocketInfo("dhcp4", "/second/dhcp4/socket");
testUnixSocketInfo("dhcp6", "/second/dhcp6/socket");
CtrlAgentProcessPtr process = getCtrlAgentProcess();
ASSERT_TRUE(process);
ConstHttpListenerPtr listener = process->getHttpListener();
ASSERT_FALSE(listener);
EXPECT_FALSE(process->isListening());
}
// Tests that registerCommands actually registers anything.
TEST_F(CtrlAgentControllerTest, registeredCommands) {
ASSERT_NO_THROW(initProcess());

View File

@@ -522,16 +522,11 @@ D2Process::reconfigureCommandChannel() {
ConstElementPtr http_config =
getD2CfgMgr()->getHttpControlSocketInfo();
sock_changed = (http_config && current_http_control_socket_ &&
!http_config->equals(*current_http_control_socket_));
if (!http_config || !current_http_control_socket_ || sock_changed) {
// Open the new sockets and close old ones, keeping reused.
if (http_config) {
HttpCommandMgr::instance().openCommandSockets(http_config);
} else if (current_http_control_socket_) {
HttpCommandMgr::instance().closeCommandSockets();
}
// Open the new sockets and close old ones, keeping reused.
if (http_config) {
HttpCommandMgr::instance().openCommandSockets(http_config);
} else if (current_http_control_socket_) {
HttpCommandMgr::instance().closeCommandSockets();
}
// Commit the new socket configuration.

View File

@@ -1218,7 +1218,7 @@ TEST_F(HttpCtrlChannelD2Test, configSet) {
ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
// Create a config with invalid content that should fail to parse.
string config_test_txt =
string config_set_txt =
"{ \"command\": \"config-set\", \n"
" \"arguments\": { \n"
" \"DhcpDdns\": \n"
@@ -1242,7 +1242,7 @@ TEST_F(HttpCtrlChannelD2Test, configSet) {
// Send the config-set command.
string response;
sendHttpCommand(config_test_txt, response);
sendHttpCommand(config_set_txt, response);
// Should fail with a syntax error.
EXPECT_EQ("[ { \"result\": 1, \"text\": \"missing parameter 'name' (<string>:10:14)\" } ]",
@@ -1255,7 +1255,7 @@ TEST_F(HttpCtrlChannelD2Test, configSet) {
EXPECT_EQ(1, keys->size());
// Create a valid config with two keys and no command channel.
config_test_txt =
config_set_txt =
"{ \"command\": \"config-set\", \n"
" \"arguments\": { \n"
" \"DhcpDdns\": \n"
@@ -1280,7 +1280,7 @@ TEST_F(HttpCtrlChannelD2Test, configSet) {
EXPECT_TRUE(HttpCommandMgr::instance().getHttpListener());
// Send the config-set command.
sendHttpCommand(config_test_txt, response);
sendHttpCommand(config_set_txt, response);
// Verify the HTTP control channel socket no longer exists.
ASSERT_NO_THROW(HttpCommandMgr::instance().closeCommandSockets());
@@ -1355,7 +1355,7 @@ TEST_F(HttpsCtrlChannelD2Test, configSet) {
ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
// Create a config with invalid content that should fail to parse.
string config_test_txt =
string config_set_txt =
"{ \"command\": \"config-set\", \n"
" \"arguments\": { \n"
" \"DhcpDdns\": \n"
@@ -1379,7 +1379,7 @@ TEST_F(HttpsCtrlChannelD2Test, configSet) {
// Send the config-set command.
string response;
sendHttpCommand(config_test_txt, response);
sendHttpCommand(config_set_txt, response);
// Should fail with a syntax error.
EXPECT_EQ("[ { \"result\": 1, \"text\": \"missing parameter 'name' (<string>:10:14)\" } ]",
@@ -1392,7 +1392,7 @@ TEST_F(HttpsCtrlChannelD2Test, configSet) {
EXPECT_EQ(1, keys->size());
// Create a valid config with two keys and no command channel.
config_test_txt =
config_set_txt =
"{ \"command\": \"config-set\", \n"
" \"arguments\": { \n"
" \"DhcpDdns\": \n"
@@ -1417,7 +1417,7 @@ TEST_F(HttpsCtrlChannelD2Test, configSet) {
EXPECT_TRUE(HttpCommandMgr::instance().getHttpListener());
// Send the config-set command.
sendHttpCommand(config_test_txt, response);
sendHttpCommand(config_set_txt, response);
// Verify the HTTP control channel socket no longer exists.
ASSERT_NO_THROW(HttpCommandMgr::instance().closeCommandSockets());
@@ -1984,4 +1984,374 @@ TEST_F(HttpsCtrlChannelD2Test, connectionTimeoutNoData) {
testConnectionTimeoutNoData();
}
// Verify that the "config-set" command will reuse listener
TEST_F(HttpCtrlChannelD2Test, noListenerChange) {
string d2_cfg_txt =
" { \n"
" \"ip-address\": \"192.168.77.1\", \n"
" \"port\": 777, \n"
" \"forward-ddns\" : {}, \n"
" \"reverse-ddns\" : {}, \n"
" \"tsig-keys\": [ \n"
" {\"name\": \"d2_key.example.com\", \n"
" \"algorithm\": \"hmac-md5\", \n"
" \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\"} \n"
" ], \n"
" \"control-socket\": { \n"
" \"socket-type\": \"http\", \n"
" \"socket-address\": \"127.0.0.1\", \n"
" \"socket-port\": 18125 \n"
" } \n"
" } \n";
ASSERT_TRUE(server_);
ConstElementPtr config;
ASSERT_NO_THROW(config = parseDHCPDDNS(d2_cfg_txt, true));
ASSERT_NO_THROW(d2Controller()->initProcess());
D2ProcessPtr proc = d2Controller()->getProcess();
ASSERT_TRUE(proc);
ConstElementPtr answer = proc->configure(config, false);
ASSERT_TRUE(answer);
EXPECT_EQ("{ \"arguments\": { \"hash\": \"029AE1208415D6911B5651A6F82D054F55B7877D2589CFD1DCEB5BFFCD3B13A3\" }, \"result\": 0, \"text\": \"Configuration applied successfully.\" }",
answer->str());
ASSERT_NO_THROW(d2Controller()->registerCommands());
// Check that the config was indeed applied.
D2CfgMgrPtr cfg_mgr = proc->getD2CfgMgr();
ASSERT_TRUE(cfg_mgr);
D2CfgContextPtr d2_context = cfg_mgr->getD2CfgContext();
ASSERT_TRUE(d2_context);
TSIGKeyInfoMapPtr keys = d2_context->getKeys();
ASSERT_TRUE(keys);
EXPECT_EQ(1, keys->size());
ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
auto const listener = HttpCommandMgr::instance().getHttpListener().get();
ASSERT_FALSE(HttpCommandMgr::instance().getHttpListener()->getTlsContext());
// Create a config with same content that should not recreate listener.
string config_set_txt =
"{ \"command\": \"config-set\", \n"
" \"arguments\": { \n"
" \"DhcpDdns\": \n";
config_set_txt += d2_cfg_txt;
config_set_txt += "}} \n";
// Send the config-set command.
string response;
sendHttpCommand(config_set_txt, response);
ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
EXPECT_EQ(listener, HttpCommandMgr::instance().getHttpListener().get());
ASSERT_FALSE(HttpCommandMgr::instance().getHttpListener()->getTlsContext());
// Verify the configuration was successful.
EXPECT_EQ("[ { \"arguments\": { \"hash\": \"029AE1208415D6911B5651A6F82D054F55B7877D2589CFD1DCEB5BFFCD3B13A3\" }, \"result\": 0, \"text\": \"Configuration applied successfully.\" } ]",
response);
// Check that the config was applied.
d2_context = cfg_mgr->getD2CfgContext();
keys = d2_context->getKeys();
ASSERT_TRUE(keys);
EXPECT_EQ(1, keys->size());
}
// Verify that the "config-set" command will reuse listener
TEST_F(HttpsCtrlChannelD2Test, noListenerChange) {
string ca_dir(string(TEST_CA_DIR));
ostringstream d2_st;
d2_st << " { \n"
<< " \"ip-address\": \"192.168.77.1\", \n"
<< " \"port\": 777, \n"
<< " \"forward-ddns\" : {}, \n"
<< " \"reverse-ddns\" : {}, \n"
<< " \"tsig-keys\": [ \n"
<< " {\"name\": \"d2_key.example.com\", \n"
<< " \"algorithm\": \"hmac-md5\", \n"
<< " \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\"} \n"
<< " ], \n"
<< " \"control-socket\": { \n"
<< " \"socket-type\": \"https\", \n"
<< " \"socket-address\": \"127.0.0.1\", \n"
<< " \"socket-port\": 18125, \n"
<< " \"trust-anchor\": \"" << ca_dir << "/kea-ca.crt\", \n"
<< " \"cert-file\": \"" << ca_dir << "/kea-server.crt\", \n"
<< " \"key-file\": \"" << ca_dir << "/kea-server.key\" \n"
<< " } \n"
<< " } \n";
ASSERT_TRUE(server_);
ConstElementPtr config;
ASSERT_NO_THROW(config = parseDHCPDDNS(d2_st.str(), true));
ASSERT_NO_THROW(d2Controller()->initProcess());
D2ProcessPtr proc = d2Controller()->getProcess();
ASSERT_TRUE(proc);
ConstElementPtr answer = proc->configure(config, false);
ASSERT_TRUE(answer);
// Verify the configuration was successful. The config contains random
// file paths (CA directory), so the hash will be different each time.
// As such, we can do simplified checks:
// - verify the "result": 0 is there
// - verify the "text": "Configuration applied successfully." is there
string answer_txt = answer->str();
EXPECT_NE(answer_txt.find("\"result\": 0"), std::string::npos);
EXPECT_NE(answer_txt.find("\"text\": \"Configuration applied successfully.\""),
std::string::npos);
ASSERT_NO_THROW(d2Controller()->registerCommands());
// Check that the config was indeed applied.
D2CfgMgrPtr cfg_mgr = proc->getD2CfgMgr();
ASSERT_TRUE(cfg_mgr);
D2CfgContextPtr d2_context = cfg_mgr->getD2CfgContext();
ASSERT_TRUE(d2_context);
TSIGKeyInfoMapPtr keys = d2_context->getKeys();
ASSERT_TRUE(keys);
EXPECT_EQ(1, keys->size());
ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
auto const listener = HttpCommandMgr::instance().getHttpListener().get();
ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener()->getTlsContext());
auto const context = HttpCommandMgr::instance().getHttpListener()->getTlsContext().get();
// Create a config with same content that should not recreate listener.
string config_set_txt =
"{ \"command\": \"config-set\", \n"
" \"arguments\": { \n"
" \"DhcpDdns\": \n";
config_set_txt += d2_st.str();
config_set_txt += "}} \n";
// Send the config-set command.
string response;
sendHttpCommand(config_set_txt, response);
ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
EXPECT_EQ(listener, HttpCommandMgr::instance().getHttpListener().get());
ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener()->getTlsContext());
// The TLS settings have been applied
EXPECT_NE(context, HttpCommandMgr::instance().getHttpListener()->getTlsContext().get());
// Verify the configuration was successful.
EXPECT_NE(response.find("\"result\": 0"), std::string::npos);
EXPECT_NE(response.find("\"text\": \"Configuration applied successfully.\""),
std::string::npos);
// Check that the config was applied.
d2_context = cfg_mgr->getD2CfgContext();
keys = d2_context->getKeys();
ASSERT_TRUE(keys);
EXPECT_EQ(1, keys->size());
}
// Verify that the "config-set" command will reuse listener
TEST_F(HttpCtrlChannelD2Test, ignoreHttpToHttpsSwitch) {
string d2_cfg_txt =
" { \n"
" \"ip-address\": \"192.168.77.1\", \n"
" \"port\": 777, \n"
" \"forward-ddns\" : {}, \n"
" \"reverse-ddns\" : {}, \n"
" \"tsig-keys\": [ \n"
" {\"name\": \"d2_key.example.com\", \n"
" \"algorithm\": \"hmac-md5\", \n"
" \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\"} \n"
" ], \n"
" \"control-socket\": { \n"
" \"socket-type\": \"http\", \n"
" \"socket-address\": \"127.0.0.1\", \n"
" \"socket-port\": 18125 \n"
" } \n"
" } \n";
ASSERT_TRUE(server_);
ConstElementPtr config;
ASSERT_NO_THROW(config = parseDHCPDDNS(d2_cfg_txt, true));
ASSERT_NO_THROW(d2Controller()->initProcess());
D2ProcessPtr proc = d2Controller()->getProcess();
ASSERT_TRUE(proc);
ConstElementPtr answer = proc->configure(config, false);
ASSERT_TRUE(answer);
EXPECT_EQ("{ \"arguments\": { \"hash\": \"029AE1208415D6911B5651A6F82D054F55B7877D2589CFD1DCEB5BFFCD3B13A3\" }, \"result\": 0, \"text\": \"Configuration applied successfully.\" }",
answer->str());
ASSERT_NO_THROW(d2Controller()->registerCommands());
// Check that the config was indeed applied.
D2CfgMgrPtr cfg_mgr = proc->getD2CfgMgr();
ASSERT_TRUE(cfg_mgr);
D2CfgContextPtr d2_context = cfg_mgr->getD2CfgContext();
ASSERT_TRUE(d2_context);
TSIGKeyInfoMapPtr keys = d2_context->getKeys();
ASSERT_TRUE(keys);
EXPECT_EQ(1, keys->size());
ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
auto const listener = HttpCommandMgr::instance().getHttpListener().get();
ASSERT_FALSE(HttpCommandMgr::instance().getHttpListener()->getTlsContext());
string ca_dir(string(TEST_CA_DIR));
ostringstream d2_st;
d2_st << " { \n"
<< " \"ip-address\": \"192.168.77.1\", \n"
<< " \"port\": 777, \n"
<< " \"forward-ddns\" : {}, \n"
<< " \"reverse-ddns\" : {}, \n"
<< " \"tsig-keys\": [ \n"
<< " {\"name\": \"d2_key.example.com\", \n"
<< " \"algorithm\": \"hmac-md5\", \n"
<< " \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\"} \n"
<< " ], \n"
<< " \"control-socket\": { \n"
<< " \"socket-type\": \"https\", \n"
<< " \"socket-address\": \"127.0.0.1\", \n"
<< " \"socket-port\": 18125, \n"
<< " \"trust-anchor\": \"" << ca_dir << "/kea-ca.crt\", \n"
<< " \"cert-file\": \"" << ca_dir << "/kea-server.crt\", \n"
<< " \"key-file\": \"" << ca_dir << "/kea-server.key\" \n"
<< " } \n"
<< " } \n";
// Create a config with HTTPS and same content that should not recreate listener.
string config_set_txt =
"{ \"command\": \"config-set\", \n"
" \"arguments\": { \n"
" \"DhcpDdns\": \n";
config_set_txt += d2_st.str();
config_set_txt += "}} \n";
// Send the config-set command.
string response;
sendHttpCommand(config_set_txt, response);
ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
EXPECT_EQ(listener, HttpCommandMgr::instance().getHttpListener().get());
ASSERT_FALSE(HttpCommandMgr::instance().getHttpListener()->getTlsContext());
// Verify the configuration was successful.
EXPECT_NE(response.find("\"result\": 0"), std::string::npos);
EXPECT_NE(response.find("\"text\": \"Configuration applied successfully.\""),
std::string::npos);
// Check that the config was applied.
d2_context = cfg_mgr->getD2CfgContext();
keys = d2_context->getKeys();
ASSERT_TRUE(keys);
EXPECT_EQ(1, keys->size());
}
// Verify that the "config-set" command will reuse listener
TEST_F(HttpsCtrlChannelD2Test, ignoreHttpsToHttpSwitch) {
string ca_dir(string(TEST_CA_DIR));
ostringstream d2_st;
d2_st << " { \n"
<< " \"ip-address\": \"192.168.77.1\", \n"
<< " \"port\": 777, \n"
<< " \"forward-ddns\" : {}, \n"
<< " \"reverse-ddns\" : {}, \n"
<< " \"tsig-keys\": [ \n"
<< " {\"name\": \"d2_key.example.com\", \n"
<< " \"algorithm\": \"hmac-md5\", \n"
<< " \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\"} \n"
<< " ], \n"
<< " \"control-socket\": { \n"
<< " \"socket-type\": \"https\", \n"
<< " \"socket-address\": \"127.0.0.1\", \n"
<< " \"socket-port\": 18125, \n"
<< " \"trust-anchor\": \"" << ca_dir << "/kea-ca.crt\", \n"
<< " \"cert-file\": \"" << ca_dir << "/kea-server.crt\", \n"
<< " \"key-file\": \"" << ca_dir << "/kea-server.key\" \n"
<< " } \n"
<< " } \n";
ASSERT_TRUE(server_);
ConstElementPtr config;
ASSERT_NO_THROW(config = parseDHCPDDNS(d2_st.str(), true));
ASSERT_NO_THROW(d2Controller()->initProcess());
D2ProcessPtr proc = d2Controller()->getProcess();
ASSERT_TRUE(proc);
ConstElementPtr answer = proc->configure(config, false);
ASSERT_TRUE(answer);
// Verify the configuration was successful. The config contains random
// file paths (CA directory), so the hash will be different each time.
// As such, we can do simplified checks:
// - verify the "result": 0 is there
// - verify the "text": "Configuration applied successfully." is there
string answer_txt = answer->str();
EXPECT_NE(answer_txt.find("\"result\": 0"), std::string::npos);
EXPECT_NE(answer_txt.find("\"text\": \"Configuration applied successfully.\""),
std::string::npos);
ASSERT_NO_THROW(d2Controller()->registerCommands());
// Check that the config was indeed applied.
D2CfgMgrPtr cfg_mgr = proc->getD2CfgMgr();
ASSERT_TRUE(cfg_mgr);
D2CfgContextPtr d2_context = cfg_mgr->getD2CfgContext();
ASSERT_TRUE(d2_context);
TSIGKeyInfoMapPtr keys = d2_context->getKeys();
ASSERT_TRUE(keys);
EXPECT_EQ(1, keys->size());
ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
auto const listener = HttpCommandMgr::instance().getHttpListener().get();
ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener()->getTlsContext());
auto const context = HttpCommandMgr::instance().getHttpListener()->getTlsContext().get();
string d2_cfg_txt =
" { \n"
" \"ip-address\": \"192.168.77.1\", \n"
" \"port\": 777, \n"
" \"forward-ddns\" : {}, \n"
" \"reverse-ddns\" : {}, \n"
" \"tsig-keys\": [ \n"
" {\"name\": \"d2_key.example.com\", \n"
" \"algorithm\": \"hmac-md5\", \n"
" \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\"} \n"
" ], \n"
" \"control-socket\": { \n"
" \"socket-type\": \"http\", \n"
" \"socket-address\": \"127.0.0.1\", \n"
" \"socket-port\": 18125 \n"
" } \n"
" } \n";
// Create a config with HTTP and same content that should not recreate listener.
string config_set_txt =
"{ \"command\": \"config-set\", \n"
" \"arguments\": { \n"
" \"DhcpDdns\": \n";
config_set_txt += d2_cfg_txt;
config_set_txt += "}} \n";
// Send the config-set command.
string response;
sendHttpCommand(config_set_txt, response);
ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
EXPECT_EQ(listener, HttpCommandMgr::instance().getHttpListener().get());
ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener()->getTlsContext());
// The TLS settings have not changed
EXPECT_EQ(context, HttpCommandMgr::instance().getHttpListener()->getTlsContext().get());
// Verify the configuration was successful.
EXPECT_EQ("[ { \"arguments\": { \"hash\": \"029AE1208415D6911B5651A6F82D054F55B7877D2589CFD1DCEB5BFFCD3B13A3\" }, \"result\": 0, \"text\": \"Configuration applied successfully.\" } ]",
response);
// Check that the config was applied.
d2_context = cfg_mgr->getD2CfgContext();
keys = d2_context->getKeys();
ASSERT_TRUE(keys);
EXPECT_EQ(1, keys->size());
}
} // end of anonymous namespace

View File

@@ -338,15 +338,10 @@ void configureCommandChannel() {
ConstElementPtr current_http_config =
CfgMgr::instance().getCurrentCfg()->getHttpControlSocketInfo();
sock_changed = (http_config && current_http_config &&
!http_config->equals(*current_http_config));
if (!http_config || !current_http_config || sock_changed) {
if (http_config) {
HttpCommandMgr::instance().openCommandSockets(http_config);
} else if (current_http_config) {
HttpCommandMgr::instance().closeCommandSockets();
}
if (http_config) {
HttpCommandMgr::instance().openCommandSockets(http_config);
} else if (current_http_config) {
HttpCommandMgr::instance().closeCommandSockets();
}
}

View File

@@ -983,7 +983,7 @@ TEST_F(HttpCtrlChannelDhcpv4Test, configSet) {
// Define strings to permutate the config arguments
// (Note the line feeds makes errors easy to find)
string set_config_txt = "{ \"command\": \"config-set\" \n";
string config_set_txt = "{ \"command\": \"config-set\" \n";
string args_txt = " \"arguments\": { \n";
string dhcp4_cfg_txt =
" \"Dhcp4\": { \n"
@@ -1056,7 +1056,7 @@ TEST_F(HttpCtrlChannelDhcpv4Test, configSet) {
std::ostringstream os;
// Create a valid config with all the parts should parse
os << set_config_txt << ","
os << config_set_txt << ","
<< args_txt
<< dhcp4_cfg_txt
<< subnet1
@@ -1085,7 +1085,7 @@ TEST_F(HttpCtrlChannelDhcpv4Test, configSet) {
// Create a config with malformed subnet that should fail to parse.
os.str("");
os << set_config_txt << ","
os << config_set_txt << ","
<< args_txt
<< dhcp4_cfg_txt
<< bad_subnet
@@ -1114,7 +1114,7 @@ TEST_F(HttpCtrlChannelDhcpv4Test, configSet) {
// Create a valid config with two subnets and no command channel.
// It should succeed, client should still receive the response
os.str("");
os << set_config_txt << ","
os << config_set_txt << ","
<< args_txt
<< dhcp4_cfg_txt
<< subnet1
@@ -1153,7 +1153,7 @@ TEST_F(HttpsCtrlChannelDhcpv4Test, configSet) {
// Define strings to permutate the config arguments
// (Note the line feeds makes errors easy to find)
string ca_dir(string(TEST_CA_DIR));
string set_config_txt = "{ \"command\": \"config-set\" \n";
string config_set_txt = "{ \"command\": \"config-set\" \n";
string args_txt = " \"arguments\": { \n";
string dhcp4_cfg_txt =
" \"Dhcp4\": { \n"
@@ -1227,7 +1227,7 @@ TEST_F(HttpsCtrlChannelDhcpv4Test, configSet) {
std::ostringstream os;
// Create a valid config with all the parts should parse
os << set_config_txt << ","
os << config_set_txt << ","
<< args_txt
<< dhcp4_cfg_txt
<< subnet1
@@ -1266,7 +1266,7 @@ TEST_F(HttpsCtrlChannelDhcpv4Test, configSet) {
// Create a config with malformed subnet that should fail to parse.
os.str("");
os << set_config_txt << ","
os << config_set_txt << ","
<< args_txt
<< dhcp4_cfg_txt
<< bad_subnet
@@ -1299,7 +1299,7 @@ TEST_F(HttpsCtrlChannelDhcpv4Test, configSet) {
// Create a valid config with two subnets and no command channel.
// It should succeed, client should still receive the response
os.str("");
os << set_config_txt << ","
os << config_set_txt << ","
<< args_txt
<< dhcp4_cfg_txt
<< subnet1
@@ -1411,7 +1411,7 @@ TEST_F(HttpCtrlChannelDhcpv4Test, configTest) {
// Define strings to permutate the config arguments
// (Note the line feeds makes errors easy to find)
string set_config_txt = "{ \"command\": \"config-set\" \n";
string config_set_txt = "{ \"command\": \"config-set\" \n";
string config_test_txt = "{ \"command\": \"config-test\" \n";
string args_txt = " \"arguments\": { \n";
string dhcp4_cfg_txt =
@@ -1463,7 +1463,7 @@ TEST_F(HttpCtrlChannelDhcpv4Test, configTest) {
std::ostringstream os;
// Create a valid config with all the parts should parse
os << set_config_txt << ","
os << config_set_txt << ","
<< args_txt
<< dhcp4_cfg_txt
<< subnet1
@@ -1552,7 +1552,7 @@ TEST_F(HttpsCtrlChannelDhcpv4Test, configTest) {
// Define strings to permutate the config arguments
// (Note the line feeds makes errors easy to find)
string ca_dir(string(TEST_CA_DIR));
string set_config_txt = "{ \"command\": \"config-set\" \n";
string config_set_txt = "{ \"command\": \"config-set\" \n";
string config_test_txt = "{ \"command\": \"config-test\" \n";
string args_txt = " \"arguments\": { \n";
string dhcp4_cfg_txt =
@@ -1605,7 +1605,7 @@ TEST_F(HttpsCtrlChannelDhcpv4Test, configTest) {
std::ostringstream os;
// Create a valid config with all the parts should parse
os << set_config_txt << ","
os << config_set_txt << ","
<< args_txt
<< dhcp4_cfg_txt
<< subnet1
@@ -3392,4 +3392,567 @@ TEST_F(HttpsCtrlChannelDhcpv4Test, connectionTimeoutNoData) {
testConnectionTimeoutNoData();
}
// Verify that the "config-set" command will reuse listener
TEST_F(HttpCtrlChannelDhcpv4Test, noListenerChange) {
createHttpChannelServer();
// Define strings to permutate the config arguments
// (Note the line feeds makes errors easy to find)
string config_set_txt = "{ \"command\": \"config-set\" \n";
string args_txt = " \"arguments\": { \n";
string dhcp4_cfg_txt =
" \"Dhcp4\": { \n"
" \"interfaces-config\": { \n"
" \"interfaces\": [\"*\"] \n"
" }, \n"
" \"valid-lifetime\": 4000, \n"
" \"renew-timer\": 1000, \n"
" \"rebind-timer\": 2000, \n"
" \"lease-database\": { \n"
" \"type\": \"memfile\", \n"
" \"persist\":false, \n"
" \"lfc-interval\": 0 \n"
" }, \n"
" \"expired-leases-processing\": { \n"
" \"reclaim-timer-wait-time\": 0, \n"
" \"hold-reclaimed-time\": 0, \n"
" \"flush-reclaimed-timer-wait-time\": 0 \n"
" },"
" \"subnet4\": [ \n";
string subnet1 =
" {\"subnet\": \"192.2.0.0/24\", \"id\": 1, \n"
" \"pools\": [{ \"pool\": \"192.2.0.1-192.2.0.50\" }]}\n";
string subnet_footer =
" ] \n";
string option_def =
" ,\"option-def\": [\n"
" {\n"
" \"name\": \"foo\",\n"
" \"code\": 163,\n"
" \"type\": \"uint32\",\n"
" \"array\": false,\n"
" \"record-types\": \"\",\n"
" \"space\": \"dhcp4\",\n"
" \"encapsulate\": \"\"\n"
" }\n"
"]\n";
string option_data =
" ,\"option-data\": [\n"
" {\n"
" \"name\": \"foo\",\n"
" \"code\": 163,\n"
" \"space\": \"dhcp4\",\n"
" \"csv-format\": true,\n"
" \"data\": \"12345\"\n"
" }\n"
"]\n";
string control_socket =
" ,\"control-socket\": { \n"
" \"socket-type\": \"http\", \n"
" \"socket-address\": \"127.0.0.1\", \n"
" \"socket-port\": 18124 \n"
" } \n";
string logger_txt =
" ,\"loggers\": [ { \n"
" \"name\": \"kea\", \n"
" \"severity\": \"FATAL\", \n"
" \"output-options\": [{ \n"
" \"output\": \"/dev/null\", \n"
" \"maxsize\": 0"
" }] \n"
" }] \n";
std::ostringstream os;
// Create a valid config with all the parts should parse
os << config_set_txt << ","
<< args_txt
<< dhcp4_cfg_txt
<< subnet1
<< subnet_footer
<< option_def
<< option_data
<< control_socket
<< logger_txt
<< "}\n" // close dhcp4
<< "}}";
// Send the config-set command
std::string response;
sendHttpCommand(os.str(), response);
EXPECT_EQ("[ { \"arguments\": { \"hash\": \"F6137301FF10D81585E041FD5FD8E91347ACADDE64F92ED03432FB100874DE02\" }, \"result\": 0, \"text\": \"Configuration successful.\" } ]",
response);
// Check that the config was indeed applied.
const Subnet4Collection* subnets =
CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
EXPECT_EQ(1, subnets->size());
OptionDefinitionPtr def =
LibDHCP::getRuntimeOptionDef(DHCP4_OPTION_SPACE, 163);
ASSERT_TRUE(def);
// Verify the HTTP control channel socket exists.
ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
auto const listener = HttpCommandMgr::instance().getHttpListener().get();
ASSERT_FALSE(HttpCommandMgr::instance().getHttpListener()->getTlsContext());
// Send the config-set command.
sendHttpCommand(os.str(), response);
ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
EXPECT_EQ(listener, HttpCommandMgr::instance().getHttpListener().get());
ASSERT_FALSE(HttpCommandMgr::instance().getHttpListener()->getTlsContext());
EXPECT_EQ("[ { \"arguments\": { \"hash\": \"F6137301FF10D81585E041FD5FD8E91347ACADDE64F92ED03432FB100874DE02\" }, \"result\": 0, \"text\": \"Configuration successful.\" } ]",
response);
// Check that the config was not lost
subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
EXPECT_EQ(1, subnets->size());
// Clean up after the test.
CfgMgr::instance().clear();
}
// Verify that the "config-set" command will reuse listener
TEST_F(HttpsCtrlChannelDhcpv4Test, noListenerChange) {
createHttpChannelServer();
// Define strings to permutate the config arguments
// (Note the line feeds makes errors easy to find)
string ca_dir(string(TEST_CA_DIR));
string config_set_txt = "{ \"command\": \"config-set\" \n";
string args_txt = " \"arguments\": { \n";
string dhcp4_cfg_txt =
" \"Dhcp4\": { \n"
" \"interfaces-config\": { \n"
" \"interfaces\": [\"*\"] \n"
" }, \n"
" \"valid-lifetime\": 4000, \n"
" \"renew-timer\": 1000, \n"
" \"rebind-timer\": 2000, \n"
" \"lease-database\": { \n"
" \"type\": \"memfile\", \n"
" \"persist\":false, \n"
" \"lfc-interval\": 0 \n"
" }, \n"
" \"expired-leases-processing\": { \n"
" \"reclaim-timer-wait-time\": 0, \n"
" \"hold-reclaimed-time\": 0, \n"
" \"flush-reclaimed-timer-wait-time\": 0 \n"
" },"
" \"subnet4\": [ \n";
string subnet1 =
" {\"subnet\": \"192.2.0.0/24\", \"id\": 1, \n"
" \"pools\": [{ \"pool\": \"192.2.0.1-192.2.0.50\" }]}\n";
string subnet_footer =
" ] \n";
string option_def =
" ,\"option-def\": [\n"
" {\n"
" \"name\": \"foo\",\n"
" \"code\": 163,\n"
" \"type\": \"uint32\",\n"
" \"array\": false,\n"
" \"record-types\": \"\",\n"
" \"space\": \"dhcp4\",\n"
" \"encapsulate\": \"\"\n"
" }\n"
"]\n";
string option_data =
" ,\"option-data\": [\n"
" {\n"
" \"name\": \"foo\",\n"
" \"code\": 163,\n"
" \"space\": \"dhcp4\",\n"
" \"csv-format\": true,\n"
" \"data\": \"12345\"\n"
" }\n"
"]\n";
string control_socket_header =
" ,\"control-socket\": { \n";
string control_socket_footer =
" \"socket-type\": \"http\", \n"
" \"socket-address\": \"127.0.0.1\", \n"
" \"socket-port\": 18124 \n"
" } \n";
string logger_txt =
" ,\"loggers\": [ { \n"
" \"name\": \"kea\", \n"
" \"severity\": \"FATAL\", \n"
" \"output-options\": [{ \n"
" \"output\": \"/dev/null\", \n"
" \"maxsize\": 0"
" }] \n"
" }] \n";
std::ostringstream os;
// Create a valid config with all the parts should parse
os << config_set_txt << ","
<< args_txt
<< dhcp4_cfg_txt
<< subnet1
<< subnet_footer
<< option_def
<< option_data
<< control_socket_header
<< " \"trust-anchor\": \"" << ca_dir << "/kea-ca.crt\", \n"
<< " \"cert-file\": \"" << ca_dir << "/kea-server.crt\", \n"
<< " \"key-file\": \"" << ca_dir << "/kea-server.key\", \n"
<< control_socket_footer
<< logger_txt
<< "}\n" // close dhcp4
<< "}}";
// Send the config-set command
std::string response;
sendHttpCommand(os.str(), response);
// Verify the configuration was successful. The config contains random
// file paths (CA directory), so the hash will be different each time.
// As such, we can do simplified checks:
// - verify the "result": 0 is there
// - verify the "text": "Configuration successful." is there
EXPECT_NE(response.find("\"result\": 0"), std::string::npos);
EXPECT_NE(response.find("\"text\": \"Configuration successful.\""),
std::string::npos);
// Check that the config was indeed applied.
const Subnet4Collection* subnets =
CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
EXPECT_EQ(1, subnets->size());
OptionDefinitionPtr def =
LibDHCP::getRuntimeOptionDef(DHCP4_OPTION_SPACE, 163);
ASSERT_TRUE(def);
// Verify the HTTP control channel socket exists.
ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
auto const listener = HttpCommandMgr::instance().getHttpListener().get();
ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener()->getTlsContext());
auto const context = HttpCommandMgr::instance().getHttpListener()->getTlsContext().get();
// Send the config-set command.
sendHttpCommand(os.str(), response);
ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
EXPECT_EQ(listener, HttpCommandMgr::instance().getHttpListener().get());
ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener()->getTlsContext());
// The TLS settings have been applied
EXPECT_NE(context, HttpCommandMgr::instance().getHttpListener()->getTlsContext().get());
EXPECT_NE(response.find("\"result\": 0"), std::string::npos);
EXPECT_NE(response.find("\"text\": \"Configuration successful.\""),
std::string::npos);
// Check that the config was not lost
subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
EXPECT_EQ(1, subnets->size());
// Clean up after the test.
CfgMgr::instance().clear();
}
// Verify that the "config-set" command will reuse listener
TEST_F(HttpCtrlChannelDhcpv4Test, ignoredHttpToHttpsSwitch) {
createHttpChannelServer();
// Define strings to permutate the config arguments
// (Note the line feeds makes errors easy to find)
string ca_dir(string(TEST_CA_DIR));
string config_set_txt = "{ \"command\": \"config-set\" \n";
string args_txt = " \"arguments\": { \n";
string dhcp4_cfg_txt =
" \"Dhcp4\": { \n"
" \"interfaces-config\": { \n"
" \"interfaces\": [\"*\"] \n"
" }, \n"
" \"valid-lifetime\": 4000, \n"
" \"renew-timer\": 1000, \n"
" \"rebind-timer\": 2000, \n"
" \"lease-database\": { \n"
" \"type\": \"memfile\", \n"
" \"persist\":false, \n"
" \"lfc-interval\": 0 \n"
" }, \n"
" \"expired-leases-processing\": { \n"
" \"reclaim-timer-wait-time\": 0, \n"
" \"hold-reclaimed-time\": 0, \n"
" \"flush-reclaimed-timer-wait-time\": 0 \n"
" },"
" \"subnet4\": [ \n";
string subnet1 =
" {\"subnet\": \"192.2.0.0/24\", \"id\": 1, \n"
" \"pools\": [{ \"pool\": \"192.2.0.1-192.2.0.50\" }]}\n";
string subnet_footer =
" ] \n";
string option_def =
" ,\"option-def\": [\n"
" {\n"
" \"name\": \"foo\",\n"
" \"code\": 163,\n"
" \"type\": \"uint32\",\n"
" \"array\": false,\n"
" \"record-types\": \"\",\n"
" \"space\": \"dhcp4\",\n"
" \"encapsulate\": \"\"\n"
" }\n"
"]\n";
string option_data =
" ,\"option-data\": [\n"
" {\n"
" \"name\": \"foo\",\n"
" \"code\": 163,\n"
" \"space\": \"dhcp4\",\n"
" \"csv-format\": true,\n"
" \"data\": \"12345\"\n"
" }\n"
"]\n";
string control_socket_header =
" ,\"control-socket\": { \n";
string control_socket_footer =
" \"socket-type\": \"http\", \n"
" \"socket-address\": \"127.0.0.1\", \n"
" \"socket-port\": 18124 \n"
" } \n";
string logger_txt =
" ,\"loggers\": [ { \n"
" \"name\": \"kea\", \n"
" \"severity\": \"FATAL\", \n"
" \"output-options\": [{ \n"
" \"output\": \"/dev/null\", \n"
" \"maxsize\": 0"
" }] \n"
" }] \n";
std::ostringstream os;
// Create a valid config with all the parts should parse
os << config_set_txt << ","
<< args_txt
<< dhcp4_cfg_txt
<< subnet1
<< subnet_footer
<< option_def
<< option_data
<< control_socket_header
<< control_socket_footer
<< logger_txt
<< "}\n" // close dhcp4
<< "}}";
// Send the config-set command
std::string response;
sendHttpCommand(os.str(), response);
EXPECT_EQ("[ { \"arguments\": { \"hash\": \"F6137301FF10D81585E041FD5FD8E91347ACADDE64F92ED03432FB100874DE02\" }, \"result\": 0, \"text\": \"Configuration successful.\" } ]",
response);
// Check that the config was indeed applied.
const Subnet4Collection* subnets =
CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
EXPECT_EQ(1, subnets->size());
OptionDefinitionPtr def =
LibDHCP::getRuntimeOptionDef(DHCP4_OPTION_SPACE, 163);
ASSERT_TRUE(def);
// Verify the HTTP control channel socket exists.
ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
auto const listener = HttpCommandMgr::instance().getHttpListener().get();
ASSERT_FALSE(HttpCommandMgr::instance().getHttpListener()->getTlsContext());
std::ostringstream second_config_os;
// Create a valid config with all the parts should parse
second_config_os << config_set_txt << ","
<< args_txt
<< dhcp4_cfg_txt
<< subnet1
<< subnet_footer
<< option_def
<< option_data
<< control_socket_header
<< " \"trust-anchor\": \"" << ca_dir << "/kea-ca.crt\", \n"
<< " \"cert-file\": \"" << ca_dir << "/kea-server.crt\", \n"
<< " \"key-file\": \"" << ca_dir << "/kea-server.key\", \n"
<< control_socket_footer
<< logger_txt
<< "}\n" // close dhcp4
<< "}}";
// Send the config-set command.
sendHttpCommand(second_config_os.str(), response);
ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
EXPECT_EQ(listener, HttpCommandMgr::instance().getHttpListener().get());
ASSERT_FALSE(HttpCommandMgr::instance().getHttpListener()->getTlsContext());
EXPECT_NE(response.find("\"result\": 0"), std::string::npos);
EXPECT_NE(response.find("\"text\": \"Configuration successful.\""),
std::string::npos);
// Check that the config was not lost
subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
EXPECT_EQ(1, subnets->size());
// Clean up after the test.
CfgMgr::instance().clear();
}
// Verify that the "config-set" command will reuse listener
TEST_F(HttpsCtrlChannelDhcpv4Test, ignoreHttpsToHttpSwitch) {
createHttpChannelServer();
// Define strings to permutate the config arguments
// (Note the line feeds makes errors easy to find)
string ca_dir(string(TEST_CA_DIR));
string config_set_txt = "{ \"command\": \"config-set\" \n";
string args_txt = " \"arguments\": { \n";
string dhcp4_cfg_txt =
" \"Dhcp4\": { \n"
" \"interfaces-config\": { \n"
" \"interfaces\": [\"*\"] \n"
" }, \n"
" \"valid-lifetime\": 4000, \n"
" \"renew-timer\": 1000, \n"
" \"rebind-timer\": 2000, \n"
" \"lease-database\": { \n"
" \"type\": \"memfile\", \n"
" \"persist\":false, \n"
" \"lfc-interval\": 0 \n"
" }, \n"
" \"expired-leases-processing\": { \n"
" \"reclaim-timer-wait-time\": 0, \n"
" \"hold-reclaimed-time\": 0, \n"
" \"flush-reclaimed-timer-wait-time\": 0 \n"
" },"
" \"subnet4\": [ \n";
string subnet1 =
" {\"subnet\": \"192.2.0.0/24\", \"id\": 1, \n"
" \"pools\": [{ \"pool\": \"192.2.0.1-192.2.0.50\" }]}\n";
string subnet_footer =
" ] \n";
string option_def =
" ,\"option-def\": [\n"
" {\n"
" \"name\": \"foo\",\n"
" \"code\": 163,\n"
" \"type\": \"uint32\",\n"
" \"array\": false,\n"
" \"record-types\": \"\",\n"
" \"space\": \"dhcp4\",\n"
" \"encapsulate\": \"\"\n"
" }\n"
"]\n";
string option_data =
" ,\"option-data\": [\n"
" {\n"
" \"name\": \"foo\",\n"
" \"code\": 163,\n"
" \"space\": \"dhcp4\",\n"
" \"csv-format\": true,\n"
" \"data\": \"12345\"\n"
" }\n"
"]\n";
string control_socket_header =
" ,\"control-socket\": { \n";
string control_socket_footer =
" \"socket-type\": \"http\", \n"
" \"socket-address\": \"127.0.0.1\", \n"
" \"socket-port\": 18124 \n"
" } \n";
string logger_txt =
" ,\"loggers\": [ { \n"
" \"name\": \"kea\", \n"
" \"severity\": \"FATAL\", \n"
" \"output-options\": [{ \n"
" \"output\": \"/dev/null\", \n"
" \"maxsize\": 0"
" }] \n"
" }] \n";
std::ostringstream os;
// Create a valid config with all the parts should parse
os << config_set_txt << ","
<< args_txt
<< dhcp4_cfg_txt
<< subnet1
<< subnet_footer
<< option_def
<< option_data
<< control_socket_header
<< " \"trust-anchor\": \"" << ca_dir << "/kea-ca.crt\", \n"
<< " \"cert-file\": \"" << ca_dir << "/kea-server.crt\", \n"
<< " \"key-file\": \"" << ca_dir << "/kea-server.key\", \n"
<< control_socket_footer
<< logger_txt
<< "}\n" // close dhcp4
<< "}}";
// Send the config-set command
std::string response;
sendHttpCommand(os.str(), response);
// Verify the configuration was successful. The config contains random
// file paths (CA directory), so the hash will be different each time.
// As such, we can do simplified checks:
// - verify the "result": 0 is there
// - verify the "text": "Configuration successful." is there
EXPECT_NE(response.find("\"result\": 0"), std::string::npos);
EXPECT_NE(response.find("\"text\": \"Configuration successful.\""),
std::string::npos);
// Check that the config was indeed applied.
const Subnet4Collection* subnets =
CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
EXPECT_EQ(1, subnets->size());
OptionDefinitionPtr def =
LibDHCP::getRuntimeOptionDef(DHCP4_OPTION_SPACE, 163);
ASSERT_TRUE(def);
// Verify the HTTP control channel socket exists.
ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
auto const listener = HttpCommandMgr::instance().getHttpListener().get();
ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener()->getTlsContext());
// The TLS settings have not changed
auto const context = HttpCommandMgr::instance().getHttpListener()->getTlsContext().get();
std::ostringstream second_config_os;
// Create a valid config with all the parts should parse
second_config_os << config_set_txt << ","
<< args_txt
<< dhcp4_cfg_txt
<< subnet1
<< subnet_footer
<< option_def
<< option_data
<< control_socket_header
<< control_socket_footer
<< logger_txt
<< "}\n" // close dhcp4
<< "}}";
// Send the config-set command.
sendHttpCommand(second_config_os.str(), response);
ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
EXPECT_EQ(listener, HttpCommandMgr::instance().getHttpListener().get());
ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener()->getTlsContext());
EXPECT_EQ(context, HttpCommandMgr::instance().getHttpListener()->getTlsContext().get());
EXPECT_NE(response.find("\"result\": 0"), std::string::npos);
EXPECT_NE(response.find("\"text\": \"Configuration successful.\""),
std::string::npos);
// Check that the config was not lost
subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
EXPECT_EQ(1, subnets->size());
// Clean up after the test.
CfgMgr::instance().clear();
}
} // End of anonymous namespace

View File

@@ -440,15 +440,10 @@ void configureCommandChannel() {
ConstElementPtr current_http_config =
CfgMgr::instance().getCurrentCfg()->getHttpControlSocketInfo();
sock_changed = (http_config && current_http_config &&
!http_config->equals(*current_http_config));
if (!http_config || !current_http_config || sock_changed) {
if (http_config) {
HttpCommandMgr::instance().openCommandSockets(http_config);
} else if (current_http_config) {
HttpCommandMgr::instance().closeCommandSockets();
}
if (http_config) {
HttpCommandMgr::instance().openCommandSockets(http_config);
} else if (current_http_config) {
HttpCommandMgr::instance().closeCommandSockets();
}
}

View File

@@ -1013,7 +1013,7 @@ TEST_F(HttpCtrlChannelDhcpv6Test, configSet) {
// Define strings to permutate the config arguments
// (Note the line feeds makes errors easy to find)
string set_config_txt = "{ \"command\": \"config-set\" \n";
string config_set_txt = "{ \"command\": \"config-set\" \n";
string args_txt = " \"arguments\": { \n";
string dhcp6_cfg_txt =
" \"Dhcp6\": { \n"
@@ -1087,7 +1087,7 @@ TEST_F(HttpCtrlChannelDhcpv6Test, configSet) {
std::ostringstream os;
// Create a valid config with all the parts should parse
os << set_config_txt << ","
os << config_set_txt << ","
<< args_txt
<< dhcp6_cfg_txt
<< subnet1
@@ -1116,7 +1116,7 @@ TEST_F(HttpCtrlChannelDhcpv6Test, configSet) {
// Create a config with malformed subnet that should fail to parse.
os.str("");
os << set_config_txt << ","
os << config_set_txt << ","
<< args_txt
<< dhcp6_cfg_txt
<< bad_subnet
@@ -1145,7 +1145,7 @@ TEST_F(HttpCtrlChannelDhcpv6Test, configSet) {
// Create a valid config with two subnets and no command channel.
// It should succeed, client should still receive the response
os.str("");
os << set_config_txt << ","
os << config_set_txt << ","
<< args_txt
<< dhcp6_cfg_txt
<< subnet1
@@ -1184,7 +1184,7 @@ TEST_F(HttpsCtrlChannelDhcpv6Test, configSet) {
// Define strings to permutate the config arguments
// (Note the line feeds makes errors easy to find)
string ca_dir(string(TEST_CA_DIR));
string set_config_txt = "{ \"command\": \"config-set\" \n";
string config_set_txt = "{ \"command\": \"config-set\" \n";
string args_txt = " \"arguments\": { \n";
string dhcp6_cfg_txt =
" \"Dhcp6\": { \n"
@@ -1259,7 +1259,7 @@ TEST_F(HttpsCtrlChannelDhcpv6Test, configSet) {
std::ostringstream os;
// Create a valid config with all the parts should parse
os << set_config_txt << ","
os << config_set_txt << ","
<< args_txt
<< dhcp6_cfg_txt
<< subnet1
@@ -1298,7 +1298,7 @@ TEST_F(HttpsCtrlChannelDhcpv6Test, configSet) {
// Create a config with malformed subnet that should fail to parse.
os.str("");
os << set_config_txt << ","
os << config_set_txt << ","
<< args_txt
<< dhcp6_cfg_txt
<< bad_subnet
@@ -1331,7 +1331,7 @@ TEST_F(HttpsCtrlChannelDhcpv6Test, configSet) {
// Create a valid config with two subnets and no command channel.
// It should succeed, client should still receive the response
os.str("");
os << set_config_txt << ","
os << config_set_txt << ","
<< args_txt
<< dhcp6_cfg_txt
<< subnet1
@@ -1443,7 +1443,7 @@ TEST_F(HttpCtrlChannelDhcpv6Test, configTest) {
// Define strings to permutate the config arguments
// (Note the line feeds makes errors easy to find)
string set_config_txt = "{ \"command\": \"config-set\" \n";
string config_set_txt = "{ \"command\": \"config-set\" \n";
string config_test_txt = "{ \"command\": \"config-test\" \n";
string args_txt = " \"arguments\": { \n";
string dhcp6_cfg_txt =
@@ -1496,7 +1496,7 @@ TEST_F(HttpCtrlChannelDhcpv6Test, configTest) {
std::ostringstream os;
// Create a valid config with all the parts should parse
os << set_config_txt << ","
os << config_set_txt << ","
<< args_txt
<< dhcp6_cfg_txt
<< subnet1
@@ -1585,7 +1585,7 @@ TEST_F(HttpsCtrlChannelDhcpv6Test, configTest) {
// Define strings to permutate the config arguments
// (Note the line feeds makes errors easy to find)
string ca_dir(string(TEST_CA_DIR));
string set_config_txt = "{ \"command\": \"config-set\" \n";
string config_set_txt = "{ \"command\": \"config-set\" \n";
string config_test_txt = "{ \"command\": \"config-test\" \n";
string args_txt = " \"arguments\": { \n";
string dhcp6_cfg_txt =
@@ -1639,7 +1639,7 @@ TEST_F(HttpsCtrlChannelDhcpv6Test, configTest) {
std::ostringstream os;
// Create a valid config with all the parts should parse
os << set_config_txt << ","
os << config_set_txt << ","
<< args_txt
<< dhcp6_cfg_txt
<< subnet1
@@ -3401,4 +3401,571 @@ TEST_F(HttpsCtrlChannelDhcpv6Test, connectionTimeoutNoData) {
testConnectionTimeoutNoData();
}
// Verify that the "config-set" command will reuse listener
TEST_F(HttpCtrlChannelDhcpv6Test, noListenerChange) {
createHttpChannelServer();
// Define strings to permutate the config arguments
// (Note the line feeds makes errors easy to find)
string config_set_txt = "{ \"command\": \"config-set\" \n";
string args_txt = " \"arguments\": { \n";
string dhcp6_cfg_txt =
" \"Dhcp6\": { \n"
" \"interfaces-config\": { \n"
" \"interfaces\": [\"*\"] \n"
" }, \n"
" \"preferred-lifetime\": 3000, \n"
" \"valid-lifetime\": 4000, \n"
" \"renew-timer\": 1000, \n"
" \"rebind-timer\": 2000, \n"
" \"lease-database\": { \n"
" \"type\": \"memfile\", \n"
" \"persist\":false, \n"
" \"lfc-interval\": 0 \n"
" }, \n"
" \"expired-leases-processing\": { \n"
" \"reclaim-timer-wait-time\": 0, \n"
" \"hold-reclaimed-time\": 0, \n"
" \"flush-reclaimed-timer-wait-time\": 0 \n"
" },"
" \"subnet6\": [ \n";
string subnet1 =
" {\"subnet\": \"3002::/64\", \"id\": 1, \n"
" \"pools\": [{ \"pool\": \"3002::100-3002::200\" }]}\n";
string subnet_footer =
" ] \n";
string option_def =
" ,\"option-def\": [\n"
" {\n"
" \"name\": \"foo\",\n"
" \"code\": 163,\n"
" \"type\": \"uint32\",\n"
" \"array\": false,\n"
" \"record-types\": \"\",\n"
" \"space\": \"dhcp6\",\n"
" \"encapsulate\": \"\"\n"
" }\n"
"]\n";
string option_data =
" ,\"option-data\": [\n"
" {\n"
" \"name\": \"foo\",\n"
" \"code\": 163,\n"
" \"space\": \"dhcp6\",\n"
" \"csv-format\": true,\n"
" \"data\": \"12345\"\n"
" }\n"
"]\n";
string control_socket =
" ,\"control-socket\": { \n"
" \"socket-type\": \"http\", \n"
" \"socket-address\": \"::1\", \n"
" \"socket-port\": 18126 \n"
" } \n";
string logger_txt =
" ,\"loggers\": [ { \n"
" \"name\": \"kea\", \n"
" \"severity\": \"FATAL\", \n"
" \"output-options\": [{ \n"
" \"output\": \"/dev/null\", \n"
" \"maxsize\": 0"
" }] \n"
" }] \n";
std::ostringstream os;
// Create a valid config with all the parts should parse
os << config_set_txt << ","
<< args_txt
<< dhcp6_cfg_txt
<< subnet1
<< subnet_footer
<< option_def
<< option_data
<< control_socket
<< logger_txt
<< "}\n" // close dhcp6
<< "}}";
// Send the config-set command
std::string response;
sendHttpCommand(os.str(), response);
EXPECT_EQ("[ { \"arguments\": { \"hash\": \"BCE3D0CC68CBBB49C3F5967E3FFCB4E44E55CBFB53814761B12ADB5C7CD95C1F\" }, \"result\": 0, \"text\": \"Configuration successful.\" } ]",
response);
// Check that the config was indeed applied.
const Subnet6Collection* subnets =
CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
EXPECT_EQ(1, subnets->size());
OptionDefinitionPtr def =
LibDHCP::getRuntimeOptionDef(DHCP6_OPTION_SPACE, 163);
ASSERT_TRUE(def);
// Verify the HTTP control channel socket exists.
ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
auto const listener = HttpCommandMgr::instance().getHttpListener().get();
ASSERT_FALSE(HttpCommandMgr::instance().getHttpListener()->getTlsContext());
// Send the config-set command.
sendHttpCommand(os.str(), response);
ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
EXPECT_EQ(listener, HttpCommandMgr::instance().getHttpListener().get());
ASSERT_FALSE(HttpCommandMgr::instance().getHttpListener()->getTlsContext());
EXPECT_EQ("[ { \"arguments\": { \"hash\": \"BCE3D0CC68CBBB49C3F5967E3FFCB4E44E55CBFB53814761B12ADB5C7CD95C1F\" }, \"result\": 0, \"text\": \"Configuration successful.\" } ]",
response);
// Check that the config was not lost
subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
EXPECT_EQ(1, subnets->size());
// Clean up after the test.
CfgMgr::instance().clear();
}
// Verify that the "config-set" command will reuse listener
TEST_F(HttpsCtrlChannelDhcpv6Test, noListenerChange) {
createHttpChannelServer();
// Define strings to permutate the config arguments
// (Note the line feeds makes errors easy to find)
string ca_dir(string(TEST_CA_DIR));
string config_set_txt = "{ \"command\": \"config-set\" \n";
string args_txt = " \"arguments\": { \n";
string dhcp6_cfg_txt =
" \"Dhcp6\": { \n"
" \"interfaces-config\": { \n"
" \"interfaces\": [\"*\"] \n"
" }, \n"
" \"preferred-lifetime\": 3000, \n"
" \"valid-lifetime\": 4000, \n"
" \"renew-timer\": 1000, \n"
" \"rebind-timer\": 2000, \n"
" \"lease-database\": { \n"
" \"type\": \"memfile\", \n"
" \"persist\":false, \n"
" \"lfc-interval\": 0 \n"
" }, \n"
" \"expired-leases-processing\": { \n"
" \"reclaim-timer-wait-time\": 0, \n"
" \"hold-reclaimed-time\": 0, \n"
" \"flush-reclaimed-timer-wait-time\": 0 \n"
" },"
" \"subnet6\": [ \n";
string subnet1 =
" {\"subnet\": \"3002::/64\", \"id\": 1, \n"
" \"pools\": [{ \"pool\": \"3002::100-3002::200\" }]}\n";
string subnet_footer =
" ] \n";
string option_def =
" ,\"option-def\": [\n"
" {\n"
" \"name\": \"foo\",\n"
" \"code\": 163,\n"
" \"type\": \"uint32\",\n"
" \"array\": false,\n"
" \"record-types\": \"\",\n"
" \"space\": \"dhcp6\",\n"
" \"encapsulate\": \"\"\n"
" }\n"
"]\n";
string option_data =
" ,\"option-data\": [\n"
" {\n"
" \"name\": \"foo\",\n"
" \"code\": 163,\n"
" \"space\": \"dhcp6\",\n"
" \"csv-format\": true,\n"
" \"data\": \"12345\"\n"
" }\n"
"]\n";
string control_socket_header =
" ,\"control-socket\": { \n";
string control_socket_footer =
" \"socket-type\": \"http\", \n"
" \"socket-address\": \"::1\", \n"
" \"socket-port\": 18126 \n"
" } \n";
string logger_txt =
" ,\"loggers\": [ { \n"
" \"name\": \"kea\", \n"
" \"severity\": \"FATAL\", \n"
" \"output-options\": [{ \n"
" \"output\": \"/dev/null\", \n"
" \"maxsize\": 0"
" }] \n"
" }] \n";
std::ostringstream os;
// Create a valid config with all the parts should parse
os << config_set_txt << ","
<< args_txt
<< dhcp6_cfg_txt
<< subnet1
<< subnet_footer
<< option_def
<< option_data
<< control_socket_header
<< " \"trust-anchor\": \"" << ca_dir << "/kea-ca.crt\", \n"
<< " \"cert-file\": \"" << ca_dir << "/kea-server.crt\", \n"
<< " \"key-file\": \"" << ca_dir << "/kea-server.key\", \n"
<< control_socket_footer
<< logger_txt
<< "}\n" // close dhcp6
<< "}}";
// Send the config-set command
std::string response;
sendHttpCommand(os.str(), response);
// Verify the configuration was successful. The config contains random
// file paths (CA directory), so the hash will be different each time.
// As such, we can do simplified checks:
// - verify the "result": 0 is there
// - verify the "text": "Configuration successful." is there
EXPECT_NE(response.find("\"result\": 0"), std::string::npos);
EXPECT_NE(response.find("\"text\": \"Configuration successful.\""),
std::string::npos);
// Check that the config was indeed applied.
const Subnet6Collection* subnets =
CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
EXPECT_EQ(1, subnets->size());
OptionDefinitionPtr def =
LibDHCP::getRuntimeOptionDef(DHCP6_OPTION_SPACE, 163);
ASSERT_TRUE(def);
// Verify the HTTP control channel socket exists.
ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
auto const listener = HttpCommandMgr::instance().getHttpListener().get();
ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener()->getTlsContext());
auto const context = HttpCommandMgr::instance().getHttpListener()->getTlsContext().get();
// Send the config-set command.
sendHttpCommand(os.str(), response);
ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
EXPECT_EQ(listener, HttpCommandMgr::instance().getHttpListener().get());
ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener()->getTlsContext());
// The TLS settings have been applied
EXPECT_NE(context, HttpCommandMgr::instance().getHttpListener()->getTlsContext().get());
EXPECT_NE(response.find("\"result\": 0"), std::string::npos);
EXPECT_NE(response.find("\"text\": \"Configuration successful.\""),
std::string::npos);
// Check that the config was not lost
subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
EXPECT_EQ(1, subnets->size());
// Clean up after the test.
CfgMgr::instance().clear();
}
// Verify that the "config-set" command will reuse listener
TEST_F(HttpCtrlChannelDhcpv6Test, ignoreHttpToHttpsSwitch) {
createHttpChannelServer();
// Define strings to permutate the config arguments
// (Note the line feeds makes errors easy to find)
string ca_dir(string(TEST_CA_DIR));
string config_set_txt = "{ \"command\": \"config-set\" \n";
string args_txt = " \"arguments\": { \n";
string dhcp6_cfg_txt =
" \"Dhcp6\": { \n"
" \"interfaces-config\": { \n"
" \"interfaces\": [\"*\"] \n"
" }, \n"
" \"preferred-lifetime\": 3000, \n"
" \"valid-lifetime\": 4000, \n"
" \"renew-timer\": 1000, \n"
" \"rebind-timer\": 2000, \n"
" \"lease-database\": { \n"
" \"type\": \"memfile\", \n"
" \"persist\":false, \n"
" \"lfc-interval\": 0 \n"
" }, \n"
" \"expired-leases-processing\": { \n"
" \"reclaim-timer-wait-time\": 0, \n"
" \"hold-reclaimed-time\": 0, \n"
" \"flush-reclaimed-timer-wait-time\": 0 \n"
" },"
" \"subnet6\": [ \n";
string subnet1 =
" {\"subnet\": \"3002::/64\", \"id\": 1, \n"
" \"pools\": [{ \"pool\": \"3002::100-3002::200\" }]}\n";
string subnet_footer =
" ] \n";
string option_def =
" ,\"option-def\": [\n"
" {\n"
" \"name\": \"foo\",\n"
" \"code\": 163,\n"
" \"type\": \"uint32\",\n"
" \"array\": false,\n"
" \"record-types\": \"\",\n"
" \"space\": \"dhcp6\",\n"
" \"encapsulate\": \"\"\n"
" }\n"
"]\n";
string option_data =
" ,\"option-data\": [\n"
" {\n"
" \"name\": \"foo\",\n"
" \"code\": 163,\n"
" \"space\": \"dhcp6\",\n"
" \"csv-format\": true,\n"
" \"data\": \"12345\"\n"
" }\n"
"]\n";
string control_socket_header =
" ,\"control-socket\": { \n";
string control_socket_footer =
" \"socket-type\": \"http\", \n"
" \"socket-address\": \"::1\", \n"
" \"socket-port\": 18126 \n"
" } \n";
string logger_txt =
" ,\"loggers\": [ { \n"
" \"name\": \"kea\", \n"
" \"severity\": \"FATAL\", \n"
" \"output-options\": [{ \n"
" \"output\": \"/dev/null\", \n"
" \"maxsize\": 0"
" }] \n"
" }] \n";
std::ostringstream os;
// Create a valid config with all the parts should parse
os << config_set_txt << ","
<< args_txt
<< dhcp6_cfg_txt
<< subnet1
<< subnet_footer
<< option_def
<< option_data
<< control_socket_header
<< control_socket_footer
<< logger_txt
<< "}\n" // close dhcp6
<< "}}";
// Send the config-set command
std::string response;
sendHttpCommand(os.str(), response);
EXPECT_EQ("[ { \"arguments\": { \"hash\": \"BCE3D0CC68CBBB49C3F5967E3FFCB4E44E55CBFB53814761B12ADB5C7CD95C1F\" }, \"result\": 0, \"text\": \"Configuration successful.\" } ]",
response);
// Check that the config was indeed applied.
const Subnet6Collection* subnets =
CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
EXPECT_EQ(1, subnets->size());
OptionDefinitionPtr def =
LibDHCP::getRuntimeOptionDef(DHCP6_OPTION_SPACE, 163);
ASSERT_TRUE(def);
// Verify the HTTP control channel socket exists.
ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
auto const listener = HttpCommandMgr::instance().getHttpListener().get();
ASSERT_FALSE(HttpCommandMgr::instance().getHttpListener()->getTlsContext());
std::ostringstream second_config_os;
// Create a valid config with all the parts should parse
second_config_os << config_set_txt << ","
<< args_txt
<< dhcp6_cfg_txt
<< subnet1
<< subnet_footer
<< option_def
<< option_data
<< control_socket_header
<< " \"trust-anchor\": \"" << ca_dir << "/kea-ca.crt\", \n"
<< " \"cert-file\": \"" << ca_dir << "/kea-server.crt\", \n"
<< " \"key-file\": \"" << ca_dir << "/kea-server.key\", \n"
<< control_socket_footer
<< logger_txt
<< "}\n" // close dhcp6
<< "}}";
// Send the config-set command.
sendHttpCommand(second_config_os.str(), response);
ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
EXPECT_EQ(listener, HttpCommandMgr::instance().getHttpListener().get());
ASSERT_FALSE(HttpCommandMgr::instance().getHttpListener()->getTlsContext());
EXPECT_NE(response.find("\"result\": 0"), std::string::npos);
EXPECT_NE(response.find("\"text\": \"Configuration successful.\""),
std::string::npos);
// Check that the config was not lost
subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
EXPECT_EQ(1, subnets->size());
// Clean up after the test.
CfgMgr::instance().clear();
}
// Verify that the "config-set" command will reuse listener
TEST_F(HttpsCtrlChannelDhcpv6Test, ignoreHttpsToHttpSwitch) {
createHttpChannelServer();
// Define strings to permutate the config arguments
// (Note the line feeds makes errors easy to find)
string ca_dir(string(TEST_CA_DIR));
string config_set_txt = "{ \"command\": \"config-set\" \n";
string args_txt = " \"arguments\": { \n";
string dhcp6_cfg_txt =
" \"Dhcp6\": { \n"
" \"interfaces-config\": { \n"
" \"interfaces\": [\"*\"] \n"
" }, \n"
" \"preferred-lifetime\": 3000, \n"
" \"valid-lifetime\": 4000, \n"
" \"renew-timer\": 1000, \n"
" \"rebind-timer\": 2000, \n"
" \"lease-database\": { \n"
" \"type\": \"memfile\", \n"
" \"persist\":false, \n"
" \"lfc-interval\": 0 \n"
" }, \n"
" \"expired-leases-processing\": { \n"
" \"reclaim-timer-wait-time\": 0, \n"
" \"hold-reclaimed-time\": 0, \n"
" \"flush-reclaimed-timer-wait-time\": 0 \n"
" },"
" \"subnet6\": [ \n";
string subnet1 =
" {\"subnet\": \"3002::/64\", \"id\": 1, \n"
" \"pools\": [{ \"pool\": \"3002::100-3002::200\" }]}\n";
string subnet_footer =
" ] \n";
string option_def =
" ,\"option-def\": [\n"
" {\n"
" \"name\": \"foo\",\n"
" \"code\": 163,\n"
" \"type\": \"uint32\",\n"
" \"array\": false,\n"
" \"record-types\": \"\",\n"
" \"space\": \"dhcp6\",\n"
" \"encapsulate\": \"\"\n"
" }\n"
"]\n";
string option_data =
" ,\"option-data\": [\n"
" {\n"
" \"name\": \"foo\",\n"
" \"code\": 163,\n"
" \"space\": \"dhcp6\",\n"
" \"csv-format\": true,\n"
" \"data\": \"12345\"\n"
" }\n"
"]\n";
string control_socket_header =
" ,\"control-socket\": { \n";
string control_socket_footer =
" \"socket-type\": \"http\", \n"
" \"socket-address\": \"::1\", \n"
" \"socket-port\": 18126 \n"
" } \n";
string logger_txt =
" ,\"loggers\": [ { \n"
" \"name\": \"kea\", \n"
" \"severity\": \"FATAL\", \n"
" \"output-options\": [{ \n"
" \"output\": \"/dev/null\", \n"
" \"maxsize\": 0"
" }] \n"
" }] \n";
std::ostringstream os;
// Create a valid config with all the parts should parse
os << config_set_txt << ","
<< args_txt
<< dhcp6_cfg_txt
<< subnet1
<< subnet_footer
<< option_def
<< option_data
<< control_socket_header
<< " \"trust-anchor\": \"" << ca_dir << "/kea-ca.crt\", \n"
<< " \"cert-file\": \"" << ca_dir << "/kea-server.crt\", \n"
<< " \"key-file\": \"" << ca_dir << "/kea-server.key\", \n"
<< control_socket_footer
<< logger_txt
<< "}\n" // close dhcp6
<< "}}";
// Send the config-set command
std::string response;
sendHttpCommand(os.str(), response);
// Verify the configuration was successful. The config contains random
// file paths (CA directory), so the hash will be different each time.
// As such, we can do simplified checks:
// - verify the "result": 0 is there
// - verify the "text": "Configuration successful." is there
EXPECT_NE(response.find("\"result\": 0"), std::string::npos);
EXPECT_NE(response.find("\"text\": \"Configuration successful.\""),
std::string::npos);
// Check that the config was indeed applied.
const Subnet6Collection* subnets =
CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
EXPECT_EQ(1, subnets->size());
OptionDefinitionPtr def =
LibDHCP::getRuntimeOptionDef(DHCP6_OPTION_SPACE, 163);
ASSERT_TRUE(def);
// Verify the HTTP control channel socket exists.
ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
auto const listener = HttpCommandMgr::instance().getHttpListener().get();
ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener()->getTlsContext());
// The TLS settings have not changed
auto const context = HttpCommandMgr::instance().getHttpListener()->getTlsContext().get();
std::ostringstream second_config_os;
// Create a valid config with all the parts should parse
second_config_os << config_set_txt << ","
<< args_txt
<< dhcp6_cfg_txt
<< subnet1
<< subnet_footer
<< option_def
<< option_data
<< control_socket_header
<< control_socket_footer
<< logger_txt
<< "}\n" // close dhcp6
<< "}}";
// Send the config-set command.
sendHttpCommand(second_config_os.str(), response);
ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener());
EXPECT_EQ(listener, HttpCommandMgr::instance().getHttpListener().get());
ASSERT_TRUE(HttpCommandMgr::instance().getHttpListener()->getTlsContext());
EXPECT_EQ(context, HttpCommandMgr::instance().getHttpListener()->getTlsContext().get());
EXPECT_NE(response.find("\"result\": 0"), std::string::npos);
EXPECT_NE(response.find("\"text\": \"Configuration successful.\""),
std::string::npos);
// Check that the config was not lost
subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
EXPECT_EQ(1, subnets->size());
// Clean up after the test.
CfgMgr::instance().clear();
}
} // End of anonymous namespace

View File

@@ -34,7 +34,8 @@ extern const isc::log::MessageID COMMAND_SOCKET_WRITE_FAIL = "COMMAND_SOCKET_WRI
extern const isc::log::MessageID COMMAND_WATCH_SOCKET_CLEAR_ERROR = "COMMAND_WATCH_SOCKET_CLEAR_ERROR";
extern const isc::log::MessageID COMMAND_WATCH_SOCKET_CLOSE_ERROR = "COMMAND_WATCH_SOCKET_CLOSE_ERROR";
extern const isc::log::MessageID COMMAND_WATCH_SOCKET_MARK_READY_ERROR = "COMMAND_WATCH_SOCKET_MARK_READY_ERROR";
extern const isc::log::MessageID HTTP_COMMAND_MGR_IGNORED_TLS_SETUP_CHANGES = "HTTP_COMMAND_MGR_IGNORED_TLS_SETUP_CHANGES";
extern const isc::log::MessageID HTTP_COMMAND_MGR_HTTPS_SERVICE_REUSED = "HTTP_COMMAND_MGR_HTTPS_SERVICE_REUSED";
extern const isc::log::MessageID HTTP_COMMAND_MGR_HTTP_SERVICE_REUSED = "HTTP_COMMAND_MGR_HTTP_SERVICE_REUSED";
extern const isc::log::MessageID HTTP_COMMAND_MGR_SERVICE_STARTED = "HTTP_COMMAND_MGR_SERVICE_STARTED";
extern const isc::log::MessageID HTTP_COMMAND_MGR_SERVICE_STOPPING = "HTTP_COMMAND_MGR_SERVICE_STOPPING";
extern const isc::log::MessageID HTTP_COMMAND_MGR_SERVICE_STOPPING_ALL = "HTTP_COMMAND_MGR_SERVICE_STOPPING_ALL";
@@ -73,7 +74,8 @@ const char* values[] = {
"COMMAND_WATCH_SOCKET_CLEAR_ERROR", "watch socket failed to clear: %1",
"COMMAND_WATCH_SOCKET_CLOSE_ERROR", "watch socket failed to close: %1",
"COMMAND_WATCH_SOCKET_MARK_READY_ERROR", "watch socket failed to mark ready: %1",
"HTTP_COMMAND_MGR_IGNORED_TLS_SETUP_CHANGES", "ignore a change in TLS setup of the http control socket",
"HTTP_COMMAND_MGR_HTTPS_SERVICE_REUSED", "reused HTTPS service bound to address %1:%2",
"HTTP_COMMAND_MGR_HTTP_SERVICE_REUSED", "reused HTTP service bound to address %1:%2",
"HTTP_COMMAND_MGR_SERVICE_STARTED", "started %1 service bound to address %2 port %3",
"HTTP_COMMAND_MGR_SERVICE_STOPPING", "Server is stopping %1 service %2",
"HTTP_COMMAND_MGR_SERVICE_STOPPING_ALL", "stopping %1 service %2",

View File

@@ -35,7 +35,8 @@ extern const isc::log::MessageID COMMAND_SOCKET_WRITE_FAIL;
extern const isc::log::MessageID COMMAND_WATCH_SOCKET_CLEAR_ERROR;
extern const isc::log::MessageID COMMAND_WATCH_SOCKET_CLOSE_ERROR;
extern const isc::log::MessageID COMMAND_WATCH_SOCKET_MARK_READY_ERROR;
extern const isc::log::MessageID HTTP_COMMAND_MGR_IGNORED_TLS_SETUP_CHANGES;
extern const isc::log::MessageID HTTP_COMMAND_MGR_HTTPS_SERVICE_REUSED;
extern const isc::log::MessageID HTTP_COMMAND_MGR_HTTP_SERVICE_REUSED;
extern const isc::log::MessageID HTTP_COMMAND_MGR_SERVICE_STARTED;
extern const isc::log::MessageID HTTP_COMMAND_MGR_SERVICE_STOPPING;
extern const isc::log::MessageID HTTP_COMMAND_MGR_SERVICE_STOPPING_ALL;

View File

@@ -153,11 +153,14 @@ ready status after scheduling asynchronous send. This is programmatic error
that should be reported. The command manager may or may not continue
to operate correctly.
% HTTP_COMMAND_MGR_IGNORED_TLS_SETUP_CHANGES ignore a change in TLS setup of the http control socket
The warning message is issued when the HTTP/HTTPS control socket was
reconfigured with a different TLS setup but keeping the address and port.
These changes are ignored because they can't be applied without opening a new
socket which will conflict with the existing one.
% HTTP_COMMAND_MGR_HTTPS_SERVICE_REUSED reused HTTPS service bound to address %1:%2
This informational message indicates that the server has reused existing
HTTPS service on the specified address and port. Note that any change in
the TLS setup was ignored.
% HTTP_COMMAND_MGR_HTTP_SERVICE_REUSED reused HTTP service bound to address %1:%2
This informational message indicates that the server has reused existing
HTTP service on the specified address and port.
% HTTP_COMMAND_MGR_SERVICE_STARTED started %1 service bound to address %2 port %3
This informational message indicates that the server has started

View File

@@ -127,15 +127,37 @@ HttpCommandMgrImpl::openCommandSocket(const isc::data::ConstElementPtr config) {
// Search for the specific connection and reuse the existing one if found.
auto it = sockets_.find(std::make_pair(server_address, server_port));
if (it != sockets_.end()) {
if ((cmd_config->getTrustAnchor() != it->second->config_->getTrustAnchor()) ||
(cmd_config->getCertFile() != it->second->config_->getCertFile()) ||
(cmd_config->getKeyFile() != it->second->config_->getKeyFile()) ||
(cmd_config->getCertRequired() != it->second->config_->getCertRequired())) {
LOG_WARN(command_logger, HTTP_COMMAND_MGR_IGNORED_TLS_SETUP_CHANGES);
// Overwrite the authentication setup and the emulation flag
// in the response creator config.
it->second->config_->setAuthConfig(cmd_config->getAuthConfig());
it->second->config_->setEmulateAgentResponse(cmd_config->getEmulateAgentResponse());
auto listener = it->second->listener_;
if (listener) {
// Reconfig keeping the same address and port.
if (listener->getTlsContext()) {
if (cmd_config->getTrustAnchor().empty()) {
// Can not switch from HTTPS to HTTP
LOG_INFO(command_logger, HTTP_COMMAND_MGR_HTTPS_SERVICE_REUSED)
.arg(server_address.toText())
.arg(server_port);
} else {
// Apply TLS settings each time.
TlsContextPtr tls_context;
TlsContext::configure(tls_context,
TlsRole::SERVER,
cmd_config->getTrustAnchor(),
cmd_config->getCertFile(),
cmd_config->getKeyFile(),
cmd_config->getCertRequired());
// Overwrite the authentication setup, the http headers and the emulation flag
// in the response creator config.
it->second->config_->setAuthConfig(cmd_config->getAuthConfig());
it->second->config_->setHttpHeaders(cmd_config->getHttpHeaders());
it->second->config_->setEmulateAgentResponse(cmd_config->getEmulateAgentResponse());
io_service_->post([listener, tls_context]() { listener->setTlsContext(tls_context); });
}
} else if (!cmd_config->getTrustAnchor().empty()) {
// Can not switch from HTTP to HTTPS
LOG_INFO(command_logger, HTTP_COMMAND_MGR_HTTP_SERVICE_REUSED)
.arg(server_address.toText())
.arg(server_port);
}
}
// If the connection can be reused, mark it as usable.
it->second->usable_ = true;

View File

@@ -47,6 +47,11 @@ HttpListener::getTlsContext() const {
return (impl_->getTlsContext());
}
void
HttpListener::setTlsContext(const TlsContextPtr& context) {
impl_->setTlsContext(context);
}
int
HttpListener::getNative() const {
return (impl_->getNative());

View File

@@ -118,6 +118,9 @@ public:
/// @brief Returns reference to the current TLS context.
const asiolink::TlsContextPtr& getTlsContext() const;
/// @brief Sets reference of the current TLS context.
void setTlsContext(const asiolink::TlsContextPtr& context);
/// @brief file descriptor of the underlying acceptor socket.
int getNative() const;

View File

@@ -75,6 +75,11 @@ HttpListenerImpl::getTlsContext() const {
return (tls_context_);
}
void
HttpListenerImpl::setTlsContext(const TlsContextPtr& context) {
tls_context_ = context;
}
int
HttpListenerImpl::getNative() const {
return (acceptor_ ? acceptor_->getNative() : -1);

View File

@@ -63,6 +63,9 @@ public:
/// @brief Returns reference to the current TLS context.
const asiolink::TlsContextPtr& getTlsContext() const;
/// @brief Sets reference of the current TLS context.
void setTlsContext(const asiolink::TlsContextPtr& context);
/// @brief file descriptor of the underlying acceptor socket.
int getNative() const;