From 5955c00f9ca4b8c1104974854c560cad43249e4a Mon Sep 17 00:00:00 2001 From: Mark Stemm Date: Wed, 13 Jul 2016 17:57:11 -0700 Subject: [PATCH 1/5] Add a verbose flag. Add a verbose flag -v which implies printing additional info. This is passed down to lua during load_rules and sets the per-module verbose value for the compiler and parser modules. Later commits will use this to print additional info when loading rules. --- userspace/falco/falco.cpp | 9 +++++++-- userspace/falco/lua/compiler.lua | 7 +++++++ userspace/falco/lua/parser.lua | 6 ++++++ userspace/falco/lua/rule_loader.lua | 4 +++- userspace/falco/rules.cpp | 5 +++-- userspace/falco/rules.h | 2 +- 6 files changed, 27 insertions(+), 6 deletions(-) diff --git a/userspace/falco/falco.cpp b/userspace/falco/falco.cpp index d269a473..d3b8ed14 100644 --- a/userspace/falco/falco.cpp +++ b/userspace/falco/falco.cpp @@ -55,6 +55,7 @@ static void usage() " -r Rules file (defaults to value set in configuration file, or /etc/falco_rules.yaml).\n" " -L Show the name and description of all rules and exit.\n" " -l Show the name and description of the rule with name and exit.\n" + " -v Verbose output.\n" "\n" ); } @@ -253,6 +254,7 @@ int falco_init(int argc, char **argv) string pidfilename = "/var/run/falco.pid"; bool describe_all_rules = false; string describe_rule = ""; + bool verbose = false; static struct option long_options[] = { @@ -272,7 +274,7 @@ int falco_init(int argc, char **argv) // Parse the args // while((op = getopt_long(argc, argv, - "c:ho:e:r:dp:Ll:", + "c:ho:e:r:dp:Ll:v", long_options, &long_index)) != -1) { switch(op) @@ -301,6 +303,9 @@ int falco_init(int argc, char **argv) case 'L': describe_all_rules = true; break; + case 'v': + verbose = true; + break; case 'l': describe_rule = optarg; break; @@ -397,7 +402,7 @@ int falco_init(int argc, char **argv) inspector->set_drop_event_flags(EF_DROP_FALCO); - rules->load_rules(config.m_rules_filename); + rules->load_rules(config.m_rules_filename, verbose); inspector->set_filter(rules->get_filter()); falco_logger::log(LOG_INFO, "Parsed rules from file " + config.m_rules_filename + "\n"); diff --git a/userspace/falco/lua/compiler.lua b/userspace/falco/lua/compiler.lua index df88e7fb..9c6a59be 100644 --- a/userspace/falco/lua/compiler.lua +++ b/userspace/falco/lua/compiler.lua @@ -1,6 +1,13 @@ local parser = require("parser") local compiler = {} +compiler.verbose = false + +function compiler.set_verbose(verbose) + compiler.verbose = verbose + parser.set_verbose(verbose) +end + function map(f, arr) local res = {} for i,v in ipairs(arr) do diff --git a/userspace/falco/lua/parser.lua b/userspace/falco/lua/parser.lua index 5f5f9558..62b238a6 100644 --- a/userspace/falco/lua/parser.lua +++ b/userspace/falco/lua/parser.lua @@ -11,6 +11,12 @@ local parser = {} +parser.verbose = false + +function parser.set_verbose(verbose) + parser.verbose = verbose +end + local lpeg = require "lpeg" lpeg.locale(lpeg) diff --git a/userspace/falco/lua/rule_loader.lua b/userspace/falco/lua/rule_loader.lua index f668de60..24180e51 100644 --- a/userspace/falco/lua/rule_loader.lua +++ b/userspace/falco/lua/rule_loader.lua @@ -117,7 +117,9 @@ end -- to a rule. local state = {macros={}, lists={}, filter_ast=nil, rules_by_name={}, n_rules=0, rules_by_idx={}} -function load_rules(filename) +function load_rules(filename, verbose) + + compiler.set_verbose(verbose) local f = assert(io.open(filename, "r")) local s = f:read("*all") diff --git a/userspace/falco/rules.cpp b/userspace/falco/rules.cpp index dc2b7072..25926afe 100644 --- a/userspace/falco/rules.cpp +++ b/userspace/falco/rules.cpp @@ -40,7 +40,7 @@ void falco_rules::load_compiler(string lua_main_filename) } } -void falco_rules::load_rules(string rules_filename) +void falco_rules::load_rules(string rules_filename, bool verbose) { lua_getglobal(m_ls, m_lua_load_rules.c_str()); if(lua_isfunction(m_ls, -1)) @@ -82,7 +82,8 @@ void falco_rules::load_rules(string rules_filename) lua_setglobal(m_ls, m_lua_ignored_syscalls.c_str()); lua_pushstring(m_ls, rules_filename.c_str()); - if(lua_pcall(m_ls, 1, 0, 0) != 0) + lua_pushboolean(m_ls, (verbose ? 1 : 0)); + if(lua_pcall(m_ls, 2, 0, 0) != 0) { const char* lerr = lua_tostring(m_ls, -1); string err = "Error loading rules:" + string(lerr); diff --git a/userspace/falco/rules.h b/userspace/falco/rules.h index 91bf6fa5..547ae210 100644 --- a/userspace/falco/rules.h +++ b/userspace/falco/rules.h @@ -8,7 +8,7 @@ class falco_rules public: falco_rules(sinsp* inspector, lua_State *ls, string lua_main_filename); ~falco_rules(); - void load_rules(string rules_filename); + void load_rules(string rules_filename, bool verbose); void describe_rule(string *rule); sinsp_filter* get_filter(); From 8050009aa508a05adfb32e093812bc9f24073157 Mon Sep 17 00:00:00 2001 From: Mark Stemm Date: Thu, 14 Jul 2016 12:51:37 -0700 Subject: [PATCH 2/5] Add support for event-specific filters. Instead of combining all rules into one huge filter expression and giving it to the inspector, keep each filter expression separate and annotate it with the events for which the rule applies. This uses the capabilties in draios/sysdig#627 to have multiple sets of event-specific filters. Change traverse_ast to allow a set of node types instead of a single node type. Within the compiler, a new pass over the ast get_evttypes looks for evt.type clauses, converts the evt.type as a string to any event type ids for which it may apply, and passes that back with the compiled rule. As rule conditions may refer to evt.types in negative contexts (i.e. evt.type != XXX, or not evt.type = XXX), this pass prefers rules that list event type checks at the beginning of conditions, and allows other rules with a warning. When traversing the ast looking for evt.type checks, once any "!=" or "not ..." is seen, no other evt.type checks are "allowed". If one is found, the rule is considered ambiguous wrt event types. In this case, a warning is printed and the rule is associated with a catchall set that runs for all event types. Also, instead of rejecting rules with no event type check, print a warning and associate it with the catchall set. In the rule loader, create a new global events that maps each event as a string to the list of event ids for which it may apply. Instead of calling install_filter once after all rules have been loaded, call a new function add_filter for each rule. In turn, it passes the rule and list of event ids to the inspector using add_evttype_filter(). Also, with -v (verbose) also print the exact set of events found for each event type. This is used by a upcoming change to the set of unit tests. --- userspace/falco/falco.cpp | 2 +- userspace/falco/lua/compiler.lua | 104 +++++++++++++++++++++++++++- userspace/falco/lua/parser.lua | 18 ++--- userspace/falco/lua/rule_loader.lua | 11 ++- userspace/falco/rules.cpp | 86 +++++++++++++++++++++-- userspace/falco/rules.h | 8 +++ 6 files changed, 209 insertions(+), 20 deletions(-) diff --git a/userspace/falco/falco.cpp b/userspace/falco/falco.cpp index d3b8ed14..5c5b1442 100644 --- a/userspace/falco/falco.cpp +++ b/userspace/falco/falco.cpp @@ -399,11 +399,11 @@ int falco_init(int argc, char **argv) falco_fields::init(inspector, ls); falco_logger::init(ls); + falco_rules::init(ls); inspector->set_drop_event_flags(EF_DROP_FALCO); rules->load_rules(config.m_rules_filename, verbose); - inspector->set_filter(rules->get_filter()); falco_logger::log(LOG_INFO, "Parsed rules from file " + config.m_rules_filename + "\n"); if (describe_all_rules) diff --git a/userspace/falco/lua/compiler.lua b/userspace/falco/lua/compiler.lua index 9c6a59be..d51a048f 100644 --- a/userspace/falco/lua/compiler.lua +++ b/userspace/falco/lua/compiler.lua @@ -160,7 +160,103 @@ function check_for_ignored_syscalls_events(ast, filter_type, source) end end - parser.traverse_ast(ast, "BinaryRelOp", cb) + parser.traverse_ast(ast, {BinaryRelOp=1}, cb) +end + +-- Examine the ast and find the event types for which the rule should +-- run. All evt.type references are added as event types up until the +-- first "!=" binary operator or unary not operator. If no event type +-- checks are found afterward in the rule, the rule is considered +-- optimized and is associated with the event type(s). +-- +-- Otherwise, the rule is associated with a 'catchall' category and is +-- run for all event types. (Also, a warning is printed). +-- + +function get_evttypes(name, ast, source) + + local evttypes = {} + local evtnames = {} + local found_event = false + local found_not = false + local found_event_after_not = false + + function cb(node) + if node.type == "UnaryBoolOp" then + if node.operator == "not" then + found_not = true + end + else + if node.operator == "!=" then + found_not = true + end + if node.left.type == "FieldName" and node.left.value == "evt.type" then + found_event = true + if found_not then + found_event_after_not = true + end + if node.operator == "in" then + for i, v in ipairs(node.right.elements) do + if v.type == "BareString" then + evtnames[v.value] = 1 + for id in string.gmatch(events[v.value], "%S+") do + evttypes[id] = 1 + end + end + end + else + if node.right.type == "BareString" then + evtnames[node.right.value] = 1 + for id in string.gmatch(events[node.right.value], "%S+") do + evttypes[id] = 1 + end + end + end + end + end + end + + parser.traverse_ast(ast.filter.value, {BinaryRelOp=1, UnaryBoolOp=1} , cb) + + if not found_event then + io.stderr:write("Rule "..name..": warning (no-evttype):\n") + io.stderr:write(source.."\n") + io.stderr:write(" did not contain any evt.type restriction, meaning it will run for all event types.\n") + io.stderr:write(" This has a significant performance penalty. Consider adding an evt.type restriction if possible.\n") + evttypes = {} + evtnames = {} + end + + if found_event_after_not then + io.stderr:write("Rule "..name..": warning (trailing-evttype):\n") + io.stderr:write(source.."\n") + io.stderr:write(" does not have all evt.type restrictions at the beginning of the condition,\n") + io.stderr:write(" or uses a negative match (i.e. \"not\"/\"!=\") for some evt.type restriction.\n") + io.stderr:write(" This has a performance penalty, as the rule can not be limited to specific event types.\n") + io.stderr:write(" Consider moving all evt.type restrictions to the beginning of the rule and/or\n") + io.stderr:write(" replacing negative matches with positive matches if possible.\n") + evttypes = {} + evtnames = {} + end + + evtnames_only = {} + local num_evtnames = 0 + for name, dummy in pairs(evtnames) do + table.insert(evtnames_only, name) + num_evtnames = num_evtnames + 1 + end + + if num_evtnames == 0 then + table.insert(evtnames_only, "all") + end + + table.sort(evtnames_only) + + if compiler.verbose then + io.stderr:write("Event types for rule "..name..": "..table.concat(evtnames_only, ",").."\n") + end + + return evttypes end function compiler.compile_macro(line, list_defs) @@ -186,7 +282,7 @@ end --[[ Parses a single filter, then expands macros using passed-in table of definitions. Returns resulting AST. --]] -function compiler.compile_filter(source, macro_defs, list_defs) +function compiler.compile_filter(name, source, macro_defs, list_defs) for name, items in pairs(list_defs) do source = string.gsub(source, name, table.concat(items, ", ")) @@ -213,7 +309,9 @@ function compiler.compile_filter(source, macro_defs, list_defs) error("Unexpected top-level AST type: "..ast.type) end - return ast + evttypes = get_evttypes(name, ast, source) + + return ast, evttypes end diff --git a/userspace/falco/lua/parser.lua b/userspace/falco/lua/parser.lua index 62b238a6..dd03b1d3 100644 --- a/userspace/falco/lua/parser.lua +++ b/userspace/falco/lua/parser.lua @@ -303,33 +303,33 @@ parser.print_ast = print_ast -- have the signature: -- cb(ast_node, ctx) -- ctx is optional. -function traverse_ast(ast, node_type, cb, ctx) +function traverse_ast(ast, node_types, cb, ctx) local t = ast.type - if t == node_type then + if node_types[t] ~= nil then cb(ast, ctx) end if t == "Rule" then - traverse_ast(ast.filter, node_type, cb, ctx) + traverse_ast(ast.filter, node_types, cb, ctx) elseif t == "Filter" then - traverse_ast(ast.value, node_type, cb, ctx) + traverse_ast(ast.value, node_types, cb, ctx) elseif t == "BinaryBoolOp" or t == "BinaryRelOp" then - traverse_ast(ast.left, node_type, cb, ctx) - traverse_ast(ast.right, node_type, cb, ctx) + traverse_ast(ast.left, node_types, cb, ctx) + traverse_ast(ast.right, node_types, cb, ctx) elseif t == "UnaryRelOp" or t == "UnaryBoolOp" then - traverse_ast(ast.argument, node_type, cb, ctx) + traverse_ast(ast.argument, node_types, cb, ctx) elseif t == "List" then for i, v in ipairs(ast.elements) do - traverse_ast(v, node_type, cb, ctx) + traverse_ast(v, node_types, cb, ctx) end elseif t == "MacroDef" then - traverse_ast(ast.value, node_type, cb, ctx) + traverse_ast(ast.value, node_types, cb, ctx) elseif t == "FieldName" or t == "Number" or t == "String" or t == "BareString" or t == "Macro" then -- do nothing, no traversal needed diff --git a/userspace/falco/lua/rule_loader.lua b/userspace/falco/lua/rule_loader.lua index 24180e51..cf5a439f 100644 --- a/userspace/falco/lua/rule_loader.lua +++ b/userspace/falco/lua/rule_loader.lua @@ -117,7 +117,7 @@ end -- to a rule. local state = {macros={}, lists={}, filter_ast=nil, rules_by_name={}, n_rules=0, rules_by_idx={}} -function load_rules(filename, verbose) +function load_rules(filename, rules_mgr, verbose) compiler.set_verbose(verbose) @@ -171,7 +171,8 @@ function load_rules(filename, verbose) v['level'] = priority(v['priority']) state.rules_by_name[v['rule']] = v - local filter_ast = compiler.compile_filter(v['condition'], state.macros, state.lists) + local filter_ast, evttypes = compiler.compile_filter(v['rule'], v['condition'], + state.macros, state.lists) if (filter_ast.type == "Rule") then state.n_rules = state.n_rules + 1 @@ -185,6 +186,11 @@ function load_rules(filename, verbose) -- event. mark_relational_nodes(filter_ast.filter.value, state.n_rules) + install_filter(filter_ast.filter.value) + + -- Pass the filter and event types back up + falco_rules.add_filter(rules_mgr, evttypes) + -- Rule ASTs are merged together into one big AST, with "OR" between each -- rule. if (state.filter_ast == nil) then @@ -198,7 +204,6 @@ function load_rules(filename, verbose) end end - install_filter(state.filter_ast) io.flush() end diff --git a/userspace/falco/rules.cpp b/userspace/falco/rules.cpp index 25926afe..4bb78949 100644 --- a/userspace/falco/rules.cpp +++ b/userspace/falco/rules.cpp @@ -1,4 +1,5 @@ #include "rules.h" +#include "logger.h" extern "C" { #include "lua.h" @@ -6,6 +7,11 @@ extern "C" { #include "lauxlib.h" } +const static struct luaL_reg ll_falco_rules [] = +{ + {"add_filter", &falco_rules::add_filter}, + {NULL,NULL} +}; falco_rules::falco_rules(sinsp* inspector, lua_State *ls, string lua_main_filename) { @@ -17,6 +23,48 @@ falco_rules::falco_rules(sinsp* inspector, lua_State *ls, string lua_main_filena load_compiler(lua_main_filename); } +void falco_rules::init(lua_State *ls) +{ + luaL_openlib(ls, "falco_rules", ll_falco_rules, 0); +} + +int falco_rules::add_filter(lua_State *ls) +{ + if (! lua_islightuserdata(ls, -2) || + ! lua_istable(ls, -1)) + { + falco_logger::log(LOG_ERR, "Invalid arguments passed to add_filter()\n"); + throw sinsp_exception("add_filter error"); + } + + falco_rules *rules = (falco_rules *) lua_topointer(ls, -2); + + list evttypes; + + lua_pushnil(ls); /* first key */ + while (lua_next(ls, -2) != 0) { + // key is at index -2, value is at index + // -1. We want the keys. + evttypes.push_back(luaL_checknumber(ls, -2)); + + // Remove value, keep key for next iteration + lua_pop(ls, 1); + } + + rules->add_filter(evttypes); + + return 0; +} + +void falco_rules::add_filter(list &evttypes) +{ + // While the current rule was being parsed, a sinsp_filter + // object was being populated by lua_parser. Grab that filter + // and pass it to the inspector. + sinsp_filter *filter = m_lua_parser->get_filter(true); + + m_inspector->add_evttype_filter(evttypes, filter); +} void falco_rules::load_compiler(string lua_main_filename) { @@ -45,13 +93,42 @@ void falco_rules::load_rules(string rules_filename, bool verbose) lua_getglobal(m_ls, m_lua_load_rules.c_str()); if(lua_isfunction(m_ls, -1)) { + // Create a table containing all events, so they can + // be mapped to event ids. + sinsp_evttables* einfo = m_inspector->get_event_info_tables(); + const struct ppm_event_info* etable = einfo->m_event_info; + const struct ppm_syscall_desc* stable = einfo->m_syscall_info_table; + + map events_by_name; + for(uint32_t j = 0; j < PPM_EVENT_MAX; j++) + { + auto it = events_by_name.find(etable[j].name); + + if (it == events_by_name.end()) { + events_by_name[etable[j].name] = to_string(j); + } else { + string cur = it->second; + cur += " "; + cur += to_string(j); + events_by_name[etable[j].name] = cur; + } + } + + lua_newtable(m_ls); + + for( auto kv : events_by_name) + { + lua_pushstring(m_ls, kv.first.c_str()); + lua_pushstring(m_ls, kv.second.c_str()); + lua_settable(m_ls, -3); + } + + lua_setglobal(m_ls, m_lua_events.c_str()); + // Create a table containing the syscalls/events that // are ignored by the kernel module. load_rules will // return an error if any rule references one of these // syscalls/events. - sinsp_evttables* einfo = m_inspector->get_event_info_tables(); - const struct ppm_event_info* etable = einfo->m_event_info; - const struct ppm_syscall_desc* stable = einfo->m_syscall_info_table; lua_newtable(m_ls); @@ -82,8 +159,9 @@ void falco_rules::load_rules(string rules_filename, bool verbose) lua_setglobal(m_ls, m_lua_ignored_syscalls.c_str()); lua_pushstring(m_ls, rules_filename.c_str()); + lua_pushlightuserdata(m_ls, this); lua_pushboolean(m_ls, (verbose ? 1 : 0)); - if(lua_pcall(m_ls, 2, 0, 0) != 0) + if(lua_pcall(m_ls, 3, 0, 0) != 0) { const char* lerr = lua_tostring(m_ls, -1); string err = "Error loading rules:" + string(lerr); diff --git a/userspace/falco/rules.h b/userspace/falco/rules.h index 547ae210..52cc7f4b 100644 --- a/userspace/falco/rules.h +++ b/userspace/falco/rules.h @@ -1,5 +1,7 @@ #pragma once +#include + #include "sinsp.h" #include "lua_parser.h" @@ -12,9 +14,14 @@ class falco_rules void describe_rule(string *rule); sinsp_filter* get_filter(); + static void init(lua_State *ls); + static int add_filter(lua_State *ls); + private: void load_compiler(string lua_main_filename); + void add_filter(list &evttypes); + lua_parser* m_lua_parser; sinsp* m_inspector; lua_State* m_ls; @@ -22,6 +29,7 @@ class falco_rules string m_lua_load_rules = "load_rules"; string m_lua_ignored_syscalls = "ignored_syscalls"; string m_lua_ignored_events = "ignored_events"; + string m_lua_events = "events"; string m_lua_on_event = "on_event"; string m_lua_describe_rule = "describe_rule"; }; From b76423b31dd7f6639d699400d77480b8e930b5d3 Mon Sep 17 00:00:00 2001 From: Mark Stemm Date: Fri, 10 Jun 2016 15:35:15 -0700 Subject: [PATCH 3/5] Useful scripts to collect/display perf results. Add shell scripts to make it easier to collect performance results from traces, live tests, and phoronix tests. With run_performance_tests.sh you specify the following: - a subject program to run, using --root - a name to give to this set of results, using --variant - a test to run, using --test - a file to write the results to, using --results. For tests that start with "trace", the script runs falco/sysdig on the trace file and measures the time taken to read the file. For other tests, he script handles starting falco/sysdig, starting a cpu measurement script (a wrapper around top, just to provide identical values to what you would see using top) to measure the cpu usage of falco/sysdig, and running a live test. The measurement interval for cpu usage depends on the test being run--10 seconds for most tests, 2 seconds for shorter tests. The output is written as json to the file specified in --results. Also add R scripts to easily display the results from the shell script. plot-live.r shows a linechart of the cpu usage for the provided variants over time. plot-traces.r shows grouped barcharts showing user/system/total time taken for the provided variants and traces. One bug--you have to make the results file actual json by adding leading/trailing []s. --- test/cpu_monitor.sh | 9 + test/plot-live.r | 40 +++++ test/plot-traces.r | 35 ++++ test/run_performance_tests.sh | 316 ++++++++++++++++++++++++++++++++++ 4 files changed, 400 insertions(+) create mode 100644 test/cpu_monitor.sh create mode 100644 test/plot-live.r create mode 100644 test/plot-traces.r create mode 100644 test/run_performance_tests.sh diff --git a/test/cpu_monitor.sh b/test/cpu_monitor.sh new file mode 100644 index 00000000..594536d3 --- /dev/null +++ b/test/cpu_monitor.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +SUBJ_PID=$1 +BENCHMARK=$2 +VARIANT=$3 +RESULTS_FILE=$4 +CPU_INTERVAL=$5 + +top -d $CPU_INTERVAL -b -p $SUBJ_PID | grep -E '(falco|sysdig)' --line-buffered | awk -v benchmark=$BENCHMARK -v variant=$VARIANT '{printf("{\"sample\": %d, \"benchmark\": \"%s\", \"variant\": \"%s\", \"cpu_usage\": %s},\n", NR, benchmark, variant, $9); fflush();}' >> $RESULTS_FILE diff --git a/test/plot-live.r b/test/plot-live.r new file mode 100644 index 00000000..1305da65 --- /dev/null +++ b/test/plot-live.r @@ -0,0 +1,40 @@ +require(jsonlite) +library(ggplot2) +library(GetoptLong) + +initial.options <- commandArgs(trailingOnly = FALSE) +file.arg.name <- "--file=" +script.name <- sub(file.arg.name, "", initial.options[grep(file.arg.name, initial.options)]) +script.basename <- dirname(script.name) + +if (substr(script.basename, 1, 1) != '/') { + script.basename = paste(getwd(), script.basename, sep='/') +} + +results = paste(script.basename, "results.json", sep='/') +output = "./output.png" + +GetoptLong( + "results=s", "Path to results file", + "benchmark=s", "Benchmark from results file to graph", + "variant=s@", "Variant(s) to include in graph. Can be specified multiple times", + "output=s", "Output graph file" +) + +res <- fromJSON(results, flatten=TRUE) + +res2 = res[res$benchmark == benchmark & res$variant %in% variant,] + +plot <- ggplot(data=res2, aes(x=sample, y=cpu_usage, group=variant, colour=variant)) + + geom_line() + + ylab("CPU Usage (%)") + + xlab("Time") + + ggtitle(sprintf("Falco/Sysdig CPU Usage: %s", benchmark)) + theme(legend.position=c(.2, .88)); + +print(paste("Writing graph to", output, sep=" ")) +ggsave(file=output) + + + + diff --git a/test/plot-traces.r b/test/plot-traces.r new file mode 100644 index 00000000..a38bfaf9 --- /dev/null +++ b/test/plot-traces.r @@ -0,0 +1,35 @@ +require(jsonlite) +library(ggplot2) +library(reshape) + +res <- fromJSON("/home/mstemm/results.txt", flatten=TRUE) + +plot <- ggplot(data=res, aes(x=config, y=elapsed.real)) + + geom_bar(stat = "summary", fun.y = "mean") + + coord_flip() + + facet_grid(shortfile ~ .) + + ylab("Wall Clock Time (sec)") + + xlab("Trace File/Program") + + +ggsave(file="/mnt/sf_mstemm/res-real.png") + +plot <- ggplot(data=res, aes(x=config, y=elapsed.user)) + + geom_bar(stat = "summary", fun.y = "mean") + + coord_flip() + + facet_grid(shortfile ~ .) + + ylab("User Time (sec)") + + xlab("Trace File/Program") + + +ggsave(file="/mnt/sf_mstemm/res-user.png") + +res2 <- melt(res, id.vars = c("config", "shortfile"), measure.vars = c("elapsed.sys", "elapsed.user")) +plot <- ggplot(data=res2, aes(x=config, y=value, fill=variable, order=variable)) + + geom_bar(stat = "summary", fun.y = "mean") + + coord_flip() + + facet_grid(shortfile ~ .) + + ylab("User/System Time (sec)") + + xlab("Trace File/Program") + +ggsave(file="/mnt/sf_mstemm/res-sys-user.png") diff --git a/test/run_performance_tests.sh b/test/run_performance_tests.sh new file mode 100644 index 00000000..4bdf2bc3 --- /dev/null +++ b/test/run_performance_tests.sh @@ -0,0 +1,316 @@ +#!/bin/bash + +#set -x + +trap "cleanup; exit" SIGHUP SIGINT SIGTERM + +function download_trace_files() { + + (mkdir -p $TRACEDIR && rm -rf $TRACEDIR/traces-perf && curl -fo $TRACEDIR/traces-perf.zip https://s3.amazonaws.com/download.draios.com/falco-tests/traces-perf.zip && unzip -d $TRACEDIR $TRACEDIR/traces-perf.zip && rm -f $TRACEDIR/traces-perf.zip) || exit 1 + +} + +function time_cmd() { + cmd="$1" + file="$2" + + benchmark=`basename $file .scap` + + echo -n "$benchmark: " + for i in `seq 1 5`; do + echo -n "$i " + time=`date --iso-8601=sec` + /usr/bin/time -a -o $RESULTS_FILE --format "{\"time\": \"$time\", \"benchmark\": \"$benchmark\", \"file\": \"$file\", \"variant\": \"$VARIANT\", \"elapsed\": {\"real\": %e, \"user\": %U, \"sys\": %S}}," $cmd >> $OUTPUT_FILE 2>&1 + done + echo "" +} + +function run_falco_on() { + file="$1" + + cmd="$ROOT/userspace/falco/falco -c $ROOT/../falco.yaml -r $ROOT/../rules/falco_rules.yaml --option=stdout_output.enabled=false -e $file" + + time_cmd "$cmd" "$file" +} + +function run_sysdig_on() { + file="$1" + + cmd="$ROOT/userspace/sysdig/sysdig -N -z -r $file evt.type=none" + + time_cmd "$cmd" "$file" +} + +function run_trace() { + + if [ ! -e $TRACEDIR ]; then + download_trace_files + fi + + trace_file="$1" + + if [ $trace_file == "all" ]; then + files=($TRACEDIR/traces-perf/*.scap) + else + files=($TRACEDIR/traces-perf/$trace_file.scap) + fi + + for file in ${files[@]}; do + if [[ $ROOT == *"falco"* ]]; then + run_falco_on "$file" + else + run_sysdig_on "$file" + fi + done +} + +function start_monitor_cpu_usage() { + echo " monitoring cpu usage for sysdig/falco program" + + setsid bash `dirname $0`/cpu_monitor.sh $SUBJ_PID $live_test $VARIANT $RESULTS_FILE $CPU_INTERVAL & + CPU_PID=$! + sleep 5 +} + +function start_subject_prog() { + + echo " starting falco/sysdig program" + # Do a blocking sudo command now just to ensure we have a password + sudo bash -c "" + + if [[ $ROOT == *"falco"* ]]; then + sudo $ROOT/userspace/falco/falco -c $ROOT/../falco.yaml -r $ROOT/../rules/falco_rules.yaml --option=stdout_output.enabled=false > ./prog-output.txt 2>&1 & + else + sudo $ROOT/userspace/sysdig/sysdig -N -z evt.type=none & + fi + + SUDO_PID=$! + sleep 5 + SUBJ_PID=`ps -h -o pid --ppid $SUDO_PID` + + if [ -z $SUBJ_PID ]; then + echo "Could not find pid of subject program--did it start successfully? Not continuing." + exit 1 + fi +} + +function run_htop() { + screen -S htop-screen -d -m /usr/bin/htop -d2 + sleep 90 + screen -X -S htop-screen quit +} + +function run_juttle_examples() { + pushd $SCRIPTDIR/../../juttle-engine/examples + docker-compose -f dc-juttle-engine.yml -f aws-cloudwatch/dc-aws-cloudwatch.yml -f elastic-newstracker/dc-elastic.yml -f github-tutorial/dc-elastic.yml -f nginx_logs/dc-nginx-logs.yml -f postgres-diskstats/dc-postgres.yml -f cadvisor-influx/dc-cadvisor-influx.yml up -d + sleep 120 + docker-compose -f dc-juttle-engine.yml -f aws-cloudwatch/dc-aws-cloudwatch.yml -f elastic-newstracker/dc-elastic.yml -f github-tutorial/dc-elastic.yml -f nginx_logs/dc-nginx-logs.yml -f postgres-diskstats/dc-postgres.yml -f cadvisor-influx/dc-cadvisor-influx.yml stop + docker-compose -f dc-juttle-engine.yml -f aws-cloudwatch/dc-aws-cloudwatch.yml -f elastic-newstracker/dc-elastic.yml -f github-tutorial/dc-elastic.yml -f nginx_logs/dc-nginx-logs.yml -f postgres-diskstats/dc-postgres.yml -f cadvisor-influx/dc-cadvisor-influx.yml rm -fv + popd +} + +function run_kubernetes_demo() { + pushd $SCRIPTDIR/../../infrastructure/test-infrastructures/kubernetes-demo + bash run-local.sh + bash init.sh + sleep 600 + docker stop $(docker ps -qa) + docker rm -fv $(docker ps -qa) + popd +} + +function run_live_test() { + + live_test="$1" + + echo "Running live test $live_test" + + case "$live_test" in + htop ) CPU_INTERVAL=2;; + * ) CPU_INTERVAL=10;; + esac + + start_subject_prog + start_monitor_cpu_usage + + echo " starting live program and waiting for it to finish" + case "$live_test" in + htop ) run_htop ;; + juttle-examples ) run_juttle_examples ;; + kube-demo ) run_kubernetes_demo ;; + * ) usage; cleanup; exit 1 ;; + esac + + cleanup + +} + +function cleanup() { + + if [ -n "$SUBJ_PID" ] ; then + echo " stopping falco/sysdig program $SUBJ_PID" + sudo kill $SUBJ_PID + fi + + if [ -n "$CPU_PID" ] ; then + echo " stopping cpu monitor program $CPU_PID" + kill -- -$CPU_PID + fi +} + +run_live_tests() { + test="$1" + + if [ $test == "all" ]; then + tests="htop juttle-examples kube-demo" + else + tests=$test + fi + + for test in $tests; do + run_live_test $test + done +} + +function run_phoronix_test() { + + live_test="$1" + + case "$live_test" in + pts/aio-stress | pts/fs-mark | pts/iozone | pts/network-loopback | pts/nginx | pts/pybench | pts/redis | pts/sqlite | pts/unpack-linux ) CPU_INTERVAL=2;; + * ) CPU_INTERVAL=10;; + esac + + echo "Running phoronix test $live_test" + + start_subject_prog + start_monitor_cpu_usage + + echo " starting phoronix test and waiting for it to finish" + + TEST_RESULTS_NAME=$VARIANT FORCE_TIMES_TO_RUN=1 phoronix-test-suite default-run $live_test + + cleanup + +} + +# To install and configure phoronix: +# (redhat instructions, adapt as necessary for ubuntu or other distros) +# - install phoronix: yum install phoronix-test-suite.noarch +# - install dependencies not handled by phoronix: yum install libaio-devel pcre-devel popt-devel glibc-static zlib-devel nc bc +# - fix trivial bugs in tests: +# - edit ~/.phoronix-test-suite/installed-tests/pts/network-loopback-1.0.1/network-loopback line "nc -d -l 9999 > /dev/null &" to "nc -d -l 9999 > /dev/null &" +# - edit ~/.phoronix-test-suite/test-profiles/pts/nginx-1.1.0/test-definition.xml line "-n 500000 -c 100 http://localhost:8088/test.html" to "-n 500000 -c 100 http://127.0.0.1:8088/test.html" +# - phoronix batch-install + +function run_phoronix_tests() { + + test="$1" + + if [ $test == "all" ]; then + tests="pts/aio-stress pts/apache pts/blogbench pts/compilebench pts/dbench pts/fio pts/fs-mark pts/iozone pts/network-loopback pts/nginx pts/pgbench pts/phpbench pts/postmark pts/pybench pts/redis pts/sqlite pts/unpack-linux" + else + tests=$test + fi + + for test in $tests; do + run_phoronix_test $test + done +} + +run_tests() { + + IFS=':' read -ra PARTS <<< "$TEST" + + case "${PARTS[0]}" in + trace ) run_trace "${PARTS[1]}" ;; + live ) run_live_tests "${PARTS[1]}" ;; + phoronix ) run_phoronix_tests "${PARTS[1]}" ;; + * ) usage; exit 1 ;; + esac +} + +usage() { + echo "Usage: $0 [options]" + echo "" + echo "Options:" + echo " -h/--help: show this help" + echo " -v/--variant: a variant name to attach to this set of test results" + echo " -r/--root: root directory containing falco/sysdig binaries (i.e. where you ran 'cmake')" + echo " -R/--results: append test results to this file" + echo " -o/--output: append program output to this file" + echo " -t/--test: test to run. Argument has the following format:" + echo " trace:: read the specified trace file." + echo " trace:all means run all traces" + echo " live:: run the specified live test." + echo " live:all means run all live tests." + echo " possible live tests:" + echo " live:htop: run htop -d2" + echo " live:kube-demo: run kubernetes demo from infrastructure repo" + echo " live:juttle-examples: run a juttle demo environment based on docker-compose" + echo " phoronix:: run the specified phoronix test." + echo " if is not 'all', it is passed directly to the command line of \"phoronix-test-suite run \"" + echo " if is 'all', a built-in set of phoronix tests will be chosen and run" + echo " -T/--tracedir: Look for trace files in this directory. If doesn't exist, will download trace files from s3" +} + +OPTS=`getopt -o hv:r:R:o:t:T: --long help,variant:,root:,results:,output:,test:,tracedir: -n $0 -- "$@"` + +if [ $? != 0 ]; then + echo "Exiting" >&2 + exit 1 +fi + +eval set -- "$OPTS" + +VARIANT="falco" +ROOT=`dirname $0`/../build +SCRIPTDIR=`dirname $0` +RESULTS_FILE=`dirname $0`/results.json +OUTPUT_FILE=`dirname $0`/program-output.txt +TEST=trace:all +TRACEDIR=/tmp/falco-perf-traces.$USER +CPU_INTERVAL=10 + +while true; do + case "$1" in + -h | --help ) usage; exit 1;; + -v | --variant ) VARIANT="$2"; shift 2;; + -r | --root ) ROOT="$2"; shift 2;; + -R | --results ) RESULTS_FILE="$2"; shift 2;; + -o | --output ) OUTPUT_FILE="$2"; shift 2;; + -t | --test ) TEST="$2"; shift 2;; + -T | --tracedir ) TRACEDIR="$2"; shift 2;; + * ) break;; + esac +done + +if [ -z $VARIANT ]; then + echo "A test variant name must be provided. Not continuing." + exit 1 +fi + +if [ -z $ROOT ]; then + echo "A root directory containing a falco/sysdig binary must be provided. Not continuing." + exit 1 +fi + +ROOT=`realpath $ROOT` + + +if [ -z $RESULTS_FILE ]; then + echo "An output file for test results must be provided. Not continuing." + exit 1 +fi + +if [ -z $OUTPUT_FILE ]; then + echo "An file for program output must be provided. Not continuing." + exit 1 +fi + +if [ -z $TEST ]; then + echo "A test must be provided. Not continuing." + exit 1 +fi + +run_tests From ddedf595baefb3dec40fd4d3340ca3fc359395b2 Mon Sep 17 00:00:00 2001 From: Mark Stemm Date: Wed, 13 Jul 2016 17:59:20 -0700 Subject: [PATCH 4/5] Rule updates related to event-specific filters - Move evt.type checks to the front of rules. This is necessary to avoid warnings now that event types are automatically extracted during rule parsing and used to bind each rule with a specific set of events. - Explicitly specify open for O_CREAT. With the change to event-specific filters, it's necessary to associate a search for O_CREAT with evt.type=open. --- rules/falco_rules.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rules/falco_rules.yaml b/rules/falco_rules.yaml index 93f04b27..272c1e21 100644 --- a/rules/falco_rules.yaml +++ b/rules/falco_rules.yaml @@ -224,7 +224,7 @@ - rule: db_program_spawned_process desc: a database-server related program spawned a new process other than itself. This shouldn\'t occur and is a follow on from some SQL injection attacks. - condition: proc.pname in (db_server_binaries) and not proc.name in (db_server_binaries) and spawned_process + condition: proc.pname in (db_server_binaries) and spawned_process and not proc.name in (db_server_binaries) output: "Database-related program spawned process other than itself (user=%user.name program=%proc.cmdline parent=%proc.pname)" priority: WARNING @@ -264,7 +264,7 @@ - rule: run_shell_untrusted desc: an attempt to spawn a shell by a non-shell program. Exceptions are made for trusted binaries. - condition: not container and proc.name = bash and spawned_process and proc.pname exists and not proc.pname in (cron_binaries, bash, sshd, sudo, docker, su, tmux, screen, emacs, systemd, login, flock, fbash, nginx, monit, supervisord, dragent) + condition: spawned_process and not container and proc.name = bash and proc.pname exists and not proc.pname in (cron_binaries, bash, sshd, sudo, docker, su, tmux, screen, emacs, systemd, login, flock, fbash, nginx, monit, supervisord, dragent) output: "Shell spawned by untrusted binary (user=%user.name shell=%proc.name parent=%proc.pname cmdline=%proc.cmdline)" priority: WARNING @@ -281,7 +281,7 @@ - rule: run_shell_in_container desc: a shell was spawned by a non-shell program in a container. Container entrypoints are excluded. - condition: container and proc.name = bash and spawned_process and proc.pname exists and not proc.pname in (sh, bash, docker) + condition: spawned_process and container and proc.name = bash and proc.pname exists and not proc.pname in (sh, bash, docker) output: "Shell spawned in a container other than entrypoint (user=%user.name container_id=%container.id container_name=%container.name shell=%proc.name parent=%proc.pname cmdline=%proc.cmdline)" priority: WARNING @@ -317,7 +317,7 @@ # (we may need to add additional checks against false positives, see: https://bugs.launchpad.net/ubuntu/+source/rkhunter/+bug/86153) - rule: create_files_below_dev desc: creating any files below /dev other than known programs that manage devices. Some rootkits hide files in /dev. - condition: (evt.type = creat or evt.arg.flags contains O_CREAT) and proc.name != blkid and fd.directory = /dev and not fd.name in (/dev/null,/dev/stdin,/dev/stdout,/dev/stderr,/dev/tty) + condition: (evt.type = creat or (evt.type = open and evt.arg.flags contains O_CREAT)) and proc.name != blkid and fd.directory = /dev and not fd.name in (/dev/null,/dev/stdin,/dev/stdout,/dev/stderr,/dev/tty) output: "File created below /dev by untrusted program (user=%user.name command=%proc.cmdline file=%fd.name)" priority: WARNING From 7b68fc269202199480bba34c94f3d8dae7a4c7d4 Mon Sep 17 00:00:00 2001 From: Mark Stemm Date: Wed, 13 Jul 2016 18:42:34 -0700 Subject: [PATCH 5/5] Add tests for event type rule identification Add tests that verify that the event type identification functionality is working. Notable changes: - Modify falco_test.py to additionally check for warnings when loading any set of rules and verify that the event types for each rule match expected values. This is controlled by the new multiplex fields "rules_warning" and "rules_events". - Instead of starting with an empty falco_tests.yaml from scratch from the downloaded trace files, use a checked-in version which defines two tests: - Loading the checked-in falco_rules.yaml and verify that no rules have warnings. - A sample falco_rules_warnings.yaml that has ~30 different mutations of rule filtering expressions. The test verifies for each rule whether or not the rule should result in a warning and what the extracted event types are. The generated tests from the trace files are appended to this file. - Add an empty .scap file to use with the above tests. --- test/empty.scap | Bin 0 -> 129696 bytes test/falco_rules_warnings.yaml | 186 +++++++++++++++++++++++++++++++++ test/falco_test.py | 89 +++++++++++++--- test/falco_tests.yaml.in | 62 +++++++++++ test/run_regression_tests.sh | 2 +- 5 files changed, 326 insertions(+), 13 deletions(-) create mode 100644 test/empty.scap create mode 100644 test/falco_rules_warnings.yaml create mode 100644 test/falco_tests.yaml.in diff --git a/test/empty.scap b/test/empty.scap new file mode 100644 index 0000000000000000000000000000000000000000..9e0651e36a133a7ba8a56921ed5fb6afe0826f1a GIT binary patch literal 129696 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 literal 0 HcmV?d00001 diff --git a/test/falco_rules_warnings.yaml b/test/falco_rules_warnings.yaml new file mode 100644 index 00000000..476ca3ad --- /dev/null +++ b/test/falco_rules_warnings.yaml @@ -0,0 +1,186 @@ +- rule: no_warnings + desc: Rule with no warnings + condition: evt.type=execve + output: "None" + priority: WARNING + +- rule: no_evttype + desc: No evttype at all + condition: proc.name=foo + output: "None" + priority: WARNING + +- rule: evttype_not_equals + desc: Using != for event type + condition: evt.type!=execve + output: "None" + priority: WARNING + +- rule: leading_not + desc: condition starts with not + condition: not evt.type=execve + output: "None" + priority: WARNING + +- rule: not_equals_after_evttype + desc: != after evt.type, not affecting results + condition: evt.type=execve and proc.name!=foo + output: "None" + priority: WARNING + +- rule: not_after_evttype + desc: not operator after evt.type, not affecting results + condition: evt.type=execve and not proc.name=foo + output: "None" + priority: WARNING + +- rule: leading_trailing_evttypes + desc: evttype at beginning and end + condition: evt.type=execve and proc.name=foo or evt.type=open + output: "None" + priority: WARNING + +- rule: leading_multtrailing_evttypes + desc: one evttype at beginning, multiple at end + condition: evt.type=execve and proc.name=foo or evt.type=open or evt.type=connect + output: "None" + priority: WARNING + +- rule: leading_multtrailing_evttypes_using_in + desc: one evttype at beginning, multiple at end, using in + condition: evt.type=execve and proc.name=foo or evt.type in (open, connect) + output: "None" + priority: WARNING + +- rule: not_equals_at_end + desc: not_equals at final evttype + condition: evt.type=execve and proc.name=foo or evt.type=open or evt.type!=connect + output: "None" + priority: WARNING + +- rule: not_at_end + desc: not operator for final evttype + condition: evt.type=execve and proc.name=foo or evt.type=open or not evt.type=connect + output: "None" + priority: WARNING + +- rule: not_before_trailing_evttype + desc: a not before a trailing event type + condition: evt.type=execve and not proc.name=foo or evt.type=open + output: "None" + priority: WARNING + +- rule: not_equals_before_trailing_evttype + desc: a != before a trailing event type + condition: evt.type=execve and proc.name!=foo or evt.type=open + output: "None" + priority: WARNING + +- rule: not_equals_and_not + desc: both != and not before event types + condition: evt.type=execve and proc.name!=foo or evt.type=open or not evt.type=connect + output: "None" + priority: WARNING + +- rule: not_equals_before_in + desc: != before an in with event types + condition: evt.type=execve and proc.name!=foo or evt.type in (open, connect) + output: "None" + priority: WARNING + +- rule: not_before_in + desc: a not before an in with event types + condition: evt.type=execve and not proc.name=foo or evt.type in (open, connect) + output: "None" + priority: WARNING + +- rule: not_in_before_in + desc: a not with in before an in with event types + condition: evt.type=execve and not proc.name in (foo, bar) or evt.type in (open, connect) + output: "None" + priority: WARNING + +- rule: evttype_in + desc: using in for event types + condition: evt.type in (execve, open) + output: "None" + priority: WARNING + +- rule: evttype_in_plus_trailing + desc: using in for event types and a trailing evttype + condition: evt.type in (execve, open) and proc.name=foo or evt.type=connect + output: "None" + priority: WARNING + +- rule: leading_in_not_equals_before_evttype + desc: initial in() for event types, then a != before an additional event type + condition: evt.type in (execve, open) and proc.name!=foo or evt.type=connect + output: "None" + priority: WARNING + +- rule: leading_in_not_equals_at_evttype + desc: initial in() for event types, then a != with an additional event type + condition: evt.type in (execve, open) or evt.type!=connect + output: "None" + priority: WARNING + +- rule: not_with_evttypes + desc: not in for event types + condition: not evt.type in (execve, open) + output: "None" + priority: WARNING + +- rule: not_with_evttypes_addl + desc: not in for event types, and an additional event type + condition: not evt.type in (execve, open) or evt.type=connect + output: "None" + priority: WARNING + +- rule: not_equals_before_evttype + desc: != before any event type + condition: proc.name!=foo and evt.type=execve + output: "None" + priority: WARNING + +- rule: not_equals_before_in_evttype + desc: != before any event type using in + condition: proc.name!=foo and evt.type in (execve, open) + output: "None" + priority: WARNING + +- rule: not_before_evttype + desc: not operator before any event type + condition: not proc.name=foo and evt.type=execve + output: "None" + priority: WARNING + +- rule: not_before_evttype_using_in + desc: not operator before any event type using in + condition: not proc.name=foo and evt.type in (execve, open) + output: "None" + priority: WARNING + +- rule: repeated_evttypes + desc: event types appearing multiple times + condition: evt.type=open or evt.type=open + output: "None" + priority: WARNING + +- rule: repeated_evttypes_with_in + desc: event types appearing multiple times with in + condition: evt.type in (open, open) + output: "None" + priority: WARNING + +- rule: repeated_evttypes_with_separate_in + desc: event types appearing multiple times with separate ins + condition: evt.type in (open) or evt.type in (open, open) + output: "None" + priority: WARNING + +- rule: repeated_evttypes_with_mix + desc: event types appearing multiple times with mix of = and in + condition: evt.type=open or evt.type in (open, open) + output: "None" + priority: WARNING + diff --git a/test/falco_test.py b/test/falco_test.py index b9358e17..8c4cf9f7 100644 --- a/test/falco_test.py +++ b/test/falco_test.py @@ -3,6 +3,7 @@ import os import re import json +import sets from avocado import Test from avocado.utils import process @@ -16,9 +17,34 @@ class FalcoTest(Test): """ self.falcodir = self.params.get('falcodir', '/', default=os.path.join(self.basedir, '../build')) - self.should_detect = self.params.get('detect', '*') + self.should_detect = self.params.get('detect', '*', default=False) self.trace_file = self.params.get('trace_file', '*') - self.json_output = self.params.get('json_output', '*') + + if not os.path.isabs(self.trace_file): + self.trace_file = os.path.join(self.basedir, self.trace_file) + + self.json_output = self.params.get('json_output', '*', default=False) + self.rules_file = self.params.get('rules_file', '*', default=os.path.join(self.basedir, '../rules/falco_rules.yaml')) + + if not os.path.isabs(self.rules_file): + self.rules_file = os.path.join(self.basedir, self.rules_file) + + self.rules_warning = self.params.get('rules_warning', '*', default=False) + if self.rules_warning == False: + self.rules_warning = sets.Set() + else: + self.rules_warning = sets.Set(self.rules_warning) + + # Maps from rule name to set of evttypes + self.rules_events = self.params.get('rules_events', '*', default=False) + if self.rules_events == False: + self.rules_events = {} + else: + events = {} + for item in self.rules_events: + for item2 in item: + events[item2[0]] = sets.Set(item2[1]) + self.rules_events = events if self.should_detect: self.detect_level = self.params.get('detect_level', '*') @@ -33,21 +59,38 @@ class FalcoTest(Test): self.str_variant = self.trace_file - def test(self): - self.log.info("Trace file %s", self.trace_file) + def check_rules_warnings(self, res): - # Run the provided trace file though falco - cmd = '{}/userspace/falco/falco -r {}/../rules/falco_rules.yaml -c {}/../falco.yaml -e {} -o json_output={}'.format( - self.falcodir, self.falcodir, self.falcodir, self.trace_file, self.json_output) + found_warning = sets.Set() - self.falco_proc = process.SubProcess(cmd) + for match in re.finditer('Rule ([^:]+): warning \(([^)]+)\):', res.stderr): + rule = match.group(1) + warning = match.group(2) + found_warning.add(rule) - res = self.falco_proc.run(timeout=180, sig=9) + self.log.debug("Expected warning rules: {}".format(self.rules_warning)) + self.log.debug("Actual warning rules: {}".format(found_warning)) - if res.exit_status != 0: - self.error("Falco command \"{}\" exited with non-zero return value {}".format( - cmd, res.exit_status)) + if found_warning != self.rules_warning: + self.fail("Expected rules with warnings {} does not match actual rules with warnings {}".format(self.rules_warning, found_warning)) + def check_rules_events(self, res): + + found_events = {} + + for match in re.finditer('Event types for rule ([^:]+): (\S+)', res.stderr): + rule = match.group(1) + events = sets.Set(match.group(2).split(",")) + found_events[rule] = events + + self.log.debug("Expected events for rules: {}".format(self.rules_events)) + self.log.debug("Actual events for rules: {}".format(found_events)) + + for rule in found_events.keys(): + if found_events.get(rule) != self.rules_events.get(rule): + self.fail("rule {}: expected events {} differs from actual events {}".format(rule, self.rules_events.get(rule), found_events.get(rule))) + + def check_detections(self, res): # Get the number of events detected. match = re.search('Events detected: (\d+)', res.stdout) if match is None: @@ -73,6 +116,7 @@ class FalcoTest(Test): if not events_detected > 0: self.fail("Detected {} events at level {} when should have detected > 0".format(events_detected, self.detect_level)) + def check_json_output(self, res): if self.json_output: # Just verify that any lines starting with '{' are valid json objects. # Doesn't do any deep inspection of the contents. @@ -82,6 +126,27 @@ class FalcoTest(Test): for attr in ['time', 'rule', 'priority', 'output']: if not attr in obj: self.fail("Falco JSON object {} does not contain property \"{}\"".format(line, attr)) + + def test(self): + self.log.info("Trace file %s", self.trace_file) + + # Run the provided trace file though falco + cmd = '{}/userspace/falco/falco -r {} -c {}/../falco.yaml -e {} -o json_output={} -v'.format( + self.falcodir, self.rules_file, self.falcodir, self.trace_file, self.json_output) + + self.falco_proc = process.SubProcess(cmd) + + res = self.falco_proc.run(timeout=180, sig=9) + + if res.exit_status != 0: + self.error("Falco command \"{}\" exited with non-zero return value {}".format( + cmd, res.exit_status)) + + self.check_rules_warnings(res) + if len(self.rules_events) > 0: + self.check_rules_events(res) + self.check_detections(res) + self.check_json_output(res) pass diff --git a/test/falco_tests.yaml.in b/test/falco_tests.yaml.in new file mode 100644 index 00000000..bb1eb511 --- /dev/null +++ b/test/falco_tests.yaml.in @@ -0,0 +1,62 @@ +trace_files: !mux + builtin_rules_no_warnings: + detect: False + trace_file: empty.scap + rules_warning: False + + test_warnings: + detect: False + trace_file: empty.scap + rules_file: falco_rules_warnings.yaml + rules_warning: + - no_evttype + - evttype_not_equals + - leading_not + - not_equals_at_end + - not_at_end + - not_before_trailing_evttype + - not_equals_before_trailing_evttype + - not_equals_and_not + - not_equals_before_in + - not_before_in + - not_in_before_in + - leading_in_not_equals_before_evttype + - leading_in_not_equals_at_evttype + - not_with_evttypes + - not_with_evttypes_addl + - not_equals_before_evttype + - not_equals_before_in_evttype + - not_before_evttype + - not_before_evttype_using_in + rules_events: + - no_warnings: [execve] + - no_evttype: [all] + - evttype_not_equals: [all] + - leading_not: [all] + - not_equals_after_evttype: [execve] + - not_after_evttype: [execve] + - leading_trailing_evttypes: [execve,open] + - leading_multtrailing_evttypes: [connect,execve,open] + - leading_multtrailing_evttypes_using_in: [connect,execve,open] + - not_equals_at_end: [all] + - not_at_end: [all] + - not_before_trailing_evttype: [all] + - not_equals_before_trailing_evttype: [all] + - not_equals_and_not: [all] + - not_equals_before_in: [all] + - not_before_in: [all] + - not_in_before_in: [all] + - evttype_in: [execve,open] + - evttype_in_plus_trailing: [connect,execve,open] + - leading_in_not_equals_before_evttype: [all] + - leading_in_not_equals_at_evttype: [all] + - not_with_evttypes: [all] + - not_with_evttypes_addl: [all] + - not_equals_before_evttype: [all] + - not_equals_before_in_evttype: [all] + - not_before_evttype: [all] + - not_before_evttype_using_in: [all] + - repeated_evttypes: [open] + - repeated_evttypes_with_in: [open] + - repeated_evttypes_with_separate_in: [open] + - repeated_evttypes_with_mix: [open] diff --git a/test/run_regression_tests.sh b/test/run_regression_tests.sh index 8d63073b..efc40034 100755 --- a/test/run_regression_tests.sh +++ b/test/run_regression_tests.sh @@ -36,7 +36,7 @@ EOF } function prepare_multiplex_file() { - echo "trace_files: !mux" > $MULT_FILE + cp $SCRIPTDIR/falco_tests.yaml.in $MULT_FILE prepare_multiplex_fileset traces-positive True Warning False prepare_multiplex_fileset traces-negative False Warning True