diff --git a/unit_tests/CMakeLists.txt b/unit_tests/CMakeLists.txt index 30517d0d..eb380cda 100644 --- a/unit_tests/CMakeLists.txt +++ b/unit_tests/CMakeLists.txt @@ -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 ) diff --git a/unit_tests/engine/test_alt_rule_loader.cpp b/unit_tests/engine/test_alt_rule_loader.cpp index f71a5f17..38f3d725 100644 --- a/unit_tests/engine/test_alt_rule_loader.cpp +++ b/unit_tests/engine/test_alt_rule_loader.cpp @@ -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); } diff --git a/unit_tests/engine/test_enable_rule.cpp b/unit_tests/engine/test_enable_rule.cpp index 49a133b7..d28f5310 100644 --- a/unit_tests/engine/test_enable_rule.cpp +++ b/unit_tests/engine/test_enable_rule.cpp @@ -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"; @@ -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)); +} + diff --git a/unit_tests/engine/test_falco_utils.cpp b/unit_tests/engine/test_falco_utils.cpp index e84cc661..c6c5ef5d 100644 --- a/unit_tests/engine/test_falco_utils.cpp +++ b/unit_tests/engine/test_falco_utils.cpp @@ -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")); +} diff --git a/unit_tests/engine/test_rulesets.cpp b/unit_tests/engine/test_rulesets.cpp index a0639632..9b5925d6 100644 --- a/unit_tests/engine/test_rulesets.cpp +++ b/unit_tests/engine/test_rulesets.cpp @@ -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("", true, RULESET_2); + r->disable("", 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) diff --git a/unit_tests/falco/test_configuration_rule_selection.cpp b/unit_tests/falco/test_configuration_rule_selection.cpp new file mode 100644 index 00000000..467d8ebd --- /dev/null +++ b/unit_tests/falco/test_configuration_rule_selection.cpp @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: Apache-2.0 +/* +Copyright (C) 2024 The Falco Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include +#include + +TEST(ConfigurationRuleSelection, parse_yaml) +{ + falco_configuration falco_config; + EXPECT_NO_THROW(falco_config.init_from_content(R"( +rules: + - enable: + rule: 'Terminal Shell in Container' + + - disable: + tag: experimental + + - enable: + rule: 'hello*' + )", {})); + + ASSERT_EQ(falco_config.m_rules_selection.size(), 3); + + ASSERT_EQ(falco_config.m_rules_selection[0].m_op, falco_configuration::rule_selection_operation::enable); + ASSERT_EQ(falco_config.m_rules_selection[0].m_rule, "Terminal Shell in Container"); + + ASSERT_EQ(falco_config.m_rules_selection[1].m_op, falco_configuration::rule_selection_operation::disable); + ASSERT_EQ(falco_config.m_rules_selection[1].m_tag, "experimental"); + + ASSERT_EQ(falco_config.m_rules_selection[2].m_op, falco_configuration::rule_selection_operation::enable); + ASSERT_EQ(falco_config.m_rules_selection[2].m_rule, "hello*"); +} + +TEST(ConfigurationRuleSelection, cli_options) +{ + falco_configuration falco_config; + EXPECT_NO_THROW(falco_config.init(std::vector{"rules[].disable.tag=maturity_incubating", "rules[].enable.rule=Adding ssh keys to authorized_keys"})); + + ASSERT_EQ(falco_config.m_rules_selection.size(), 2); + + ASSERT_EQ(falco_config.m_rules_selection[0].m_op, falco_configuration::rule_selection_operation::disable); + ASSERT_EQ(falco_config.m_rules_selection[0].m_tag, "maturity_incubating"); + + ASSERT_EQ(falco_config.m_rules_selection[1].m_op, falco_configuration::rule_selection_operation::enable); + ASSERT_EQ(falco_config.m_rules_selection[1].m_rule, "Adding ssh keys to authorized_keys"); +} diff --git a/userspace/engine/evttype_index_ruleset.cpp b/userspace/engine/evttype_index_ruleset.cpp index e34501ac..abbe8192 100644 --- a/userspace/engine/evttype_index_ruleset.cpp +++ b/userspace/engine/evttype_index_ruleset.cpp @@ -17,6 +17,8 @@ limitations under the License. #include "evttype_index_ruleset.h" +#include "falco_utils.h" + #include evttype_index_ruleset::evttype_index_ruleset( @@ -235,17 +237,17 @@ void evttype_index_ruleset::clear() m_filters.clear(); } -void evttype_index_ruleset::enable(const std::string &substring, bool match_exact, uint16_t ruleset_id) +void evttype_index_ruleset::enable(const std::string &pattern, match_type match, uint16_t ruleset_id) { - enable_disable(substring, match_exact, true, ruleset_id); + enable_disable(pattern, match, true, ruleset_id); } -void evttype_index_ruleset::disable(const std::string &substring, bool match_exact, uint16_t ruleset_id) +void evttype_index_ruleset::disable(const std::string &pattern, match_type match, uint16_t ruleset_id) { - enable_disable(substring, match_exact, false, ruleset_id); + enable_disable(pattern, match, false, ruleset_id); } -void evttype_index_ruleset::enable_disable(const std::string &substring, bool match_exact, bool enabled, uint16_t ruleset_id) +void evttype_index_ruleset::enable_disable(const std::string &pattern, match_type match, bool enabled, uint16_t ruleset_id) { while(m_rulesets.size() < (size_t)ruleset_id + 1) { @@ -255,17 +257,25 @@ void evttype_index_ruleset::enable_disable(const std::string &substring, bool ma for(const auto &wrap : m_filters) { bool matches; + std::string::size_type pos; - if(match_exact) + switch(match) { - size_t pos = wrap->rule.name.find(substring); + case match_type::exact: + pos = wrap->rule.name.find(pattern); - matches = (substring == "" || (pos == 0 && - substring.size() == wrap->rule.name.size())); - } - else - { - matches = (substring == "" || (wrap->rule.name.find(substring) != std::string::npos)); + matches = (pattern == "" || (pos == 0 && + pattern.size() == wrap->rule.name.size())); + break; + case match_type::substring: + matches = (pattern == "" || (wrap->rule.name.find(pattern) != std::string::npos)); + break; + case match_type::wildcard: + matches = falco::utils::matches_wildcard(pattern, wrap->rule.name); + break; + default: + // should never happen + matches = false; } if(matches) diff --git a/userspace/engine/evttype_index_ruleset.h b/userspace/engine/evttype_index_ruleset.h index 261f42ae..bf7b71a7 100644 --- a/userspace/engine/evttype_index_ruleset.h +++ b/userspace/engine/evttype_index_ruleset.h @@ -53,13 +53,13 @@ public: void on_loading_complete() override; void enable( - const std::string &substring, - bool match_exact, + const std::string &pattern, + match_type match, uint16_t rulset_id) override; void disable( - const std::string &substring, - bool match_exact, + const std::string &pattern, + match_type match, uint16_t rulset_id) override; void enable_tags( @@ -85,8 +85,8 @@ private: // Helper used by enable()/disable() void enable_disable( - const std::string &substring, - bool match_exact, + const std::string &pattern, + match_type match, bool enabled, uint16_t rulset_id); diff --git a/userspace/engine/falco_engine.cpp b/userspace/engine/falco_engine.cpp index 5b93dfee..921e4b98 100644 --- a/userspace/engine/falco_engine.cpp +++ b/userspace/engine/falco_engine.cpp @@ -242,11 +242,11 @@ std::unique_ptr falco_engine::load_rules(const std::string &rules_c } if(info->enabled) { - source->ruleset->enable(rule.name, true, m_default_ruleset_id); + source->ruleset->enable(rule.name, filter_ruleset::match_type::exact, m_default_ruleset_id); } else { - source->ruleset->disable(rule.name, true, m_default_ruleset_id); + source->ruleset->disable(rule.name, filter_ruleset::match_type::exact, m_default_ruleset_id); } } } @@ -272,17 +272,15 @@ void falco_engine::enable_rule(const std::string &substring, bool enabled, const void falco_engine::enable_rule(const std::string &substring, bool enabled, const uint16_t ruleset_id) { - bool match_exact = false; - for(const auto &it : m_sources) { if(enabled) { - it.ruleset->enable(substring, match_exact, ruleset_id); + it.ruleset->enable(substring, filter_ruleset::match_type::substring, ruleset_id); } else { - it.ruleset->disable(substring, match_exact, ruleset_id); + it.ruleset->disable(substring, filter_ruleset::match_type::substring, ruleset_id); } } } @@ -296,17 +294,37 @@ void falco_engine::enable_rule_exact(const std::string &rule_name, bool enabled, void falco_engine::enable_rule_exact(const std::string &rule_name, bool enabled, const uint16_t ruleset_id) { - bool match_exact = true; - for(const auto &it : m_sources) { if(enabled) { - it.ruleset->enable(rule_name, match_exact, ruleset_id); + it.ruleset->enable(rule_name, filter_ruleset::match_type::exact, ruleset_id); } else { - it.ruleset->disable(rule_name, match_exact, ruleset_id); + it.ruleset->disable(rule_name, filter_ruleset::match_type::exact, ruleset_id); + } + } +} + +void falco_engine::enable_rule_wildcard(const std::string &rule_name, bool enabled, const std::string &ruleset) +{ + uint16_t ruleset_id = find_ruleset_id(ruleset); + + enable_rule_wildcard(rule_name, enabled, ruleset_id); +} + +void falco_engine::enable_rule_wildcard(const std::string &rule_name, bool enabled, const uint16_t ruleset_id) +{ + for(const auto &it : m_sources) + { + if(enabled) + { + it.ruleset->enable(rule_name, filter_ruleset::match_type::wildcard, ruleset_id); + } + else + { + it.ruleset->disable(rule_name, filter_ruleset::match_type::wildcard, ruleset_id); } } } diff --git a/userspace/engine/falco_engine.h b/userspace/engine/falco_engine.h index 5d2c5c2c..b01f7705 100644 --- a/userspace/engine/falco_engine.h +++ b/userspace/engine/falco_engine.h @@ -102,6 +102,12 @@ public: // Same as above but providing a ruleset id instead void enable_rule_exact(const std::string &rule_name, bool enabled, const uint16_t ruleset_id); + // Like enable_rule, but wildcards are supported and substrings are not matched + void enable_rule_wildcard(const std::string &rule_name, bool enabled, const std::string &ruleset = s_default_ruleset); + + // Same as above but providing a ruleset id instead + void enable_rule_wildcard(const std::string &rule_name, bool enabled, const uint16_t ruleset_id); + // // Enable/Disable any rules with any of the provided tags (set, exact matches only) // diff --git a/userspace/engine/falco_utils.cpp b/userspace/engine/falco_utils.cpp index 926e2f93..692ce91e 100644 --- a/userspace/engine/falco_utils.cpp +++ b/userspace/engine/falco_utils.cpp @@ -197,6 +197,48 @@ void readfile(const std::string& filename, std::string& data) return; } +bool matches_wildcard(const std::string &pattern, const std::string &s) +{ + std::string::size_type star_pos = pattern.find("*"); + if(star_pos == std::string::npos) + { + // regular match (no wildcards) + return pattern == s; + } + + if(star_pos == 0) + { + // wildcard at the beginning "*something*..." + + std::string::size_type next_pattern_start = pattern.find_first_not_of("*"); + if(next_pattern_start == std::string::npos) + { + // pattern was just a sequence of stars *, **, ***, ... . This always matches. + return true; + } + + std::string next_pattern = pattern.substr(next_pattern_start); + std::string to_find = next_pattern.substr(0, next_pattern.find("*")); + std::string::size_type lit_pos = s.find(to_find); + if(lit_pos == std::string::npos) + { + return false; + } + + return matches_wildcard(next_pattern.substr(to_find.size()), s.substr(lit_pos + to_find.size())); + } else + { + // wildcard at the end or in the middle "something*else*..." + + if(pattern.substr(0, star_pos) != s.substr(0, star_pos)) + { + return false; + } + + return matches_wildcard(pattern.substr(star_pos), s.substr(star_pos)); + } +} + namespace network { bool is_unix_scheme(const std::string& url) diff --git a/userspace/engine/falco_utils.h b/userspace/engine/falco_utils.h index 35a6a928..4a472f1a 100644 --- a/userspace/engine/falco_utils.h +++ b/userspace/engine/falco_utils.h @@ -37,6 +37,8 @@ void readfile(const std::string& filename, std::string& data); uint32_t hardware_concurrency(); +bool matches_wildcard(const std::string &pattern, const std::string &s); + namespace network { static const std::string UNIX_SCHEME("unix://"); diff --git a/userspace/engine/filter_ruleset.h b/userspace/engine/filter_ruleset.h index b5ba3d5e..0b0ad459 100644 --- a/userspace/engine/filter_ruleset.h +++ b/userspace/engine/filter_ruleset.h @@ -41,6 +41,10 @@ public: ruleset_retriever_func_t get_ruleset; }; + enum class match_type { + exact, substring, wildcard + }; + virtual ~filter_ruleset() = default; void set_engine_state(const engine_state_funcs &engine_state); @@ -167,31 +171,37 @@ public: /*! \brief Find those rules matching the provided substring and enable them in the provided ruleset. - \param substring Substring used to match rule names. - If empty, all rules are matched. - \param match_exact If true, substring must be an exact match for a - given rule name. Otherwise, any rules having substring as a substring - in the rule name are enabled/disabled. + \param pattern Pattern used to match rule names. + \param match How to match the pattern against rules: + - exact: rules that has the same exact name as the pattern are matched + - substring: rules having the pattern as a substring in the rule are matched. + An empty pattern matches all rules. + - wildcard: rules with names that satisfies a wildcard (*) pattern are matched. + A "*" pattern matches all rules. + Wildcards can appear anywhere in the pattern (e.g. "*hello*world*") \param ruleset_id The id of the ruleset to be used */ virtual void enable( - const std::string &substring, - bool match_exact, + const std::string &pattern, + match_type match, uint16_t ruleset_id) = 0; /*! \brief Find those rules matching the provided substring and disable them in the provided ruleset. - \param substring Substring used to match rule names. - If empty, all rules are matched. - \param match_exact If true, substring must be an exact match for a - given rule name. Otherwise, any rules having substring as a substring - in the rule name are enabled/disabled. + \param pattern Pattern used to match rule names. + \param match How to match the pattern against rules: + - exact: rules that has the same exact name as the pattern are matched + - substring: rules having the pattern as a substring in the rule are matched. + An empty pattern matches all rules. + - wildcard: rules with names that satisfies a wildcard (*) pattern are matched. + A "*" pattern matches all rules. + Wildcards can appear anywhere in the pattern (e.g. "*hello*world*") \param ruleset_id The id of the ruleset to be used */ virtual void disable( - const std::string &substring, - bool match_exact, + const std::string &pattern, + match_type match, uint16_t ruleset_id) = 0; /*! diff --git a/userspace/falco/app/actions/load_rules_files.cpp b/userspace/falco/app/actions/load_rules_files.cpp index 590cc81a..521d26e1 100644 --- a/userspace/falco/app/actions/load_rules_files.cpp +++ b/userspace/falco/app/actions/load_rules_files.cpp @@ -131,6 +131,12 @@ falco::app::run_result falco::app::actions::load_rules_files(falco::app::state& return run_result::fatal(err); } + if((!s.options.disabled_rule_substrings.empty() || !s.options.disabled_rule_tags.empty() || !s.options.enabled_rule_tags.empty()) && + !s.config->m_rules_selection.empty()) + { + return run_result::fatal("Specifying -D, -t, -T command line options together with \"rules:\" configuration or -o \"rules...\" is not supported."); + } + for (const auto& substring : s.options.disabled_rule_substrings) { falco_logger::log(falco_logger::level::INFO, "Disabling rules matching substring: " + substring + "\n"); @@ -158,6 +164,27 @@ falco::app::run_result falco::app::actions::load_rules_files(falco::app::state& s.engine->enable_rule_by_tag(s.options.enabled_rule_tags, true); } + for(const auto& sel : s.config->m_rules_selection) + { + bool enable = sel.m_op == falco_configuration::rule_selection_operation::enable; + + if(sel.m_rule != "") + { + falco_logger::log(falco_logger::level::INFO, + (enable ? "Enabling" : "Disabling") + std::string(" rules with name: ") + sel.m_rule + "\n"); + + s.engine->enable_rule_wildcard(sel.m_rule, enable); + } + + if(sel.m_tag != "") + { + falco_logger::log(falco_logger::level::INFO, + (enable ? "Enabling" : "Disabling") + std::string(" rules with tag: ") + sel.m_tag + "\n"); + + s.engine->enable_rule_by_tag(std::set{sel.m_tag}, enable); // TODO wildcard support + } + } + // printout of `-L` option if (s.options.describe_all_rules || !s.options.describe_rule.empty()) { diff --git a/userspace/falco/configuration.cpp b/userspace/falco/configuration.cpp index 72eaf01f..128c2326 100644 --- a/userspace/falco/configuration.cpp +++ b/userspace/falco/configuration.cpp @@ -85,6 +85,13 @@ void falco_configuration::init(const std::vector& cmdline_options) load_yaml("default"); } +void falco_configuration::init_from_content(const std::string& config_content, const std::vector& cmdline_options) +{ + config.load_from_string(config_content); + init_cmdline_options(cmdline_options); + load_yaml("default"); +} + void falco_configuration::init(const std::string& conf_filename, std::vector& loaded_conf_files, const std::vector &cmdline_options) { @@ -558,6 +565,8 @@ void falco_configuration::load_yaml(const std::string& config_name) m_metrics_convert_memory_to_mb = config.get_scalar("metrics.convert_memory_to_mb", true); m_metrics_include_empty_values = config.get_scalar("metrics.include_empty_values", false); + config.get_sequence>(m_rules_selection, "rules"); + std::vector load_plugins; bool load_plugins_node_defined = config.is_defined("load_plugins"); diff --git a/userspace/falco/configuration.h b/userspace/falco/configuration.h index b43bae31..175da095 100644 --- a/userspace/falco/configuration.h +++ b/userspace/falco/configuration.h @@ -93,11 +93,23 @@ public: bool m_prometheus_metrics_enabled = false; }; + enum class rule_selection_operation { + enable, + disable + }; + + struct rule_selection_config { + rule_selection_operation m_op; + std::string m_tag; + std::string m_rule; + }; + falco_configuration(); virtual ~falco_configuration() = default; void init(const std::string& conf_filename, std::vector& loaded_conf_files, const std::vector& cmdline_options); void init(const std::vector& cmdline_options); + void init_from_content(const std::string& config_content, const std::vector& cmdline_options); std::string dump(); @@ -118,6 +130,9 @@ public: std::unordered_map m_loaded_rules_filenames_sha256sum; // List of loaded rule folders std::list m_loaded_rules_folders; + // Rule selection options passed by the user + std::vector m_rules_selection; + bool m_json_output; bool m_json_include_output_property; bool m_json_include_tags_property; @@ -196,6 +211,91 @@ private: }; namespace YAML { + template<> + struct convert { + static Node encode(const falco_configuration::rule_selection_config & rhs) { + Node node; + Node subnode; + if(rhs.m_rule != "") + { + subnode["rule"] = rhs.m_rule; + } + + if(rhs.m_tag != "") + { + subnode["tag"] = rhs.m_tag; + } + + if(rhs.m_op == falco_configuration::rule_selection_operation::enable) + { + node["enable"] = subnode; + } + else if(rhs.m_op == falco_configuration::rule_selection_operation::disable) + { + node["disable"] = subnode; + } + + return node; + } + + static bool decode(const Node& node, falco_configuration::rule_selection_config & rhs) { + if(!node.IsMap()) + { + return false; + } + + if(node["enable"]) + { + rhs.m_op = falco_configuration::rule_selection_operation::enable; + + const Node& enable = node["enable"]; + if(!enable.IsMap()) + { + return false; + } + + if(enable["rule"]) + { + rhs.m_rule = enable["rule"].as(); + } + if(enable["tag"]) + { + rhs.m_tag = enable["tag"].as(); + } + } + else if(node["disable"]) + { + rhs.m_op = falco_configuration::rule_selection_operation::disable; + + const Node& disable = node["disable"]; + if(!disable.IsMap()) + { + return false; + } + + if(disable["rule"]) + { + rhs.m_rule = disable["rule"].as(); + } + if(disable["tag"]) + { + rhs.m_tag = disable["tag"].as(); + } + } + else + { + return false; + } + + if (rhs.m_rule == "" && rhs.m_tag == "") + { + return false; + } + + return true; + } + }; + template<> struct convert { diff --git a/userspace/falco/yaml_helper.h b/userspace/falco/yaml_helper.h index 3d69ad56..43740026 100644 --- a/userspace/falco/yaml_helper.h +++ b/userspace/falco/yaml_helper.h @@ -150,7 +150,7 @@ public: void set_scalar(const std::string& key, const T& value) { YAML::Node node; - get_node(node, key); + get_node(node, key, true); node = value; } @@ -264,15 +264,18 @@ private: * this regular language: * * Key := NodeKey ('.' NodeKey)* - * NodeKey := (any)+ ('[' (integer)+ ']')* + * NodeKey := (any)+ ('[' (integer)+? ']')* * + * If can_append is true, an empty NodeKey will append a new entry + * to the sequence, it is rejected otherwise. + * * Some examples of accepted key strings: * - NodeName * - ListValue[3].subvalue * - MatrixValue[1][3] * - value1.subvalue2.subvalue3 */ - void get_node(YAML::Node &ret, const std::string &key) const + void get_node(YAML::Node &ret, const std::string &key, bool can_append=false) const { try { @@ -310,7 +313,27 @@ private: if (c == '[') { auto close_param_idx = key.find(']', i); - int nodeIdx = std::stoi(key.substr(i + 1, close_param_idx - i - 1)); + std::string idx_str = key.substr(i + 1, close_param_idx - i - 1); + int nodeIdx; + + bool ret_appendable = !ret.IsDefined() || ret.IsSequence(); + if (idx_str.empty() && ret_appendable && can_append) + { + YAML::Node newNode; + ret.push_back(newNode); + nodeIdx = ret.size() - 1; + } + else + { + try + { + nodeIdx = std::stoi(idx_str); + } + catch(const std::exception& e) + { + throw std::runtime_error("Parsing error: expected a numeric index, found '" + idx_str + "'"); + } + } ret.reset(ret[nodeIdx]); i = close_param_idx; if (i < key.size() - 1 && key[i + 1] == '.')