mirror of
https://github.com/falcosecurity/falco.git
synced 2025-08-01 22:47:46 +00:00
new(userspace,unit_tests): port merge-strategy to be a yaml map.
Merge-strategy for included config files must now be specified as yaml map of the form: - path: foo strategy: bar If `strategy` is omitted, or the old `string-only` form is used, `append` strategy is enforced. Signed-off-by: Federico Di Pierro <nierro92@gmail.com>
This commit is contained in:
parent
630167d9ad
commit
08a00609a1
20
falco.yaml
20
falco.yaml
@ -144,22 +144,30 @@
|
||||
#
|
||||
# Like for 'rules_files', specifying a folder will load all the configs files present in it in a lexicographical order.
|
||||
#
|
||||
# 3 merge-strategies are available: `append`, `override` and `add-only`.
|
||||
# The default merge-strategy is `append`:
|
||||
# 3 merge-strategies are available:
|
||||
# `append` (default):
|
||||
# * existing sequence keys will be appended
|
||||
# * existing scalar keys will be overridden
|
||||
# * non-existing keys will be added
|
||||
# To enable the `override` merge-strategy, `@` must be prepended to the entry, eg: "@/$HOME/override.yaml".
|
||||
# `override`:
|
||||
# * existing keys will be overridden
|
||||
# * non-existing keys will be added
|
||||
# Please note that since `@` is a reserved character by yaml spec, you need to quote the whole string.
|
||||
# To enable the `add-only` merge-strategy, `+` must be prepended to the entry, eg: "+/$HOME/append.yaml".
|
||||
# `add-only`:
|
||||
# * existing keys will be ignored
|
||||
# * non-existing keys will be added
|
||||
#
|
||||
# When a merge-strategy is enabled for a folder entry, all of the included config files will use that merge-strategy.
|
||||
# Each item on the list can be either a yaml map or a simple string.
|
||||
# The simple string will be interpreted as the config file path, and the `append` merge-strategy will be enforced.
|
||||
# When the item is a yaml map instead, it will be of the form: ` path: foo\n strategy: X`.
|
||||
# When `strategy` is omitted, once again `append` is used.
|
||||
#
|
||||
# When a merge-strategy is enabled for a folder entry, all the included config files will use that merge-strategy.
|
||||
config_files:
|
||||
- /etc/falco/config.d
|
||||
# Example of config file specified as yaml map with strategy made explicit.
|
||||
#- path: $HOME/falco_local_configs/
|
||||
# strategy: add-only
|
||||
|
||||
|
||||
# [Stable] `watch_config_files`
|
||||
#
|
||||
|
@ -245,8 +245,7 @@ TEST(Configuration, configuration_config_files_override) {
|
||||
std::filesystem::remove("conf_3.yaml");
|
||||
}
|
||||
|
||||
TEST(Configuration, configuration_config_files_sequence) {
|
||||
/* Test that included config files are able to override configs from main file */
|
||||
TEST(Configuration, configuration_config_files_sequence_strategy_default) {
|
||||
const std::string main_conf_yaml = yaml_helper::configs_key +
|
||||
":\n"
|
||||
" - conf_2.yaml\n" // default merge-strategy: append
|
||||
@ -306,11 +305,72 @@ TEST(Configuration, configuration_config_files_sequence) {
|
||||
std::filesystem::remove("conf_3.yaml");
|
||||
}
|
||||
|
||||
TEST(Configuration, configuration_config_files_sequence_override) {
|
||||
/* Test that included config files are able to override configs from main file */
|
||||
TEST(Configuration, configuration_config_files_sequence_strategy_append) {
|
||||
const std::string main_conf_yaml = yaml_helper::configs_key +
|
||||
":\n"
|
||||
" - '@conf_2.yaml'\n" // merge-strategy: override
|
||||
" - path: conf_2.yaml\n"
|
||||
" strategy: append\n"
|
||||
" - conf_3.yaml\n"
|
||||
"foo: [ bar ]\n"
|
||||
"base_value:\n"
|
||||
" id: 1\n"
|
||||
" name: foo\n";
|
||||
const std::string conf_yaml_2 =
|
||||
"foo: [ bar2 ]\n" // append to foo sequence
|
||||
"base_value_2:\n"
|
||||
" id: 2\n";
|
||||
const std::string conf_yaml_3 =
|
||||
"base_value:\n" // override base_value
|
||||
" id: 3\n";
|
||||
|
||||
std::ofstream outfile("main.yaml");
|
||||
outfile << main_conf_yaml;
|
||||
outfile.close();
|
||||
|
||||
outfile.open("conf_2.yaml");
|
||||
outfile << conf_yaml_2;
|
||||
outfile.close();
|
||||
|
||||
outfile.open("conf_3.yaml");
|
||||
outfile << conf_yaml_3;
|
||||
outfile.close();
|
||||
|
||||
std::vector<std::string> cmdline_config_options;
|
||||
falco_configuration falco_config;
|
||||
config_loaded_res res;
|
||||
ASSERT_NO_THROW(res = falco_config.init_from_file("main.yaml", cmdline_config_options));
|
||||
|
||||
// main + conf_2 + conf_3
|
||||
ASSERT_EQ(res.size(), 3);
|
||||
|
||||
ASSERT_TRUE(falco_config.m_config.is_defined("foo"));
|
||||
std::vector<std::string> foos;
|
||||
auto expected_foos = std::vector<std::string>{"bar", "bar2"};
|
||||
ASSERT_NO_THROW(falco_config.m_config.get_sequence<std::vector<std::string>>(foos, "foo"));
|
||||
ASSERT_EQ(foos.size(), 2); // 2 elements in `foo` sequence because we appended to it
|
||||
for(size_t i = 0; i < foos.size(); ++i) {
|
||||
EXPECT_EQ(foos[i], expected_foos[i])
|
||||
<< "Vectors foo's and expected_foo's differ at index " << i;
|
||||
}
|
||||
|
||||
ASSERT_TRUE(falco_config.m_config.is_defined("base_value.id"));
|
||||
ASSERT_EQ(falco_config.m_config.get_scalar<int>("base_value.id", 0), 3); // overridden!
|
||||
ASSERT_FALSE(falco_config.m_config.is_defined(
|
||||
"base_value.name")); // no more present since entire `base_value` block was overridden
|
||||
ASSERT_TRUE(falco_config.m_config.is_defined("base_value_2.id"));
|
||||
ASSERT_EQ(falco_config.m_config.get_scalar<int>("base_value_2.id", 0), 2);
|
||||
ASSERT_FALSE(falco_config.m_config.is_defined("base_value_3.id")); // not defined
|
||||
|
||||
std::filesystem::remove("main.yaml");
|
||||
std::filesystem::remove("conf_2.yaml");
|
||||
std::filesystem::remove("conf_3.yaml");
|
||||
}
|
||||
|
||||
TEST(Configuration, configuration_config_files_sequence_strategy_override) {
|
||||
const std::string main_conf_yaml = yaml_helper::configs_key +
|
||||
":\n"
|
||||
" - path: conf_2.yaml\n"
|
||||
" strategy: override\n"
|
||||
" - conf_3.yaml\n"
|
||||
"foo: [ bar ]\n"
|
||||
"base_value:\n"
|
||||
@ -367,11 +427,12 @@ TEST(Configuration, configuration_config_files_sequence_override) {
|
||||
std::filesystem::remove("conf_3.yaml");
|
||||
}
|
||||
|
||||
TEST(Configuration, configuration_config_files_sequence_addonly) {
|
||||
TEST(Configuration, configuration_config_files_sequence_strategy_addonly) {
|
||||
/* Test that included config files are able to override configs from main file */
|
||||
const std::string main_conf_yaml = yaml_helper::configs_key +
|
||||
":\n"
|
||||
" - +conf_2.yaml\n" // merge-strategy: add-only
|
||||
" - path: conf_2.yaml\n"
|
||||
" strategy: add-only\n"
|
||||
" - conf_3.yaml\n"
|
||||
"foo: [ bar ]\n"
|
||||
"base_value:\n"
|
||||
@ -429,6 +490,71 @@ TEST(Configuration, configuration_config_files_sequence_addonly) {
|
||||
std::filesystem::remove("conf_3.yaml");
|
||||
}
|
||||
|
||||
TEST(Configuration, configuration_config_files_sequence_wrong_strategy) {
|
||||
const std::string main_conf_yaml = yaml_helper::configs_key +
|
||||
":\n"
|
||||
" - path: conf_2.yaml\n"
|
||||
" strategy: wrong\n"
|
||||
" - conf_3.yaml\n"
|
||||
"foo: [ bar ]\n"
|
||||
"base_value:\n"
|
||||
" id: 1\n"
|
||||
" name: foo\n";
|
||||
const std::string conf_yaml_2 =
|
||||
"foo: [ bar2 ]\n" // append to foo sequence
|
||||
"base_value_2:\n"
|
||||
" id: 2\n";
|
||||
const std::string conf_yaml_3 =
|
||||
"base_value:\n" // override base_value
|
||||
" id: 3\n";
|
||||
|
||||
std::ofstream outfile("main.yaml");
|
||||
outfile << main_conf_yaml;
|
||||
outfile.close();
|
||||
|
||||
outfile.open("conf_2.yaml");
|
||||
outfile << conf_yaml_2;
|
||||
outfile.close();
|
||||
|
||||
outfile.open("conf_3.yaml");
|
||||
outfile << conf_yaml_3;
|
||||
outfile.close();
|
||||
|
||||
std::vector<std::string> cmdline_config_options;
|
||||
falco_configuration falco_config;
|
||||
config_loaded_res res;
|
||||
ASSERT_NO_THROW(res = falco_config.init_from_file("main.yaml", cmdline_config_options));
|
||||
|
||||
// main
|
||||
ASSERT_EQ(res.size(), 3);
|
||||
auto validation = res["main.yaml"];
|
||||
// Since we are using a wrong strategy, the validation should fail
|
||||
// but the enforced strategy should be "append"
|
||||
ASSERT_NE(validation, yaml_helper::validation_ok);
|
||||
|
||||
ASSERT_TRUE(falco_config.m_config.is_defined("foo"));
|
||||
std::vector<std::string> foos;
|
||||
auto expected_foos = std::vector<std::string>{"bar", "bar2"};
|
||||
ASSERT_NO_THROW(falco_config.m_config.get_sequence<std::vector<std::string>>(foos, "foo"));
|
||||
ASSERT_EQ(foos.size(), 2); // 2 elements in `foo` sequence because we appended to it
|
||||
for(size_t i = 0; i < foos.size(); ++i) {
|
||||
EXPECT_EQ(foos[i], expected_foos[i])
|
||||
<< "Vectors foo's and expected_foo's differ at index " << i;
|
||||
}
|
||||
|
||||
ASSERT_TRUE(falco_config.m_config.is_defined("base_value.id"));
|
||||
ASSERT_EQ(falco_config.m_config.get_scalar<int>("base_value.id", 0), 3); // overridden!
|
||||
ASSERT_FALSE(falco_config.m_config.is_defined(
|
||||
"base_value.name")); // no more present since entire `base_value` block was overridden
|
||||
ASSERT_TRUE(falco_config.m_config.is_defined("base_value_2.id"));
|
||||
ASSERT_EQ(falco_config.m_config.get_scalar<int>("base_value_2.id", 0), 2);
|
||||
ASSERT_FALSE(falco_config.m_config.is_defined("base_value_3.id")); // not defined
|
||||
|
||||
std::filesystem::remove("main.yaml");
|
||||
std::filesystem::remove("conf_2.yaml");
|
||||
std::filesystem::remove("conf_3.yaml");
|
||||
}
|
||||
|
||||
TEST(Configuration, configuration_config_files_unexistent) {
|
||||
/* Test that including an unexistent file just skips it */
|
||||
const std::string main_conf_yaml = yaml_helper::configs_key +
|
||||
|
@ -85,25 +85,31 @@ public:
|
||||
inline static const std::string validation_failed = "failed";
|
||||
inline static const std::string validation_none = "none";
|
||||
|
||||
enum include_files_strategy {
|
||||
enum config_files_strategy {
|
||||
STRATEGY_APPEND, // append to existing sequence keys, override scalar keys and add new ones
|
||||
STRATEGY_OVERRIDE, // override existing keys (sequences too) and add new ones
|
||||
STRATEGY_ADDONLY // only add new keys and ignore existing ones
|
||||
};
|
||||
|
||||
static inline enum include_files_strategy get_include_file_strategy(
|
||||
std::string& include_file_name) {
|
||||
if(include_file_name.length() > 0) {
|
||||
if(include_file_name[0] == '+') {
|
||||
include_file_name.erase(0, 1);
|
||||
return STRATEGY_ADDONLY;
|
||||
}
|
||||
if(include_file_name[0] == '@') {
|
||||
include_file_name.erase(0, 1);
|
||||
return STRATEGY_OVERRIDE;
|
||||
}
|
||||
static enum config_files_strategy strategy_from_string(const std::string& strategy) {
|
||||
if(strategy == "override") {
|
||||
return yaml_helper::STRATEGY_OVERRIDE;
|
||||
}
|
||||
if(strategy == "add-only") {
|
||||
return yaml_helper::STRATEGY_ADDONLY;
|
||||
}
|
||||
return yaml_helper::STRATEGY_APPEND;
|
||||
}
|
||||
|
||||
static std::string strategy_to_string(const enum config_files_strategy strategy) {
|
||||
switch(strategy) {
|
||||
case yaml_helper::STRATEGY_OVERRIDE:
|
||||
return "override";
|
||||
case yaml_helper::STRATEGY_ADDONLY:
|
||||
return "add-only";
|
||||
default:
|
||||
return "append";
|
||||
}
|
||||
return STRATEGY_APPEND;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -158,7 +164,7 @@ public:
|
||||
}
|
||||
|
||||
void include_config_file(const std::string& include_file_path,
|
||||
enum include_files_strategy strategy = STRATEGY_APPEND,
|
||||
enum config_files_strategy strategy = STRATEGY_APPEND,
|
||||
const nlohmann::json& schema = {},
|
||||
std::vector<std::string>* schema_warnings = nullptr) {
|
||||
auto loaded_nodes = load_from_file_int(include_file_path, schema, schema_warnings);
|
||||
|
@ -38,7 +38,30 @@ const char config_schema_string[] = LONG_STRING_CONST(
|
||||
"config_files": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"strategy": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"append",
|
||||
"override",
|
||||
"add-only"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"path"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"watch_config_files": {
|
||||
|
@ -162,13 +162,12 @@ void falco_configuration::merge_config_files(const std::string &config_name,
|
||||
m_loaded_configs_filenames.push_back(config_name);
|
||||
const auto ppath = std::filesystem::path(config_name);
|
||||
// Parse files to be included
|
||||
std::vector<std::string> include_files;
|
||||
m_config.get_sequence<std::vector<std::string>>(include_files, yaml_helper::configs_key);
|
||||
for(auto include_file : include_files) {
|
||||
// This can modify include_file by dropping the '+/@' prefixes.
|
||||
const auto merge_strategy = yaml_helper::get_include_file_strategy(include_file);
|
||||
|
||||
auto include_file_path = std::filesystem::path(include_file);
|
||||
std::list<falco_configuration::config_files_config> include_files;
|
||||
m_config.get_sequence<std::list<falco_configuration::config_files_config>>(
|
||||
include_files,
|
||||
yaml_helper::configs_key);
|
||||
for(const auto &include_file : include_files) {
|
||||
auto include_file_path = std::filesystem::path(include_file.m_path);
|
||||
if(include_file_path == ppath) {
|
||||
throw std::logic_error("Config error: '" + yaml_helper::configs_key +
|
||||
"' directive tried to recursively include main config file: " +
|
||||
@ -179,15 +178,15 @@ void falco_configuration::merge_config_files(const std::string &config_name,
|
||||
continue;
|
||||
}
|
||||
if(std::filesystem::is_regular_file(include_file_path)) {
|
||||
m_loaded_configs_filenames.push_back(include_file);
|
||||
m_config.include_config_file(include_file,
|
||||
merge_strategy,
|
||||
m_loaded_configs_filenames.push_back(include_file.m_path);
|
||||
m_config.include_config_file(include_file.m_path,
|
||||
include_file.m_strategy,
|
||||
m_config_schema,
|
||||
&validation_status);
|
||||
// Only report top most schema validation status
|
||||
res[include_file] = validation_status[0];
|
||||
res[include_file.m_path] = validation_status[0];
|
||||
} else if(std::filesystem::is_directory(include_file_path)) {
|
||||
m_loaded_configs_folders.push_back(include_file);
|
||||
m_loaded_configs_folders.push_back(include_file.m_path);
|
||||
std::vector<std::string> v;
|
||||
const auto it_options = std::filesystem::directory_options::follow_directory_symlink |
|
||||
std::filesystem::directory_options::skip_permission_denied;
|
||||
@ -200,7 +199,7 @@ void falco_configuration::merge_config_files(const std::string &config_name,
|
||||
std::sort(v.begin(), v.end());
|
||||
for(const auto &f : v) {
|
||||
m_config.include_config_file(f,
|
||||
merge_strategy,
|
||||
include_file.m_strategy,
|
||||
m_config_schema,
|
||||
&validation_status);
|
||||
// Only report top most schema validation status
|
||||
|
@ -54,6 +54,11 @@ public:
|
||||
std::string m_open_params;
|
||||
};
|
||||
|
||||
struct config_files_config {
|
||||
std::string m_path;
|
||||
yaml_helper::config_files_strategy m_strategy;
|
||||
};
|
||||
|
||||
struct kmod_config {
|
||||
int16_t m_buf_size_preset;
|
||||
bool m_drop_failed_exit;
|
||||
@ -425,4 +430,39 @@ struct convert<falco_configuration::plugin_config> {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct convert<falco_configuration::config_files_config> {
|
||||
static Node encode(const falco_configuration::config_files_config& rhs) {
|
||||
Node node;
|
||||
node["path"] = rhs.m_path;
|
||||
node["strategy"] = yaml_helper::strategy_to_string(rhs.m_strategy);
|
||||
return node;
|
||||
}
|
||||
|
||||
static bool decode(const Node& node, falco_configuration::config_files_config& rhs) {
|
||||
if(!node.IsMap()) {
|
||||
// Single string mode defaults to append strategy
|
||||
rhs.m_path = node.as<std::string>();
|
||||
rhs.m_strategy = yaml_helper::STRATEGY_APPEND;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Path is required
|
||||
if(!node["path"]) {
|
||||
return false;
|
||||
}
|
||||
rhs.m_path = node["path"].as<std::string>();
|
||||
|
||||
// Strategy is not required
|
||||
if(!node["strategy"]) {
|
||||
rhs.m_strategy = yaml_helper::STRATEGY_APPEND;
|
||||
} else {
|
||||
std::string strategy = node["strategy"].as<std::string>();
|
||||
rhs.m_strategy = yaml_helper::strategy_from_string(strategy);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace YAML
|
||||
|
Loading…
Reference in New Issue
Block a user