#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;
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);
/// 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.
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)
d_process.h \
logging_info.h \
log_parser.h \
- process_messages.h
+ process_messages.h \
+ redact_config.h
-// 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
#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>
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);
}
-// 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
/// @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
@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
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
--- /dev/null
+// 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
--- /dev/null
+// 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
#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>
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