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(falco): add rule selection configuration in falco.yaml #3178

Merged
merged 4 commits into from May 14, 2024
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
33 changes: 33 additions & 0 deletions falco.yaml
Expand Up @@ -177,6 +177,39 @@ rules_files:
- /etc/falco/falco_rules.local.yaml
- /etc/falco/rules.d

# [Incubating] `rules`
#
# --- [Description]
#
# Falco rules can be enabled or disabled by name (with wildcards *) and/or by tag.
#
# This configuration is applied after all rules files have been loaded, including
# their overrides, and will take precedence over the enabled/disabled configuration
# specified or overridden in the rules files.
#
# The ordering matters and selections are evaluated in order. For instance, if you
# need to only enable a rule you would first disable all of them and then only
# enable what you need, regardless of the enabled status in the files.
#
# --- [Examples]
#
# Only enable two rules:
#
# rules:
# - disable:
# rule: "*"
# - enable:
# rule: Netcat Remote Code Execution in Container
# - enable:
# rule: Delete or rename shell history
#
# Disable all rules with a specific tag:
#
# rules:
# - disable:
# tag: network
#

################
# Falco engine #
################
Expand Down
1 change: 1 addition & 0 deletions unit_tests/CMakeLists.txt
Expand Up @@ -44,6 +44,7 @@ add_executable(falco_unit_tests
engine/test_rule_loader.cpp
engine/test_rulesets.cpp
falco/test_configuration.cpp
falco/test_configuration_rule_selection.cpp
falco/app/actions/test_select_event_sources.cpp
falco/app/actions/test_load_config.cpp
)
Expand Down
3 changes: 1 addition & 2 deletions unit_tests/engine/test_alt_rule_loader.cpp
Expand Up @@ -310,9 +310,8 @@ TEST(engine_loader_alt_loader, pass_compile_output_to_ruleset)
// Enable all rules for a ruleset id. Because the compile
// output contained one rule with priority >= INFO, that rule
// should be enabled.
bool match_exact = true;
uint16_t ruleset_id = 0;
ruleset->enable("", match_exact, ruleset_id);
ruleset->enable("", filter_ruleset::match_type::substring, ruleset_id);

EXPECT_EQ(ruleset->enabled_count(ruleset_id), 1);
}
Expand Down
68 changes: 68 additions & 0 deletions unit_tests/engine/test_enable_rule.cpp
Expand Up @@ -44,6 +44,36 @@ static std::string single_rule = R"END(
tags: [exec process]
)END";

static std::string multi_rule = R"END(
- rule: first actual rule
desc: A test rule
condition: evt.type=execve
output: A test rule matched (evt.type=%evt.type)
priority: INFO
source: syscall
tags: [process]

- rule: second disabled rule
desc: A disabled rule
condition: evt.type=execve
output: A disabled 2 rule matched (evt.type=%evt.type)
priority: INFO
source: syscall
enabled: false
tags: [exec process]

- rule: third disabled rule
desc: A disabled rule
condition: evt.type=execve
output: A disabled 3 rule matched (evt.type=%evt.type)
priority: INFO
source: syscall
enabled: false
tags: [exec]
)END";



// This must be kept in line with the (private) falco_engine::s_default_ruleset
static const std::string default_ruleset = "falco-default-ruleset";

Expand Down Expand Up @@ -216,3 +246,41 @@ TEST_F(test_falco_engine, enable_rule_name_exact)
EXPECT_EQ(0, m_engine->num_rules_for_ruleset(ruleset_3));
EXPECT_EQ(2, m_engine->num_rules_for_ruleset(ruleset_4));
}

TEST_F(test_falco_engine, enable_rule_name_wildcard)
{
load_rules(multi_rule, "multi_rule.yaml");

EXPECT_EQ(1, m_engine->num_rules_for_ruleset(default_ruleset));
EXPECT_EQ(0, m_engine->num_rules_for_ruleset(ruleset_1));
EXPECT_EQ(0, m_engine->num_rules_for_ruleset(ruleset_2));
EXPECT_EQ(0, m_engine->num_rules_for_ruleset(ruleset_3));
EXPECT_EQ(0, m_engine->num_rules_for_ruleset(ruleset_4));

// As long as there are no *, exact matches work
m_engine->enable_rule_wildcard("first actual rule", true, ruleset_1);
EXPECT_EQ(1, m_engine->num_rules_for_ruleset(ruleset_1));
EXPECT_EQ(0, m_engine->num_rules_for_ruleset(ruleset_2));
EXPECT_EQ(0, m_engine->num_rules_for_ruleset(ruleset_3));
EXPECT_EQ(0, m_engine->num_rules_for_ruleset(ruleset_4));

m_engine->enable_rule_wildcard("*rule", true, ruleset_2);
EXPECT_EQ(1, m_engine->num_rules_for_ruleset(ruleset_1));
EXPECT_EQ(3, m_engine->num_rules_for_ruleset(ruleset_2));
EXPECT_EQ(0, m_engine->num_rules_for_ruleset(ruleset_3));
EXPECT_EQ(0, m_engine->num_rules_for_ruleset(ruleset_4));

// This should enable the second rule
m_engine->enable_rule_wildcard("*second*r*", true, ruleset_3);
EXPECT_EQ(1, m_engine->num_rules_for_ruleset(ruleset_1));
EXPECT_EQ(3, m_engine->num_rules_for_ruleset(ruleset_2));
EXPECT_EQ(1, m_engine->num_rules_for_ruleset(ruleset_3));
EXPECT_EQ(0, m_engine->num_rules_for_ruleset(ruleset_4));

m_engine->enable_rule_wildcard("*", true, ruleset_4);
EXPECT_EQ(1, m_engine->num_rules_for_ruleset(ruleset_1));
EXPECT_EQ(3, m_engine->num_rules_for_ruleset(ruleset_2));
EXPECT_EQ(1, m_engine->num_rules_for_ruleset(ruleset_3));
EXPECT_EQ(3, m_engine->num_rules_for_ruleset(ruleset_4));
}

21 changes: 21 additions & 0 deletions unit_tests/engine/test_falco_utils.cpp
Expand Up @@ -72,3 +72,24 @@ TEST(FalcoUtils, parse_prometheus_interval)
*/
ASSERT_EQ(falco::utils::parse_prometheus_interval("200"), 0UL);
}

TEST(FalcoUtils, matches_wildcard)
{
ASSERT_TRUE(falco::utils::matches_wildcard("*", "anything"));
ASSERT_TRUE(falco::utils::matches_wildcard("**", "anything"));
ASSERT_TRUE(falco::utils::matches_wildcard("*", ""));
ASSERT_TRUE(falco::utils::matches_wildcard("no star", "no star"));
ASSERT_TRUE(falco::utils::matches_wildcard("", ""));
ASSERT_TRUE(falco::utils::matches_wildcard("hello*world", "hello new world"));
ASSERT_TRUE(falco::utils::matches_wildcard("hello*world*", "hello new world yes"));
ASSERT_TRUE(falco::utils::matches_wildcard("*hello*world", "come on hello this world"));
ASSERT_TRUE(falco::utils::matches_wildcard("*hello*****world", "come on hello this world"));

ASSERT_FALSE(falco::utils::matches_wildcard("no star", ""));
ASSERT_FALSE(falco::utils::matches_wildcard("", "no star"));
ASSERT_FALSE(falco::utils::matches_wildcard("star", "no star"));
ASSERT_FALSE(falco::utils::matches_wildcard("hello*world", "hello new thing"));
ASSERT_FALSE(falco::utils::matches_wildcard("hello*world", "hello new world yes"));
ASSERT_FALSE(falco::utils::matches_wildcard("*hello*world", "come on hello this world yes"));
ASSERT_FALSE(falco::utils::matches_wildcard("*hello*world*", "come on hello this yes"));
}
38 changes: 31 additions & 7 deletions unit_tests/engine/test_rulesets.cpp
Expand Up @@ -74,46 +74,70 @@ TEST(Ruleset, enable_disable_rules_using_names)
r->add(rule_C, filter, ast);

/* Enable `rule_A` for RULESET_0 */
r->enable(rule_A.name, true, RULESET_0);
r->enable(rule_A.name, filter_ruleset::match_type::exact, RULESET_0);
ASSERT_EQ(r->enabled_count(RULESET_0), 1);
ASSERT_EQ(r->enabled_count(RULESET_1), 0);
ASSERT_EQ(r->enabled_count(RULESET_2), 0);

/* Disable `rule_A` for RULESET_1, this should have no effect */
r->disable(rule_A.name, true, RULESET_1);
r->disable(rule_A.name, filter_ruleset::match_type::exact, RULESET_1);
ASSERT_EQ(r->enabled_count(RULESET_0), 1);
ASSERT_EQ(r->enabled_count(RULESET_1), 0);
ASSERT_EQ(r->enabled_count(RULESET_2), 0);

/* Enable a not existing rule for RULESET_2, this should have no effect */
r->disable("<NA>", true, RULESET_2);
r->disable("<NA>", filter_ruleset::match_type::exact, RULESET_2);
ASSERT_EQ(r->enabled_count(RULESET_0), 1);
ASSERT_EQ(r->enabled_count(RULESET_1), 0);
ASSERT_EQ(r->enabled_count(RULESET_2), 0);

/* Enable all rules for RULESET_0 */
r->enable("rule_", false, RULESET_0);
r->enable("rule_", filter_ruleset::match_type::substring, RULESET_0);
ASSERT_EQ(r->enabled_count(RULESET_0), 3);
ASSERT_EQ(r->enabled_count(RULESET_1), 0);
ASSERT_EQ(r->enabled_count(RULESET_2), 0);

/* Try to disable all rules with exact match for RULESET_0, this should have no effect */
r->disable("rule_", true, RULESET_0);
r->disable("rule_", filter_ruleset::match_type::exact, RULESET_0);
ASSERT_EQ(r->enabled_count(RULESET_0), 3);
ASSERT_EQ(r->enabled_count(RULESET_1), 0);
ASSERT_EQ(r->enabled_count(RULESET_2), 0);

/* Disable all rules for RULESET_0 */
r->disable("rule_", false, RULESET_0);
r->disable("rule_", filter_ruleset::match_type::substring, RULESET_0);
ASSERT_EQ(r->enabled_count(RULESET_0), 0);
ASSERT_EQ(r->enabled_count(RULESET_1), 0);
ASSERT_EQ(r->enabled_count(RULESET_2), 0);

/* Enable rule_C for RULESET_2 without exact_match */
r->enable("_C", false, RULESET_2);
r->enable("_C", filter_ruleset::match_type::substring, RULESET_2);
ASSERT_EQ(r->enabled_count(RULESET_0), 0);
ASSERT_EQ(r->enabled_count(RULESET_1), 0);
ASSERT_EQ(r->enabled_count(RULESET_2), 1);

/* Disable rule_C for RULESET_2 without exact_match */
r->disable("_C", filter_ruleset::match_type::substring, RULESET_2);
ASSERT_EQ(r->enabled_count(RULESET_0), 0);
ASSERT_EQ(r->enabled_count(RULESET_1), 0);
ASSERT_EQ(r->enabled_count(RULESET_2), 0);

/* Enable all rules for RULESET_0 with wildcard */
r->enable("*", filter_ruleset::match_type::wildcard, RULESET_0);
ASSERT_EQ(r->enabled_count(RULESET_0), 3);
ASSERT_EQ(r->enabled_count(RULESET_1), 0);
ASSERT_EQ(r->enabled_count(RULESET_2), 0);

/* Disable rule C for RULESET_0 with wildcard */
r->disable("*C*", filter_ruleset::match_type::wildcard, RULESET_0);
ASSERT_EQ(r->enabled_count(RULESET_0), 2);
ASSERT_EQ(r->enabled_count(RULESET_1), 0);
ASSERT_EQ(r->enabled_count(RULESET_2), 0);

/* Disable all rules for RULESET_0 with wildcard */
r->disable("*_*", filter_ruleset::match_type::wildcard, RULESET_0);
ASSERT_EQ(r->enabled_count(RULESET_0), 0);
ASSERT_EQ(r->enabled_count(RULESET_1), 0);
ASSERT_EQ(r->enabled_count(RULESET_2), 0);
}

TEST(Ruleset, enable_disable_rules_using_tags)
Expand Down
24 changes: 12 additions & 12 deletions unit_tests/falco/test_configuration.cpp
Expand Up @@ -138,7 +138,7 @@ TEST(Configuration, configuration_config_files_secondary_fail)
std::vector<std::string> cmdline_config_options;
std::vector<std::string> loaded_conf_files;
falco_configuration falco_config;
ASSERT_ANY_THROW(falco_config.init("main.yaml", loaded_conf_files, cmdline_config_options));
ASSERT_ANY_THROW(falco_config.init_from_file("main.yaml", loaded_conf_files, cmdline_config_options));

std::filesystem::remove("main.yaml");
std::filesystem::remove("conf_2.yaml");
Expand Down Expand Up @@ -187,7 +187,7 @@ TEST(Configuration, configuration_config_files_ok)
std::vector<std::string> cmdline_config_options;
std::vector<std::string> loaded_conf_files;
falco_configuration falco_config;
ASSERT_NO_THROW(falco_config.init("main.yaml", loaded_conf_files, cmdline_config_options));
ASSERT_NO_THROW(falco_config.init_from_file("main.yaml", loaded_conf_files, cmdline_config_options));

// main + conf_2 + conf_3
ASSERT_EQ(loaded_conf_files.size(), 3);
Expand Down Expand Up @@ -260,7 +260,7 @@ TEST(Configuration, configuration_config_files_relative_main)
std::vector<std::string> cmdline_config_options;
std::vector<std::string> loaded_conf_files;
falco_configuration falco_config;
ASSERT_NO_THROW(falco_config.init(temp_main.string(), loaded_conf_files, cmdline_config_options));
ASSERT_NO_THROW(falco_config.init_from_file(temp_main.string(), loaded_conf_files, cmdline_config_options));

// main + conf_2 + conf_3
ASSERT_EQ(loaded_conf_files.size(), 3);
Expand Down Expand Up @@ -317,7 +317,7 @@ TEST(Configuration, configuration_config_files_override)
std::vector<std::string> cmdline_config_options;
std::vector<std::string> loaded_conf_files;
falco_configuration falco_config;
ASSERT_NO_THROW(falco_config.init("main.yaml", loaded_conf_files, cmdline_config_options));
ASSERT_NO_THROW(falco_config.init_from_file("main.yaml", loaded_conf_files, cmdline_config_options));

// main + conf_2 + conf_3
ASSERT_EQ(loaded_conf_files.size(), 3);
Expand Down Expand Up @@ -355,7 +355,7 @@ TEST(Configuration, configuration_config_files_unexistent)
std::vector<std::string> cmdline_config_options;
std::vector<std::string> loaded_conf_files;
falco_configuration falco_config;
ASSERT_NO_THROW(falco_config.init("main.yaml", loaded_conf_files, cmdline_config_options));
ASSERT_NO_THROW(falco_config.init_from_file("main.yaml", loaded_conf_files, cmdline_config_options));

// main
ASSERT_EQ(loaded_conf_files.size(), 1);
Expand Down Expand Up @@ -393,7 +393,7 @@ TEST(Configuration, configuration_config_files_scalar_configs_files)
std::vector<std::string> cmdline_config_options;
std::vector<std::string> loaded_conf_files;
falco_configuration falco_config;
ASSERT_NO_THROW(falco_config.init("main.yaml", loaded_conf_files, cmdline_config_options));
ASSERT_NO_THROW(falco_config.init_from_file("main.yaml", loaded_conf_files, cmdline_config_options));

// main + conf_2
ASSERT_EQ(loaded_conf_files.size(), 2);
Expand Down Expand Up @@ -430,7 +430,7 @@ TEST(Configuration, configuration_config_files_empty_configs_files)
std::vector<std::string> cmdline_config_options;
std::vector<std::string> loaded_conf_files;
falco_configuration falco_config;
ASSERT_NO_THROW(falco_config.init("main.yaml", loaded_conf_files, cmdline_config_options));
ASSERT_NO_THROW(falco_config.init_from_file("main.yaml", loaded_conf_files, cmdline_config_options));

// main
ASSERT_EQ(loaded_conf_files.size(), 1);
Expand Down Expand Up @@ -462,7 +462,7 @@ TEST(Configuration, configuration_config_files_self)
std::vector<std::string> cmdline_config_options;
std::vector<std::string> loaded_conf_files;
falco_configuration falco_config;
ASSERT_ANY_THROW(falco_config.init("main.yaml", loaded_conf_files, cmdline_config_options));
ASSERT_ANY_THROW(falco_config.init_from_file("main.yaml", loaded_conf_files, cmdline_config_options));

std::filesystem::remove("main.yaml");
}
Expand Down Expand Up @@ -516,7 +516,7 @@ TEST(Configuration, configuration_config_files_directory)
std::vector<std::string> cmdline_config_options;
std::vector<std::string> loaded_conf_files;
falco_configuration falco_config;
ASSERT_NO_THROW(falco_config.init("main.yaml", loaded_conf_files, cmdline_config_options));
ASSERT_NO_THROW(falco_config.init_from_file("main.yaml", loaded_conf_files, cmdline_config_options));

// main + conf_2 + conf_3.
// test/foo is not parsed.
Expand Down Expand Up @@ -567,7 +567,7 @@ TEST(Configuration, configuration_config_files_cmdline)

std::vector<std::string> loaded_conf_files;
falco_configuration falco_config;
ASSERT_NO_THROW(falco_config.init("main.yaml", loaded_conf_files, cmdline_config_options));
ASSERT_NO_THROW(falco_config.init_from_file("main.yaml", loaded_conf_files, cmdline_config_options));

// main + conf_2
ASSERT_EQ(loaded_conf_files.size(), 2);
Expand Down Expand Up @@ -799,7 +799,7 @@ TEST(Configuration, configuration_webserver_ip)
std::vector<std::string> cmdline_config_options;
cmdline_config_options.push_back(option);

EXPECT_NO_THROW(falco_config.init(cmdline_config_options));
EXPECT_NO_THROW(falco_config.init_from_content("", cmdline_config_options));

ASSERT_EQ(falco_config.m_webserver_config.m_listen_address, address);
}
Expand Down Expand Up @@ -836,6 +836,6 @@ TEST(Configuration, configuration_webserver_ip)
std::vector<std::string> cmdline_config_options;
cmdline_config_options.push_back(option);

EXPECT_ANY_THROW(falco_config.init(cmdline_config_options));
EXPECT_ANY_THROW(falco_config.init_from_content("", cmdline_config_options));
}
}