]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#3831] Initial impl of restricted ctl sockets
authorThomas Markwalder <tmark@isc.org>
Thu, 15 May 2025 20:43:31 +0000 (16:43 -0400)
committerThomas Markwalder <tmark@isc.org>
Mon, 19 May 2025 12:12:55 +0000 (12:12 +0000)
Working, have some UTs that still need to be fixed

/doc/examples/kea4/advanced.json
/doc/examples/kea4/all-keys-netconf.json
/doc/examples/kea4/all-keys-netconf.json
/doc/examples/kea4/all-keys.json
/doc/examples/kea4/comments.json
/doc/examples/kea4/config-backend.json
/doc/examples/kea4/ha-load-balancing-server1-mt-with-tls.json
/doc/examples/kea4/ha-load-balancing-server2-mt.json
/doc/examples/kea6/advanced.json
/doc/examples/kea6/all-keys-netconf.json
/doc/examples/kea6/all-keys.json
/doc/examples/kea6/comments.json
/doc/examples/kea6/config-backend.json
/doc/examples/kea6/ha-hot-standby-server1-with-tls.json
/doc/examples/kea6/ha-hot-standby-server2.json
    removed /tmp path from socket-name

/src/bin/dhcp4/tests/config_parser_unittest.cc
/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc
/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
/src/bin/dhcp4/tests/dhcp4_test_utils.cc
/src/bin/dhcp4/tests/dhcp4_test_utils.h
/src/bin/dhcp6/tests/config_parser_unittest.cc
/src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc
/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
/src/bin/dhcp6/tests/dhcp6_test_utils.cc
/src/bin/dhcp6/tests/dhcp6_test_utils.h
    updated tests

/src/lib/config/Makefile.am
/src/lib/config/meson.build
    defined CONTROL_SOCKET_DIR

/src/lib/config/tests/unix_command_config_unittests.cc
/src/lib/config/tests/unix_command_mgr_unittests.cc
    updated tests

/src/lib/config/unix_command_config.*
    UnixCommandConfig - added PathChecker singleton and methods
    to set and validate socket path/permissions

/src/lib/util/filesystem.*
    Added getPermsissions() and hasPermsission()

/src/lib/util/tests/filesystem_unittests.cc
    new permissions tests

33 files changed:
doc/examples/kea4/advanced.json
doc/examples/kea4/all-keys-netconf.json
doc/examples/kea4/all-keys.json
doc/examples/kea4/comments.json
doc/examples/kea4/config-backend.json
doc/examples/kea4/ha-load-balancing-server1-mt-with-tls.json
doc/examples/kea4/ha-load-balancing-server2-mt.json
doc/examples/kea6/advanced.json
doc/examples/kea6/all-keys-netconf.json
doc/examples/kea6/all-keys.json
doc/examples/kea6/comments.json
doc/examples/kea6/config-backend.json
doc/examples/kea6/ha-hot-standby-server1-with-tls.json
doc/examples/kea6/ha-hot-standby-server2.json
src/bin/dhcp4/tests/config_parser_unittest.cc
src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc
src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
src/bin/dhcp4/tests/dhcp4_test_utils.cc
src/bin/dhcp4/tests/dhcp4_test_utils.h
src/bin/dhcp6/tests/config_parser_unittest.cc
src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc
src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
src/bin/dhcp6/tests/dhcp6_test_utils.cc
src/bin/dhcp6/tests/dhcp6_test_utils.h
src/lib/config/Makefile.am
src/lib/config/meson.build
src/lib/config/tests/unix_command_config_unittests.cc
src/lib/config/tests/unix_command_mgr_unittests.cc
src/lib/config/unix_command_config.cc
src/lib/config/unix_command_config.h
src/lib/util/filesystem.cc
src/lib/util/filesystem.h
src/lib/util/tests/filesystem_unittests.cc

index 30d3333ac623cf1e5ecf85d6a9ecaed39ae3291c..f0b810ea4696eb2b32ea9b3e6c9e5c97f002946a 100644 (file)
@@ -89,7 +89,7 @@
     // Guide for list of supported commands.
     "control-socket": {
         "socket-type": "unix",
-        "socket-name": "/tmp/kea4-ctrl-socket"
+        "socket-name": "kea4-ctrl-socket"
     },
 
     // Addresses will be assigned with a lifetime of 4000 seconds.
index 0a6908c0046d2ed4fa5ade79bc6a430419c8262a..33bfbbf415ca0dd8737dfbddfbbd2e8f41e2824d 100644 (file)
             // Location of the UNIX domain socket file the DHCPv4 server uses
             // to receive control commands from the Kea Control Agent or the
             // local server administrator.
-            "socket-name": "/tmp/kea4-ctrl-socket",
+            "socket-name": "kea4-ctrl-socket",
 
             // Control socket type used by the Kea DHCPv4 server. The 'unix'
             // socket is currently the only supported type.
index fd7f0461677ed146cfaaeec38d07a70c5e63cb01..09c5947dd56f71a9d1cf2f50d09aeeb3df64906c 100644 (file)
                // Location of the UNIX domain socket file the DHCPv4
                // server uses to receive control commands from the
                // local server administrator.
-               "socket-name": "/tmp/kea4-ctrl-socket"
+               "socket-name": "kea4-ctrl-socket"
             },
             {
                 // Control socket type used by the Kea DHCPv4 server.
index 8e1a49d95658831096c62daa9abed587e4a64873..bbe4fa0e860d8301d9373d30062ecd19fa308088 100644 (file)
@@ -55,7 +55,7 @@
    "control-sockets": [
        {
            "socket-type": "unix",
-           "socket-name": "/tmp/kea4-ctrl-socket",
+           "socket-name": "kea4-ctrl-socket",
            "user-context": { "comment": "Indirect comment" }
        },
        {
index 39267b2b6057190a4c12ff0018562515f74c98ae..0bcfba91b9e505d41232b9bbc9d0f2e32b5deea4 100644 (file)
@@ -55,7 +55,7 @@
     // details.
     "control-socket": {
         "socket-type": "unix",
-        "socket-name": "/tmp/kea4-ctrl-socket"
+        "socket-name": "kea4-ctrl-socket"
     },
 
     // Hooks libraries that enable configuration backend are loaded.
index c7d08e77256daa82d4ab1ff6274f1261e98a37b7..168f5027091cbd09c7fb85464c15a9cc1ed98f96 100644 (file)
@@ -25,7 +25,7 @@
     // The Control Agent is used only to handle user commands.
     "control-socket": {
         "socket-type": "unix",
-        "socket-name": "/tmp/kea4-ctrl-socket"
+        "socket-name": "kea4-ctrl-socket"
     },
 
     // Multi-threading parameters.
index 223d69bd2d3db762c85caa828f55bb4b88523819..149bb55e27d2ca45067c0b69f34289fc89cd60e8 100644 (file)
@@ -24,7 +24,7 @@
     // The Control Agent is used only to handle user commands.
     "control-socket": {
         "socket-type": "unix",
-        "socket-name": "/tmp/kea4-ctrl-socket"
+        "socket-name": "kea4-ctrl-socket"
     },
 
     // Multi-threading parameters.
index 6d4537c64a3999eb9da00af49262bf165dd7a2f2..53edc1285c6a33e16ea2a93719da7cc20cf6211d 100644 (file)
@@ -80,7 +80,7 @@
     // Guide for list of supported commands.
     "control-socket": {
         "socket-type": "unix",
-        "socket-name": "/tmp/kea6-ctrl-socket"
+        "socket-name": "kea6-ctrl-socket"
     },
 
     // Addresses will be assigned with preferred and valid lifetimes
index 4ac572841b09709c1402013f66839c7e624dd86c..82f5e20574a7c2128756d02e228dc4c4904f473b 100644 (file)
@@ -98,7 +98,7 @@
             // Location of the UNIX domain socket file the DHCPv6 server uses
             // to receive control commands from the Kea Control Agent or the
             // local server administrator.
-            "socket-name": "/tmp/kea6-ctrl-socket",
+            "socket-name": "kea6-ctrl-socket",
 
             // Control socket type used by the Kea DHCPv6 server. The 'unix'
             // socket is currently the only supported type.
index 3b41bc556efe4bb4c84df541eb56e2370341b7a1..e3833361ae81bd55e2cc289c66c7e374ed693096 100644 (file)
                // Location of the UNIX domain socket file the DHCPv6
                // server uses to receive control commands from the
                // local server administrator.
-               "socket-name": "/tmp/kea6-ctrl-socket"
+               "socket-name": "kea6-ctrl-socket"
             },
             {
                 // Control socket type used by the Kea DHCPv6 server.
index ce63a0e310d1553f4691ae9fe8fb177ed8e26c6a..bc6ea097dac19d220669a5c046d148da8ee276c7 100644 (file)
@@ -55,7 +55,7 @@
    "control-sockets": [
        {
            "socket-type": "unix",
-           "socket-name": "/tmp/kea6-ctrl-socket",
+           "socket-name": "kea6-ctrl-socket",
            "user-context": { "comment": "Indirect comment" }
        },
        {
index 2fc33b7927d7ce3fbb4cc67c5d9caa77ab2f3e1e..d0a1f8a97b6a2da222bd727098ad56bdb6207501 100644 (file)
@@ -55,7 +55,7 @@
     // details.
     "control-socket": {
         "socket-type": "unix",
-        "socket-name": "/tmp/kea6-ctrl-socket"
+        "socket-name": "kea6-ctrl-socket"
     },
 
     // Hooks libraries that enable configuration backend are loaded.
index 9fab5429c932d2e6d5476be139e642e76b9686e7..6e67645b57b03716a92e3e37b0bf97f4408f92e9 100644 (file)
@@ -24,7 +24,7 @@
     // API between the HA peers.
     "control-socket": {
         "socket-type": "unix",
-        "socket-name": "/tmp/kea6-ctrl-socket"
+        "socket-name": "kea6-ctrl-socket"
     },
 
     // Use Memfile lease database backend to store leases in a CSV file.
index 6a9a680ed9d1199846fc368c83f5fb836dcc0d21..037fd978107427f131bd1a4b1448d27d1b239524 100644 (file)
@@ -23,7 +23,7 @@
     // API between the HA peers.
     "control-socket": {
         "socket-type": "unix",
-        "socket-name": "/tmp/kea6-ctrl-socket"
+        "socket-name": "kea6-ctrl-socket"
     },
 
     // Use Memfile lease database backend to store leases in a CSV file.
index 0c3fe0a898647c36213bd573a2d49ae5de0cc7b9..067c589366f75c286ed32f8a7f27bfa8183855ed 100644 (file)
@@ -38,6 +38,7 @@
 #include <testutils/test_to_element.h>
 #include <util/chrono_time_utils.h>
 #include <util/doubles.h>
+#include <util/filesystem.h>
 
 #include <boost/scoped_ptr.hpp>
 
@@ -63,6 +64,7 @@ using namespace isc::dhcp;
 using namespace isc::dhcp::test;
 using namespace isc::hooks;
 using namespace isc::test;
+using namespace isc::util;
 using namespace std;
 
 namespace {
@@ -239,7 +241,7 @@ const char* PARSER_CONFIGS[] = {
     "    \"control-sockets\": ["
     "        {"
     "            \"socket-type\": \"unix\","
-    "            \"socket-name\": \"/tmp/kea4-ctrl-socket\","
+    "            \"socket-name\": \"kea4-ctrl-socket\","
     "            \"user-context\": { \"comment\": \"Indirect comment\" }"
     "        },"
     "        {"
@@ -337,6 +339,7 @@ public:
         resetConfiguration();
 
         resetHooksPath();
+        resetSocketPath();
     }
 
     ~Dhcp4ParserTest() {
@@ -348,6 +351,7 @@ public:
         static_cast<void>(remove(UNLOAD_MARKER_FILE));
 
         resetHooksPath();
+        resetSocketPath();
     };
 
     /// @brief Sets the Hooks path from which hooks can be loaded.
@@ -363,6 +367,22 @@ public:
         HooksLibrariesParser::getHooksPath(true);
     }
 
+    /// @brief Sets the path in which the socket can be created.
+    /// @param explicit_path path to use as the socket path.
+    void setSocketTestPath(const std::string explicit_path = "") {
+        UnixCommandConfig::getSocketPath(true, (!explicit_path.empty() ?
+                                         explicit_path : TEST_DATA_BUILDDIR));
+
+        auto path = UnixCommandConfig::getSocketPath();
+        UnixCommandConfig::setSocketPathPerms(file::getPermissions(path));
+    }
+
+    /// @brief Resets the socket path to the default.
+    void resetSocketPath() {
+        UnixCommandConfig::getSocketPath(true);
+        UnixCommandConfig::setSocketPathPerms();
+    }
+
     // Checks if the result of DHCP server configuration has
     // expected code (0 for success, other for failures).
     // Also stores result in rcode_ and comment_.
@@ -6919,6 +6939,7 @@ TEST_F(Dhcp4ParserTest, hostsDatabases) {
 
 // This test checks comments. Please keep it last.
 TEST_F(Dhcp4ParserTest, comments) {
+    setSocketTestPath();
 
     string config = PARSER_CONFIGS[6];
     extractConfig(config);
@@ -7020,7 +7041,7 @@ TEST_F(Dhcp4ParserTest, comments) {
     ASSERT_TRUE(socket->get("socket-type"));
     EXPECT_EQ("\"unix\"", socket->get("socket-type")->str());
     ASSERT_TRUE(socket->get("socket-name"));
-    EXPECT_EQ("\"/tmp/kea4-ctrl-socket\"", socket->get("socket-name")->str());
+    EXPECT_EQ("\"kea4-ctrl-socket\"", socket->get("socket-name")->str());
 
     // Check UNIX control socket comment and user context.
     ConstElementPtr ctx_socket = socket->get("user-context");
index 699deb763217f0b9c3a7e24bf45095b75708f144..f949bbd13d0eed597438bec01ed57de96f23f2d0 100644 (file)
@@ -123,12 +123,7 @@ public:
     /// Sets socket path to its default value.
     CtrlChannelDhcpv4SrvTest() : interfaces_("\"*\"") {
         resetLogPath();
-        const char* env = getenv("KEA_SOCKET_TEST_DIR");
-        if (env) {
-            socket_path_ = string(env) + "/kea4.sock";
-        } else {
-            socket_path_ = sandbox.join("/kea4.sock");
-        }
+        setSocketTestPath();
         reset();
         IfaceMgr::instance().setTestMode(false);
         IfaceMgr::instance().setDetectCallback(std::bind(&IfaceMgr::checkDetectIfaces,
@@ -138,6 +133,7 @@ public:
     /// @brief Destructor
     ~CtrlChannelDhcpv4SrvTest() {
         resetLogPath();
+        resetSocketPath();
         if (test_timer_) {
             test_timer_->cancel();
             getIOService()->stopAndPoll();
@@ -170,6 +166,25 @@ public:
         LogConfigParser::getLogPath(true);
     }
 
+    /// @brief Sets the path in which the socket can be created.
+    /// @param explicit_path path to use as the socket path.
+    void setSocketTestPath(const std::string explicit_path = "") {
+        UnixCommandConfig::getSocketPath(true,
+                                         (!explicit_path.empty() ?
+                                          explicit_path : TEST_DATA_BUILDDIR));
+
+        auto path = UnixCommandConfig::getSocketPath();
+        UnixCommandConfig::setSocketPathPerms(file::getPermissions(path));
+        socket_path_ = path + "/kea4.sock";
+    }
+
+    /// @brief Resets the socket path to the default.
+    void resetSocketPath() {
+        UnixCommandConfig::getSocketPath(true);
+        UnixCommandConfig::setSocketPathPerms();
+    }
+
+
     /// @brief Returns pointer to the server's IO service.
     ///
     /// @return Pointer to the server's IO service or null pointer if the server
index 06459ceaebb39fbeb73ab1bf0967bfdfd6888194..43bd2aa9473ad15e44122dc9673209c970bfd5ec 100644 (file)
@@ -2964,6 +2964,7 @@ class DBInitializer {
 
 void
 Dhcpv4SrvTest::checkConfigFiles() {
+    setSocketTestPath();
 #if defined (HAVE_MYSQL)
     MySqlHostDataSourceInit mysql_init;
 #endif
index 4e15fd53a1c9198bf89a85e5d2d27b8def349889..6c6dd1836b1325f8e9fda1dd728fb11bc9bbf649 100644 (file)
@@ -9,6 +9,7 @@
 #include <asiolink/io_address.h>
 #include <cc/data.h>
 #include <cc/command_interpreter.h>
+#include <config/unix_command_config.h>
 #include <dhcp4/json_config_parser.h>
 #include <dhcp4/tests/dhcp4_test_utils.h>
 #include <dhcp/libdhcp++.h>
@@ -33,6 +34,7 @@ using namespace std;
 using namespace isc::asiolink;
 using namespace isc::data;
 using namespace isc::util;
+using namespace isc::config;
 using namespace boost::posix_time;
 
 namespace isc {
@@ -89,6 +91,7 @@ Dhcpv4SrvTest::Dhcpv4SrvTest()
 }
 
 Dhcpv4SrvTest::~Dhcpv4SrvTest() {
+    resetSocketPath();
     // Make sure that we revert to default value
     CfgMgr::instance().clear();
 
@@ -101,6 +104,21 @@ Dhcpv4SrvTest::~Dhcpv4SrvTest() {
     MultiThreadingMgr::instance().apply(false, 0, 0);
 }
 
+void
+Dhcpv4SrvTest::setSocketTestPath(const std::string explicit_path /* = "" */) {
+    UnixCommandConfig::getSocketPath(true, (!explicit_path.empty() ?
+                                            explicit_path : TEST_DATA_BUILDDIR));
+
+    auto path = UnixCommandConfig::getSocketPath();
+    UnixCommandConfig::setSocketPathPerms(file::getPermissions(path));
+}
+
+void
+Dhcpv4SrvTest::resetSocketPath() {
+    UnixCommandConfig::getSocketPath(true);
+    UnixCommandConfig::setSocketPathPerms();
+}
+
 void Dhcpv4SrvTest::addPrlOption(Pkt4Ptr& pkt) {
 
     OptionUint8ArrayPtr option_prl =
index bf1eff28b2b4e9ce9a8db7a27f8ded4be0ad1e4e..3cdf73216fe90ec3c96ddbf8285c5b93e26ef0e9 100644 (file)
@@ -385,6 +385,13 @@ public:
     /// Removes existing configuration.
     virtual ~Dhcpv4SrvTest();
 
+    /// @brief Sets the path in which the socket can be created.
+    /// @param explicit_path path to use as the socket path.
+    void setSocketTestPath(const std::string explicit_path = "");
+
+    /// @brief Resets the socket path to the default.
+    void resetSocketPath();
+
     /// @brief Add 'Parameter Request List' option to the packet.
     ///
     /// This function adds PRL option comprising the following option codes:
index 41518a612d1546e41c2b513310c32ec8d4f195f8..a531afd668d8cadc1537922ba63abae0caf5fcc0 100644 (file)
@@ -320,7 +320,7 @@ const char* PARSER_CONFIGS[] = {
     "    \"control-sockets\": ["
     "        {"
     "            \"socket-type\": \"unix\","
-    "            \"socket-name\": \"/tmp/kea6-ctrl-socket\","
+    "            \"socket-name\": \"kea6-ctrl-socket\","
     "            \"user-context\": { \"comment\": \"Indirect comment\" }"
     "        },"
     "        {"
@@ -7699,6 +7699,7 @@ TEST_F(Dhcp6ParserTest, hostsDatabases) {
 
 // This test checks comments. Please keep it last.
 TEST_F(Dhcp6ParserTest, comments) {
+    setSocketTestPath();
 
     string config = PARSER_CONFIGS[9];
     extractConfig(config);
@@ -7812,7 +7813,7 @@ TEST_F(Dhcp6ParserTest, comments) {
     ASSERT_TRUE(socket->get("socket-type"));
     EXPECT_EQ("\"unix\"", socket->get("socket-type")->str());
     ASSERT_TRUE(socket->get("socket-name"));
-    EXPECT_EQ("\"/tmp/kea6-ctrl-socket\"", socket->get("socket-name")->str());
+    EXPECT_EQ("\"kea6-ctrl-socket\"", socket->get("socket-name")->str());
 
     // Check UNIX control socket comment and user context.
     ConstElementPtr ctx_socket = socket->get("user-context");
index 28c00750894e47935605667e2aced28e88810774..3b9df41e2d20728356911d7b7833a4da02aef3da 100644 (file)
@@ -156,16 +156,12 @@ public:
     ///
     /// Sets socket path to its default value.
     CtrlChannelDhcpv6SrvTest() : interfaces_("\"*\"") {
-        const char* env = getenv("KEA_SOCKET_TEST_DIR");
-        if (env) {
-            socket_path_ = string(env) + "/kea6.sock";
-        } else {
-            socket_path_ = sandbox.join("/kea6.sock");
-        }
         reset();
         IfaceMgr::instance().setTestMode(false);
         IfaceMgr::instance().setDetectCallback(std::bind(&IfaceMgr::checkDetectIfaces,
                                                IfaceMgr::instancePtr().get(), ph::_1));
+        setSocketTestPath();
+        socket_path_ = UnixCommandConfig::getSocketPath() + "/kea6.sock";
     }
 
     /// @brief Destructor
index b92dbb8088134e50b82e2cb4fa2c4e4f946086e6..a4f7159b9b36a62d4a173b85459d5fe206d05ecd 100644 (file)
@@ -385,6 +385,7 @@ class DBInitializer {
 
 void
 Dhcpv6SrvTest::checkConfigFiles() {
+    setSocketTestPath();
 #if defined (HAVE_MYSQL)
     MySqlHostDataSourceInit mysql_init;
 #endif
index fc8d3915ff9cec564aaa36bea09bcea1921458bc..38da0847c30a717eba3bd0abd3be95adc7d4a2ff 100644 (file)
@@ -7,6 +7,7 @@
 #include <config.h>
 #include <gtest/gtest.h>
 #include <cc/command_interpreter.h>
+#include <config/unix_command_config.h>
 #include <dhcp/option6_status_code.h>
 #include <dhcp/testutils/pkt_captures.h>
 #include <dhcpsrv/cfg_multi_threading.h>
@@ -27,6 +28,7 @@ using namespace isc::asiolink;
 using namespace isc::stats;
 using namespace isc::util;
 using namespace isc::process;
+using namespace isc::config;
 using namespace boost::posix_time;
 
 namespace isc {
@@ -41,6 +43,7 @@ BaseServerTest::BaseServerTest() {
     original_datadir_ = CfgMgr::instance().getDataDir();
     CfgMgr::instance().getDataDir(true, TEST_DATA_BUILDDIR);
     resetLogPath();
+    resetSocketPath();
 }
 
 BaseServerTest::~BaseServerTest() {
@@ -60,6 +63,7 @@ BaseServerTest::~BaseServerTest() {
     // Revert to unit test logging, in case the test reconfigured it.
     isc::log::initLogger();
     resetLogPath();
+    resetSocketPath();
 }
 
 void
@@ -73,6 +77,22 @@ BaseServerTest::resetLogPath() {
     LogConfigParser::getLogPath(true);
 }
 
+void
+BaseServerTest::setSocketTestPath(const std::string explicit_path /* = "" */) {
+    UnixCommandConfig::getSocketPath(true,
+                                     (!explicit_path.empty() ?
+                                     explicit_path : TEST_DATA_BUILDDIR));
+
+    auto path = UnixCommandConfig::getSocketPath();
+    UnixCommandConfig::setSocketPathPerms(file::getPermissions(path));
+}
+
+void
+BaseServerTest::resetSocketPath() {
+    UnixCommandConfig::getSocketPath(true);
+    UnixCommandConfig::setSocketPathPerms();
+}
+
 Dhcpv6SrvTest::Dhcpv6SrvTest()
     : NakedDhcpv6SrvTest(), srv_(new NakedDhcpv6Srv(0)), multi_threading_(false) {
     subnet_ = Subnet6::create(isc::asiolink::IOAddress("2001:db8:1::"),
index 4d950723e6171171f4ac487b9d7fc0483bd24ec6..c612add7e0bbbd5f86fa5cc96d4ea99d19dbade9 100644 (file)
@@ -133,8 +133,14 @@ public:
     /// @brief Resets the log path to TEST_DATA_BUILDDIR.
     void resetLogPath();
 
-private:
+    /// @brief Sets the path in which the socket can be created.
+    /// @param explicit_path path to use as the socket path.
+    void setSocketTestPath(const std::string explicit_path = "");
+
+    /// @brief Resets the socket path to the default.
+    void resetSocketPath();
 
+private:
     /// @brief Holds the original data directory.
     std::string original_datadir_;
 };
index dc14a7ec9a2f315b4cd2a8d167220c95f2d137d3..f00d118cbdbcfb8d3bf1ff862cddb111608abc70 100644 (file)
@@ -2,6 +2,8 @@ SUBDIRS = . tests
 
 AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
 AM_CPPFLAGS += $(BOOST_INCLUDES) $(CRYPTO_CFLAGS) $(CRYPTO_INCLUDES)
+control_socket_dir = @runstatedir@/@PACKAGE@
+AM_CPPFLAGS += -DCONTROL_SOCKET_DIR=\"$(control_socket_dir)\"
 
 AM_CXXFLAGS = $(KEA_CXXFLAGS)
 
index 6c13133b139e9d3155f62f29d54aa5dadd2981a6..093fcc896fbe21624d786257c4aeed18f8401cc5 100644 (file)
@@ -21,6 +21,9 @@ kea_config_lib = shared_library(
     build_rpath: BUILD_RPATH,
     link_with: LIBS_BUILT_SO_FAR,
     version: '81.0.0',
+    cpp_args: [
+        f'-DCONTROL_SOCKET_DIR="@RUNSTATEDIR_INSTALLED@"'
+    ],
 )
 LIBS_BUILT_SO_FAR = [kea_config_lib] + LIBS_BUILT_SO_FAR
 subdir('tests')
index d69eac78b28edbe84e9341488a26485776bd8ca5..d7e5147827d9f64b36b4d45af0a0a0c0d6d88936 100644 (file)
@@ -11,6 +11,7 @@
 #include <http/basic_auth_config.h>
 #include <testutils/gtest_utils.h>
 #include <testutils/test_to_element.h>
+#include <util/filesystem.h>
 
 using namespace isc;
 using namespace isc::asiolink;
@@ -19,6 +20,7 @@ using namespace isc::data;
 using namespace isc::dhcp;
 using namespace isc::http;
 using namespace isc::test;
+using namespace isc::util;
 using namespace std;
 
 namespace {
@@ -28,10 +30,29 @@ class UnixCommandConfigTest : public ::testing::Test {
 public:
     /// @brief Constructor.
     UnixCommandConfigTest() : unix_config_() {
+        setSocketTestPath();
     }
 
     /// @brief Destructor.
     ~UnixCommandConfigTest() {
+        resetSocketPath();
+    }
+
+    /// @brief Sets the path in which the socket can be created.
+    /// @param explicit_path path to use as the hooks path.
+    void setSocketTestPath(const std::string explicit_path = "") {
+        UnixCommandConfig::getSocketPath(true,
+                                         (!explicit_path.empty() ?
+                                          explicit_path : TEST_DATA_BUILDDIR));
+
+        auto path = UnixCommandConfig::getSocketPath();
+        UnixCommandConfig::setSocketPathPerms(file::getPermissions(path));
+    }
+
+    /// @brief Resets the socket path to the default.
+    void resetSocketPath() {
+        UnixCommandConfig::getSocketPath(true);
+        UnixCommandConfig::setSocketPathPerms();
     }
 
     /// @brief UNIX control socket configuration.
@@ -41,22 +62,20 @@ public:
 // This test verifies the default UNIX control socket configuration.
 TEST_F(UnixCommandConfigTest, default) {
     ElementPtr json = Element::createMap();
-    ASSERT_THROW(unix_config_.reset(new UnixCommandConfig(json)), BadSocketInfo);
+    ASSERT_THROW(unix_config_.reset(new UnixCommandConfig(json)), DhcpConfigError);
     json->set("socket-name", Element::create("name"));
     ASSERT_NO_THROW_LOG(unix_config_.reset(new UnixCommandConfig(json)));
 
     // Check default values.
     EXPECT_EQ("unix", unix_config_->getSocketType());
-    EXPECT_EQ("name", unix_config_->getSocketName());
+    EXPECT_EQ((UnixCommandConfig::getSocketPath() + "/name"),
+              unix_config_->getSocketName());
 
-    // Check unparse.
-    string expected = R"(
-        {
-            "socket-type": "unix",
-            "socket-name": "name"
-        }
-    )";
-    runToElementTest(expected, *unix_config_);
+    std::ostringstream os;
+    os << "{ \"socket-type\": \"unix\", \"socket-name\": \""
+       << UnixCommandConfig::getSocketPath()
+       << "/name\" }";
+    runToElementTest(os.str(), *unix_config_);
 }
 
 // This test verifies direct error cases.
@@ -93,6 +112,12 @@ TEST_F(UnixCommandConfigTest, errors) {
             R"( { "socket-name": 8000 } )",
             "invalid type specified for parameter 'socket-name' "
             "(<string>:1:19)"
+        },
+        {
+            "bad socket-name path",
+            R"( { "socket-name": "/tmp/mysocket" } )",
+            "'socket-name' is invalid: invalid path specified: '/tmp',"
+            " supported path is '" + UnixCommandConfig::getSocketPath() + "'"
         }
     };
     for (auto const& s : scenarios) {
index f7616231d4c121c1109bce847f770194735ee592..36276a55e2662dbf3522daa1531cb42337513af6 100644 (file)
 #include <cc/dhcp_config_error.h>
 #include <config/command_mgr.h>
 #include <config/unix_command_mgr.h>
+#include <util/filesystem.h>
 #include <string>
 
 using namespace isc::asiolink;
 using namespace isc::config;
 using namespace isc::data;
 using namespace isc::dhcp;
+using namespace isc::util;
 using namespace std;
 
 // Test class for Unix Command Manager
@@ -28,6 +30,7 @@ public:
 
     /// Default constructor
     UnixCommandMgrTest() : io_service_(new IOService()) {
+        resetSocketPath();
         UnixCommandMgr::instance().setIOService(io_service_);
         UnixCommandMgr::instance().closeCommandSockets();
     }
@@ -35,19 +38,24 @@ public:
     /// Default destructor
     virtual ~UnixCommandMgrTest() {
         UnixCommandMgr::instance().closeCommandSockets();
+        resetSocketPath();
     }
 
-    /// @brief Returns socket path (using either hardcoded path or env variable)
-    /// @return path to the unix socket
-    std::string getSocketPath() {
-        std::string socket_path;
-        const char* env = getenv("KEA_SOCKET_TEST_DIR");
-        if (env) {
-            socket_path = std::string(env) + "/test-socket";
-        } else {
-            socket_path = sandbox.join("test-socket");
-        }
-        return (socket_path);
+    /// @brief Sets the path in which the socket can be created.
+    /// @param explicit_path path to use as the hooks path.
+    void setSocketTestPath(const std::string explicit_path = "") {
+        UnixCommandConfig::getSocketPath(true,
+                                         (!explicit_path.empty() ?
+                                          explicit_path : TEST_DATA_BUILDDIR));
+
+        auto path = UnixCommandConfig::getSocketPath();
+        UnixCommandConfig::setSocketPathPerms(file::getPermissions(path));
+    }
+
+    /// @brief Resets the socket path to the default.
+    void resetSocketPath() {
+        UnixCommandConfig::getSocketPath(true);
+        UnixCommandConfig::setSocketPathPerms();
     }
 
     /// @brief IO service used by these tests.
@@ -57,6 +65,8 @@ public:
 // This test verifies that a Unix socket can be opened properly and that input
 // parameters (socket-type and socket-name) are verified.
 TEST_F(UnixCommandMgrTest, unixCreate) {
+    setSocketTestPath();
+
     // Null pointer is obviously a bad idea.
     EXPECT_THROW(UnixCommandMgr::instance().openCommandSocket(ConstElementPtr()),
                  BadSocketInfo);
@@ -64,7 +74,7 @@ TEST_F(UnixCommandMgrTest, unixCreate) {
     // So is passing no parameters.
     ElementPtr socket_info = Element::createMap();
     EXPECT_THROW(UnixCommandMgr::instance().openCommandSocket(socket_info),
-                 BadSocketInfo);
+                 DhcpConfigError);
 
     // We don't support ipx sockets
     socket_info->set("socket-type", Element::create("ipx"));
@@ -73,9 +83,9 @@ TEST_F(UnixCommandMgrTest, unixCreate) {
 
     socket_info->set("socket-type", Element::create("unix"));
     EXPECT_THROW(UnixCommandMgr::instance().openCommandSocket(socket_info),
-                 BadSocketInfo);
+                 DhcpConfigError);
 
-    socket_info->set("socket-name", Element::create(getSocketPath()));
+    socket_info->set("socket-name", Element::create("test_socket"));
     EXPECT_NO_THROW(UnixCommandMgr::instance().openCommandSocket(socket_info));
     EXPECT_GE(UnixCommandMgr::instance().getControlSocketFD(), 0);
 
@@ -85,8 +95,10 @@ TEST_F(UnixCommandMgrTest, unixCreate) {
 
 // This test checks that when unix path is too long, the socket cannot be opened.
 TEST_F(UnixCommandMgrTest, unixCreateTooLong) {
+    setSocketTestPath();
+
     ElementPtr socket_info = Element::fromJSON("{ \"socket-type\": \"unix\","
-        "\"socket-name\": \"/tmp/toolongtoolongtoolongtoolongtoolongtoolong"
+        "\"socket-name\": \"toolongtoolongtoolongtoolongtoolongtoolong"
         "toolongtoolongtoolongtoolongtoolongtoolongtoolongtoolongtoolong"
         "\" }");
 
@@ -97,10 +109,12 @@ TEST_F(UnixCommandMgrTest, unixCreateTooLong) {
 // Verifies that a socket cannot be concurrently opened more than once.
 // It should be reused instead.
 TEST_F(UnixCommandMgrTest, exclusiveOpen) {
+    setSocketTestPath();
+
     // Pass in valid parameters.
     ElementPtr socket_info = Element::createMap();
     socket_info->set("socket-type", Element::create("unix"));
-    socket_info->set("socket-name", Element::create(getSocketPath()));
+    socket_info->set("socket-name", Element::create("test_socket"));
 
     EXPECT_NO_THROW(UnixCommandMgr::instance().openCommandSocket(socket_info));
     EXPECT_GE(UnixCommandMgr::instance().getControlSocketFD(), 0);
index 99d91b2f368ec428b8fb638479f939b52aff0c90..d9dc7b96cf768f1182db653a4cf7105e6ba3e7ad 100644 (file)
@@ -10,6 +10,7 @@
 #include <cc/dhcp_config_error.h>
 #include <config/command_mgr.h>
 #include <config/unix_command_config.h>
+#include <util/filesystem.h>
 #include <limits>
 
 using namespace isc;
@@ -17,11 +18,18 @@ using namespace isc::asiolink;
 using namespace isc::config;
 using namespace isc::data;
 using namespace isc::dhcp;
+using namespace isc::util::file;
 using namespace std;
 
 namespace isc {
 namespace config {
 
+namespace {
+    // Singleton PathChecker to set and hold valid legal log path.
+    PathCheckerPtr socket_path_checker_;
+    mode_t socket_path_perms_ = UnixCommandConfig::DEFAULT_SOCKET_PATH_PERMS;
+};
+
 UnixCommandConfig::UnixCommandConfig(ConstElementPtr config)
     : socket_type_("unix"), socket_name_() {
     if (config->getType() != Element::map) {
@@ -50,15 +58,20 @@ UnixCommandConfig::UnixCommandConfig(ConstElementPtr config)
     }
     // Get socket name.
     ConstElementPtr socket_name = config->get("socket-name");
-    if (socket_name) {
-        if (socket_name->getType() != Element::string) {
-            isc_throw(DhcpConfigError,
-                      "invalid type specified for parameter 'socket-name' ("
-                      << socket_name->getPosition() << ")");
-        }
-        socket_name_ = socket_name->stringValue();
-    } else {
-        isc_throw(BadSocketInfo, "Mandatory 'socket-name' parameter missing");
+    if (!socket_name) {
+        isc_throw(DhcpConfigError, "Mandatory 'socket-name' parameter missing");
+    }
+
+    if (socket_name->getType() != Element::string) {
+        isc_throw(DhcpConfigError,
+                  "invalid type specified for parameter 'socket-name' ("
+                  << socket_name->getPosition() << ")");
+    }
+
+    try {
+        socket_name_ = validatePath(socket_name->stringValue());
+    } catch (const std::exception& ex) {
+        isc_throw(DhcpConfigError, "'socket-name' is invalid: " << ex.what());
     }
 
     // Get user context.
@@ -80,5 +93,48 @@ UnixCommandConfig::toElement() const {
     return (result);
 }
 
+std::string
+UnixCommandConfig::getSocketPath(bool reset /* = false */,
+                                 const std::string explicit_path /* = "" */) {
+    if (!socket_path_checker_ || reset) {
+        socket_path_checker_.reset(new PathChecker(CONTROL_SOCKET_DIR,
+                                                   "KEA_CONTROL_SOCKET_DIR"));
+        if (!explicit_path.empty()) {
+            socket_path_checker_->getPath(true, explicit_path);
+        }
+    }
+
+    return (socket_path_checker_->getPath());
+}
+
+std::string
+UnixCommandConfig::validatePath(const std::string socket_path,
+                                bool enforce /* = true */) {
+    if (!socket_path_checker_) {
+        getSocketPath();
+    }
+
+    auto valid_path = socket_path_checker_->validatePath(socket_path, enforce);
+    if (enforce && !(socket_path_checker_->pathHasPermissions(socket_path_perms_))) {
+        isc_throw (DhcpConfigError,
+                   "socket path:" << socket_path_checker_->getPath()
+                   << " does not exist or does not have permssions = "
+                   << std::hex << socket_path_perms_);
+    }
+
+    return (valid_path);
+}
+
+mode_t
+UnixCommandConfig::getSocketPathPerms() {
+    return(socket_path_perms_);
+}
+
+void
+UnixCommandConfig::setSocketPathPerms(mode_t perms
+                                      /* = DEFAULT_SOCKET_PATH_PERMS */) {
+    socket_path_perms_ = perms;
+}
+
 } // end of isc::config
 } // end of isc
index c1b0dca514e7083306d4aa8a2f6a60f20dc4e2f2..dbb9dd43dffb834fb1defbbf8b5c36bcdf42e28c 100644 (file)
@@ -21,6 +21,9 @@ namespace config {
 class UnixCommandConfig : public isc::data::UserContext,
                           public isc::data::CfgToElement {
 public:
+    /// @brief Defines the default permissions for unix socket parent directory.
+    static const mode_t DEFAULT_SOCKET_PATH_PERMS = (S_IRWXU | S_IRGRP | S_IWGRP);
+
     /// @brief Constructor.
     ///
     /// @param config Pointer to the configuration to parse.
@@ -29,6 +32,44 @@ public:
     /// @brief Virtual destructor.
     ~UnixCommandConfig() = default;
 
+    /// @brief Fetches the supported control socket path.
+    ///
+    /// The first call to this function with no arguments will set the default
+    /// path to either the value of CONTROL_SOCKET_DIR or the environment
+    /// variable KEA_CONTROL_SOCKET_DIR if it is defined. Subsequent calls with
+    /// no arguments will simply return this value.
+    ///
+    /// @param reset recalculate when true, defaults to false.
+    /// @param explicit_path set the default socket path to this value. This is
+    /// for testing purposes only.
+    ///
+    /// @return String containing the default socket path.
+    static std::string getSocketPath(bool reset = false,
+                                     const std::string explicit_path = "");
+
+    /// @brief Validates a path against the supported path for unix control
+    /// sockets.
+    ///
+    /// @param socket_path path to validate.
+    /// @param enforce enables validation against the supported path and
+    /// permissions.
+    /// If false simply returns the input path.
+    ///
+    /// @return validated path
+    static std::string validatePath(const std::string socket_path,
+                                    bool enforce = true);
+
+    /// @brief Fetches the required socket path permissions mask
+    ///
+    /// @return permissions mask
+    static mode_t getSocketPathPerms();
+
+    /// @brief Sets the required socket path permissions mask
+    ///
+    /// This is for testing purposes only.
+    /// @param perms permissions mask to use
+    static void setSocketPathPerms(mode_t perms = DEFAULT_SOCKET_PATH_PERMS);
+
     /// @brief Returns socket type.
     ///
     /// @return The socket type ("unix").
index 15db19efa8219e0d27c0be5cd15e3e9ee4922d3e..c4902121224be29cb0fc46c2b85cf0422a55625d 100644 (file)
@@ -51,6 +51,21 @@ exists(string const& path) {
     return (::stat(path.c_str(), &statbuf) == 0);
 }
 
+mode_t
+getPermissions(const std::string path) {
+    struct stat statbuf;
+    if (::stat(path.c_str(), &statbuf) < 0) {
+        return (0);
+    }
+
+    return (statbuf.st_mode & 0x01FF);
+}
+
+bool
+hasPermissions(const std::string path, const mode_t& permissions) {
+    return (getPermissions(path) == permissions);
+}
+
 bool
 isDir(string const& path) {
     struct stat statbuf;
@@ -308,6 +323,11 @@ PathChecker::validateDirectory(const std::string input_path_str,
     return (path_);
 }
 
+bool
+PathChecker::pathHasPermissions(mode_t permissions) {
+    return(hasPermissions(path_, permissions));
+}
+
 }  // namespace file
 }  // namespace util
 }  // namespace isc
index e3ed083795167241e66b66555a71e5a279430b67..2db17cbff353c35c79877f3a72187b2258ddc043 100644 (file)
@@ -31,6 +31,22 @@ getContent(const std::string& file_name);
 bool
 exists(const std::string& path);
 
+/// @brief Fetches the file permissions mask.
+///
+/// @param path The path being checked.
+/// @return File permissios mask or 0 if the path does not exist.
+mode_t
+getPermissions(const std::string path);
+
+/// @brief Check if there if file or directory has the given permissions.
+///
+/// @param path The path being checked.
+/// @param permissions mask of expected permissions.
+///
+/// @return True if the path points to a file or a directory, false otherwise.
+bool
+hasPermissions(const std::string path, const mode_t& permissions);
+
 /// @brief Check if there is a directory at the given path.
 ///
 /// @param path The path being checked.
@@ -229,6 +245,13 @@ public:
     std::string validateDirectory(const std::string input_path_str,
                                   bool enforce_path = true) const;
 
+    /// @brief Tests that the supported path has the given permissions. 
+    ///
+    /// @param permissions mode_t mask of required permissions.
+    /// @return True if the path's permissions exactly match the permissions
+    /// parameter.
+    bool pathHasPermissions(mode_t permissions);
+
     /// @brief Fetches the default path.
     std::string getDefaultPath() const {
         return (default_path_);
index af1346eeaa2fd21d8f029f253f1e7aafb4bcefb5..58e9d18529c70f795913838815e76b2e765f1bc9 100644 (file)
@@ -72,6 +72,16 @@ TEST_F(FileUtilTest, isFile) {
     EXPECT_FALSE(isFile(TEST_DATA_BUILDDIR));
 }
 
+/// @brief Check hasPermissions.
+TEST_F(FileUtilTest, hasPermissions) {
+    const std::string path = ABS_SRCDIR "/filesystem_unittests.cc";
+    ASSERT_TRUE(isFile(path));
+    mode_t current_permissions = getPermissions(path);
+    EXPECT_TRUE(hasPermissions(path, current_permissions));
+    current_permissions = ~current_permissions;
+    EXPECT_FALSE(hasPermissions(path, current_permissions));
+}
+
 /// @brief Test fixture class for testing operations on umask.
 struct UMaskUtilTest : ::testing::Test {
     /// @brief Constructor.