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