From aa073586f1c4a936d3a64ef7cca7b77482d38b1a Mon Sep 17 00:00:00 2001 From: Mark Stemm Date: Thu, 5 Oct 2017 17:20:54 -0700 Subject: [PATCH] Add ability to filter events by priority/cleanups Clean up the handling of priority levels within rules. It used to be a mix of strings handled in various places. Now, in falco_common.h there's a consistent type for priority-as-number as well as a list of priority-as-string values. Priorities are passed around as numbers instead of strings. It's still permissive about capitalization. Also add the ability to load rules by severity. New falco config option "priority="/-o priority= specifies the minimum priority level of rules that will be loaded. Add unit tests for same. The test suppresses INFO notifications for a rule/trace file combination that would otherwise generate them. --- falco.yaml | 6 ++++ test/falco_test.py | 5 ++-- test/falco_tests.yaml | 15 ++++++++++ userspace/engine/falco_common.cpp | 10 +++++++ userspace/engine/falco_common.h | 16 +++++++++++ userspace/engine/falco_engine.cpp | 10 +++++-- userspace/engine/falco_engine.h | 6 +++- userspace/engine/lua/rule_loader.lua | 42 +++++++++++++++++++--------- userspace/engine/rules.cpp | 6 ++-- userspace/engine/rules.h | 5 +++- userspace/falco/configuration.cpp | 13 +++++++++ userspace/falco/configuration.h | 2 ++ userspace/falco/falco.cpp | 4 ++- userspace/falco/falco_outputs.cpp | 7 +++-- userspace/falco/falco_outputs.h | 2 +- userspace/falco/lua/output.lua | 34 ++++++---------------- 16 files changed, 132 insertions(+), 51 deletions(-) diff --git a/falco.yaml b/falco.yaml index d407b75b..1dbfcd47 100644 --- a/falco.yaml +++ b/falco.yaml @@ -15,6 +15,12 @@ log_syslog: true # "alert", "critical", "error", "warning", "notice", "info", "debug". log_level: info +# Minimum rule priority level to load and run. All rules having a +# priority more severe than this level will be loaded/run. Can be one +# of "emergency", "alert", "critical", "error", "warning", "notice", +# "info", "debug". +priority: debug + # A throttling mechanism implemented as a token bucket limits the # rate of falco notifications. This throttling is controlled by the following configuration # options: diff --git a/test/falco_test.py b/test/falco_test.py index 7aaf3595..8b9cf615 100644 --- a/test/falco_test.py +++ b/test/falco_test.py @@ -30,6 +30,7 @@ class FalcoTest(Test): self.trace_file = os.path.join(self.basedir, self.trace_file) self.json_output = self.params.get('json_output', '*', default=False) + self.priority = self.params.get('priority', '*', default='debug') self.rules_file = self.params.get('rules_file', '*', default=os.path.join(self.basedir, '../rules/falco_rules.yaml')) if not isinstance(self.rules_file, list): @@ -347,8 +348,8 @@ class FalcoTest(Test): trace_arg = "-e {}".format(self.trace_file) # Run falco - cmd = '{} {} {} -c {} {} -o json_output={} -v'.format( - self.falco_binary_path, self.rules_args, self.disabled_args, self.conf_file, trace_arg, self.json_output) + cmd = '{} {} {} -c {} {} -o json_output={} -o priority={} -v'.format( + self.falco_binary_path, self.rules_args, self.disabled_args, self.conf_file, trace_arg, self.json_output, self.priority) for tag in self.disable_tags: cmd += ' -T {}'.format(tag) diff --git a/test/falco_tests.yaml b/test/falco_tests.yaml index 0449b1f0..485e87fb 100644 --- a/test/falco_tests.yaml +++ b/test/falco_tests.yaml @@ -129,6 +129,21 @@ trace_files: !mux - rules/double_rule.yaml trace_file: trace_files/cat_write.scap + multiple_rules_suppress_info: + detect: True + detect_level: + - WARNING + - ERROR + priority: WARNING + detect_counts: + - open_from_cat: 8 + - exec_from_cat: 1 + - access_from_cat: 0 + rules_file: + - rules/single_rule.yaml + - rules/double_rule.yaml + trace_file: trace_files/cat_write.scap + multiple_rules_overriding: detect: False rules_file: diff --git a/userspace/engine/falco_common.cpp b/userspace/engine/falco_common.cpp index ac427a12..ffbbb2ef 100644 --- a/userspace/engine/falco_common.cpp +++ b/userspace/engine/falco_common.cpp @@ -21,6 +21,16 @@ along with falco. If not, see . #include "config_falco_engine.h" #include "falco_common.h" +std::vector falco_common::priority_names = { + "Emergency", + "Alert", + "Critical", + "Error", + "Warning", + "Notice", + "Informational", + "Debug"}; + falco_common::falco_common() { m_ls = lua_open(); diff --git a/userspace/engine/falco_common.h b/userspace/engine/falco_common.h index 075e18fc..c7bcd82e 100644 --- a/userspace/engine/falco_common.h +++ b/userspace/engine/falco_common.h @@ -74,6 +74,22 @@ public: void set_inspector(sinsp *inspector); + // Priority levels, as a vector of strings + static std::vector priority_names; + + // Same as numbers/indices into the above vector + enum priority_type + { + PRIORITY_EMERGENCY = 0, + PRIORITY_ALERT = 1, + PRIORITY_CRITICAL = 2, + PRIORITY_ERROR = 3, + PRIORITY_WARNING = 4, + PRIORITY_NOTICE = 5, + PRIORITY_INFORMATIONAL = 6, + PRIORITY_DEBUG = 7 + }; + protected: lua_State *m_ls; diff --git a/userspace/engine/falco_engine.cpp b/userspace/engine/falco_engine.cpp index ea00514b..fb7beb6d 100644 --- a/userspace/engine/falco_engine.cpp +++ b/userspace/engine/falco_engine.cpp @@ -41,6 +41,7 @@ using namespace std; falco_engine::falco_engine(bool seed_rng) : m_rules(NULL), m_next_ruleset_id(0), + m_min_priority(falco_common::PRIORITY_DEBUG), m_sampling_ratio(1), m_sampling_multiplier(0), m_replace_container_info(false) { @@ -89,7 +90,7 @@ void falco_engine::load_rules(const string &rules_content, bool verbose, bool al bool json_output = false; falco_formats::init(m_inspector, m_ls, json_output); - m_rules->load_rules(rules_content, verbose, all_events, m_extra, m_replace_container_info); + m_rules->load_rules(rules_content, verbose, all_events, m_extra, m_replace_container_info, m_min_priority); } void falco_engine::load_rules_file(const string &rules_filename, bool verbose, bool all_events) @@ -134,6 +135,11 @@ void falco_engine::enable_rule_by_tag(const set &tags, bool enabled) enable_rule_by_tag(tags, enabled, m_default_ruleset); } +void falco_engine::set_min_priority(falco_common::priority_type priority) +{ + m_min_priority = priority; +} + uint16_t falco_engine::find_ruleset_id(const std::string &ruleset) { auto it = m_known_rulesets.lower_bound(ruleset); @@ -178,7 +184,7 @@ unique_ptr falco_engine::process_event(sinsp_evt *ev, res->evt = ev; const char *p = lua_tostring(m_ls, -3); res->rule = p; - res->priority = lua_tostring(m_ls, -2); + res->priority_num = (falco_common::priority_type) lua_tonumber(m_ls, -2); res->format = lua_tostring(m_ls, -1); lua_pop(m_ls, 3); } diff --git a/userspace/engine/falco_engine.h b/userspace/engine/falco_engine.h index d06c45e7..55695bea 100644 --- a/userspace/engine/falco_engine.h +++ b/userspace/engine/falco_engine.h @@ -67,10 +67,13 @@ public: // Wrapper that assumes the default ruleset void enable_rule_by_tag(const std::set &tags, bool enabled); + // Only load rules having this priority or more severe. + void set_min_priority(falco_common::priority_type priority); + struct rule_result { sinsp_evt *evt; std::string rule; - std::string priority; + falco_common::priority_type priority_num; std::string format; }; @@ -158,6 +161,7 @@ private: uint16_t m_next_ruleset_id; std::map m_known_rulesets; std::unique_ptr m_evttype_filter; + falco_common::priority_type m_min_priority; // // Here's how the sampling ratio and multiplier influence diff --git a/userspace/engine/lua/rule_loader.lua b/userspace/engine/lua/rule_loader.lua index 1d9c60dd..8fc44b68 100644 --- a/userspace/engine/lua/rule_loader.lua +++ b/userspace/engine/lua/rule_loader.lua @@ -58,6 +58,17 @@ function map(f, arr) return res end +priorities = {"Emergency", "Alert", "Critical", "Error", "Warning", "Notice", "Informational", "Debug"} + +local function priority_num_for(s) + s = string.lower(s) + for i,v in ipairs(priorities) do + if (string.find(string.lower(v), "^"..s)) then + return i - 1 -- (numbers start at 0, lua indices start at 1) + end + end + error("Invalid priority level: "..s) +end --[[ Take a filter AST and set it up in the libsinsp runtime, using the filter API. @@ -171,7 +182,7 @@ function table.tostring( tbl ) end -function load_rules(rules_content, rules_mgr, verbose, all_events, extra, replace_container_info) +function load_rules(rules_content, rules_mgr, verbose, all_events, extra, replace_container_info, min_priority) compiler.set_verbose(verbose) compiler.set_all_events(all_events) @@ -293,18 +304,23 @@ function load_rules(rules_content, rules_mgr, verbose, all_events, extra, replac end end - -- Note that we can overwrite rules, but the rules are still - -- loaded in the order in which they first appeared, - -- potentially across multiple files. - if state.rules_by_name[v['rule']] == nil then - state.ordered_rule_names[#state.ordered_rule_names+1] = v['rule'] + -- Convert the priority-as-string to a priority-as-number now + v['priority_num'] = priority_num_for(v['priority']) + + if v['priority_num'] <= min_priority then + -- Note that we can overwrite rules, but the rules are still + -- loaded in the order in which they first appeared, + -- potentially across multiple files. + if state.rules_by_name[v['rule']] == nil then + state.ordered_rule_names[#state.ordered_rule_names+1] = v['rule'] + end + + -- The output field might be a folded-style, which adds a + -- newline to the end. Remove any trailing newlines. + v['output'] = compiler.trim(v['output']) + + state.rules_by_name[v['rule']] = v end - - -- The output field might be a folded-style, which adds a - -- newline to the end. Remove any trailing newlines. - v['output'] = compiler.trim(v['output']) - - state.rules_by_name[v['rule']] = v end else error ("Unknown rule object: "..table.tostring(v)) @@ -504,7 +520,7 @@ function on_event(evt_, rule_id) -- Prefix output with '*' so formatting is permissive output = "*"..rule.output - return rule.rule, rule.priority, output + return rule.rule, rule.priority_num, output end function print_stats() diff --git a/userspace/engine/rules.cpp b/userspace/engine/rules.cpp index cec545ec..c54d3839 100644 --- a/userspace/engine/rules.cpp +++ b/userspace/engine/rules.cpp @@ -145,7 +145,8 @@ void falco_rules::enable_rule(string &rule, bool enabled) void falco_rules::load_rules(const string &rules_content, bool verbose, bool all_events, - string &extra, bool replace_container_info) + string &extra, bool replace_container_info, + falco_common::priority_type min_priority) { lua_getglobal(m_ls, m_lua_load_rules.c_str()); if(lua_isfunction(m_ls, -1)) @@ -221,7 +222,8 @@ void falco_rules::load_rules(const string &rules_content, lua_pushboolean(m_ls, (all_events ? 1 : 0)); lua_pushstring(m_ls, extra.c_str()); lua_pushboolean(m_ls, (replace_container_info ? 1 : 0)); - if(lua_pcall(m_ls, 6, 0, 0) != 0) + lua_pushnumber(m_ls, min_priority); + if(lua_pcall(m_ls, 7, 0, 0) != 0) { const char* lerr = lua_tostring(m_ls, -1); string err = "Error loading rules:" + string(lerr); diff --git a/userspace/engine/rules.h b/userspace/engine/rules.h index 1770aacc..964c5072 100644 --- a/userspace/engine/rules.h +++ b/userspace/engine/rules.h @@ -24,6 +24,8 @@ along with falco. If not, see . #include "lua_parser.h" +#include "falco_common.h" + class falco_engine; class falco_rules @@ -32,7 +34,8 @@ class falco_rules falco_rules(sinsp* inspector, falco_engine *engine, lua_State *ls); ~falco_rules(); void load_rules(const string &rules_content, bool verbose, bool all_events, - std::string &extra, bool replace_container_info); + std::string &extra, bool replace_container_info, + falco_common::priority_type min_priority); void describe_rule(string *rule); static void init(lua_State *ls); diff --git a/userspace/falco/configuration.cpp b/userspace/falco/configuration.cpp index 74d828a0..08eb3f5c 100644 --- a/userspace/falco/configuration.cpp +++ b/userspace/falco/configuration.cpp @@ -108,6 +108,19 @@ void falco_configuration::init(string conf_filename, list &cmdline_optio m_notifications_rate = m_config->get_scalar("outputs", "rate", 1); m_notifications_max_burst = m_config->get_scalar("outputs", "max_burst", 1000); + string priority = m_config->get_scalar("priority", "debug"); + vector::iterator it; + + auto comp = [priority] (string &s) { + return (strcasecmp(s.c_str(), priority.c_str()) == 0); + }; + + if((it = std::find_if(falco_common::priority_names.begin(), falco_common::priority_names.end(), comp)) == falco_common::priority_names.end()) + { + throw invalid_argument("Unknown priority \"" + priority + "\"--must be one of emergency, alert, critical, error, warning, notice, informational, debug"); + } + m_min_priority = (falco_common::priority_type) (it - falco_common::priority_names.begin()); + falco_logger::log_stderr = m_config->get_scalar("log_stderr", false); falco_logger::log_syslog = m_config->get_scalar("log_syslog", true); } diff --git a/userspace/falco/configuration.h b/userspace/falco/configuration.h index 42f3b681..ff9999d6 100644 --- a/userspace/falco/configuration.h +++ b/userspace/falco/configuration.h @@ -146,6 +146,8 @@ class falco_configuration std::vector m_outputs; uint32_t m_notifications_rate; uint32_t m_notifications_max_burst; + + falco_common::priority_type m_min_priority; private: void init_cmdline_options(std::list &cmdline_options); diff --git a/userspace/falco/falco.cpp b/userspace/falco/falco.cpp index 690271ab..75f01e5a 100644 --- a/userspace/falco/falco.cpp +++ b/userspace/falco/falco.cpp @@ -212,7 +212,7 @@ uint64_t do_inspect(falco_engine *engine, unique_ptr res = engine->process_event(ev); if(res) { - outputs->handle_event(res->evt, res->rule, res->priority, res->format); + outputs->handle_event(res->evt, res->rule, res->priority_num, res->format); } num_evts++; @@ -461,6 +461,8 @@ int falco_init(int argc, char **argv) config.m_rules_filenames = rules_filenames; } + engine->set_min_priority(config.m_min_priority); + for (auto filename : config.m_rules_filenames) { engine->load_rules_file(filename, verbose, all_events); diff --git a/userspace/falco/falco_outputs.cpp b/userspace/falco/falco_outputs.cpp index 69c3b271..49cfcdf1 100644 --- a/userspace/falco/falco_outputs.cpp +++ b/userspace/falco/falco_outputs.cpp @@ -105,7 +105,7 @@ void falco_outputs::add_output(output_config oc) } -void falco_outputs::handle_event(sinsp_evt *ev, string &rule, string &priority, string &format) +void falco_outputs::handle_event(sinsp_evt *ev, string &rule, falco_common::priority_type priority, string &format) { if(!m_notifications_tb.claim()) { @@ -119,10 +119,11 @@ void falco_outputs::handle_event(sinsp_evt *ev, string &rule, string &priority, { lua_pushlightuserdata(m_ls, ev); lua_pushstring(m_ls, rule.c_str()); - lua_pushstring(m_ls, priority.c_str()); + lua_pushstring(m_ls, falco_common::priority_names[priority].c_str()); + lua_pushnumber(m_ls, priority); lua_pushstring(m_ls, format.c_str()); - if(lua_pcall(m_ls, 4, 0, 0) != 0) + if(lua_pcall(m_ls, 5, 0, 0) != 0) { const char* lerr = lua_tostring(m_ls, -1); string err = "Error invoking function output: " + string(lerr); diff --git a/userspace/falco/falco_outputs.h b/userspace/falco/falco_outputs.h index a1cd2876..7f3f1281 100644 --- a/userspace/falco/falco_outputs.h +++ b/userspace/falco/falco_outputs.h @@ -49,7 +49,7 @@ public: // ev is an event that has matched some rule. Pass the event // to all configured outputs. // - void handle_event(sinsp_evt *ev, std::string &rule, std::string &priority, std::string &format); + void handle_event(sinsp_evt *ev, std::string &rule, falco_common::priority_type priority, std::string &format); private: bool m_initialized; diff --git a/userspace/falco/lua/output.lua b/userspace/falco/lua/output.lua index a6aa42fb..f4d571f3 100644 --- a/userspace/falco/lua/output.lua +++ b/userspace/falco/lua/output.lua @@ -18,13 +18,9 @@ local mod = {} -levels = {"Emergency", "Alert", "Critical", "Error", "Warning", "Notice", "Informational", "Debug"} - -mod.levels = levels - local outputs = {} -function mod.stdout(level, msg) +function mod.stdout(priority, priority_num, msg) print (msg) end @@ -41,17 +37,17 @@ function mod.file_validate(options) end -function mod.file(level, msg, options) +function mod.file(priority, priority_num, msg, options) file = io.open(options.filename, "a+") file:write(msg, "\n") file:close() end -function mod.syslog(level, msg, options) - falco.syslog(level, msg) +function mod.syslog(priority, priority_num, msg, options) + falco.syslog(priority_num, msg) end -function mod.program(level, msg, options) +function mod.program(priority, priority_num, msg, options) -- XXX Ideally we'd check that the program ran -- successfully. However, the luajit we're using returns true even -- when the shell can't run the program. @@ -62,31 +58,19 @@ function mod.program(level, msg, options) file:close() end -local function level_of(s) - s = string.lower(s) - for i,v in ipairs(levels) do - if (string.find(string.lower(v), "^"..s)) then - return i - 1 -- (syslog levels start at 0, lua indices start at 1) - end - end - error("Invalid severity level: "..s) -end - -function output_event(event, rule, priority, format) - local level = level_of(priority) - +function output_event(event, rule, priority, priority_num, format) -- If format starts with a *, remove it, as we're adding our own -- prefix here. if format:sub(1,1) == "*" then format = format:sub(2) end - format = "*%evt.time: "..levels[level+1].." "..format + format = "*%evt.time: "..priority.." "..format - msg = formats.format_event(event, rule, levels[level+1], format) + msg = formats.format_event(event, rule, priority, format) for index,o in ipairs(outputs) do - o.output(level, msg, o.config) + o.output(priority, priority_num, msg, o.config) end end