extern const isc::log::MessageID COMMAND_HTTP_LISTENER_STARTED = "COMMAND_HTTP_LISTENER_STARTED";
extern const isc::log::MessageID COMMAND_HTTP_LISTENER_STOPPED = "COMMAND_HTTP_LISTENER_STOPPED";
extern const isc::log::MessageID COMMAND_HTTP_LISTENER_STOPPING = "COMMAND_HTTP_LISTENER_STOPPING";
+extern const isc::log::MessageID COMMAND_HTTP_SOCKET_SECURITY_WARN = "COMMAND_HTTP_SOCKET_SECURITY_WARN";
extern const isc::log::MessageID COMMAND_PROCESS_ERROR1 = "COMMAND_PROCESS_ERROR1";
extern const isc::log::MessageID COMMAND_PROCESS_ERROR2 = "COMMAND_PROCESS_ERROR2";
extern const isc::log::MessageID COMMAND_RECEIVED = "COMMAND_RECEIVED";
"COMMAND_HTTP_LISTENER_STARTED", "Command HTTP listener started with %1 threads, listening on address: %2 port: %3, use TLS: %4",
"COMMAND_HTTP_LISTENER_STOPPED", "Command HTTP listener for address: %1 port: %2 stopped.",
"COMMAND_HTTP_LISTENER_STOPPING", "Stopping Command HTTP listener for address: %1 port: %2",
+ "COMMAND_HTTP_SOCKET_SECURITY_WARN", "command socket configuration is NOT SECURE: %1",
"COMMAND_PROCESS_ERROR1", "Error while processing command: %1",
"COMMAND_PROCESS_ERROR2", "Error while processing command: %1",
"COMMAND_RECEIVED", "Received command '%1'",
extern const isc::log::MessageID COMMAND_HTTP_LISTENER_STARTED;
extern const isc::log::MessageID COMMAND_HTTP_LISTENER_STOPPED;
extern const isc::log::MessageID COMMAND_HTTP_LISTENER_STOPPING;
+extern const isc::log::MessageID COMMAND_HTTP_SOCKET_SECURITY_WARN;
extern const isc::log::MessageID COMMAND_PROCESS_ERROR1;
extern const isc::log::MessageID COMMAND_PROCESS_ERROR2;
extern const isc::log::MessageID COMMAND_RECEIVED;
and the path specified for a control channel unix socket-name does
not have the required socket permissions. The server will still use the
specified path but is warning that doing so may pose a security risk.
+
+% COMMAND_HTTP_SOCKET_SECURITY_WARN command socket configuration is NOT SECURE: %1
+This warning message is issued when security enforcement is disabled
+and command socket configuration does not use HTTPS/TLS or baseic HTTP
+authentication. The server will still use the socket as configured but
+is warning that doing so may pose a security risk.
#include <cc/dhcp_config_error.h>
#include <config/command_mgr.h>
+#include <config/config_log.h>
#include <config/http_command_config.h>
#include <http/basic_auth_config.h>
+#include <util/filesystem.h>
#include <limits>
using namespace isc;
using namespace isc::data;
using namespace isc::dhcp;
using namespace isc::http;
+using namespace isc::util;
using namespace std;
namespace isc {
}
// Check the TLS setup.
- checkTlsSetup(socket_type_ == "https");
+ bool has_tls = checkTlsSetup(socket_type_ == "https");
+ if (!auth_config_ && !has_tls) {
+ if (file::PathChecker::shouldEnforceSecurity()) {
+ isc_throw(DhcpConfigError, "Unsecured HTTP control channel ("
+ << config->getPosition() << ")");
+
+ }
+
+ std::ostringstream oss;
+ config->toJSON(oss);
+ LOG_WARN(command_logger, COMMAND_HTTP_SOCKET_SECURITY_WARN)
+ .arg(oss.str());
+ }
// Get user context.
ConstElementPtr user_context = config->get("user-context");
}
}
-void
+bool
HttpCommandConfig::checkTlsSetup(bool require_tls) const {
bool have_ca = !trust_anchor_.empty();
bool have_cert = !cert_file_.empty();
isc_throw(DhcpConfigError,
"no TLS setup for a HTTPS control socket");
}
- return;
+ return (false);
}
// TLS is used: all 3 parameters are required.
if (!have_ca) {
isc_throw(DhcpConfigError, "key-file parameter is missing or empty:"
" all or none of TLS parameters must be set");
}
+
+ return (true);
}
ElementPtr
/// @brief Check TLS configuration.
///
/// @param require_tls When true TLS is required.
- void checkTlsSetup(bool require_tls) const;
+ /// @return true if TLS parameters are all present, false
+ /// otherwise.
+ /// @throw DhcpConfigError if socket type is https and
+ /// TLS is missing or incomplete, or if socket type is
+ /// http and some TLS parameters are specified but not
+ /// all.
+ bool checkTlsSetup(bool require_tls) const;
/// @brief Socket type ("http" or "https").
std::string socket_type_;
#include <config/http_command_config.h>
#include <http/basic_auth_config.h>
+#include <util/filesystem.h>
#include <testutils/gtest_utils.h>
#include <testutils/test_to_element.h>
+#include <testutils/log_utils.h>
using namespace isc;
using namespace isc::asiolink;
using namespace isc::dhcp;
using namespace isc::http;
using namespace isc::test;
+using namespace isc::util;
+using namespace isc::dhcp::test;
using namespace std;
namespace {
/// @brief Test fixture for HTTP control socket configuration.
-class HttpCommandConfigTest : public ::testing::Test {
+class HttpCommandConfigTest : public LogContentTest {
public:
/// @brief Constructor.
HttpCommandConfigTest() : http_config_() {
HttpCommandConfig::DEFAULT_SOCKET_ADDRESS = IOAddress("127.0.0.1");
HttpCommandConfig::DEFAULT_SOCKET_PORT = 8000;
HttpCommandConfig::DEFAULT_AUTHENTICATION_REALM = "";
+ file::PathChecker::enableEnforcement(true);
}
/// @brief Destructor.
HttpCommandConfig::DEFAULT_SOCKET_ADDRESS = IOAddress("127.0.0.1");
HttpCommandConfig::DEFAULT_SOCKET_PORT = 8000;
HttpCommandConfig::DEFAULT_AUTHENTICATION_REALM = "";
+ file::PathChecker::enableEnforcement(true);
}
/// @brief HTTP control socket configuration.
// This test verifies the default HTTP control socket configuration.
TEST_F(HttpCommandConfigTest, default) {
+ // "Default" config should throw since security is enabled.
ElementPtr json = Element::createMap();
+ ASSERT_THROW_MSG(http_config_.reset(new HttpCommandConfig(json)),
+ DhcpConfigError, "Unsecured HTTP control channel (:0:0)");
+
+ // Turn off security enforcment. Configuration will succeed but we
+ // should see WARN logs.
+ file::PathChecker::enableEnforcement(false);
ASSERT_NO_THROW_LOG(http_config_.reset(new HttpCommandConfig(json)));
// Check default values.
)";
runToElementTest(expected, *http_config_);
+ ASSERT_EQ(1, countFile("COMMAND_HTTP_SOCKET_SECURITY_WARN command socket"
+ " configuration is NOT SECURE: { }"));
+
// Change class defaults.
HttpCommandConfig::DEFAULT_SOCKET_ADDRESS = IOAddress("::1");
HttpCommandConfig::DEFAULT_SOCKET_PORT = 8080;
ASSERT_NO_THROW_LOG(http_config_.reset(new HttpCommandConfig(json)));
EXPECT_EQ("::1", http_config_->getSocketAddress().toText());
EXPECT_EQ(8080, http_config_->getSocketPort());
+
+ ASSERT_EQ(2, countFile("COMMAND_HTTP_SOCKET_SECURITY_WARN command socket"
+ " configuration is NOT SECURE: { }"));
}
// This test verifies direct error cases.
// This test verifies a HTTP control socket configuration with HTTP headers
// can be parsed and unparsed.
TEST_F(HttpCommandConfigTest, headers) {
+ // Disable security enforcement.
+ file::PathChecker::enableEnforcement(false);
// Configure with HTTP headers.
string config = R"(
{
]
})";
runToElementTest(expected, *http_config_);
+
+ // Should have security WARN log.
+ ASSERT_EQ(1, countFile("COMMAND_HTTP_SOCKET_SECURITY_WARN command socket"
+ " configuration is NOT SECURE: { \"http-headers\": "
+ "[ { \"name\": \"Strict-Transport-Security\", \"value\":"
+ " \"max-age=31536000\" }, { \"name\": \"Foo\", \"value\": \"bar\""
+ " } ] }"));
}
// This test verifies a HTTP control socket configuration with authentication
}
})";
runToElementTest(expected, *http_config_);
+
+ // Should be no security warnings.
+ ASSERT_EQ(0, countFile("COMMAND_HTTP_SOCKET_SECURITY_WARN"));
}
// This test verifies a HTTP control socket configuration with TLS can
#include <http/response.h>
#include <http/response_parser.h>
#include <http/testutils/test_http_client.h>
+#include <util/filesystem.h>
#include <testutils/gtest_utils.h>
#include <gtest/gtest.h>
HttpCommandMgrTest()
: io_service_(new IOService()), test_timer_(io_service_), client_(),
http_config_() {
+ file::PathChecker::enableEnforcement(false);
resetState(io_service_);
test_timer_.setup(std::bind(&HttpCommandMgrTest::timeoutHandler, this, true),
TEST_TIMEOUT, IntervalTimer::ONE_SHOT);
resetState();
IfaceMgr::instance().deleteAllExternalSockets();
io_service_->stopAndPoll();
+ file::PathChecker::enableEnforcement(true);
}
/// @brief Resets state.
#include <config.h>
#include <config/http_command_response_creator_factory.h>
+#include <util/filesystem.h>
#include <boost/pointer_cast.hpp>
#include <gtest/gtest.h>
using namespace isc::config;
using namespace isc::data;
+using namespace isc::util;
using namespace std;
namespace {
+/// @brief Test fixture class for @ref class HttpCommandResponseCreatorFactory
+class HttpCommandResponseCreatorFactoryTest : public ::testing::Test {
+public:
+ /// @brief Constructor.
+ HttpCommandResponseCreatorFactoryTest() {
+ file::PathChecker::enableEnforcement(false);
+ }
+
+ /// @brief Desstructor.
+ virtual ~HttpCommandResponseCreatorFactoryTest() {
+ file::PathChecker::enableEnforcement(true);
+ }
+};
+
// This test verifies the default factory constructor and
// the create() method.
-TEST(HttpCommandResponseCreatorFactory, create) {
+TEST_F(HttpCommandResponseCreatorFactoryTest, create) {
// Configure the HTTP control socket.
string config = R"(
{
#include <http/post_request.h>
#include <http/post_request_json.h>
#include <http/response_json.h>
+#include <util/filesystem.h>
#include <gtest/gtest.h>
#include <boost/pointer_cast.hpp>
using namespace isc::config;
using namespace isc::data;
using namespace isc::http;
+using namespace isc::util;
using namespace std;
namespace ph = std::placeholders;
/// create "empty" request. It also removes registered commands from the
/// command manager.
HttpCommandResponseCreatorTest() {
+ file::PathChecker::enableEnforcement(false);
// Deregisters commands.
config::CommandMgr::instance().deregisterAll();
// Register our "foo" command.
/// Removes registered commands from the command manager.
virtual ~HttpCommandResponseCreatorTest() {
config::CommandMgr::instance().deregisterAll();
+ file::PathChecker::enableEnforcement(true);
}
/// @brief Create HTTP control socket configuration (from text).