diff --git a/src/bin/agent/ca_messages.mes b/src/bin/agent/ca_messages.mes index f592955f3d..fce41e1b6b 100644 --- a/src/bin/agent/ca_messages.mes +++ b/src/bin/agent/ca_messages.mes @@ -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 diff --git a/src/bin/agent/ca_process.cc b/src/bin/agent/ca_process.cc index a736ccdb4e..7ee3e142fb 100644 --- a/src/bin/agent/ca_process.cc +++ b/src/bin/agent/ca_process.cc @@ -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); diff --git a/src/bin/agent/tests/Makefile.am b/src/bin/agent/tests/Makefile.am index 100d2467df..5497c9ef60 100644 --- a/src/bin/agent/tests/Makefile.am +++ b/src/bin/agent/tests/Makefile.am @@ -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) diff --git a/src/bin/agent/tests/ca_controller_unittests.cc b/src/bin/agent/tests/ca_controller_unittests.cc index 9a18c27a0a..3890606e63 100644 --- a/src/bin/agent/tests/ca_controller_unittests.cc +++ b/src/bin/agent/tests/ca_controller_unittests.cc @@ -21,6 +21,7 @@ #include +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(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(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(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()); diff --git a/src/bin/d2/d2_process.cc b/src/bin/d2/d2_process.cc index 617686b9c1..7d17961b8f 100644 --- a/src/bin/d2/d2_process.cc +++ b/src/bin/d2/d2_process.cc @@ -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. diff --git a/src/bin/d2/tests/d2_http_command_unittest.cc b/src/bin/d2/tests/d2_http_command_unittest.cc index 0f8999af3d..00c2afcbf9 100644 --- a/src/bin/d2/tests/d2_http_command_unittest.cc +++ b/src/bin/d2/tests/d2_http_command_unittest.cc @@ -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' (: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' (: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 diff --git a/src/bin/dhcp4/json_config_parser.cc b/src/bin/dhcp4/json_config_parser.cc index df0ac5b54a..3f0e252cd6 100644 --- a/src/bin/dhcp4/json_config_parser.cc +++ b/src/bin/dhcp4/json_config_parser.cc @@ -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(); } } diff --git a/src/bin/dhcp4/tests/http_control_socket_unittest.cc b/src/bin/dhcp4/tests/http_control_socket_unittest.cc index 4b6d690221..1a43c8205e 100644 --- a/src/bin/dhcp4/tests/http_control_socket_unittest.cc +++ b/src/bin/dhcp4/tests/http_control_socket_unittest.cc @@ -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 diff --git a/src/bin/dhcp6/json_config_parser.cc b/src/bin/dhcp6/json_config_parser.cc index cecf3536e4..b65faec709 100644 --- a/src/bin/dhcp6/json_config_parser.cc +++ b/src/bin/dhcp6/json_config_parser.cc @@ -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(); } } diff --git a/src/bin/dhcp6/tests/http_control_socket_unittest.cc b/src/bin/dhcp6/tests/http_control_socket_unittest.cc index 7a64400268..b2cc74ef26 100644 --- a/src/bin/dhcp6/tests/http_control_socket_unittest.cc +++ b/src/bin/dhcp6/tests/http_control_socket_unittest.cc @@ -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 diff --git a/src/lib/config/config_messages.cc b/src/lib/config/config_messages.cc index b63a98c3b8..0e9c07473f 100644 --- a/src/lib/config/config_messages.cc +++ b/src/lib/config/config_messages.cc @@ -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", diff --git a/src/lib/config/config_messages.h b/src/lib/config/config_messages.h index 6ab6b17c3b..fd55744e49 100644 --- a/src/lib/config/config_messages.h +++ b/src/lib/config/config_messages.h @@ -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; diff --git a/src/lib/config/config_messages.mes b/src/lib/config/config_messages.mes index 2c268cd7e9..7c4fd50dc3 100644 --- a/src/lib/config/config_messages.mes +++ b/src/lib/config/config_messages.mes @@ -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 diff --git a/src/lib/config/http_command_mgr.cc b/src/lib/config/http_command_mgr.cc index 9fcc44d11f..0725d7821e 100644 --- a/src/lib/config/http_command_mgr.cc +++ b/src/lib/config/http_command_mgr.cc @@ -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; diff --git a/src/lib/http/listener.cc b/src/lib/http/listener.cc index 9c05e99689..3b137c37c5 100644 --- a/src/lib/http/listener.cc +++ b/src/lib/http/listener.cc @@ -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()); diff --git a/src/lib/http/listener.h b/src/lib/http/listener.h index 9f35e62642..0180eb0d9e 100644 --- a/src/lib/http/listener.h +++ b/src/lib/http/listener.h @@ -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; diff --git a/src/lib/http/listener_impl.cc b/src/lib/http/listener_impl.cc index 85fd74ee3d..58754cb349 100644 --- a/src/lib/http/listener_impl.cc +++ b/src/lib/http/listener_impl.cc @@ -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); diff --git a/src/lib/http/listener_impl.h b/src/lib/http/listener_impl.h index f36a1330b8..6ae863b109 100644 --- a/src/lib/http/listener_impl.h +++ b/src/lib/http/listener_impl.h @@ -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;