new(falco): implement rule selection configuration in falco.yaml

Signed-off-by: Luca Guerra <luca@guerra.sh>
This commit is contained in:
Luca Guerra
2024-04-19 10:30:32 +00:00
committed by poiana
parent 60e6798f9b
commit 35bd348e21
17 changed files with 476 additions and 56 deletions

View File

@@ -44,6 +44,7 @@ add_executable(falco_unit_tests
engine/test_rule_loader.cpp engine/test_rule_loader.cpp
engine/test_rulesets.cpp engine/test_rulesets.cpp
falco/test_configuration.cpp falco/test_configuration.cpp
falco/test_configuration_rule_selection.cpp
falco/app/actions/test_select_event_sources.cpp falco/app/actions/test_select_event_sources.cpp
falco/app/actions/test_load_config.cpp falco/app/actions/test_load_config.cpp
) )

View File

@@ -310,9 +310,8 @@ TEST(engine_loader_alt_loader, pass_compile_output_to_ruleset)
// Enable all rules for a ruleset id. Because the compile // Enable all rules for a ruleset id. Because the compile
// output contained one rule with priority >= INFO, that rule // output contained one rule with priority >= INFO, that rule
// should be enabled. // should be enabled.
bool match_exact = true;
uint16_t ruleset_id = 0; 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); EXPECT_EQ(ruleset->enabled_count(ruleset_id), 1);
} }

View File

@@ -44,6 +44,36 @@ static std::string single_rule = R"END(
tags: [exec process] tags: [exec process]
)END"; )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 // This must be kept in line with the (private) falco_engine::s_default_ruleset
static const std::string default_ruleset = "falco-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(0, m_engine->num_rules_for_ruleset(ruleset_3));
EXPECT_EQ(2, m_engine->num_rules_for_ruleset(ruleset_4)); 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));
}

View File

@@ -72,3 +72,24 @@ TEST(FalcoUtils, parse_prometheus_interval)
*/ */
ASSERT_EQ(falco::utils::parse_prometheus_interval("200"), 0UL); 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"));
}

View File

@@ -74,46 +74,70 @@ TEST(Ruleset, enable_disable_rules_using_names)
r->add(rule_C, filter, ast); r->add(rule_C, filter, ast);
/* Enable `rule_A` for RULESET_0 */ /* 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_0), 1);
ASSERT_EQ(r->enabled_count(RULESET_1), 0); ASSERT_EQ(r->enabled_count(RULESET_1), 0);
ASSERT_EQ(r->enabled_count(RULESET_2), 0); ASSERT_EQ(r->enabled_count(RULESET_2), 0);
/* Disable `rule_A` for RULESET_1, this should have no effect */ /* 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_0), 1);
ASSERT_EQ(r->enabled_count(RULESET_1), 0); ASSERT_EQ(r->enabled_count(RULESET_1), 0);
ASSERT_EQ(r->enabled_count(RULESET_2), 0); ASSERT_EQ(r->enabled_count(RULESET_2), 0);
/* Enable a not existing rule for RULESET_2, this should have no effect */ /* 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_0), 1);
ASSERT_EQ(r->enabled_count(RULESET_1), 0); ASSERT_EQ(r->enabled_count(RULESET_1), 0);
ASSERT_EQ(r->enabled_count(RULESET_2), 0); ASSERT_EQ(r->enabled_count(RULESET_2), 0);
/* Enable all rules for RULESET_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_0), 3);
ASSERT_EQ(r->enabled_count(RULESET_1), 0); ASSERT_EQ(r->enabled_count(RULESET_1), 0);
ASSERT_EQ(r->enabled_count(RULESET_2), 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 */ /* 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_0), 3);
ASSERT_EQ(r->enabled_count(RULESET_1), 0); ASSERT_EQ(r->enabled_count(RULESET_1), 0);
ASSERT_EQ(r->enabled_count(RULESET_2), 0); ASSERT_EQ(r->enabled_count(RULESET_2), 0);
/* Disable all rules for RULESET_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_0), 0);
ASSERT_EQ(r->enabled_count(RULESET_1), 0); ASSERT_EQ(r->enabled_count(RULESET_1), 0);
ASSERT_EQ(r->enabled_count(RULESET_2), 0); ASSERT_EQ(r->enabled_count(RULESET_2), 0);
/* Enable rule_C for RULESET_2 without exact_match */ /* 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_0), 0);
ASSERT_EQ(r->enabled_count(RULESET_1), 0); ASSERT_EQ(r->enabled_count(RULESET_1), 0);
ASSERT_EQ(r->enabled_count(RULESET_2), 1); 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) TEST(Ruleset, enable_disable_rules_using_tags)

View File

@@ -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 <gtest/gtest.h>
#include <falco/configuration.h>
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<std::string>{"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");
}

View File

@@ -17,6 +17,8 @@ limitations under the License.
#include "evttype_index_ruleset.h" #include "evttype_index_ruleset.h"
#include "falco_utils.h"
#include <algorithm> #include <algorithm>
evttype_index_ruleset::evttype_index_ruleset( evttype_index_ruleset::evttype_index_ruleset(
@@ -235,17 +237,17 @@ void evttype_index_ruleset::clear()
m_filters.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) 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) for(const auto &wrap : m_filters)
{ {
bool matches; 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 && matches = (pattern == "" || (pos == 0 &&
substring.size() == wrap->rule.name.size())); pattern.size() == wrap->rule.name.size()));
} break;
else case match_type::substring:
{ matches = (pattern == "" || (wrap->rule.name.find(pattern) != std::string::npos));
matches = (substring == "" || (wrap->rule.name.find(substring) != 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) if(matches)

View File

@@ -53,13 +53,13 @@ public:
void on_loading_complete() override; void on_loading_complete() override;
void enable( void enable(
const std::string &substring, const std::string &pattern,
bool match_exact, match_type match,
uint16_t rulset_id) override; uint16_t rulset_id) override;
void disable( void disable(
const std::string &substring, const std::string &pattern,
bool match_exact, match_type match,
uint16_t rulset_id) override; uint16_t rulset_id) override;
void enable_tags( void enable_tags(
@@ -85,8 +85,8 @@ private:
// Helper used by enable()/disable() // Helper used by enable()/disable()
void enable_disable( void enable_disable(
const std::string &substring, const std::string &pattern,
bool match_exact, match_type match,
bool enabled, bool enabled,
uint16_t rulset_id); uint16_t rulset_id);

View File

@@ -242,11 +242,11 @@ std::unique_ptr<load_result> falco_engine::load_rules(const std::string &rules_c
} }
if(info->enabled) 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 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) 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) for(const auto &it : m_sources)
{ {
if(enabled) if(enabled)
{ {
it.ruleset->enable(substring, match_exact, ruleset_id); it.ruleset->enable(substring, filter_ruleset::match_type::substring, ruleset_id);
} }
else 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) 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) for(const auto &it : m_sources)
{ {
if(enabled) if(enabled)
{ {
it.ruleset->enable(rule_name, match_exact, ruleset_id); it.ruleset->enable(rule_name, filter_ruleset::match_type::exact, ruleset_id);
} }
else 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);
} }
} }
} }

View File

@@ -102,6 +102,12 @@ public:
// Same as above but providing a ruleset id instead // 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); 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) // Enable/Disable any rules with any of the provided tags (set, exact matches only)
// //

View File

@@ -197,6 +197,48 @@ void readfile(const std::string& filename, std::string& data)
return; 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 namespace network
{ {
bool is_unix_scheme(const std::string& url) bool is_unix_scheme(const std::string& url)

View File

@@ -37,6 +37,8 @@ void readfile(const std::string& filename, std::string& data);
uint32_t hardware_concurrency(); uint32_t hardware_concurrency();
bool matches_wildcard(const std::string &pattern, const std::string &s);
namespace network namespace network
{ {
static const std::string UNIX_SCHEME("unix://"); static const std::string UNIX_SCHEME("unix://");

View File

@@ -41,6 +41,10 @@ public:
ruleset_retriever_func_t get_ruleset; ruleset_retriever_func_t get_ruleset;
}; };
enum class match_type {
exact, substring, wildcard
};
virtual ~filter_ruleset() = default; virtual ~filter_ruleset() = default;
void set_engine_state(const engine_state_funcs &engine_state); 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 \brief Find those rules matching the provided substring and enable
them in the provided ruleset. them in the provided ruleset.
\param substring Substring used to match rule names. \param pattern Pattern used to match rule names.
If empty, all rules are matched. \param match How to match the pattern against rules:
\param match_exact If true, substring must be an exact match for a - exact: rules that has the same exact name as the pattern are matched
given rule name. Otherwise, any rules having substring as a substring - substring: rules having the pattern as a substring in the rule are matched.
in the rule name are enabled/disabled. 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 \param ruleset_id The id of the ruleset to be used
*/ */
virtual void enable( virtual void enable(
const std::string &substring, const std::string &pattern,
bool match_exact, match_type match,
uint16_t ruleset_id) = 0; uint16_t ruleset_id) = 0;
/*! /*!
\brief Find those rules matching the provided substring and disable \brief Find those rules matching the provided substring and disable
them in the provided ruleset. them in the provided ruleset.
\param substring Substring used to match rule names. \param pattern Pattern used to match rule names.
If empty, all rules are matched. \param match How to match the pattern against rules:
\param match_exact If true, substring must be an exact match for a - exact: rules that has the same exact name as the pattern are matched
given rule name. Otherwise, any rules having substring as a substring - substring: rules having the pattern as a substring in the rule are matched.
in the rule name are enabled/disabled. 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 \param ruleset_id The id of the ruleset to be used
*/ */
virtual void disable( virtual void disable(
const std::string &substring, const std::string &pattern,
bool match_exact, match_type match,
uint16_t ruleset_id) = 0; uint16_t ruleset_id) = 0;
/*! /*!

View File

@@ -131,6 +131,12 @@ falco::app::run_result falco::app::actions::load_rules_files(falco::app::state&
return run_result::fatal(err); 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) for (const auto& substring : s.options.disabled_rule_substrings)
{ {
falco_logger::log(falco_logger::level::INFO, "Disabling rules matching substring: " + substring + "\n"); 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); 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<std::string>{sel.m_tag}, enable); // TODO wildcard support
}
}
// printout of `-L` option // printout of `-L` option
if (s.options.describe_all_rules || !s.options.describe_rule.empty()) if (s.options.describe_all_rules || !s.options.describe_rule.empty())
{ {

View File

@@ -85,6 +85,13 @@ void falco_configuration::init(const std::vector<std::string>& cmdline_options)
load_yaml("default"); load_yaml("default");
} }
void falco_configuration::init_from_content(const std::string& config_content, const std::vector<std::string>& 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<std::string>& loaded_conf_files, void falco_configuration::init(const std::string& conf_filename, std::vector<std::string>& loaded_conf_files,
const std::vector<std::string> &cmdline_options) const std::vector<std::string> &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<bool>("metrics.convert_memory_to_mb", true); m_metrics_convert_memory_to_mb = config.get_scalar<bool>("metrics.convert_memory_to_mb", true);
m_metrics_include_empty_values = config.get_scalar<bool>("metrics.include_empty_values", false); m_metrics_include_empty_values = config.get_scalar<bool>("metrics.include_empty_values", false);
config.get_sequence<std::vector<rule_selection_config>>(m_rules_selection, "rules");
std::vector<std::string> load_plugins; std::vector<std::string> load_plugins;
bool load_plugins_node_defined = config.is_defined("load_plugins"); bool load_plugins_node_defined = config.is_defined("load_plugins");

View File

@@ -93,11 +93,23 @@ public:
bool m_prometheus_metrics_enabled = false; 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(); falco_configuration();
virtual ~falco_configuration() = default; virtual ~falco_configuration() = default;
void init(const std::string& conf_filename, std::vector<std::string>& loaded_conf_files, const std::vector<std::string>& cmdline_options); void init(const std::string& conf_filename, std::vector<std::string>& loaded_conf_files, const std::vector<std::string>& cmdline_options);
void init(const std::vector<std::string>& cmdline_options); void init(const std::vector<std::string>& cmdline_options);
void init_from_content(const std::string& config_content, const std::vector<std::string>& cmdline_options);
std::string dump(); std::string dump();
@@ -118,6 +130,9 @@ public:
std::unordered_map<std::string, std::string> m_loaded_rules_filenames_sha256sum; std::unordered_map<std::string, std::string> m_loaded_rules_filenames_sha256sum;
// List of loaded rule folders // List of loaded rule folders
std::list<std::string> m_loaded_rules_folders; std::list<std::string> m_loaded_rules_folders;
// Rule selection options passed by the user
std::vector<rule_selection_config> m_rules_selection;
bool m_json_output; bool m_json_output;
bool m_json_include_output_property; bool m_json_include_output_property;
bool m_json_include_tags_property; bool m_json_include_tags_property;
@@ -196,6 +211,91 @@ private:
}; };
namespace YAML { namespace YAML {
template<>
struct convert<falco_configuration::rule_selection_config> {
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<std::string>();
}
if(enable["tag"])
{
rhs.m_tag = enable["tag"].as<std::string>();
}
}
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<std::string>();
}
if(disable["tag"])
{
rhs.m_tag = disable["tag"].as<std::string>();
}
}
else
{
return false;
}
if (rhs.m_rule == "" && rhs.m_tag == "")
{
return false;
}
return true;
}
};
template<> template<>
struct convert<falco_configuration::plugin_config> { struct convert<falco_configuration::plugin_config> {

View File

@@ -150,7 +150,7 @@ public:
void set_scalar(const std::string& key, const T& value) void set_scalar(const std::string& key, const T& value)
{ {
YAML::Node node; YAML::Node node;
get_node(node, key); get_node(node, key, true);
node = value; node = value;
} }
@@ -264,7 +264,10 @@ private:
* this regular language: * this regular language:
* *
* Key := NodeKey ('.' NodeKey)* * 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: * Some examples of accepted key strings:
* - NodeName * - NodeName
@@ -272,7 +275,7 @@ private:
* - MatrixValue[1][3] * - MatrixValue[1][3]
* - value1.subvalue2.subvalue3 * - 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 try
{ {
@@ -310,7 +313,27 @@ private:
if (c == '[') if (c == '[')
{ {
auto close_param_idx = key.find(']', i); 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]); ret.reset(ret[nodeIdx]);
i = close_param_idx; i = close_param_idx;
if (i < key.size() - 1 && key[i + 1] == '.') if (i < key.size() - 1 && key[i + 1] == '.')