Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new(engine): add selective rule overrides #2981

Merged
merged 7 commits into from
Dec 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
350 changes: 350 additions & 0 deletions unit_tests/engine/test_rule_loader.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,350 @@
#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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very neat setup @LucaGuerra much more elegant ❤️ than what I had tried in the past 🤦‍♀️

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed we actually do something similar in at least three different locations in the test suite (including this one). This is out of scope for this PR but we need to consolidate them and have one fixture to allow loading engine + rulesets.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Big +1, it would be super useful to do this cleanup!

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);
m_load_result_json = m_load_result->as_json(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;
nlohmann::json m_load_result_json;
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)
incertum marked this conversation as resolved.
Show resolved Hide resolved
{
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");
}

TEST_F(engine_loader_test, rule_incorrect_override_type)
{
std::string rules_content = R"END(
- rule: failing_rule
desc: legit rule description
condition: evt.type = close
output: user=%user.name command=%proc.cmdline file=%fd.name
priority: INFO

- rule: failing_rule
desc: an appended incorrect field
condition: and proc.name = cat
priority: WARNING
override:
desc: replace
condition: append
priority: append
)END";

std::string rule_name = "failing_rule";

ASSERT_FALSE(load_rules(rules_content, "rules.yaml"));
ASSERT_EQ(m_load_result_json["errors"][0]["message"], "Key 'priority' cannot be appended to, use 'replace' instead");
ASSERT_TRUE(std::string(m_load_result_json["errors"][0]["context"]["snippet"]).find("priority: append") != std::string::npos);
}

TEST_F(engine_loader_test, rule_incorrect_append_override)
{
std::string rules_content = R"END(
- rule: failing_rule
desc: legit rule description
condition: evt.type = close
output: user=%user.name command=%proc.cmdline file=%fd.name
priority: INFO

- rule: failing_rule
desc: an appended incorrect field
condition: and proc.name = cat
append: true
override:
desc: replace
condition: append
)END";

std::string rule_name = "failing_rule";

ASSERT_FALSE(load_rules(rules_content, "rules.yaml"));
ASSERT_TRUE(std::string(m_load_result_json["errors"][0]["message"]).find("'override' and 'append: true' cannot be used together") != std::string::npos);
}

TEST_F(engine_loader_test, rule_override_without_rule)
{
std::string rules_content = R"END(
- rule: failing_rule
desc: an appended field
condition: and proc.name = cat
override:
desc: replace
condition: append
)END";

std::string rule_name = "failing_rule";

ASSERT_FALSE(load_rules(rules_content, "rules.yaml"));
ASSERT_TRUE(std::string(m_load_result_json["errors"][0]["message"]).find("no rule by that name already exists") != std::string::npos);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit] you are overriding a rule, but no rule by that name already exists or similar

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an "error message contains" not equals :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:)

}

TEST_F(engine_loader_test, rule_override_without_field)
{
std::string rules_content = R"END(
- rule: failing_rule
desc: legit rule description
condition: evt.type = close
output: user=%user.name command=%proc.cmdline file=%fd.name
priority: INFO

- rule: failing_rule
desc: an appended incorrect field
override:
desc: replace
condition: append
)END";

std::string rule_name = "failing_rule";

ASSERT_FALSE(load_rules(rules_content, "rules.yaml"));
ASSERT_EQ(m_load_result_json["errors"][0]["message"], "An append override for 'condition' was specified but 'condition' is not defined");
}

TEST_F(engine_loader_test, rule_override_extra_field)
{
std::string rules_content = R"END(
- rule: failing_rule
desc: legit rule description
condition: evt.type = close
output: user=%user.name command=%proc.cmdline file=%fd.name
priority: INFO

- rule: failing_rule
desc: an appended incorrect field
condition: and proc.name = cat
priority: WARNING
override:
desc: replace
condition: append
)END";

std::string rule_name = "failing_rule";

ASSERT_FALSE(load_rules(rules_content, "rules.yaml"));
ASSERT_TRUE(std::string(m_load_result_json["errors"][0]["message"]).find("Unexpected key 'priority'") != std::string::npos);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit] same can we make the error message more expressive?

}
2 changes: 1 addition & 1 deletion userspace/engine/falco_engine_version.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ limitations under the License.

// The version of this Falco engine
#define FALCO_ENGINE_VERSION_MAJOR 0
#define FALCO_ENGINE_VERSION_MINOR 30
#define FALCO_ENGINE_VERSION_MINOR 31
#define FALCO_ENGINE_VERSION_PATCH 0

#define FALCO_ENGINE_VERSION \
Expand Down
8 changes: 7 additions & 1 deletion userspace/engine/rule_loader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ static const std::string item_type_strings[] = {
"condition expression",
"rule output",
"rule output expression",
"rule priority"
"rule priority",
"overrides"
};

const std::string& rule_loader::context::item_type_as_string(enum item_type it)
Expand Down Expand Up @@ -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)
: ec(ec), msg(msg), ctx(ctx)
{
Expand Down
Loading
Loading