mirror of
https://github.com/falcosecurity/falco.git
synced 2025-09-03 15:46:33 +00:00
new(engine): add selective overrides
Signed-off-by: Luca Guerra <luca@guerra.sh>
This commit is contained in:
235
unit_tests/engine/test_rule_loader.cpp
Normal file
235
unit_tests/engine/test_rule_loader.cpp
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include "falco_engine.h"
|
||||||
|
#include "rule_loader_reader.h"
|
||||||
|
#include "rule_loader_compiler.h"
|
||||||
|
|
||||||
|
class engine_loader_test : public ::testing::Test {
|
||||||
|
protected:
|
||||||
|
void SetUp() override
|
||||||
|
{
|
||||||
|
m_sample_ruleset = "sample-ruleset";
|
||||||
|
m_sample_source = falco_common::syscall_source;
|
||||||
|
|
||||||
|
// create a falco engine ready to load the ruleset
|
||||||
|
m_inspector.reset(new sinsp());
|
||||||
|
m_engine.reset(new falco_engine());
|
||||||
|
m_filter_factory = std::shared_ptr<gen_event_filter_factory>(
|
||||||
|
new sinsp_filter_factory(m_inspector.get(), m_filterlist));
|
||||||
|
m_formatter_factory = std::shared_ptr<gen_event_formatter_factory>(
|
||||||
|
new sinsp_evt_formatter_factory(m_inspector.get(), m_filterlist));
|
||||||
|
m_engine->add_source(m_sample_source, m_filter_factory, m_formatter_factory);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TearDown() override
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool load_rules(std::string rules_content, std::string rules_filename)
|
||||||
|
{
|
||||||
|
bool ret = false;
|
||||||
|
falco::load_result::rules_contents_t rc = {{rules_filename, rules_content}};
|
||||||
|
m_load_result = m_engine->load_rules(rules_content, rules_filename);
|
||||||
|
m_load_result_string = m_load_result->as_string(true, rc);
|
||||||
|
ret = m_load_result->successful();
|
||||||
|
|
||||||
|
if (ret)
|
||||||
|
{
|
||||||
|
m_engine->enable_rule("", true, m_sample_ruleset);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string m_sample_ruleset;
|
||||||
|
std::string m_sample_source;
|
||||||
|
sinsp_filter_check_list m_filterlist;
|
||||||
|
std::shared_ptr<gen_event_filter_factory> m_filter_factory;
|
||||||
|
std::shared_ptr<gen_event_formatter_factory> m_formatter_factory;
|
||||||
|
std::unique_ptr<falco_engine> m_engine;
|
||||||
|
std::unique_ptr<falco::load_result> m_load_result;
|
||||||
|
std::string m_load_result_string;
|
||||||
|
std::unique_ptr<sinsp> m_inspector;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string s_sample_ruleset = "sample-ruleset";
|
||||||
|
std::string s_sample_source = falco_common::syscall_source;
|
||||||
|
|
||||||
|
TEST_F(engine_loader_test, list_append)
|
||||||
|
{
|
||||||
|
std::string rules_content = R"END(
|
||||||
|
- list: shell_binaries
|
||||||
|
items: [ash, bash, csh, ksh, sh, tcsh, zsh, dash]
|
||||||
|
|
||||||
|
- rule: legit_rule
|
||||||
|
desc: legit rule description
|
||||||
|
condition: evt.type=open and proc.name in (shell_binaries)
|
||||||
|
output: user=%user.name command=%proc.cmdline file=%fd.name
|
||||||
|
priority: INFO
|
||||||
|
|
||||||
|
- list: shell_binaries
|
||||||
|
items: [pwsh]
|
||||||
|
override:
|
||||||
|
items: append
|
||||||
|
)END";
|
||||||
|
|
||||||
|
std::string rule_name = "legit_rule";
|
||||||
|
ASSERT_TRUE(load_rules(rules_content, "legit_rules.yaml")) << m_load_result_string;
|
||||||
|
|
||||||
|
auto rule_description = m_engine->describe_rule(&rule_name, {});
|
||||||
|
ASSERT_EQ(rule_description["rules"][0]["details"]["condition_compiled"].template get<std::string>(),
|
||||||
|
"(evt.type = open and proc.name in (ash, bash, csh, ksh, sh, tcsh, zsh, dash, pwsh))");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(engine_loader_test, condition_append)
|
||||||
|
{
|
||||||
|
std::string rules_content = R"END(
|
||||||
|
- macro: interactive
|
||||||
|
condition: >
|
||||||
|
((proc.aname=sshd and proc.name != sshd) or
|
||||||
|
proc.name=systemd-logind or proc.name=login)
|
||||||
|
|
||||||
|
- rule: legit_rule
|
||||||
|
desc: legit rule description
|
||||||
|
condition: evt.type=open and interactive
|
||||||
|
output: user=%user.name command=%proc.cmdline file=%fd.name
|
||||||
|
priority: INFO
|
||||||
|
|
||||||
|
- macro: interactive
|
||||||
|
condition: or proc.name = ssh
|
||||||
|
override:
|
||||||
|
condition: append
|
||||||
|
)END";
|
||||||
|
|
||||||
|
std::string rule_name = "legit_rule";
|
||||||
|
ASSERT_TRUE(load_rules(rules_content, "legit_rules.yaml")) << m_load_result_string;
|
||||||
|
|
||||||
|
auto rule_description = m_engine->describe_rule(&rule_name, {});
|
||||||
|
ASSERT_EQ(rule_description["rules"][0]["details"]["condition_compiled"].template get<std::string>(),
|
||||||
|
"(evt.type = open and (((proc.aname = sshd and proc.name != sshd) or proc.name = systemd-logind or proc.name = login) or proc.name = ssh))");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(engine_loader_test, rule_override_append)
|
||||||
|
{
|
||||||
|
std::string rules_content = R"END(
|
||||||
|
- rule: legit_rule
|
||||||
|
desc: legit rule description
|
||||||
|
condition: evt.type=open
|
||||||
|
output: user=%user.name command=%proc.cmdline file=%fd.name
|
||||||
|
priority: INFO
|
||||||
|
|
||||||
|
- rule: legit_rule
|
||||||
|
desc: with append
|
||||||
|
condition: and proc.name = cat
|
||||||
|
output: proc=%proc.name
|
||||||
|
override:
|
||||||
|
desc: append
|
||||||
|
condition: append
|
||||||
|
output: append
|
||||||
|
)END";
|
||||||
|
|
||||||
|
std::string rule_name = "legit_rule";
|
||||||
|
ASSERT_TRUE(load_rules(rules_content, "legit_rules.yaml")) << m_load_result_string;
|
||||||
|
|
||||||
|
auto rule_description = m_engine->describe_rule(&rule_name, {});
|
||||||
|
ASSERT_EQ(rule_description["rules"][0]["info"]["condition"].template get<std::string>(),
|
||||||
|
"evt.type=open and proc.name = cat");
|
||||||
|
|
||||||
|
ASSERT_EQ(rule_description["rules"][0]["info"]["output"].template get<std::string>(),
|
||||||
|
"user=%user.name command=%proc.cmdline file=%fd.name proc=%proc.name");
|
||||||
|
|
||||||
|
ASSERT_EQ(rule_description["rules"][0]["info"]["description"].template get<std::string>(),
|
||||||
|
"legit rule description with append");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST_F(engine_loader_test, rule_append)
|
||||||
|
{
|
||||||
|
std::string rules_content = R"END(
|
||||||
|
- rule: legit_rule
|
||||||
|
desc: legit rule description
|
||||||
|
condition: evt.type=open
|
||||||
|
output: user=%user.name command=%proc.cmdline file=%fd.name
|
||||||
|
priority: INFO
|
||||||
|
|
||||||
|
- rule: legit_rule
|
||||||
|
condition: and proc.name = cat
|
||||||
|
append: true
|
||||||
|
)END";
|
||||||
|
|
||||||
|
std::string rule_name = "legit_rule";
|
||||||
|
ASSERT_TRUE(load_rules(rules_content, "legit_rules.yaml")) << m_load_result_string;
|
||||||
|
|
||||||
|
auto rule_description = m_engine->describe_rule(&rule_name, {});
|
||||||
|
ASSERT_EQ(rule_description["rules"][0]["details"]["condition_compiled"].template get<std::string>(),
|
||||||
|
"(evt.type = open and proc.name = cat)");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST_F(engine_loader_test, rule_override_replace)
|
||||||
|
{
|
||||||
|
std::string rules_content = R"END(
|
||||||
|
- rule: legit_rule
|
||||||
|
desc: legit rule description
|
||||||
|
condition: evt.type=open
|
||||||
|
output: user=%user.name command=%proc.cmdline file=%fd.name
|
||||||
|
priority: INFO
|
||||||
|
|
||||||
|
- rule: legit_rule
|
||||||
|
desc: a replaced legit description
|
||||||
|
condition: evt.type = close
|
||||||
|
override:
|
||||||
|
desc: replace
|
||||||
|
condition: replace
|
||||||
|
)END";
|
||||||
|
|
||||||
|
std::string rule_name = "legit_rule";
|
||||||
|
ASSERT_TRUE(load_rules(rules_content, "legit_rules.yaml")) << m_load_result_string;
|
||||||
|
|
||||||
|
auto rule_description = m_engine->describe_rule(&rule_name, {});
|
||||||
|
ASSERT_EQ(rule_description["rules"][0]["info"]["condition"].template get<std::string>(),
|
||||||
|
"evt.type = close");
|
||||||
|
|
||||||
|
ASSERT_EQ(rule_description["rules"][0]["info"]["output"].template get<std::string>(),
|
||||||
|
"user=%user.name command=%proc.cmdline file=%fd.name");
|
||||||
|
|
||||||
|
ASSERT_EQ(rule_description["rules"][0]["info"]["description"].template get<std::string>(),
|
||||||
|
"a replaced legit description");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(engine_loader_test, rule_override_append_replace)
|
||||||
|
{
|
||||||
|
std::string rules_content = R"END(
|
||||||
|
- rule: legit_rule
|
||||||
|
desc: legit rule description
|
||||||
|
condition: evt.type = close
|
||||||
|
output: user=%user.name command=%proc.cmdline file=%fd.name
|
||||||
|
priority: INFO
|
||||||
|
|
||||||
|
- rule: legit_rule
|
||||||
|
desc: a replaced legit description
|
||||||
|
condition: and proc.name = cat
|
||||||
|
priority: WARNING
|
||||||
|
override:
|
||||||
|
desc: replace
|
||||||
|
condition: append
|
||||||
|
priority: replace
|
||||||
|
)END";
|
||||||
|
|
||||||
|
std::string rule_name = "legit_rule";
|
||||||
|
ASSERT_TRUE(load_rules(rules_content, "legit_rules.yaml")) << m_load_result_string;
|
||||||
|
|
||||||
|
auto rule_description = m_engine->describe_rule(&rule_name, {});
|
||||||
|
ASSERT_EQ(rule_description["rules"][0]["info"]["condition"].template get<std::string>(),
|
||||||
|
"evt.type = close and proc.name = cat");
|
||||||
|
|
||||||
|
ASSERT_EQ(rule_description["rules"][0]["info"]["output"].template get<std::string>(),
|
||||||
|
"user=%user.name command=%proc.cmdline file=%fd.name");
|
||||||
|
|
||||||
|
ASSERT_EQ(rule_description["rules"][0]["info"]["description"].template get<std::string>(),
|
||||||
|
"a replaced legit description");
|
||||||
|
|
||||||
|
ASSERT_EQ(rule_description["rules"][0]["info"]["priority"].template get<std::string>(),
|
||||||
|
"Warning");
|
||||||
|
}
|
@@ -41,7 +41,8 @@ static const std::string item_type_strings[] = {
|
|||||||
"condition expression",
|
"condition expression",
|
||||||
"rule output",
|
"rule output",
|
||||||
"rule output expression",
|
"rule output expression",
|
||||||
"rule priority"
|
"rule priority",
|
||||||
|
"overrides"
|
||||||
};
|
};
|
||||||
|
|
||||||
const std::string& rule_loader::context::item_type_as_string(enum item_type it)
|
const std::string& rule_loader::context::item_type_as_string(enum item_type it)
|
||||||
@@ -553,6 +554,11 @@ rule_loader::rule_info::rule_info(context &ctx)
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rule_loader::rule_update_info::rule_update_info(context &ctx)
|
||||||
|
: ctx(ctx), cond_ctx(ctx)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
rule_loader::rule_load_exception::rule_load_exception(falco::load_result::error_code ec, const std::string& msg, const context& ctx)
|
rule_loader::rule_load_exception::rule_load_exception(falco::load_result::error_code ec, const std::string& msg, const context& ctx)
|
||||||
: ec(ec), msg(msg), ctx(ctx)
|
: ec(ec), msg(msg), ctx(ctx)
|
||||||
{
|
{
|
||||||
|
@@ -19,6 +19,7 @@ limitations under the License.
|
|||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <optional>
|
||||||
#include <yaml-cpp/yaml.h>
|
#include <yaml-cpp/yaml.h>
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
#include "falco_source.h"
|
#include "falco_source.h"
|
||||||
@@ -56,7 +57,8 @@ namespace rule_loader
|
|||||||
CONDITION_EXPRESSION,
|
CONDITION_EXPRESSION,
|
||||||
RULE_OUTPUT,
|
RULE_OUTPUT,
|
||||||
RULE_OUTPUT_EXPRESSION,
|
RULE_OUTPUT_EXPRESSION,
|
||||||
RULE_PRIORITY
|
RULE_PRIORITY,
|
||||||
|
OVERRIDE
|
||||||
};
|
};
|
||||||
|
|
||||||
static const std::string& item_type_as_string(enum item_type it);
|
static const std::string& item_type_as_string(enum item_type it);
|
||||||
@@ -451,4 +453,38 @@ namespace rule_loader
|
|||||||
bool warn_evttypes;
|
bool warn_evttypes;
|
||||||
bool skip_if_unknown_filter;
|
bool skip_if_unknown_filter;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\brief Represents infos about a rule update (append or replace) request
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct rule_update_info
|
||||||
|
{
|
||||||
|
rule_update_info(context &ctx);
|
||||||
|
~rule_update_info() = default;
|
||||||
|
rule_update_info(rule_update_info&&) = default;
|
||||||
|
rule_update_info& operator = (rule_update_info&&) = default;
|
||||||
|
rule_update_info(const rule_update_info&) = default;
|
||||||
|
rule_update_info& operator = (const rule_update_info&) = default;
|
||||||
|
|
||||||
|
bool has_any_value()
|
||||||
|
{
|
||||||
|
return cond.has_value() || output.has_value() || desc.has_value() || tags.has_value() ||
|
||||||
|
exceptions.has_value() || priority.has_value() || enabled.has_value() ||
|
||||||
|
warn_evttypes.has_value() || skip_if_unknown_filter.has_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
context ctx;
|
||||||
|
context cond_ctx;
|
||||||
|
std::string name;
|
||||||
|
std::optional<std::string> cond;
|
||||||
|
std::optional<std::string> output;
|
||||||
|
std::optional<std::string> desc;
|
||||||
|
std::optional<std::set<std::string>> tags;
|
||||||
|
std::optional<std::vector<rule_exception_info>> exceptions;
|
||||||
|
std::optional<falco_common::priority_type> priority;
|
||||||
|
std::optional<bool> enabled;
|
||||||
|
std::optional<bool> warn_evttypes;
|
||||||
|
std::optional<bool> skip_if_unknown_filter;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
@@ -48,8 +48,14 @@ static inline void define_info(indexed_vector<T>& infos, T& info, uint32_t id)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T, typename U>
|
||||||
static inline void append_info(T* prev, T& info, uint32_t id)
|
static inline void append_info(T* prev, U& info, uint32_t id)
|
||||||
|
{
|
||||||
|
prev->visibility = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename U>
|
||||||
|
static inline void replace_info(T* prev, U& info, uint32_t id)
|
||||||
{
|
{
|
||||||
prev->visibility = id;
|
prev->visibility = id;
|
||||||
}
|
}
|
||||||
@@ -185,7 +191,7 @@ void rule_loader::collector::append(configuration& cfg, list_info& info)
|
|||||||
{
|
{
|
||||||
auto prev = m_list_infos.at(info.name);
|
auto prev = m_list_infos.at(info.name);
|
||||||
THROW(!prev,
|
THROW(!prev,
|
||||||
"List has 'append' key but no list by that name already exists",
|
"List has 'append' key or an append override but no list by that name already exists",
|
||||||
info.ctx);
|
info.ctx);
|
||||||
prev->items.insert(prev->items.end(), info.items.begin(), info.items.end());
|
prev->items.insert(prev->items.end(), info.items.begin(), info.items.end());
|
||||||
append_info(prev, info, m_cur_index++);
|
append_info(prev, info, m_cur_index++);
|
||||||
@@ -233,15 +239,15 @@ void rule_loader::collector::define(configuration& cfg, rule_info& info)
|
|||||||
define_info(m_rule_infos, info, m_cur_index++);
|
define_info(m_rule_infos, info, m_cur_index++);
|
||||||
}
|
}
|
||||||
|
|
||||||
void rule_loader::collector::append(configuration& cfg, rule_info& info)
|
void rule_loader::collector::append(configuration& cfg, rule_update_info& info)
|
||||||
{
|
{
|
||||||
auto prev = m_rule_infos.at(info.name);
|
auto prev = m_rule_infos.at(info.name);
|
||||||
|
|
||||||
THROW(!prev,
|
THROW(!prev,
|
||||||
"Rule has 'append' key but no rule by that name already exists",
|
"Rule has 'append' key or an append override but no rule by that name already exists",
|
||||||
info.ctx);
|
info.ctx);
|
||||||
THROW(info.cond.empty() && info.exceptions.empty(),
|
THROW(!info.has_any_value(),
|
||||||
"Appended rule must have exceptions or condition property",
|
"Appended rule must have at least one field that can be appended to",
|
||||||
info.ctx);
|
info.ctx);
|
||||||
|
|
||||||
// note: source can be nullptr in case we've collected a
|
// note: source can be nullptr in case we've collected a
|
||||||
@@ -256,13 +262,35 @@ void rule_loader::collector::append(configuration& cfg, rule_info& info)
|
|||||||
info.ctx);
|
info.ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!info.cond.empty())
|
if (info.cond.has_value() && !info.cond->empty())
|
||||||
{
|
{
|
||||||
prev->cond += " ";
|
prev->cond += " ";
|
||||||
prev->cond += info.cond;
|
prev->cond += *info.cond;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto &ex : info.exceptions)
|
if (info.output.has_value() && !info.output->empty())
|
||||||
|
{
|
||||||
|
prev->output += " ";
|
||||||
|
prev->output += *info.output;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.desc.has_value() && !info.desc->empty())
|
||||||
|
{
|
||||||
|
prev->desc += " ";
|
||||||
|
prev->desc += *info.desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.tags.has_value())
|
||||||
|
{
|
||||||
|
for (auto &tag: *info.tags)
|
||||||
|
{
|
||||||
|
prev->tags.insert(tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.exceptions.has_value())
|
||||||
|
{
|
||||||
|
for (auto &ex : *info.exceptions)
|
||||||
{
|
{
|
||||||
auto prev_ex = find_if(prev->exceptions.begin(), prev->exceptions.end(),
|
auto prev_ex = find_if(prev->exceptions.begin(), prev->exceptions.end(),
|
||||||
[&ex](const rule_loader::rule_exception_info& i)
|
[&ex](const rule_loader::rule_exception_info& i)
|
||||||
@@ -290,9 +318,82 @@ void rule_loader::collector::append(configuration& cfg, rule_info& info)
|
|||||||
prev_ex->values.end(), ex.values.begin(), ex.values.end());
|
prev_ex->values.end(), ex.values.begin(), ex.values.end());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
append_info(prev, info, m_cur_index++);
|
append_info(prev, info, m_cur_index++);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void rule_loader::collector::selective_replace(configuration& cfg, rule_update_info& info)
|
||||||
|
{
|
||||||
|
auto prev = m_rule_infos.at(info.name);
|
||||||
|
|
||||||
|
THROW(!prev,
|
||||||
|
"An replace to a rule was requested but no rule by that name already exists",
|
||||||
|
info.ctx);
|
||||||
|
THROW(!info.has_any_value(),
|
||||||
|
"The rule must have at least one field that can be replaced",
|
||||||
|
info.ctx);
|
||||||
|
|
||||||
|
// note: source can be nullptr in case we've collected a
|
||||||
|
// rule for which the source is unknown
|
||||||
|
falco_source* source = nullptr;
|
||||||
|
if (!prev->unknown_source)
|
||||||
|
{
|
||||||
|
// note: if the source is not unknown, this should not return nullptr
|
||||||
|
source = cfg.sources.at(prev->source);
|
||||||
|
THROW(!source,
|
||||||
|
std::string("Unknown source ") + prev->source,
|
||||||
|
info.ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.cond.has_value())
|
||||||
|
{
|
||||||
|
prev->cond = *info.cond;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.output.has_value())
|
||||||
|
{
|
||||||
|
prev->output = *info.output;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.desc.has_value())
|
||||||
|
{
|
||||||
|
prev->desc = *info.desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.tags.has_value())
|
||||||
|
{
|
||||||
|
prev->tags = *info.tags;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.exceptions.has_value())
|
||||||
|
{
|
||||||
|
prev->exceptions = *info.exceptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.priority.has_value())
|
||||||
|
{
|
||||||
|
prev->priority = *info.priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.enabled.has_value())
|
||||||
|
{
|
||||||
|
prev->enabled = *info.enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.warn_evttypes.has_value())
|
||||||
|
{
|
||||||
|
prev->warn_evttypes = *info.warn_evttypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.skip_if_unknown_filter.has_value())
|
||||||
|
{
|
||||||
|
prev->skip_if_unknown_filter = *info.skip_if_unknown_filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
replace_info(prev, info, m_cur_index++);
|
||||||
|
}
|
||||||
|
|
||||||
void rule_loader::collector::enable(configuration& cfg, rule_info& info)
|
void rule_loader::collector::enable(configuration& cfg, rule_info& info)
|
||||||
{
|
{
|
||||||
auto prev = m_rule_infos.at(info.name);
|
auto prev = m_rule_infos.at(info.name);
|
||||||
|
@@ -85,13 +85,18 @@ public:
|
|||||||
*/
|
*/
|
||||||
virtual void append(configuration& cfg, list_info& info);
|
virtual void append(configuration& cfg, list_info& info);
|
||||||
virtual void append(configuration& cfg, macro_info& info);
|
virtual void append(configuration& cfg, macro_info& info);
|
||||||
virtual void append(configuration& cfg, rule_info& info);
|
virtual void append(configuration& cfg, rule_update_info& info);
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\brief Updates the 'enabled' flag of an existing definition
|
\brief Updates the 'enabled' flag of an existing definition
|
||||||
*/
|
*/
|
||||||
virtual void enable(configuration& cfg, rule_info& info);
|
virtual void enable(configuration& cfg, rule_info& info);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\brief Selectively replaces some fields of an existing definition
|
||||||
|
*/
|
||||||
|
virtual void selective_replace(configuration& cfg, rule_update_info& info);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint32_t m_cur_index;
|
uint32_t m_cur_index;
|
||||||
indexed_vector<rule_info> m_rule_infos;
|
indexed_vector<rule_info> m_rule_infos;
|
||||||
|
@@ -17,6 +17,8 @@ limitations under the License.
|
|||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <set>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
#include "rule_loader_reader.h"
|
#include "rule_loader_reader.h"
|
||||||
#include "falco_engine_version.h"
|
#include "falco_engine_version.h"
|
||||||
@@ -45,6 +47,14 @@ static void decode_val_generic(const YAML::Node& item, const char *key, T& out,
|
|||||||
THROW(!YAML::convert<T>::decode(val, out), "Can't decode YAML scalar value", valctx);
|
THROW(!YAML::convert<T>::decode(val, out), "Can't decode YAML scalar value", valctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
static void decode_val_generic(const YAML::Node& item, const char *key, std::optional<T>& out, const rule_loader::context& ctx, bool optional)
|
||||||
|
{
|
||||||
|
T decoded;
|
||||||
|
decode_val_generic(item, key, decoded, ctx, optional);
|
||||||
|
out = decoded;
|
||||||
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
static void decode_val(const YAML::Node& item, const char *key, T& out, const rule_loader::context& ctx)
|
static void decode_val(const YAML::Node& item, const char *key, T& out, const rule_loader::context& ctx)
|
||||||
{
|
{
|
||||||
@@ -115,6 +125,73 @@ static void decode_tags(const YAML::Node& item, std::set<T>& out,
|
|||||||
decode_seq(item, "tags", inserter, ctx, optional);
|
decode_seq(item, "tags", inserter, ctx, optional);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
static void decode_tags(const YAML::Node& item, std::optional<std::set<T>>& out,
|
||||||
|
const rule_loader::context& ctx)
|
||||||
|
{
|
||||||
|
std::set<T> decoded;
|
||||||
|
decode_tags(item, decoded, ctx);
|
||||||
|
out = decoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void decode_overrides(const YAML::Node& item,
|
||||||
|
std::set<std::string>& overridable_append,
|
||||||
|
std::set<std::string>& overridable_replace,
|
||||||
|
std::set<std::string>& out_append,
|
||||||
|
std::set<std::string>& out_replace,
|
||||||
|
const rule_loader::context& ctx)
|
||||||
|
{
|
||||||
|
const YAML::Node& val = item["override"];
|
||||||
|
|
||||||
|
if(!val.IsDefined())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
rule_loader::context overridectx(item, rule_loader::context::OVERRIDE, "", ctx);
|
||||||
|
|
||||||
|
for(YAML::const_iterator it=val.begin();it!=val.end();++it)
|
||||||
|
{
|
||||||
|
std::string key = it->first.as<std::string>();
|
||||||
|
std::string operation = it->second.as<std::string>();
|
||||||
|
|
||||||
|
bool is_overridable_append = overridable_append.find(it->first.as<std::string>()) != overridable_append.end();
|
||||||
|
bool is_overridable_replace = overridable_replace.find(it->first.as<std::string>()) != overridable_replace.end();
|
||||||
|
|
||||||
|
if (operation == "append")
|
||||||
|
{
|
||||||
|
rule_loader::context keyctx(it->first, rule_loader::context::OVERRIDE, key, overridectx);
|
||||||
|
THROW(!is_overridable_append, std::string("Key '") + key + std::string("' cannot be appended to"), keyctx);
|
||||||
|
|
||||||
|
out_append.insert(key);
|
||||||
|
}
|
||||||
|
else if (operation == "replace")
|
||||||
|
{
|
||||||
|
rule_loader::context keyctx(it->first, rule_loader::context::OVERRIDE, key, overridectx);
|
||||||
|
THROW(!is_overridable_replace, std::string("Key '") + key + std::string("' cannot be replaced"), keyctx);
|
||||||
|
|
||||||
|
out_replace.insert(key);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rule_loader::context operationctx(it->second, rule_loader::context::VALUE_FOR, key, overridectx);
|
||||||
|
std::stringstream err_ss;
|
||||||
|
err_ss << "Invalid override operation for key '" << key << "': '" << operation << "'. "
|
||||||
|
<< "Allowed values are: ";
|
||||||
|
if (is_overridable_append)
|
||||||
|
{
|
||||||
|
err_ss << "append ";
|
||||||
|
}
|
||||||
|
if (is_overridable_replace)
|
||||||
|
{
|
||||||
|
err_ss << "replace ";
|
||||||
|
}
|
||||||
|
|
||||||
|
THROW(true, err_ss.str(), operationctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Don't call this directly, call decode_exception_{fields,comps,values} instead
|
// Don't call this directly, call decode_exception_{fields,comps,values} instead
|
||||||
static void decode_exception_info_entry(
|
static void decode_exception_info_entry(
|
||||||
const YAML::Node& item,
|
const YAML::Node& item,
|
||||||
@@ -187,7 +264,7 @@ static void decode_exception_values(
|
|||||||
|
|
||||||
static void read_rule_exceptions(
|
static void read_rule_exceptions(
|
||||||
const YAML::Node& item,
|
const YAML::Node& item,
|
||||||
rule_loader::rule_info& v,
|
std::vector<rule_loader::rule_exception_info>& exceptions,
|
||||||
const rule_loader::context& parent,
|
const rule_loader::context& parent,
|
||||||
bool append)
|
bool append)
|
||||||
{
|
{
|
||||||
@@ -239,10 +316,33 @@ static void read_rule_exceptions(
|
|||||||
v_ex.values.push_back(v_ex_val);
|
v_ex.values.push_back(v_ex_val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
v.exceptions.push_back(v_ex);
|
exceptions.push_back(v_ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void read_rule_exceptions(
|
||||||
|
const YAML::Node& item,
|
||||||
|
std::optional<std::vector<rule_loader::rule_exception_info>>& exceptions,
|
||||||
|
const rule_loader::context& parent,
|
||||||
|
bool append)
|
||||||
|
{
|
||||||
|
std::vector<rule_loader::rule_exception_info> decoded;
|
||||||
|
read_rule_exceptions(item, decoded, parent, append);
|
||||||
|
exceptions = decoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline static bool check_override_defined(const YAML::Node& item, const std::set<std::string>& overrides, const std::string& override_type, const std::string& key, const rule_loader::context& ctx)
|
||||||
|
{
|
||||||
|
if (overrides.find(key) == overrides.end())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
THROW(!item[key].IsDefined(), std::string("An ") + override_type + " override for '" + key + "' was specified but '" + key + "' is not defined", ctx);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static void read_item(
|
static void read_item(
|
||||||
rule_loader::configuration& cfg,
|
rule_loader::configuration& cfg,
|
||||||
rule_loader::collector& collector,
|
rule_loader::collector& collector,
|
||||||
@@ -338,6 +438,19 @@ static void read_item(
|
|||||||
|
|
||||||
decode_optional_val(item, "append", append, ctx);
|
decode_optional_val(item, "append", append, ctx);
|
||||||
|
|
||||||
|
std::set<std::string> override_append, override_replace;
|
||||||
|
std::set<std::string> overridable {"items"};
|
||||||
|
decode_overrides(item, overridable, overridable, override_append, override_replace, ctx);
|
||||||
|
|
||||||
|
if(append == true && !override_replace.empty())
|
||||||
|
{
|
||||||
|
THROW(true, "Cannot specify a replace override when 'append: true' is set", ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since a list only has items, if we have chosen to append them we can append the entire object
|
||||||
|
// otherwise we just want to redefine the list.
|
||||||
|
append |= override_append.find("items") != override_append.end();
|
||||||
|
|
||||||
if(append)
|
if(append)
|
||||||
{
|
{
|
||||||
collector.append(cfg, v);
|
collector.append(cfg, v);
|
||||||
@@ -366,6 +479,19 @@ static void read_item(
|
|||||||
|
|
||||||
decode_optional_val(item, "append", append, ctx);
|
decode_optional_val(item, "append", append, ctx);
|
||||||
|
|
||||||
|
std::set<std::string> override_append, override_replace;
|
||||||
|
std::set<std::string> overridable {"condition"};
|
||||||
|
decode_overrides(item, overridable, overridable, override_append, override_replace, ctx);
|
||||||
|
|
||||||
|
if(append == true && !override_replace.empty())
|
||||||
|
{
|
||||||
|
THROW(true, "Cannot specify a replace override when 'append: true' is set", ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since a macro only has a condition, if we have chosen to append to it we can append the entire object
|
||||||
|
// otherwise we just want to redefine the macro.
|
||||||
|
append |= override_append.find("condition") != override_append.end();
|
||||||
|
|
||||||
if(append)
|
if(append)
|
||||||
{
|
{
|
||||||
collector.append(cfg, v);
|
collector.append(cfg, v);
|
||||||
@@ -384,28 +510,142 @@ static void read_item(
|
|||||||
decode_val(item, "rule", name, tmp);
|
decode_val(item, "rule", name, tmp);
|
||||||
|
|
||||||
rule_loader::context ctx(item, rule_loader::context::RULE, name, parent);
|
rule_loader::context ctx(item, rule_loader::context::RULE, name, parent);
|
||||||
rule_loader::rule_info v(ctx);
|
|
||||||
|
bool has_append_flag = false;
|
||||||
|
decode_optional_val(item, "append", has_append_flag, ctx);
|
||||||
|
|
||||||
|
std::set<std::string> override_append, override_replace;
|
||||||
|
std::set<std::string> overridable_append {"condition", "output", "desc", "tags", "exceptions"};
|
||||||
|
std::set<std::string> overridable_replace {
|
||||||
|
"condition", "output", "desc", "priority", "tags", "exceptions", "enabled", "warn_evttypes", "skip-if-unknown-filter"};
|
||||||
|
decode_overrides(item, overridable_append, overridable_replace, override_append, override_replace, ctx);
|
||||||
|
bool has_overrides_append = !override_append.empty();
|
||||||
|
bool has_overrides_replace = !override_replace.empty();
|
||||||
|
bool has_overrides = has_overrides_append || has_overrides_replace;
|
||||||
|
|
||||||
|
THROW((has_append_flag && has_overrides), "Keys 'override' and 'append' cannot be used together.", ctx);
|
||||||
|
|
||||||
|
if(has_overrides)
|
||||||
|
{
|
||||||
|
if (has_overrides_append)
|
||||||
|
{
|
||||||
|
rule_loader::rule_update_info v(ctx);
|
||||||
|
v.name = name;
|
||||||
|
if (check_override_defined(item, override_append, "append", "condition", ctx))
|
||||||
|
{
|
||||||
|
decode_val(item, "condition", v.cond, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (check_override_defined(item, override_append, "append", "exceptions", ctx))
|
||||||
|
{
|
||||||
|
read_rule_exceptions(item, v.exceptions, ctx, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (check_override_defined(item, override_append, "append", "output", ctx))
|
||||||
|
{
|
||||||
|
decode_val(item, "output", v.output, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (check_override_defined(item, override_append, "append", "desc", ctx))
|
||||||
|
{
|
||||||
|
decode_val(item, "desc", v.desc, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (check_override_defined(item, override_append, "append", "tags", ctx))
|
||||||
|
{
|
||||||
|
decode_tags(item, v.tags, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
collector.append(cfg, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (has_overrides_replace)
|
||||||
|
{
|
||||||
|
rule_loader::rule_update_info v(ctx);
|
||||||
|
v.name = name;
|
||||||
|
if (check_override_defined(item, override_replace, "replace", "condition", ctx))
|
||||||
|
{
|
||||||
|
decode_val(item, "condition", v.cond, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (check_override_defined(item, override_replace, "replace", "exceptions", ctx))
|
||||||
|
{
|
||||||
|
read_rule_exceptions(item, v.exceptions, ctx, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (check_override_defined(item, override_replace, "replace", "output", ctx))
|
||||||
|
{
|
||||||
|
decode_val(item, "output", v.output, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (check_override_defined(item, override_replace, "replace", "desc", ctx))
|
||||||
|
{
|
||||||
|
decode_val(item, "desc", v.desc, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (check_override_defined(item, override_replace, "replace", "tags", ctx))
|
||||||
|
{
|
||||||
|
decode_tags(item, v.tags, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (check_override_defined(item, override_replace, "replace", "priority", ctx))
|
||||||
|
{
|
||||||
|
std::string priority;
|
||||||
|
decode_val(item, "priority", priority, ctx);
|
||||||
|
rule_loader::context prictx(item["priority"], rule_loader::context::RULE_PRIORITY, "", ctx);
|
||||||
|
falco_common::priority_type parsed_priority;
|
||||||
|
THROW(!falco_common::parse_priority(priority, parsed_priority), "Invalid priority", prictx);
|
||||||
|
v.priority = parsed_priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (check_override_defined(item, override_replace, "replace", "enabled", ctx))
|
||||||
|
{
|
||||||
|
decode_val(item, "enabled", v.enabled, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (check_override_defined(item, override_replace, "replace", "warn_evttypes", ctx))
|
||||||
|
{
|
||||||
|
decode_val(item, "warn_evttypes", v.warn_evttypes, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (check_override_defined(item, override_replace, "replace", "skip-if-unknown-filter", ctx))
|
||||||
|
{
|
||||||
|
decode_val(item, "skip-if-unknown-filter", v.skip_if_unknown_filter, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
collector.selective_replace(cfg, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(has_append_flag)
|
||||||
|
{
|
||||||
|
rule_loader::rule_update_info v(ctx);
|
||||||
v.name = name;
|
v.name = name;
|
||||||
|
|
||||||
bool append = false;
|
|
||||||
v.enabled = true;
|
|
||||||
v.warn_evttypes = true;
|
|
||||||
v.skip_if_unknown_filter = false;
|
|
||||||
|
|
||||||
decode_optional_val(item, "append", append, ctx);
|
|
||||||
|
|
||||||
if(append)
|
|
||||||
{
|
|
||||||
decode_optional_val(item, "condition", v.cond, ctx);
|
|
||||||
if(item["condition"].IsDefined())
|
if(item["condition"].IsDefined())
|
||||||
{
|
{
|
||||||
v.cond_ctx = rule_loader::context(item["condition"], rule_loader::context::RULE_CONDITION, "", ctx);
|
v.cond_ctx = rule_loader::context(item["condition"], rule_loader::context::RULE_CONDITION, "", ctx);
|
||||||
|
decode_val(item, "condition", v.cond, ctx);
|
||||||
}
|
}
|
||||||
read_rule_exceptions(item, v, ctx, append);
|
|
||||||
|
if(item["exceptions"].IsDefined())
|
||||||
|
{
|
||||||
|
read_rule_exceptions(item, v.exceptions, ctx, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
THROW((!v.cond.has_value() && !v.exceptions.has_value()),
|
||||||
|
"Appended rule must have exceptions or condition property",
|
||||||
|
v.ctx);
|
||||||
|
|
||||||
collector.append(cfg, v);
|
collector.append(cfg, v);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
rule_loader::rule_info v(ctx);
|
||||||
|
v.name = name;
|
||||||
|
v.enabled = true;
|
||||||
|
v.warn_evttypes = true;
|
||||||
|
v.skip_if_unknown_filter = false;
|
||||||
|
|
||||||
// If the rule does *not* have any of
|
// If the rule does *not* have any of
|
||||||
// condition/output/desc/priority, it *must*
|
// condition/output/desc/priority, it *must*
|
||||||
// have an enabled property. Use the enabled
|
// have an enabled property. Use the enabled
|
||||||
@@ -443,7 +683,7 @@ static void read_item(
|
|||||||
decode_optional_val(item, "warn_evttypes", v.warn_evttypes, ctx);
|
decode_optional_val(item, "warn_evttypes", v.warn_evttypes, ctx);
|
||||||
decode_optional_val(item, "skip-if-unknown-filter", v.skip_if_unknown_filter, ctx);
|
decode_optional_val(item, "skip-if-unknown-filter", v.skip_if_unknown_filter, ctx);
|
||||||
decode_tags(item, v.tags, ctx);
|
decode_tags(item, v.tags, ctx);
|
||||||
read_rule_exceptions(item, v, ctx, append);
|
read_rule_exceptions(item, v.exceptions, ctx, has_append_flag);
|
||||||
collector.define(cfg, v);
|
collector.define(cfg, v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user