]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#1721] Added redactConfig function
authorFrancis Dupont <fdupont@isc.org>
Mon, 22 Mar 2021 23:23:12 +0000 (00:23 +0100)
committerAndrei Pavel <andrei@isc.org>
Fri, 21 May 2021 13:22:00 +0000 (13:22 +0000)
src/bin/agent/ca_cfg_mgr.cc
src/bin/agent/ca_cfg_mgr.h
src/lib/process/Makefile.am
src/lib/process/d_cfg_mgr.cc
src/lib/process/d_cfg_mgr.h
src/lib/process/libprocess.dox
src/lib/process/redact_config.cc [new file with mode: 0644]
src/lib/process/redact_config.h [new file with mode: 0644]
src/lib/process/tests/d_cfg_mgr_unittests.cc

index d65fee037fa5344386c98f319e7d67459b44ef58..67472c209a610317e4e2bec729b2d4aa09de0fc7 100644 (file)
@@ -11,6 +11,7 @@
 #include <cc/simple_parser.h>
 #include <cc/command_interpreter.h>
 #include <http/basic_auth_config.h>
+#include <process/redact_config.h>
 #include <exceptions/exceptions.h>
 
 using namespace isc::config;
@@ -141,62 +142,17 @@ CtrlAgentCfgMgr::parse(ConstElementPtr config_set, bool check_only) {
 ConstElementPtr
 CtrlAgentCfgMgr::redactConfig(ConstElementPtr config) const {
     bool redacted = false;
-    ConstElementPtr result = redactElement(config, redacted);
+    const std::set<std::string> follow = {
+        "Control-agent", "authentication", "clients"
+    };
+    ConstElementPtr result =
+        isc::process::redactConfig(config, redacted, follow);
     if (redacted) {
         return (result);
     }
     return (config);
 }
 
-ConstElementPtr
-CtrlAgentCfgMgr::redactElement(ConstElementPtr elem, bool& redacted) const {
-    // From isc::data::copy.
-    if (!elem) {
-        isc_throw(BadValue, "redactElement got a null pointer");
-    }
-    // Redact lists.
-    if (elem->getType() == Element::list) {
-        ElementPtr result = ElementPtr(new ListElement());
-        for (auto item : elem->listValue()) {
-            // add wants a ElementPtr so use a shallow copy.
-            ElementPtr copy = data::copy(redactElement(item, redacted), 0);
-            result->add(copy);
-        }
-        if (redacted) {
-            return (result);
-        }
-        return (elem);
-    }
-    // Redact maps.
-    if (elem->getType() == Element::map) {
-        ElementPtr result = ElementPtr(new MapElement());
-        for (auto kv : elem->mapValue()) {
-            auto key = kv.first;
-            auto value = kv.second;
-
-            if (key == "password") {
-                // Handle passwords.
-                redacted = true;
-                result->set(key, Element::create(std::string("*****")));
-            } else if ((key == "Control-agent") ||
-                       (key == "authentication") ||
-                       (key == "clients")) {
-                // Handle the arc where are passwords.
-                result->set(key, redactElement(value, redacted));
-            } else {
-                // Default case: no password here.
-                result->set(key, value);
-            }
-        }
-        if (redacted) {
-            return (result);
-        }
-        return (elem);
-    }
-    // Handle other element types.
-    return (elem);
-}
-
 data::ConstElementPtr
 CtrlAgentCfgContext::getControlSocketInfo(const std::string& service) const {
     auto si = ctrl_sockets_.find(service);
index 78c722d827ee2aef09779c188470219a2bcad577..7ee947fc88c3a1f61a2951981d470cb9ad8ba3c6 100644 (file)
@@ -308,20 +308,6 @@ protected:
     /// replaced by asterisks so can be safely logged to an unprivileged place.
     virtual isc::data::ConstElementPtr
     redactConfig(isc::data::ConstElementPtr config) const;
-
-private:
-    /// @brief Redact an element.
-    ///
-    /// Recursive helper of redactConfig.
-    ///
-    /// @param elem An element to redact.
-    /// @param redacted The reference to redacted flag: true means the result
-    /// was redacted so cannot be shared.
-    /// @return unmodified element or a copy of the element: in the second
-    /// case embedded passwords were replaced by asterisks and the redacted
-    /// flag was set to true.
-    virtual isc::data::ConstElementPtr
-    redactElement(isc::data::ConstElementPtr elem, bool& redacted) const;
 };
 
 /// @brief Defines a shared pointer to CtrlAgentCfgMgr.
index 183ac0ab830beac37cf9d6970a9156fe311e8cdd..b61304ea5c06b9f0207507473097a46400567740 100644 (file)
@@ -28,6 +28,7 @@ libkea_process_la_SOURCES += daemon.cc daemon.h
 libkea_process_la_SOURCES += log_parser.cc log_parser.h
 libkea_process_la_SOURCES += logging_info.cc logging_info.h
 libkea_process_la_SOURCES += process_messages.cc process_messages.h
+libkea_process_la_SOURCES += redact_config.cc redact_config.h
 
 libkea_process_la_CXXFLAGS = $(AM_CXXFLAGS)
 libkea_process_la_CPPFLAGS = $(AM_CPPFLAGS)
@@ -95,4 +96,5 @@ libkea_process_include_HEADERS = \
        d_process.h \
        logging_info.h \
        log_parser.h \
-       process_messages.h
+       process_messages.h \
+       redact_config.h
index c98e50e6b5d7e3f859789162f3af4261928879e1..dc10ff178bba5af7ec1758de3b91c0160a2cee67 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2020 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2021 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
@@ -11,6 +11,7 @@
 #include <process/d_log.h>
 #include <process/d_cfg_mgr.h>
 #include <process/daemon.h>
+#include <process/redact_config.h>
 #include <util/encode/hex.h>
 #include <util/strutil.h>
 
@@ -56,8 +57,15 @@ DCfgMgrBase::setContext(ConfigPtr& context) {
     context_ = context;
 }
 
-isc::data::ConstElementPtr
-DCfgMgrBase::redactConfig(isc::data::ConstElementPtr config_set) const {
+ConstElementPtr
+DCfgMgrBase::redactConfig(ConstElementPtr config_set) const {
+    bool redacted = false;
+    set<string> follow = { };
+    ConstElementPtr result =
+        isc::process::redactConfig(config_set, redacted, follow);
+    if (redacted) {
+        return (result);
+    }
     return (config_set);
 }
 
index b09326adb7fd32733be3b064e9f26aed560b5c35..9b595b262f23b989f60b1bb6e3dc9803f0d3cf5a 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2020 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2021 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
@@ -212,8 +212,12 @@ protected:
 
     /// @brief Redact the configuration.
     ///
-    /// This method replaces passwords by asterisks. By default it does
-    /// nothing: derived class must redefine it.
+
+    /// This method replaces passwords and secrets by asterisks. By
+    /// default it follows all subtrees at the exception of user
+    /// contexts. Please derive the method to allow a reasonable
+    /// performance by following only subtrees where the syntax allows
+    /// the presence of passwords and secrets.
     ///
     /// @param config the Element tree structure that describes the configuration.
     /// @return unmodified config or a copy of the config where passwords were
index 22e03c07f1024e8633743408dd46aa650d05333f..94d971c198fb2e9c4a2ee0f50512155f86b36645 100644 (file)
@@ -166,7 +166,7 @@ during startup and the subsequent receipt of a SIGHUP:
 
 @section redact Redact Passwords
 
-There are two tools to remove sensitive data as passwords from logs:
+There are two tools to remove sensitive data as passwords or secrets from logs:
  - redactedAccessString for database access strings
  - redactConfig for full configurations
 
@@ -174,11 +174,8 @@ The redactConfig method must be defined in derived classes following this
 procedure:
  - take the grammar (bison input file with the .yy extension)
  - get the arcs between the start symbol and tokens handling sensitive
-   data e.g. PASSWORD
- - walk the full configuration following only these arcs (so ignoring
-   other parts of the configuration tree)
- - replace sensitive data by something else e.g. "*****"
- - try to share unchanged subtrees vs. copy to identical.
+   data i.e. passwords and secrets
+ - give the set of keywords of these args to the redactConfig function
 
 @section cplMTConsiderations Multi-Threading Consideration for Controllable Process Layer
 
diff --git a/src/lib/process/redact_config.cc b/src/lib/process/redact_config.cc
new file mode 100644 (file)
index 0000000..61a4b9c
--- /dev/null
@@ -0,0 +1,70 @@
+// Copyright (C) 2021 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
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <process/redact_config.h>
+
+using namespace isc::data;
+using namespace std;
+
+namespace isc {
+namespace process {
+
+ConstElementPtr
+redactConfig(ConstElementPtr elem, bool& redacted, const set<string>& follow) {
+    // From isc::data::copy.
+    if (!elem) {
+        isc_throw(BadValue, "redactConfig got a null pointer");
+    }
+
+    // Redact lists.
+    if (elem->getType() == Element::list) {
+        ElementPtr result = ElementPtr(new ListElement());
+        for (auto item : elem->listValue()) {
+            // add wants a ElementPtr so use a shallow copy.
+            ElementPtr copy =
+                data::copy(redactConfig(item, redacted, follow), 0);
+            result->add(copy);
+        }
+        if (redacted) {
+            return (result);
+        }
+        return (elem);
+    }
+
+    // Redact maps.
+    if (elem->getType() == Element::map) {
+        ElementPtr result = ElementPtr(new MapElement());
+        for (auto kv : elem->mapValue()) {
+            auto key = kv.first;
+            auto value = kv.second;
+
+            if ((key == "password") || (key == "secret")) {
+                // Handle passwords.
+                redacted = true;
+                result->set(key, Element::create(std::string("*****")));
+            } else if (key == "user-context") {
+                // Skip user contexts.
+                result->set(key, value);
+            } else if (follow.empty() || follow.count(key)) {
+                // Handle this subtree where are passwords or secrets.
+                result->set(key, redactConfig(value, redacted, follow));
+            } else {
+                // Not follow: no passwords and secrets in this subtree.
+                result->set(key, value);
+            }
+        }
+        if (redacted) {
+            return (result);
+        }
+        return (elem);
+    }
+
+    // Handle other element types.
+    return (elem);
+}
+
+} // namespace process
+} // namespace isc
diff --git a/src/lib/process/redact_config.h b/src/lib/process/redact_config.h
new file mode 100644 (file)
index 0000000..a2926fd
--- /dev/null
@@ -0,0 +1,39 @@
+// Copyright (C) 2021 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
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef REDACT_CONFIG_H
+#define REDACT_CONFIG_H
+
+#include <cc/data.h>
+#include <set>
+
+namespace isc {
+namespace process {
+
+/// @brief Redact a configuration.
+///
+/// This method walks on the configuration tree:
+///  - it copies only subtrees where a change was done.
+///  - it replaces passwords and secrets by asterisks.
+///  - it skips user context.
+///  - if a not empty list of keywords is given it follows only them.
+///
+/// @param config the Element tree structure that describes the configuration.
+/// @param redacted The reference to redacted flag: true means the result
+/// was redacted so cannot be shared.
+/// @param follow The set of keywords of subtrees where a password or a
+/// secret can be found.
+/// @return unmodified config or a copy of the config where passwords and
+/// secrets were replaced by asterisks so can be safely logged to an
+/// unprivileged place.
+isc::data::ConstElementPtr redactConfig(isc::data::ConstElementPtr elem,
+                                        bool& redacted,
+                                        const std::set<std::string>& follow);
+
+} // namespace process
+} // namespace isc
+
+#endif // REDACT_CONFIG_H
index fa5ad7fcd714515cd79740387e0671e9eaf394c7..2add043786bcd28092f9e157dbe2aca170f9a283 100644 (file)
@@ -10,6 +10,7 @@
 #include <exceptions/exceptions.h>
 #include <process/testutils/d_test_stubs.h>
 #include <process/d_cfg_mgr.h>
+#include <process/redact_config.h>
 
 #include <boost/foreach.hpp>
 #include <boost/date_time/posix_time/posix_time.hpp>
@@ -281,4 +282,47 @@ TEST_F(DStubCfgMgrTest, simpleParseConfigWithCallback) {
     EXPECT_TRUE(checkAnswer(1));
 }
 
+// This test checks that redactConfig works as expected.
+TEST_F(DStubCfgMgrTest, redactConfig) {
+    // Basic case.
+    bool redacted = false;
+    set<string> empty = { };
+    string config = "{ \"foo\": 1 }";
+    ConstElementPtr elem;
+    ASSERT_NO_THROW(elem = Element::fromJSON(config));
+    ConstElementPtr ret;
+    ASSERT_NO_THROW(ret = redactConfig(elem, redacted, empty));
+    EXPECT_FALSE(redacted);
+    EXPECT_EQ(ret->str(), elem->str());
+
+    // Verify redaction.
+    redacted = false;
+    config = "{ \"password\": \"foo\", \"secret\": \"bar\" }";
+    ASSERT_NO_THROW(elem = Element::fromJSON(config));
+    ASSERT_NO_THROW(ret = redactConfig(elem, redacted, empty));
+    EXPECT_TRUE(redacted);
+    string expected = "{ \"password\": \"*****\", \"secret\": \"*****\" }";
+    EXPECT_EQ(expected, ret->str());
+
+    // Verify that user context are skipped.
+    redacted = false;
+    config = "{ \"user-context\": { \"password\": \"foo\" } }";
+    ASSERT_NO_THROW(elem = Element::fromJSON(config));
+    ASSERT_NO_THROW(ret = redactConfig(elem, redacted, empty));
+    EXPECT_FALSE(redacted);
+    EXPECT_EQ(ret->str(), elem->str());
+
+    // Verify that only given subtrees are handled.
+    redacted = false;
+    set<string> keys = { "foo" };
+    config = "{ \"foo\": { \"password\": \"foo\" }, ";
+    config += "\"next\": { \"secret\": \"bar\" } }";
+    ASSERT_NO_THROW(elem = Element::fromJSON(config));
+    ASSERT_NO_THROW(ret = redactConfig(elem, redacted, keys));
+    EXPECT_TRUE(redacted);
+    expected = "{ \"foo\": { \"password\": \"*****\" }, ";
+    expected += "\"next\": { \"secret\": \"bar\" } }";
+    EXPECT_EQ(expected, ret->str());
+}
+
 } // end of anonymous namespace