diff --git a/test/falco_test.py b/test/falco_test.py index 2c0131c7061..079723ceeb1 100644 --- a/test/falco_test.py +++ b/test/falco_test.py @@ -17,6 +17,8 @@ def setUp(self): """ self.falcodir = self.params.get('falcodir', '/', default=os.path.join(self.basedir, '../build')) + self.stderr_contains = self.params.get('stderr_contains', '*', default='') + self.exit_status = self.params.get('exit_status', '*', default=0) self.should_detect = self.params.get('detect', '*', default=False) self.trace_file = self.params.get('trace_file', '*') @@ -197,9 +199,18 @@ def test(self): res = self.falco_proc.run(timeout=180, sig=9) + if self.stderr_contains != '': + match = re.search(self.stderr_contains, res.stderr) + if match is None: + self.fail("Stderr of falco process did not contain content matching {}".format(self.stderr_contains)) + + if res.exit_status != self.exit_status: + self.error("Falco command \"{}\" exited with unexpected return value {} (!= {})".format( + cmd, res.exit_status, self.exit_status)) + + # No need to check any outputs if the falco process exited abnormally. if res.exit_status != 0: - self.error("Falco command \"{}\" exited with non-zero return value {}".format( - cmd, res.exit_status)) + return self.check_rules_warnings(res) if len(self.rules_events) > 0: diff --git a/test/falco_tests.yaml.in b/test/falco_tests.yaml.in index 694470049dc..37fe61d3571 100644 --- a/test/falco_tests.yaml.in +++ b/test/falco_tests.yaml.in @@ -95,6 +95,13 @@ trace_files: !mux - rules/double_rule.yaml trace_file: trace_files/cat_write.scap + invalid_rule_output: + exit_status: 1 + stderr_contains: "Runtime error: Error loading rules:.* Invalid output format 'An open was seen %not_a_real_field': 'invalid formatting token not_a_real_field'. Exiting." + rules_file: + - rules/invalid_rule_output.yaml + trace_file: trace_files/cat_write.scap + disabled_rules: detect: False rules_file: diff --git a/test/rules/invalid_rule_output.yaml b/test/rules/invalid_rule_output.yaml new file mode 100644 index 00000000000..91c15cd3edc --- /dev/null +++ b/test/rules/invalid_rule_output.yaml @@ -0,0 +1,5 @@ +- rule: rule_with_invalid_output + desc: A rule with an invalid output field + condition: evt.type=open + output: "An open was seen %not_a_real_field" + priority: WARNING \ No newline at end of file diff --git a/userspace/engine/CMakeLists.txt b/userspace/engine/CMakeLists.txt index dfc85495362..96ec10a7dc2 100644 --- a/userspace/engine/CMakeLists.txt +++ b/userspace/engine/CMakeLists.txt @@ -4,7 +4,7 @@ include_directories("${PROJECT_SOURCE_DIR}/../sysdig/userspace/libsinsp") include_directories("${PROJECT_BINARY_DIR}/userspace/engine") include_directories("${LUAJIT_INCLUDE}") -add_library(falco_engine STATIC rules.cpp falco_common.cpp falco_engine.cpp) +add_library(falco_engine STATIC rules.cpp falco_common.cpp falco_engine.cpp formats.cpp) target_include_directories(falco_engine PUBLIC "${LUAJIT_INCLUDE}") diff --git a/userspace/engine/falco_engine.cpp b/userspace/engine/falco_engine.cpp index e7dfcacc2da..3155b7a7be7 100644 --- a/userspace/engine/falco_engine.cpp +++ b/userspace/engine/falco_engine.cpp @@ -24,6 +24,8 @@ along with falco. If not, see . #include "falco_engine.h" #include "config_falco_engine.h" +#include "formats.h" + extern "C" { #include "lpeg.h" #include "lyaml.h" @@ -38,7 +40,8 @@ string lua_print_stats = "print_stats"; using namespace std; falco_engine::falco_engine(bool seed_rng) - : m_rules(NULL), m_sampling_ratio(1), m_sampling_multiplier(0) + : m_rules(NULL), m_sampling_ratio(1), m_sampling_multiplier(0), + m_replace_container_info(false) { luaopen_lpeg(m_ls); luaopen_yaml(m_ls); @@ -72,7 +75,16 @@ void falco_engine::load_rules(const string &rules_content, bool verbose, bool al { m_rules = new falco_rules(m_inspector, this, m_ls); } - m_rules->load_rules(rules_content, verbose, all_events); + + // Note that falco_formats is added to both the lua state used + // by the falco engine as well as the separate lua state used + // by falco outputs. Within the engine, only + // formats.formatter is used, so we can unconditionally set + // json_output to false. + 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); } void falco_engine::load_rules_file(const string &rules_filename, bool verbose, bool all_events) @@ -98,20 +110,20 @@ void falco_engine::enable_rule(string &pattern, bool enabled) m_evttype_filter.enable(pattern, enabled); } -falco_engine::rule_result *falco_engine::process_event(sinsp_evt *ev) +unique_ptr falco_engine::process_event(sinsp_evt *ev) { if(should_drop_evt()) { - return NULL; + return unique_ptr(); } if(!m_evttype_filter.run(ev)) { - return NULL; + return unique_ptr(); } - struct rule_result *res = new rule_result(); + unique_ptr res(new rule_result()); lua_getglobal(m_ls, lua_on_event.c_str()); @@ -184,6 +196,12 @@ void falco_engine::set_sampling_multiplier(double sampling_multiplier) m_sampling_multiplier = sampling_multiplier; } +void falco_engine::set_extra(string &extra, bool replace_container_info) +{ + m_extra = extra; + m_replace_container_info = replace_container_info; +} + inline bool falco_engine::should_drop_evt() { if(m_sampling_multiplier == 0) diff --git a/userspace/engine/falco_engine.h b/userspace/engine/falco_engine.h index 7b6c097140b..c540429edb1 100644 --- a/userspace/engine/falco_engine.h +++ b/userspace/engine/falco_engine.h @@ -63,7 +63,7 @@ class falco_engine : public falco_common // the rule that matched. If no rule matched, returns NULL. // // the reutrned rule_result is allocated and must be delete()d. - rule_result *process_event(sinsp_evt *ev); + std::unique_ptr process_event(sinsp_evt *ev); // // Print details on the given rule. If rule is NULL, print @@ -96,6 +96,16 @@ class falco_engine : public falco_common // void set_sampling_multiplier(double sampling_multiplier); + // + // You can optionally add "extra" formatting fields to the end + // of all output expressions. You can also choose to replace + // %container.info with the extra information or add it to the + // end of the expression. This is used in open source falco to + // add k8s/mesos/container information to outputs when + // available. + // + void set_extra(string &extra, bool replace_container_info); + private: // @@ -132,5 +142,8 @@ class falco_engine : public falco_common double m_sampling_multiplier; std::string m_lua_main_filename = "rule_loader.lua"; + + std::string m_extra; + bool m_replace_container_info; }; diff --git a/userspace/falco/formats.cpp b/userspace/engine/formats.cpp similarity index 94% rename from userspace/falco/formats.cpp rename to userspace/engine/formats.cpp index d3ef1f00e77..625f9cbfd57 100644 --- a/userspace/falco/formats.cpp +++ b/userspace/engine/formats.cpp @@ -39,7 +39,7 @@ void falco_formats::init(sinsp* inspector, lua_State *ls, bool json_output) s_inspector = inspector; s_json_output = json_output; - luaL_openlib(ls, "falco", ll_falco, 0); + luaL_openlib(ls, "formats", ll_falco, 0); } int falco_formats::formatter(lua_State *ls) @@ -52,7 +52,7 @@ int falco_formats::formatter(lua_State *ls) } catch(sinsp_exception& e) { - throw falco_exception("Invalid output format '" + format + "'.\n"); + luaL_error(ls, "Invalid output format '%s': '%s'", format.c_str(), e.what()); } lua_pushlightuserdata(ls, formatter); @@ -64,14 +64,14 @@ int falco_formats::free_formatter(lua_State *ls) { if (!lua_islightuserdata(ls, -1)) { - throw falco_exception("Invalid argument passed to free_formatter"); + luaL_error(ls, "Invalid argument passed to free_formatter"); } sinsp_evt_formatter *formatter = (sinsp_evt_formatter *) lua_topointer(ls, 1); delete(formatter); - return 1; + return 0; } int falco_formats::format_event (lua_State *ls) diff --git a/userspace/falco/formats.h b/userspace/engine/formats.h similarity index 97% rename from userspace/falco/formats.h rename to userspace/engine/formats.h index 83a3609cb12..e5a2781aa18 100644 --- a/userspace/falco/formats.h +++ b/userspace/engine/formats.h @@ -43,7 +43,4 @@ class falco_formats static int format_event(lua_State *ls); static sinsp* s_inspector; - - private: - lua_State* m_ls; }; diff --git a/userspace/engine/lua/rule_loader.lua b/userspace/engine/lua/rule_loader.lua index 837b73ec619..174bfd18e9e 100644 --- a/userspace/engine/lua/rule_loader.lua +++ b/userspace/engine/lua/rule_loader.lua @@ -162,7 +162,7 @@ function table.tostring( tbl ) end -function load_rules(rules_content, rules_mgr, verbose, all_events) +function load_rules(rules_content, rules_mgr, verbose, all_events, extra, replace_container_info) compiler.set_verbose(verbose) compiler.set_all_events(all_events) @@ -257,6 +257,36 @@ function load_rules(rules_content, rules_mgr, verbose, all_events) if (v['enabled'] == false) then falco_rules.enable_rule(rules_mgr, v['rule'], 0) end + + -- If the format string contains %container.info, replace it + -- with extra. Otherwise, add extra onto the end of the format + -- string. + if string.find(v['output'], "%container.info", nil, true) ~= nil then + + -- There may not be any extra, or we're not supposed + -- to replace it, in which case we use the generic + -- "%container.name (id=%container.id)" + if replace_container_info == false then + v['output'] = string.gsub(v['output'], "%%container.info", "%%container.name (id=%%container.id)") + if extra ~= "" then + v['output'] = v['output'].." "..extra + end + else + safe_extra = string.gsub(extra, "%%", "%%%%") + v['output'] = string.gsub(v['output'], "%%container.info", safe_extra) + end + else + -- Just add the extra to the end + if extra ~= "" then + v['output'] = v['output'].." "..extra + end + end + + -- Ensure that the output field is properly formatted by + -- creating a formatter from it. Any error will be thrown + -- up to the top level. + formatter = formats.formatter(v['output']) + formats.free_formatter(formatter) else error ("Unexpected type in load_rule: "..filter_ast.type) end diff --git a/userspace/engine/rules.cpp b/userspace/engine/rules.cpp index 8b14a1c3556..75bdfa08a2b 100644 --- a/userspace/engine/rules.cpp +++ b/userspace/engine/rules.cpp @@ -108,7 +108,9 @@ void falco_rules::enable_rule(string &rule, bool enabled) m_engine->enable_rule(rule, enabled); } -void falco_rules::load_rules(const string &rules_content, bool verbose, bool all_events) +void falco_rules::load_rules(const string &rules_content, + bool verbose, bool all_events, + string &extra, bool replace_container_info) { lua_getglobal(m_ls, m_lua_load_rules.c_str()); if(lua_isfunction(m_ls, -1)) @@ -182,7 +184,9 @@ void falco_rules::load_rules(const string &rules_content, bool verbose, bool all lua_pushlightuserdata(m_ls, this); lua_pushboolean(m_ls, (verbose ? 1 : 0)); lua_pushboolean(m_ls, (all_events ? 1 : 0)); - if(lua_pcall(m_ls, 4, 0, 0) != 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) { 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 da2e7b0630f..d81fa405577 100644 --- a/userspace/engine/rules.h +++ b/userspace/engine/rules.h @@ -31,7 +31,8 @@ class falco_rules public: falco_rules(sinsp* inspector, falco_engine *engine, lua_State *ls); ~falco_rules(); - void load_rules(const string &rules_content, bool verbose, bool all_events); + void load_rules(const string &rules_content, bool verbose, bool all_events, + std::string &extra, bool replace_container_info); void describe_rule(string *rule); static void init(lua_State *ls); diff --git a/userspace/falco/CMakeLists.txt b/userspace/falco/CMakeLists.txt index 9111bcfada5..41988076522 100644 --- a/userspace/falco/CMakeLists.txt +++ b/userspace/falco/CMakeLists.txt @@ -9,7 +9,7 @@ include_directories("${CURL_INCLUDE_DIR}") include_directories("${YAMLCPP_INCLUDE_DIR}") include_directories("${DRAIOS_DEPENDENCIES_DIR}/yaml-${DRAIOS_YAML_VERSION}/target/include") -add_executable(falco configuration.cpp formats.cpp logger.cpp falco_outputs.cpp falco.cpp) +add_executable(falco configuration.cpp logger.cpp falco_outputs.cpp falco.cpp) target_link_libraries(falco falco_engine sinsp) target_link_libraries(falco diff --git a/userspace/falco/falco.cpp b/userspace/falco/falco.cpp index b7c9d150b6f..e15bf2b2da8 100644 --- a/userspace/falco/falco.cpp +++ b/userspace/falco/falco.cpp @@ -171,11 +171,10 @@ uint64_t do_inspect(falco_engine *engine, // engine, which will match the event against the set // of rules. If a match is found, pass the event to // the outputs. - falco_engine::rule_result *res = engine->process_event(ev); + unique_ptr res = engine->process_event(ev); if(res) { outputs->handle_event(res->evt, res->rule, res->priority, res->format); - delete(res); } num_evts++; @@ -337,10 +336,10 @@ int falco_init(int argc, char **argv) inspector = new sinsp(); engine = new falco_engine(); engine->set_inspector(inspector); + engine->set_extra(output_format, replace_container_info); outputs = new falco_outputs(); outputs->set_inspector(inspector); - outputs->set_extra(output_format, replace_container_info); // Some combinations of arguments are not allowed. if (daemon && pidfilename == "") { diff --git a/userspace/falco/falco_outputs.cpp b/userspace/falco/falco_outputs.cpp index 545ade7517a..f77eb80231b 100644 --- a/userspace/falco/falco_outputs.cpp +++ b/userspace/falco/falco_outputs.cpp @@ -27,7 +27,6 @@ along with falco. If not, see . using namespace std; falco_outputs::falco_outputs() - : m_replace_container_info(false) { } @@ -47,17 +46,14 @@ void falco_outputs::init(bool json_output) falco_common::init(m_lua_main_filename.c_str(), FALCO_SOURCE_LUA_DIR); + // Note that falco_formats is added to both the lua state used + // by the falco engine as well as the separate lua state used + // by falco outputs. falco_formats::init(m_inspector, m_ls, json_output); falco_logger::init(m_ls); } -void falco_outputs::set_extra(string &extra, bool replace_container_info) -{ - m_extra = extra; - m_replace_container_info = replace_container_info; -} - void falco_outputs::add_output(output_config oc) { uint8_t nargs = 1; @@ -94,42 +90,12 @@ void falco_outputs::handle_event(sinsp_evt *ev, string &level, string &priority, { lua_getglobal(m_ls, m_lua_output_event.c_str()); - // If the format string contains %container.info, replace it - // with extra. Otherwise, add extra onto the end of the format - // string. - string format_w_extra = format; - size_t pos; - - if((pos = format_w_extra.find("%container.info")) != string::npos) - { - // There may not be any extra, or we're not supposed - // to replace it, in which case we use the generic - // "%container.name (id=%container.id)" - if(m_extra == "" || ! m_replace_container_info) - { - // 15 == strlen(%container.info) - format_w_extra.replace(pos, 15, "%container.name (id=%container.id)"); - } - else - { - format_w_extra.replace(pos, 15, m_extra); - } - } - else - { - // Just add the extra to the end - if (m_extra != "") - { - format_w_extra += " " + m_extra; - } - } - if(lua_isfunction(m_ls, -1)) { lua_pushlightuserdata(m_ls, ev); lua_pushstring(m_ls, level.c_str()); lua_pushstring(m_ls, priority.c_str()); - lua_pushstring(m_ls, format_w_extra.c_str()); + lua_pushstring(m_ls, format.c_str()); if(lua_pcall(m_ls, 4, 0, 0) != 0) { diff --git a/userspace/falco/falco_outputs.h b/userspace/falco/falco_outputs.h index 1f13f653489..247d837c831 100644 --- a/userspace/falco/falco_outputs.h +++ b/userspace/falco/falco_outputs.h @@ -44,8 +44,6 @@ class falco_outputs : public falco_common void add_output(output_config oc); - void set_extra(string &extra, bool replace_container_info); - // // ev is an event that has matched some rule. Pass the event // to all configured outputs. @@ -56,6 +54,4 @@ class falco_outputs : public falco_common std::string m_lua_add_output = "add_output"; std::string m_lua_output_event = "output_event"; std::string m_lua_main_filename = "output.lua"; - std::string m_extra; - bool m_replace_container_info; }; diff --git a/userspace/falco/lua/output.lua b/userspace/falco/lua/output.lua index bd757916e36..e2ec0127e26 100644 --- a/userspace/falco/lua/output.lua +++ b/userspace/falco/lua/output.lua @@ -75,14 +75,14 @@ end function output_event(event, rule, priority, format) local level = level_of(priority) format = "*%evt.time: "..levels[level+1].." "..format - formatter = falco.formatter(format) - msg = falco.format_event(event, rule, levels[level+1], formatter) + formatter = formats.formatter(format) + msg = formats.format_event(event, rule, levels[level+1], formatter) for index,o in ipairs(outputs) do o.output(level, msg, o.config) end - falco.free_formatter(formatter) + formats.free_formatter(formatter) end function add_output(output_name, config)