mirror of
https://gitlab.isc.org/isc-projects/kea
synced 2025-08-30 21:45:37 +00:00
[#1721] Added redactConfig function
This commit is contained in:
committed by
Andrei Pavel
parent
406859c21f
commit
396c9ea6b9
@@ -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);
|
||||
|
@@ -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.
|
||||
|
@@ -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
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
||||
|
70
src/lib/process/redact_config.cc
Normal file
70
src/lib/process/redact_config.cc
Normal file
@@ -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
|
39
src/lib/process/redact_config.h
Normal file
39
src/lib/process/redact_config.h
Normal file
@@ -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
|
@@ -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
|
||||
|
Reference in New Issue
Block a user