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)