diff --git a/src/bin/agent/ca_cfg_mgr.cc b/src/bin/agent/ca_cfg_mgr.cc index d65fee037f..67472c209a 100644 --- a/src/bin/agent/ca_cfg_mgr.cc +++ b/src/bin/agent/ca_cfg_mgr.cc @@ -11,6 +11,7 @@ #include #include #include +#include #include 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 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); diff --git a/src/bin/agent/ca_cfg_mgr.h b/src/bin/agent/ca_cfg_mgr.h index 78c722d827..7ee947fc88 100644 --- a/src/bin/agent/ca_cfg_mgr.h +++ b/src/bin/agent/ca_cfg_mgr.h @@ -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. diff --git a/src/lib/process/Makefile.am b/src/lib/process/Makefile.am index 183ac0ab83..b61304ea5c 100644 --- a/src/lib/process/Makefile.am +++ b/src/lib/process/Makefile.am @@ -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 diff --git a/src/lib/process/d_cfg_mgr.cc b/src/lib/process/d_cfg_mgr.cc index c98e50e6b5..dc10ff178b 100644 --- a/src/lib/process/d_cfg_mgr.cc +++ b/src/lib/process/d_cfg_mgr.cc @@ -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 #include #include +#include #include #include @@ -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 follow = { }; + ConstElementPtr result = + isc::process::redactConfig(config_set, redacted, follow); + if (redacted) { + return (result); + } return (config_set); } diff --git a/src/lib/process/d_cfg_mgr.h b/src/lib/process/d_cfg_mgr.h index b09326adb7..9b595b262f 100644 --- a/src/lib/process/d_cfg_mgr.h +++ b/src/lib/process/d_cfg_mgr.h @@ -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 diff --git a/src/lib/process/libprocess.dox b/src/lib/process/libprocess.dox index 22e03c07f1..94d971c198 100644 --- a/src/lib/process/libprocess.dox +++ b/src/lib/process/libprocess.dox @@ -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 index 0000000000..61a4b9ce42 --- /dev/null +++ b/src/lib/process/redact_config.cc @@ -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 + +using namespace isc::data; +using namespace std; + +namespace isc { +namespace process { + +ConstElementPtr +redactConfig(ConstElementPtr elem, bool& redacted, const set& 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 index 0000000000..a2926fde38 --- /dev/null +++ b/src/lib/process/redact_config.h @@ -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 +#include + +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& follow); + +} // namespace process +} // namespace isc + +#endif // REDACT_CONFIG_H diff --git a/src/lib/process/tests/d_cfg_mgr_unittests.cc b/src/lib/process/tests/d_cfg_mgr_unittests.cc index fa5ad7fcd7..2add043786 100644 --- a/src/lib/process/tests/d_cfg_mgr_unittests.cc +++ b/src/lib/process/tests/d_cfg_mgr_unittests.cc @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -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 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 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