]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#3133] Updated code and tests
authorFrancis Dupont <fdupont@isc.org>
Wed, 27 Mar 2024 22:48:19 +0000 (23:48 +0100)
committerThomas Markwalder <tmark@isc.org>
Tue, 16 Apr 2024 19:00:57 +0000 (19:00 +0000)
src/bin/d2/tests/d2_simple_parser_unittest.cc
src/bin/d2/tests/get_config_unittest.cc
src/lib/d2srv/d2_config.cc
src/lib/d2srv/d2_config.h

index 75bdd9cf4631549210493aaae52d08f52222627e..fcdbb26560e2fa0ab4daee9ac7e3af083803d338 100644 (file)
@@ -12,6 +12,7 @@
 #include <testutils/test_to_element.h>
 
 #include <boost/lexical_cast.hpp>
+#include <fstream>
 
 using namespace isc;
 using namespace isc::data;
@@ -80,18 +81,21 @@ void checkStringValue(const ConstElementPtr& element,
 /// @param name is the value to compare against key's name_.
 /// @param algorithm is the string value to compare against key's algorithm.
 /// @param secret is the value to compare against key's secret.
+/// @param secret_file is the file name where the secret can be found.
+/// @param digestbits is the minimum truncated length in bits.
 ///
 /// @return returns true if there is a match across the board, otherwise it
 /// returns false.
 bool checkKey(TSIGKeyInfoPtr key, const std::string& name,
               const std::string& algorithm, const std::string& secret,
-              uint32_t digestbits = 0) {
+              std::string secret_file, uint32_t digestbits = 0) {
     // Return value, assume its a match.
     return (((key) &&
         (key->getName() == name) &&
-        (key->getAlgorithm() == algorithm)  &&
+        (key->getAlgorithm() == algorithm) &&
         (key->getDigestbits() == digestbits) &&
-        (key->getSecret() == secret)  &&
+        (key->getSecret() == secret) &&
+        (key->getSecretFile() == secret_file) &&
         (key->getTSIGKey())));
 }
 
@@ -232,7 +236,8 @@ protected:
     /// ensure any D2 object(s) that were created by a prior invocation are
     /// destroyed. This permits parsing to be conducted more than once
     /// in the same test.
-    virtual void reset(){};
+    virtual void reset() {
+    }
 
     /// @brief Adds default values to the given element tree
     ///
@@ -303,7 +308,8 @@ class TSIGKeyInfoParserTest : public D2SimpleParserTest {
 public:
     /// @brief Constructor
     TSIGKeyInfoParserTest()
-        : D2SimpleParserTest(D2ParserContext::PARSER_TSIG_KEY) {
+        : D2SimpleParserTest(D2ParserContext::PARSER_TSIG_KEY),
+          test_file_name_(TEST_DATA_BUILDDIR "/sf-test") {
     }
 
     /// @brief Free up the keys created by parsing
@@ -313,6 +319,7 @@ public:
 
     /// @brief Destructor
     virtual ~TSIGKeyInfoParserTest() {
+        static_cast<void>(remove(test_file_name_.c_str()));
         reset();
     };
 
@@ -340,8 +347,10 @@ public:
 
     /// @brief Retains the TSIGKeyInfo created by a successful parsing
     TSIGKeyInfoPtr key_;
-};
 
+    /// @brief Secret file name.
+    std::string test_file_name_;
+};
 
 /// @brief Test fixture class for testing TSIGKeyInfo list parsing.
 class TSIGKeyInfoListParserTest : public D2SimpleParserTest {
@@ -599,6 +608,7 @@ public:
 /// 1. Name cannot be blank.
 /// 2. Algorithm cannot be blank.
 /// 3. Secret cannot be blank.
+/// 4. Secret file cannot be invalid when specified.
 TEST_F(TSIGKeyInfoParserTest, invalidEntry) {
 
     // Name cannot be blank.
@@ -643,8 +653,59 @@ TEST_F(TSIGKeyInfoParserTest, invalidEntry) {
               "}";
     PARSE_FAIL(config, "Cannot make D2TsigKey: non-zero bits left over"
                        " bogus (<string>:1:1)");
-}
 
+    // Secret file must exist.
+    config = "{"
+              " \"name\": \"d2_key_one\" , "
+              " \"algorithm\": \"HMAC-MD5\" , "
+              " \"digest-bits\": 120 , "
+              " \"secret-file\": \"/does/not/exist\" "
+              "}";
+    PARSE_FAIL(config, "tsig-key : Expected a file at path "
+               "'/does/not/exist' (<string>:1:91)");
+
+    // Secret file must be a regular file.
+    config = "{"
+              " \"name\": \"d2_key_one\" , "
+              " \"algorithm\": \"HMAC-MD5\" , "
+              " \"digest-bits\": 120 , "
+              " \"secret-file\": \"/\" "
+              "}";
+    PARSE_FAIL(config, "tsig-key : Expected '/' to be a regular file "
+               "(<string>:1:91)");
+
+    // Secret file must not be empty.
+    std::ofstream fs(test_file_name_.c_str(),
+                     std::ofstream::out | std::ofstream::trunc);
+    ASSERT_TRUE(fs.is_open());
+    fs.close();
+
+    config = "{"
+              " \"name\": \"d2_key_one\" , "
+              " \"algorithm\": \"HMAC-MD5\" , "
+              " \"digest-bits\": 120 , "
+              " \"secret-file\": \"";
+    config += test_file_name_ + "\" }";
+    std::string expected = "tsig-key : Expected '";
+    expected += test_file_name_ + "' to not be empty (<string>:1:91)";
+    PARSE_FAIL(config, expected);
+
+    // Secret file content must be valid.
+    fs = std::ofstream(test_file_name_.c_str(),
+                       std::ofstream::out | std::ofstream::trunc);
+    ASSERT_TRUE(fs.is_open());
+    fs << "bogus";
+    fs.close();
+
+    config = "{"
+              " \"name\": \"d2_key_one\" , "
+              " \"algorithm\": \"HMAC-MD5\" , "
+              " \"digest-bits\": 120 , "
+              " \"secret-file\": \"";
+    config += test_file_name_ + "\" }";
+    PARSE_FAIL(config, "Cannot make D2TsigKey: non-zero bits left over"
+                       " bogus (<string>:1:1)");
+}
 
 /// @brief Verifies that TSIGKeyInfo parsing creates a proper TSIGKeyInfo
 /// when given a valid combination of entries.
@@ -662,7 +723,36 @@ TEST_F(TSIGKeyInfoParserTest, validEntry) {
 
     // Verify the key contents.
     EXPECT_TRUE(checkKey(key_, "d2_key_one", "HMAC-MD5",
-                         "dGhpcyBrZXkgd2lsbCBtYXRjaA==", 120));
+                         "dGhpcyBrZXkgd2lsbCBtYXRjaA==", "", 120));
+
+    // Verify unparsing.
+    runToElementTest<TSIGKeyInfo>(config, *key_);
+}
+
+/// @brief Verifies that TSIGKeyInfo parsing creates a proper TSIGKeyInfo
+/// when given a valid secret file.
+TEST_F(TSIGKeyInfoParserTest, validSecretFile) {
+    // Valid entries for TSIG key, all items are required.
+    std::string config = "{"
+                         " \"name\": \"d2_key_one\" , "
+                         " \"algorithm\": \"HMAC-MD5\" , "
+                         " \"digest-bits\": 120 , "
+                         " \"secret-file\": \"";
+    config += test_file_name_ + "\" }";
+    // Create and fill the secret file.
+    std::ofstream fs(test_file_name_.c_str(),
+                     std::ofstream::out | std::ofstream::trunc);
+    ASSERT_TRUE(fs.is_open());
+    fs << "dGhpcyBrZXkgd2lsbCBtYXRjaA==";
+    fs.close();
+    // Verify that it parses.
+    PARSE_OK(config);
+    ASSERT_TRUE(key_);
+
+    // Verify the key contents.
+    EXPECT_TRUE(checkKey(key_, "d2_key_one", "HMAC-MD5",
+                         "dGhpcyBrZXkgd2lsbCBtYXRjaA==",
+                         test_file_name_, 120));
 
     // Verify unparsing.
     runToElementTest<TSIGKeyInfo>(config, *key_);
@@ -770,7 +860,7 @@ TEST_F(TSIGKeyInfoListParserTest, validTSIGKeyList) {
 
     // Verify the key contents.
     EXPECT_TRUE(checkKey(key, "key1", TSIGKeyInfo::HMAC_MD5_STR,
-                         ref_secret, 80));
+                         ref_secret, "", 80));
 
     // Find the 2nd key and retrieve it.
     gotit = keys_->find("key2");
@@ -779,7 +869,7 @@ TEST_F(TSIGKeyInfoListParserTest, validTSIGKeyList) {
 
     // Verify the key contents.
     EXPECT_TRUE(checkKey(key, "key2", TSIGKeyInfo::HMAC_SHA1_STR,
-                         ref_secret, 80));
+                         ref_secret, "", 80));
 
     // Find the 3rd key and retrieve it.
     gotit = keys_->find("key3");
@@ -788,7 +878,7 @@ TEST_F(TSIGKeyInfoListParserTest, validTSIGKeyList) {
 
     // Verify the key contents.
     EXPECT_TRUE(checkKey(key, "key3", TSIGKeyInfo::HMAC_SHA256_STR,
-                         ref_secret, 128));
+                         ref_secret, "", 128));
 
     // Find the 4th key and retrieve it.
     gotit = keys_->find("key4");
@@ -797,7 +887,7 @@ TEST_F(TSIGKeyInfoListParserTest, validTSIGKeyList) {
 
     // Verify the key contents.
     EXPECT_TRUE(checkKey(key, "key4", TSIGKeyInfo::HMAC_SHA224_STR,
-                         ref_secret, 112));
+                         ref_secret, "", 112));
 
     // Find the 5th key and retrieve it.
     gotit = keys_->find("key5");
@@ -806,7 +896,7 @@ TEST_F(TSIGKeyInfoListParserTest, validTSIGKeyList) {
 
     // Verify the key contents.
     EXPECT_TRUE(checkKey(key, "key5", TSIGKeyInfo::HMAC_SHA384_STR,
-                         ref_secret, 192));
+                         ref_secret, "", 192));
 
     // Find the 6th key and retrieve it.
     gotit = keys_->find("key6");
@@ -815,7 +905,7 @@ TEST_F(TSIGKeyInfoListParserTest, validTSIGKeyList) {
 
     // Verify the key contents.
     EXPECT_TRUE(checkKey(key, "key6", TSIGKeyInfo::HMAC_SHA512_STR,
-                         ref_secret, 256));
+                         ref_secret, "", 256));
 }
 
 /// @brief Tests the enforcement of data validation when parsing DnsServerInfos.
index 0936e82160d26298761813bcf6c7ca6e604f1160..223aebdb69153158126611da05586a06684333be 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2017-2021 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2017-2024 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -51,6 +51,9 @@ const bool generate_action = true;
 const bool generate_action = false;
 #endif
 
+/// @brief the test secret file name.
+std::string test_file_name(TEST_DATA_BUILDDIR "/sf-test");
+
 /// @brief Read a file into a string
 std::string
 readFile(const std::string& file_path) {
@@ -107,6 +110,28 @@ void pathReplacer(ConstElementPtr d2_cfg) {
     first_lib->set("library", Element::create(lib_path));
 }
 
+/// @brief Replace the test secret file name.
+void testFileNameReplacer(ConstElementPtr d2_cfg) {
+    ConstElementPtr tsig_keys = d2_cfg->get("tsig-keys");
+    if (!tsig_keys || (tsig_keys->size() != 4)) {
+        return;
+    }
+    ElementPtr third_key = tsig_keys->getNonConst(2);
+    if (third_key->contains("secret")) {
+        return;
+    }
+    third_key->set("secret-file", Element::create(test_file_name));
+}
+
+/// @brief Create and fill the secret file.
+void fillSecretFile() {
+    std::ofstream fs(test_file_name.c_str(),
+                     std::ofstream::out | std::ofstream::trunc);
+    ASSERT_TRUE(fs.is_open());
+    fs << "Gwk53fvy3CmbupoI9TgigA==";
+    fs.close();
+}
+
 }
 
 /// Test fixture class
@@ -119,9 +144,12 @@ public:
         Daemon::setVerbose(false);
         // Create fresh context.
         resetConfiguration();
+        // Fill test secret file.
+        fillSecretFile();
     }
 
     ~D2GetConfigTest() {
+        static_cast<void>(remove(test_file_name.c_str()));
         resetConfiguration();
     }
 
@@ -168,6 +196,9 @@ public:
         // update hooks-libraries
         pathReplacer(d2);
 
+        // update tsig-keys.
+        testFileNameReplacer(d2);
+
         // try DHCPDDNS configure
         ConstElementPtr status;
         try {
@@ -270,6 +301,7 @@ TEST_F(D2GetConfigTest, sample1) {
         ASSERT_NO_THROW(d2 = jsonj->get("DhcpDdns"));
         ASSERT_TRUE(d2);
         pathReplacer(d2);
+        testFileNameReplacer(d2);
         // check that unparsed and expected values match
         EXPECT_TRUE(isEquivalent(unparsed, jsonj));
         // check on pretty prints too
index 3a8fdc4bafd6bc2a7cfae67860a739307bca2858..1a1726b8713d58b61a636da47eed9cc4db298776 100644 (file)
@@ -6,11 +6,12 @@
 
 #include <config.h>
 
+#include <asiolink/io_error.h>
 #include <d2srv/d2_log.h>
 #include <d2srv/d2_cfg_mgr.h>
 #include <dhcpsrv/parsers/dhcp_parsers.h>
 #include <exceptions/exceptions.h>
-#include <asiolink/io_error.h>
+#include <util/filesystem.h>
 
 #include <boost/scoped_ptr.hpp>
 #include <boost/algorithm/string/predicate.hpp>
@@ -18,8 +19,8 @@
 #include <sstream>
 #include <string>
 
-using namespace isc::process;
 using namespace isc::data;
+using namespace isc::process;
 
 namespace isc {
 namespace d2 {
@@ -127,9 +128,10 @@ const char* TSIGKeyInfo::HMAC_SHA384_STR = "HMAC-SHA384";
 const char* TSIGKeyInfo::HMAC_SHA512_STR = "HMAC-SHA512";
 
 TSIGKeyInfo::TSIGKeyInfo(const std::string& name, const std::string& algorithm,
-                         const std::string& secret, uint32_t digestbits)
-    :name_(name), algorithm_(algorithm), secret_(secret),
-     digestbits_(digestbits), tsig_key_() {
+                         const std::string& secret, std::string secret_file,
+                         uint32_t digestbits)
+    : name_(name), algorithm_(algorithm), secret_(secret),
+      secret_file_(secret_file), digestbits_(digestbits), tsig_key_() {
     remakeKey();
 }
 
@@ -184,8 +186,12 @@ TSIGKeyInfo::toElement() const {
     result->set("name", Element::create(name_));
     // Set algorithm
     result->set("algorithm", Element::create(algorithm_));
-    // Set secret
-    result->set("secret", Element::create(secret_));
+    // Set secret[-file]
+    if (!secret_file_.empty()) {
+        result->set("secret-file", Element::create(secret_file_));
+    } else {
+        result->set("secret", Element::create(secret_));
+    }
     // Set digest-bits
     result->set("digest-bits",
                 Element::create(static_cast<int64_t>(digestbits_)));
@@ -400,7 +406,24 @@ TSIGKeyInfoParser::parse(ConstElementPtr key_config) {
     std::string name = getString(key_config, "name");
     std::string algorithm = getString(key_config, "algorithm");
     uint32_t digestbits = getInteger(key_config, "digest-bits");
-    std::string secret = getString(key_config, "secret");
+    std::string secret_file;
+    std::string secret;
+    if (key_config->contains("secret-file")) {
+        secret_file = getString(key_config, "secret-file");
+        try {
+            secret = util::file::getContent(secret_file);
+            if (secret.empty()) {
+                isc_throw(BadValue, "Expected '" << secret_file
+                          << "' to not be empty");
+            }
+        } catch (const std::exception& ex) {
+            isc_throw(D2CfgError, "tsig-key : " << ex.what()
+                      << " (" << getPosition("secret-file", key_config)
+                      << ")");
+        }
+    } else {
+        secret = getString(key_config, "secret");
+    }
     ConstElementPtr user_context = key_config->get("user-context");
 
     // Algorithm must be valid.
@@ -434,7 +457,8 @@ TSIGKeyInfoParser::parse(ConstElementPtr key_config) {
     // with an invalid secret content.
     TSIGKeyInfoPtr key_info;
     try {
-        key_info.reset(new TSIGKeyInfo(name, algorithm, secret, digestbits));
+        key_info.reset(new TSIGKeyInfo(name, algorithm, secret,
+                                       secret_file, digestbits));
     } catch (const std::exception& ex) {
         isc_throw(D2CfgError, ex.what() << " ("
                   << key_config->getPosition() << ")");
index 0abf6f2dd2835c30ce906566ac5ca06d68140b0a..2b57ddf3871703dc8c7028162f5922ded0543bfa 100644 (file)
@@ -285,7 +285,7 @@ public:
     /// -# "HMAC-SHA384"
     /// -# "HMAC-SHA512"
     ///
-    /// @param secret  The base-64 encoded secret component for this key.
+    /// @param secret The base-64 encoded secret component for this key.
     /// (A suitable string for use here could be obtained by running the
     /// BIND 9 dnssec-keygen program; the contents of resulting key file
     /// will look similar to:
@@ -299,13 +299,15 @@ public:
     ///   Activate: 20140515143700
     /// @endcode
     /// where the value the "Key:" entry is the secret component of the key.)
+    /// @param secret_file The file name where the secret can be found.
     /// @param digestbits the minimum truncated length in bits
     ///
     /// @throw D2CfgError if values supplied are invalid:
     /// name cannot be blank, algorithm must be a supported value,
     /// secret must be a non-blank, base64 encoded string.
     TSIGKeyInfo(const std::string& name, const std::string& algorithm,
-                const std::string& secret, uint32_t digestbits = 0);
+                const std::string& secret, std::string secret_file = "",
+                uint32_t digestbits = 0);
 
     /// @brief Destructor
     virtual ~TSIGKeyInfo();
@@ -338,6 +340,13 @@ public:
         return (secret_);
     }
 
+    /// @brief Getter which returns the secret file name.
+    ///
+    /// @return returns the secret file name.
+    const std::string getSecretFile() const {
+        return (secret_file_);
+    }
+
     /// @brief Getter which returns the TSIG key used to sign and verify
     /// messages
     ///
@@ -390,6 +399,9 @@ private:
     /// @brief The base64 encoded string secret value component of this key.
     std::string secret_;
 
+    /// @brief The secret file name (usually empty).
+    std::string secret_file_;
+
     /// @brief The minimum truncated length in bits
     /// (0 means no truncation is allowed and is the default)
     uint32_t digestbits_;