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

@@ -17,6 +17,8 @@ limitations under the License.
#include "evttype_index_ruleset.h"
#include "falco_utils.h"
#include <algorithm>
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)

View File

@@ -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);

View File

@@ -242,11 +242,11 @@ std::unique_ptr<load_result> 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);
}
}
}

View File

@@ -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)
//

View File

@@ -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)

View File

@@ -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://");

View File

@@ -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;
/*!

View File

@@ -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<std::string>{sel.m_tag}, enable); // TODO wildcard support
}
}
// printout of `-L` option
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");
}
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,
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_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;
bool load_plugins_node_defined = config.is_defined("load_plugins");

View File

@@ -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<std::string>& loaded_conf_files, 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();
@@ -118,6 +130,9 @@ public:
std::unordered_map<std::string, std::string> m_loaded_rules_filenames_sha256sum;
// List of loaded rule 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_include_output_property;
bool m_json_include_tags_property;
@@ -196,6 +211,91 @@ private:
};
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<>
struct convert<falco_configuration::plugin_config> {

View File

@@ -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] == '.')