From b57eb8659f28e01d7178af044e42da9828032771 Mon Sep 17 00:00:00 2001 From: Mark Stemm Date: Tue, 26 Jul 2016 08:05:15 -0700 Subject: [PATCH 1/5] Add ignores for test-related files. Ignore results.json and similar names. Also ignore the file created when running phoronix tests. --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 7a98caebb6b..d1217019bb5 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,8 @@ test/traces-negative test/traces-positive test/traces-info test/job-results +test/.phoronix-test-suite +test/results*.json.* userspace/falco/lua/re.lua userspace/falco/lua/lpeg.so From fc9690b1d30e6fd8205f221a72123194aae9cbb3 Mon Sep 17 00:00:00 2001 From: Mark Stemm Date: Fri, 15 Jul 2016 13:26:14 -0700 Subject: [PATCH 2/5] Create embeddable falco engine. Create standalone classes falco_engine/falco_outputs that can be embedded in other programs. falco_engine is responsible for matching events against rules, and falco_output is responsible for formatting an alert string given an event and writing the alert string to all configured outputs. falco_engine's main interfaces are: - load_rules/load_rules_file: Given a path to a rules file or a string containing a set of rules, load the rules. Also loads needed lua code. - process_event(): check the event against the set of rules and return the results of a match, if any. - describe_rule(): print details on a specific rule or all rules. - print_stats(): print stats on the rules that matched. - enable_rule(): enable/disable any rules matching a pattern. New falco command line option -D allows you to disable one or more rules on the command line. falco_output's main interfaces are: - init(): load needed lua code. - add_output(): add an output channel for alert notifications. - handle_event(): given an event that matches one or more rules, format an alert message and send it to any output channels. Each of falco_engine/falco_output maintains a separate lua state and loads separate sets of lua files. The code to create and initialize the lua state is in a base class falco_common. falco_engine no longer logs anything. In the case of errors, it throws exceptions. falco_logger is now only used as a logging mechanism for falco itself and as an output method for alert messages. (This should really probably be split, but it's ok for now). falco_engine contains an sinsp_evttype_filter object containing the set of eventtype filters. Instead of calling m_inspector->add_evttype_filter() to add a filter created by the compiler, call falco_engine::add_evttype_filter() instead. This means that the inspector runs with a NULL filter and all events are returned from do_inspect. This depends on https://github.com/draios/sysdig/pull/633 which has a wrapper around a set of eventtype filters. Some additional changes along with creating these classes: - Some cleanups of unnecessary header files, cmake include_directory()s, etc to only include necessary includes and only include them in header files when required. - Try to avoid 'using namespace std' in header files, or assuming someone else has done that. Generally add 'using namespace std' to all source files. - Instead of using sinsp_exception for all errors, define a falco_engine_exception class for exceptions coming from the falco engine and use it instead. For falco program code, switch to general exceptions under std::exception and catch + display an error for all exceptions, not just sinsp_exceptions. - Remove fields.{cpp,h}. This was dead code. - Start tracking counts of rules by priority string (i.e. what's in the falco rules file) as compared to priority level (i.e. roughtly corresponding to a syslog level). This keeps the rule processing and rule output halves separate. This led to some test changes. The regex used in the test is now case insensitive to be a bit more flexible. - Now that https://github.com/draios/sysdig/pull/632 is merged, we can delete the rules object (and its lua_parser) safely. - Move loading the initial lua script to the constructor. Otherwise, calling load_rules() twice re-loads the lua script and throws away any state like the mapping from rule index to rule. - Allow an empty rules file. Finally, fix most memory leaks found by valgrind: - falco_configuration wasn't deleting the allocated m_config yaml config. - several ifstreams were being created simply to test which falco config file to use. - In the lua output methods, an event formatter was being created using falco.formatter() but there was no corresponding free_formatter(). This depends on changes in https://github.com/draios/sysdig/pull/640. --- test/falco_test.py | 2 +- test/run_regression_tests.sh | 10 +- userspace/falco/CMakeLists.txt | 3 +- userspace/falco/config_falco.h.in | 3 - userspace/falco/configuration.cpp | 38 +++-- userspace/falco/configuration.h | 16 +- userspace/falco/falco.cpp | 254 +++++++--------------------- userspace/falco/falco_common.cpp | 90 ++++++++++ userspace/falco/falco_common.h | 69 ++++++++ userspace/falco/falco_engine.cpp | 143 ++++++++++++++++ userspace/falco/falco_engine.h | 76 +++++++++ userspace/falco/falco_outputs.cpp | 89 ++++++++++ userspace/falco/falco_outputs.h | 41 +++++ userspace/falco/fields.cpp | 76 --------- userspace/falco/fields.h | 21 --- userspace/falco/formats.cpp | 23 ++- userspace/falco/formats.h | 3 + userspace/falco/logger.cpp | 3 - userspace/falco/lua/output.lua | 44 ++--- userspace/falco/lua/rule_loader.lua | 48 ++---- userspace/falco/rules.cpp | 66 +++----- userspace/falco/rules.h | 14 +- 22 files changed, 696 insertions(+), 436 deletions(-) create mode 100644 userspace/falco/falco_common.cpp create mode 100644 userspace/falco/falco_common.h create mode 100644 userspace/falco/falco_engine.cpp create mode 100644 userspace/falco/falco_engine.h create mode 100644 userspace/falco/falco_outputs.cpp create mode 100644 userspace/falco/falco_outputs.h delete mode 100644 userspace/falco/fields.cpp delete mode 100644 userspace/falco/fields.h diff --git a/test/falco_test.py b/test/falco_test.py index 8c4cf9f7139..7b72d5eef8a 100644 --- a/test/falco_test.py +++ b/test/falco_test.py @@ -105,7 +105,7 @@ def check_detections(self, res): if events_detected == 0: self.fail("Detected {} events when should have detected > 0".format(events_detected)) - level_line = '{}: (\d+)'.format(self.detect_level) + level_line = '(?i){}: (\d+)'.format(self.detect_level) match = re.search(level_line, res.stdout) if match is None: diff --git a/test/run_regression_tests.sh b/test/run_regression_tests.sh index efc400347db..dd06ca0ce91 100755 --- a/test/run_regression_tests.sh +++ b/test/run_regression_tests.sh @@ -38,12 +38,12 @@ EOF function prepare_multiplex_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 - prepare_multiplex_fileset traces-info True Informational False + prepare_multiplex_fileset traces-positive True WARNING False + prepare_multiplex_fileset traces-negative False WARNING True + prepare_multiplex_fileset traces-info True INFO False - prepare_multiplex_fileset traces-positive True Warning True - prepare_multiplex_fileset traces-info True Informational True + prepare_multiplex_fileset traces-positive True WARNING True + prepare_multiplex_fileset traces-info True INFO True echo "Contents of $MULT_FILE:" cat $MULT_FILE diff --git a/userspace/falco/CMakeLists.txt b/userspace/falco/CMakeLists.txt index fb241159e7f..510c0b54a4f 100644 --- a/userspace/falco/CMakeLists.txt +++ b/userspace/falco/CMakeLists.txt @@ -8,7 +8,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 fields.cpp rules.cpp logger.cpp falco.cpp) +add_executable(falco configuration.cpp formats.cpp rules.cpp logger.cpp falco_common.cpp falco_engine.cpp falco_outputs.cpp falco.cpp) target_link_libraries(falco sinsp) target_link_libraries(falco @@ -18,7 +18,6 @@ target_link_libraries(falco "${YAMLCPP_LIB}") -set(FALCO_LUA_MAIN "rule_loader.lua") configure_file(config_falco.h.in config_falco.h) install(TARGETS falco DESTINATION bin) diff --git a/userspace/falco/config_falco.h.in b/userspace/falco/config_falco.h.in index cf04adaf4f9..0f0ab12413d 100644 --- a/userspace/falco/config_falco.h.in +++ b/userspace/falco/config_falco.h.in @@ -8,7 +8,4 @@ #define FALCO_INSTALL_CONF_FILE "/etc/falco.yaml" #define FALCO_SOURCE_LUA_DIR "${PROJECT_SOURCE_DIR}/userspace/falco/lua/" - -#define FALCO_LUA_MAIN "${FALCO_LUA_MAIN}" - #define PROBE_NAME "${PROBE_NAME}" diff --git a/userspace/falco/configuration.cpp b/userspace/falco/configuration.cpp index 8a71af46400..9e88b9a3f98 100644 --- a/userspace/falco/configuration.cpp +++ b/userspace/falco/configuration.cpp @@ -1,22 +1,32 @@ #include "configuration.h" -#include "config_falco.h" -#include "sinsp.h" #include "logger.h" using namespace std; +falco_configuration::falco_configuration() + : m_config(NULL) +{ +} + +falco_configuration::~falco_configuration() +{ + if (m_config) + { + delete m_config; + } +} // If we don't have a configuration file, we just use stdout output and all other defaults -void falco_configuration::init(std::list &cmdline_options) +void falco_configuration::init(list &cmdline_options) { init_cmdline_options(cmdline_options); - output_config stdout_output; + falco_outputs::output_config stdout_output; stdout_output.name = "stdout"; m_outputs.push_back(stdout_output); } -void falco_configuration::init(string conf_filename, std::list &cmdline_options) +void falco_configuration::init(string conf_filename, list &cmdline_options) { string m_config_file = conf_filename; m_config = new yaml_configuration(m_config_file); @@ -26,7 +36,7 @@ void falco_configuration::init(string conf_filename, std::list &cmd m_rules_filename = m_config->get_scalar("rules_file", "/etc/falco_rules.yaml"); m_json_output = m_config->get_scalar("json_output", false); - output_config file_output; + falco_outputs::output_config file_output; file_output.name = "file"; if (m_config->get_scalar("file_output", "enabled", false)) { @@ -34,27 +44,27 @@ void falco_configuration::init(string conf_filename, std::list &cmd filename = m_config->get_scalar("file_output", "filename", ""); if (filename == string("")) { - throw sinsp_exception("Error reading config file (" + m_config_file + "): file output enabled but no filename in configuration block"); + throw invalid_argument("Error reading config file (" + m_config_file + "): file output enabled but no filename in configuration block"); } file_output.options["filename"] = filename; m_outputs.push_back(file_output); } - output_config stdout_output; + falco_outputs::output_config stdout_output; stdout_output.name = "stdout"; if (m_config->get_scalar("stdout_output", "enabled", false)) { m_outputs.push_back(stdout_output); } - output_config syslog_output; + falco_outputs::output_config syslog_output; syslog_output.name = "syslog"; if (m_config->get_scalar("syslog_output", "enabled", false)) { m_outputs.push_back(syslog_output); } - output_config program_output; + falco_outputs::output_config program_output; program_output.name = "program"; if (m_config->get_scalar("program_output", "enabled", false)) { @@ -70,7 +80,7 @@ void falco_configuration::init(string conf_filename, std::list &cmd if (m_outputs.size() == 0) { - throw sinsp_exception("Error reading config file (" + m_config_file + "): No outputs configured. Please configure at least one output file output enabled but no filename in configuration block"); + throw invalid_argument("Error reading config file (" + m_config_file + "): No outputs configured. Please configure at least one output file output enabled but no filename in configuration block"); } falco_logger::log_stderr = m_config->get_scalar("log_stderr", false); @@ -90,7 +100,7 @@ static bool split(const string &str, char delim, pair &parts) return true; } -void falco_configuration::init_cmdline_options(std::list &cmdline_options) +void falco_configuration::init_cmdline_options(list &cmdline_options) { for(const string &option : cmdline_options) { @@ -98,13 +108,13 @@ void falco_configuration::init_cmdline_options(std::list &cmdline_o } } -void falco_configuration::set_cmdline_option(const std::string &opt) +void falco_configuration::set_cmdline_option(const string &opt) { pair keyval; pair subkey; if (! split(opt, '=', keyval)) { - throw sinsp_exception("Error parsing config option \"" + opt + "\". Must be of the form key=val or key.subkey=val"); + throw invalid_argument("Error parsing config option \"" + opt + "\". Must be of the form key=val or key.subkey=val"); } if (split(keyval.first, '.', subkey)) { diff --git a/userspace/falco/configuration.h b/userspace/falco/configuration.h index d389aa37888..8e13fd9474d 100644 --- a/userspace/falco/configuration.h +++ b/userspace/falco/configuration.h @@ -1,13 +1,12 @@ #pragma once #include +#include +#include +#include #include -struct output_config -{ - std::string name; - std::map options; -}; +#include "falco_outputs.h" class yaml_configuration { @@ -17,7 +16,7 @@ class yaml_configuration { m_path = path; YAML::Node config; - std::vector outputs; + std::vector outputs; try { m_root = YAML::LoadFile(path); @@ -118,12 +117,15 @@ class yaml_configuration class falco_configuration { public: + falco_configuration(); + virtual ~falco_configuration(); + void init(std::string conf_filename, std::list &cmdline_options); void init(std::list &cmdline_options); std::string m_rules_filename; bool m_json_output; - std::vector m_outputs; + std::vector m_outputs; private: void init_cmdline_options(std::list &cmdline_options); diff --git a/userspace/falco/falco.cpp b/userspace/falco/falco.cpp index d32301b64c0..8359c2a2d7c 100644 --- a/userspace/falco/falco.cpp +++ b/userspace/falco/falco.cpp @@ -1,32 +1,19 @@ #define __STDC_FORMAT_MACROS #include -#include +#include #include -#include #include #include -#include #include #include -extern "C" { -#include "lua.h" -#include "lualib.h" -#include "lauxlib.h" -#include "lpeg.h" -#include "lyaml.h" -} - #include -#include "config_falco.h" -#include "configuration.h" -#include "rules.h" -#include "formats.h" -#include "fields.h" + #include "logger.h" -#include "utils.h" -#include + +#include "configuration.h" +#include "falco_engine.h" bool g_terminate = false; // @@ -53,6 +40,7 @@ static void usage() " -p, --pidfile When run as a daemon, write pid to specified file\n" " -e Read the events from (in .scap format) instead of tapping into live.\n" " -r Rules file (defaults to value set in configuration file, or /etc/falco_rules.yaml).\n" + " -D Disable any rules matching the regex . Can be specified multiple times.\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" @@ -61,7 +49,7 @@ static void usage() ); } -static void display_fatal_err(const string &msg, bool daemon) +static void display_fatal_err(const string &msg) { falco_logger::log(LOG_ERR, msg); @@ -75,23 +63,18 @@ static void display_fatal_err(const string &msg, bool daemon) } } -string lua_on_event = "on_event"; -string lua_add_output = "add_output"; -string lua_print_stats = "print_stats"; - // Splitting into key=value or key.subkey=value will be handled by configuration class. std::list cmdline_options; // // Event processing loop // -void do_inspect(sinsp* inspector, - falco_rules* rules, - lua_State* ls) +void do_inspect(falco_engine *engine, + falco_outputs *outputs, + sinsp* inspector) { int32_t res; sinsp_evt* ev; - string line; // // Loop through the events @@ -129,110 +112,18 @@ void do_inspect(sinsp* inspector, continue; } - lua_getglobal(ls, lua_on_event.c_str()); - - if(lua_isfunction(ls, -1)) - { - lua_pushlightuserdata(ls, ev); - lua_pushnumber(ls, ev->get_check_id()); - - if(lua_pcall(ls, 2, 0, 0) != 0) - { - const char* lerr = lua_tostring(ls, -1); - string err = "Error invoking function output: " + string(lerr); - throw sinsp_exception(err); - } - } - else - { - throw sinsp_exception("No function " + lua_on_event + " found in lua compiler module"); - } - } -} - -void add_lua_path(lua_State *ls, string path) -{ - string cpath = string(path); - path += "?.lua"; - cpath += "?.so"; - - lua_getglobal(ls, "package"); - - lua_getfield(ls, -1, "path"); - string cur_path = lua_tostring(ls, -1 ); - cur_path += ';'; - lua_pop(ls, 1); - - cur_path.append(path.c_str()); - - lua_pushstring(ls, cur_path.c_str()); - lua_setfield(ls, -2, "path"); - - lua_getfield(ls, -1, "cpath"); - string cur_cpath = lua_tostring(ls, -1 ); - cur_cpath += ';'; - lua_pop(ls, 1); - - cur_cpath.append(cpath.c_str()); - - lua_pushstring(ls, cur_cpath.c_str()); - lua_setfield(ls, -2, "cpath"); - - lua_pop(ls, 1); -} - -void add_output(lua_State *ls, output_config oc) -{ - - uint8_t nargs = 1; - lua_getglobal(ls, lua_add_output.c_str()); - - if(!lua_isfunction(ls, -1)) - { - throw sinsp_exception("No function " + lua_add_output + " found. "); - } - lua_pushstring(ls, oc.name.c_str()); - - // If we have options, build up a lua table containing them - if (oc.options.size()) - { - nargs = 2; - lua_createtable(ls, 0, oc.options.size()); - - for (auto it = oc.options.cbegin(); it != oc.options.cend(); ++it) - { - lua_pushstring(ls, (*it).second.c_str()); - lua_setfield(ls, -2, (*it).first.c_str()); - } - } - - if(lua_pcall(ls, nargs, 0, 0) != 0) - { - const char* lerr = lua_tostring(ls, -1); - throw sinsp_exception(string(lerr)); - } - -} - -// Print statistics on the the rules that triggered -void print_stats(lua_State *ls) -{ - lua_getglobal(ls, lua_print_stats.c_str()); - - if(lua_isfunction(ls, -1)) - { - if(lua_pcall(ls, 0, 0, 0) != 0) + // As the inspector has no filter at its level, all + // events are returned here. Pass them to the falco + // 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); + if(res) { - const char* lerr = lua_tostring(ls, -1); - string err = "Error invoking function print_stats: " + string(lerr); - throw sinsp_exception(err); + outputs->handle_event(res->evt, res->rule, res->priority, res->format); + delete(res); } } - else - { - throw sinsp_exception("No function " + lua_print_stats + " found in lua rule loader module"); - } - } // @@ -242,15 +133,13 @@ int falco_init(int argc, char **argv) { int result = EXIT_SUCCESS; sinsp* inspector = NULL; - falco_rules* rules = NULL; + falco_engine *engine = NULL; + falco_outputs *outputs = NULL; int op; int long_index = 0; - string lua_main_filename; string scap_filename; string conf_filename; string rules_filename; - string lua_dir = FALCO_LUA_DIR; - lua_State* ls = NULL; bool daemon = false; string pidfilename = "/var/run/falco.pid"; bool describe_all_rules = false; @@ -271,12 +160,20 @@ int falco_init(int argc, char **argv) try { inspector = new sinsp(); + engine = new falco_engine(); + engine->set_inspector(inspector); + + outputs = new falco_outputs(); + outputs->set_inspector(inspector); + + set disabled_rule_patterns; + string pattern; // // Parse the args // while((op = getopt_long(argc, argv, - "c:ho:e:r:dp:Ll:vA", + "c:ho:e:r:D:dp:Ll:vA", long_options, &long_index)) != -1) { switch(op) @@ -296,6 +193,10 @@ int falco_init(int argc, char **argv) case 'r': rules_filename = optarg; break; + case 'D': + pattern = optarg; + disabled_rule_patterns.insert(pattern); + break; case 'd': daemon = true; break; @@ -325,29 +226,29 @@ int falco_init(int argc, char **argv) // Some combinations of arguments are not allowed. if (daemon && pidfilename == "") { - throw sinsp_exception("If -d is provided, a pid file must also be provided"); + throw std::invalid_argument("If -d is provided, a pid file must also be provided"); } - ifstream* conf_stream; + ifstream conf_stream; if (conf_filename.size()) { - conf_stream = new ifstream(conf_filename); - if (!conf_stream->good()) + conf_stream.open(conf_filename); + if (!conf_stream.is_open()) { - throw sinsp_exception("Could not find configuration file at " + conf_filename); + throw std::runtime_error("Could not find configuration file at " + conf_filename); } } else { - conf_stream = new ifstream(FALCO_SOURCE_CONF_FILE); - if (conf_stream->good()) + conf_stream.open(FALCO_SOURCE_CONF_FILE); + if (!conf_stream.is_open()) { conf_filename = FALCO_SOURCE_CONF_FILE; } else { - conf_stream = new ifstream(FALCO_INSTALL_CONF_FILE); - if (conf_stream->good()) + conf_stream.open(FALCO_INSTALL_CONF_FILE); + if (!conf_stream.is_open()) { conf_filename = FALCO_INSTALL_CONF_FILE; } @@ -376,61 +277,40 @@ int falco_init(int argc, char **argv) config.m_rules_filename = rules_filename; } - lua_main_filename = lua_dir + FALCO_LUA_MAIN; - if (!std::ifstream(lua_main_filename)) - { - lua_dir = FALCO_SOURCE_LUA_DIR; - lua_main_filename = lua_dir + FALCO_LUA_MAIN; - if (!std::ifstream(lua_main_filename)) - { - falco_logger::log(LOG_ERR, "Could not find Falco Lua libraries (tried " + - string(FALCO_LUA_DIR FALCO_LUA_MAIN) + ", " + - lua_main_filename + "). Exiting.\n"); - result = EXIT_FAILURE; - goto exit; - } - } - - // Initialize Lua interpreter - ls = lua_open(); - luaL_openlibs(ls); - luaopen_lpeg(ls); - luaopen_yaml(ls); - add_lua_path(ls, lua_dir); + engine->load_rules_file(rules_filename, verbose, all_events); - rules = new falco_rules(inspector, ls, lua_main_filename); + falco_logger::log(LOG_INFO, "Parsed rules from file " + rules_filename + "\n"); - falco_formats::init(inspector, ls, config.m_json_output); - falco_fields::init(inspector, ls); - - falco_logger::init(ls); - falco_rules::init(ls); + for (auto pattern : disabled_rule_patterns) + { + falco_logger::log(LOG_INFO, "Disabling rules matching pattern: " + pattern + "\n"); + engine->enable_rule(pattern, false); + } + outputs->init(config.m_json_output); if(!all_events) { inspector->set_drop_event_flags(EF_DROP_FALCO); } - rules->load_rules(config.m_rules_filename, verbose, all_events); - falco_logger::log(LOG_INFO, "Parsed rules from file " + config.m_rules_filename + "\n"); if (describe_all_rules) { - rules->describe_rule(NULL); + engine->describe_rule(NULL); goto exit; } if (describe_rule != "") { - rules->describe_rule(&describe_rule); + engine->describe_rule(&describe_rule); goto exit; } inspector->set_hostname_and_port_resolution_mode(false); - for(std::vector::iterator it = config.m_outputs.begin(); it != config.m_outputs.end(); ++it) + for(auto output : config.m_outputs) { - add_output(ls, *it); + outputs->add_output(output); } if(signal(SIGINT, signal_callback) == SIG_ERR) @@ -522,23 +402,17 @@ int falco_init(int argc, char **argv) open("/dev/null", O_RDWR); } - do_inspect(inspector, - rules, - ls); + do_inspect(engine, + outputs, + inspector); inspector->close(); - print_stats(ls); + engine->print_stats(); } - catch(sinsp_exception& e) + catch(exception &e) { - display_fatal_err("Runtime error: " + string(e.what()) + ". Exiting.\n", daemon); - - result = EXIT_FAILURE; - } - catch(...) - { - display_fatal_err("Unexpected error, Exiting\n", daemon); + display_fatal_err("Runtime error: " + string(e.what()) + ". Exiting.\n"); result = EXIT_FAILURE; } @@ -546,11 +420,9 @@ int falco_init(int argc, char **argv) exit: delete inspector; + delete engine; + delete outputs; - if(ls) - { - lua_close(ls); - } return result; } diff --git a/userspace/falco/falco_common.cpp b/userspace/falco/falco_common.cpp new file mode 100644 index 00000000000..47874180e4d --- /dev/null +++ b/userspace/falco/falco_common.cpp @@ -0,0 +1,90 @@ +#include + +#include "config_falco.h" +#include "falco_common.h" + +falco_common::falco_common() +{ + m_ls = lua_open(); + luaL_openlibs(m_ls); +} + +falco_common::~falco_common() +{ + if(m_ls) + { + lua_close(m_ls); + } +} + +void falco_common::set_inspector(sinsp *inspector) +{ + m_inspector = inspector; +} + +void falco_common::init(string &lua_main_filename) +{ + ifstream is; + string lua_dir = FALCO_LUA_DIR; + string lua_main_path = lua_dir + lua_main_filename; + + is.open(lua_main_path); + if (!is.is_open()) + { + lua_dir = FALCO_SOURCE_LUA_DIR; + lua_main_path = lua_dir + lua_main_filename; + + is.open(lua_main_path); + if (!is.is_open()) + { + throw falco_exception("Could not find Falco Lua entrypoint (tried " + + string(FALCO_LUA_DIR) + lua_main_filename + ", " + + string(FALCO_SOURCE_LUA_DIR) + lua_main_filename + ")"); + } + } + + // Initialize Lua interpreter + add_lua_path(lua_dir); + + // Load the main program, which defines all the available functions. + string scriptstr((istreambuf_iterator(is)), + istreambuf_iterator()); + + if(luaL_loadstring(m_ls, scriptstr.c_str()) || lua_pcall(m_ls, 0, 0, 0)) + { + throw falco_exception("Failed to load script " + + lua_main_path + ": " + lua_tostring(m_ls, -1)); + } +} + +void falco_common::add_lua_path(string &path) +{ + string cpath = string(path); + path += "?.lua"; + cpath += "?.so"; + + lua_getglobal(m_ls, "package"); + + lua_getfield(m_ls, -1, "path"); + string cur_path = lua_tostring(m_ls, -1 ); + cur_path += ';'; + lua_pop(m_ls, 1); + + cur_path.append(path.c_str()); + + lua_pushstring(m_ls, cur_path.c_str()); + lua_setfield(m_ls, -2, "path"); + + lua_getfield(m_ls, -1, "cpath"); + string cur_cpath = lua_tostring(m_ls, -1 ); + cur_cpath += ';'; + lua_pop(m_ls, 1); + + cur_cpath.append(cpath.c_str()); + + lua_pushstring(m_ls, cur_cpath.c_str()); + lua_setfield(m_ls, -2, "cpath"); + + lua_pop(m_ls, 1); +} + diff --git a/userspace/falco/falco_common.h b/userspace/falco/falco_common.h new file mode 100644 index 00000000000..b3c49e06548 --- /dev/null +++ b/userspace/falco/falco_common.h @@ -0,0 +1,69 @@ +#pragma once + +#include +#include + +extern "C" { +#include "lua.h" +#include "lualib.h" +#include "lauxlib.h" +} + +#include + +// +// Most falco_* classes can throw exceptions. Unless directly related +// to low-level failures like inability to open file, etc, they will +// be of this type. +// + +struct falco_exception : std::exception +{ + falco_exception() + { + } + + virtual ~falco_exception() throw() + { + } + + falco_exception(std::string error_str) + { + m_error_str = error_str; + } + + char const* what() const throw() + { + return m_error_str.c_str(); + } + + std::string m_error_str; +}; + +// +// This is the base class of falco_engine/falco_output. It is +// responsible for managing a lua state and associated inspector and +// loading a single "main" lua file into that state. +// + +class falco_common +{ +public: + falco_common(); + virtual ~falco_common(); + + void init(std::string &lua_main_filename); + + void set_inspector(sinsp *inspector); + +protected: + lua_State *m_ls; + + sinsp *m_inspector; + +private: + void add_lua_path(std::string &path); +}; + + + diff --git a/userspace/falco/falco_engine.cpp b/userspace/falco/falco_engine.cpp new file mode 100644 index 00000000000..f144721e163 --- /dev/null +++ b/userspace/falco/falco_engine.cpp @@ -0,0 +1,143 @@ +#include +#include + +#include "falco_engine.h" + +extern "C" { +#include "lpeg.h" +#include "lyaml.h" +} + +#include "utils.h" + + +string lua_on_event = "on_event"; +string lua_print_stats = "print_stats"; + +using namespace std; + +falco_engine::falco_engine() +{ + luaopen_lpeg(m_ls); + luaopen_yaml(m_ls); + + falco_common::init(m_lua_main_filename); + falco_rules::init(m_ls); +} + +falco_engine::~falco_engine() +{ + if (m_rules) + { + delete m_rules; + } +} + +void falco_engine::load_rules(const string &rules_content, bool verbose, bool all_events) +{ + // The engine must have been given an inspector by now. + if(! m_inspector) + { + throw falco_exception("No inspector provided"); + } + + if(!m_rules) + { + m_rules = new falco_rules(m_inspector, this, m_ls); + } + m_rules->load_rules(rules_content, verbose, all_events); +} + +void falco_engine::load_rules_file(const string &rules_filename, bool verbose, bool all_events) +{ + ifstream is; + + is.open(rules_filename); + if (!is.is_open()) + { + throw falco_exception("Could not open rules filename " + + rules_filename + " " + + "for reading"); + } + + string rules_content((istreambuf_iterator(is)), + istreambuf_iterator()); + + load_rules(rules_content, verbose, all_events); +} + +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) +{ + if(!m_evttype_filter.run(ev)) + { + return NULL; + } + + struct rule_result *res = new rule_result(); + + lua_getglobal(m_ls, lua_on_event.c_str()); + + if(lua_isfunction(m_ls, -1)) + { + lua_pushlightuserdata(m_ls, ev); + lua_pushnumber(m_ls, ev->get_check_id()); + + if(lua_pcall(m_ls, 2, 3, 0) != 0) + { + const char* lerr = lua_tostring(m_ls, -1); + string err = "Error invoking function output: " + string(lerr); + throw falco_exception(err); + } + res->evt = ev; + const char *p = lua_tostring(m_ls, -3); + res->rule = p; + res->priority = lua_tostring(m_ls, -2); + res->format = lua_tostring(m_ls, -1); + } + else + { + throw falco_exception("No function " + lua_on_event + " found in lua compiler module"); + } + + return res; +} + +void falco_engine::describe_rule(string *rule) +{ + return m_rules->describe_rule(rule); +} + +// Print statistics on the the rules that triggered +void falco_engine::print_stats() +{ + lua_getglobal(m_ls, lua_print_stats.c_str()); + + if(lua_isfunction(m_ls, -1)) + { + if(lua_pcall(m_ls, 0, 0, 0) != 0) + { + const char* lerr = lua_tostring(m_ls, -1); + string err = "Error invoking function print_stats: " + string(lerr); + throw falco_exception(err); + } + } + else + { + throw falco_exception("No function " + lua_print_stats + " found in lua rule loader module"); + } + +} + +void falco_engine::add_evttype_filter(string &rule, + list &evttypes, + sinsp_filter* filter) +{ + m_evttype_filter.add(rule, evttypes, filter); +} + + diff --git a/userspace/falco/falco_engine.h b/userspace/falco/falco_engine.h new file mode 100644 index 00000000000..63675af9f73 --- /dev/null +++ b/userspace/falco/falco_engine.h @@ -0,0 +1,76 @@ +#pragma once + +#include + +#include "sinsp.h" +#include "filter.h" + +#include "rules.h" + +#include "config_falco.h" +#include "falco_common.h" + +// +// This class acts as the primary interface between a program and the +// falco rules engine. Falco outputs (writing to files/syslog/etc) are +// handled in a separate class falco_outputs. +// + +class falco_engine : public falco_common +{ +public: + falco_engine(); + virtual ~falco_engine(); + + // + // Load rules either directly or from a filename. + // + void load_rules_file(const std::string &rules_filename, bool verbose, bool all_events); + void load_rules(const std::string &rules_content, bool verbose, bool all_events); + + // + // Enable/Disable any rules matching the provided pattern (regex). + // + void enable_rule(std::string &pattern, bool enabled); + + struct rule_result { + sinsp_evt *evt; + std::string rule; + std::string priority; + std::string format; + }; + + // + // Given an event, check it against the set of rules in the + // engine and if a matching rule is found, return details on + // 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); + + // + // Print details on the given rule. If rule is NULL, print + // details on all rules. + // + void describe_rule(std::string *rule); + + // + // Print statistics on how many events matched each rule. + // + void print_stats(); + + // + // Add a filter, which is related to the specified list of + // event types, to the engine. + // + void add_evttype_filter(std::string &rule, + list &evttypes, + sinsp_filter* filter); + +private: + falco_rules *m_rules; + sinsp_evttype_filter m_evttype_filter; + + std::string m_lua_main_filename = "rule_loader.lua"; +}; + diff --git a/userspace/falco/falco_outputs.cpp b/userspace/falco/falco_outputs.cpp new file mode 100644 index 00000000000..7929f1c14b0 --- /dev/null +++ b/userspace/falco/falco_outputs.cpp @@ -0,0 +1,89 @@ + +#include "falco_outputs.h" + +#include "formats.h" +#include "logger.h" + +using namespace std; + +falco_outputs::falco_outputs() +{ + +} + +falco_outputs::~falco_outputs() +{ + +} + +void falco_outputs::init(bool json_output) +{ + // The engine must have been given an inspector by now. + if(! m_inspector) + { + throw falco_exception("No inspector provided"); + } + + falco_common::init(m_lua_main_filename); + + falco_formats::init(m_inspector, m_ls, json_output); + + falco_logger::init(m_ls); +} + +void falco_outputs::add_output(output_config oc) +{ + uint8_t nargs = 1; + lua_getglobal(m_ls, m_lua_add_output.c_str()); + + if(!lua_isfunction(m_ls, -1)) + { + throw falco_exception("No function " + m_lua_add_output + " found. "); + } + lua_pushstring(m_ls, oc.name.c_str()); + + // If we have options, build up a lua table containing them + if (oc.options.size()) + { + nargs = 2; + lua_createtable(m_ls, 0, oc.options.size()); + + for (auto it = oc.options.cbegin(); it != oc.options.cend(); ++it) + { + lua_pushstring(m_ls, (*it).second.c_str()); + lua_setfield(m_ls, -2, (*it).first.c_str()); + } + } + + if(lua_pcall(m_ls, nargs, 0, 0) != 0) + { + const char* lerr = lua_tostring(m_ls, -1); + throw falco_exception(string(lerr)); + } + +} + +void falco_outputs::handle_event(sinsp_evt *ev, string &level, string &priority, string &format) +{ + lua_getglobal(m_ls, m_lua_output_event.c_str()); + + 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.c_str()); + + if(lua_pcall(m_ls, 4, 0, 0) != 0) + { + const char* lerr = lua_tostring(m_ls, -1); + string err = "Error invoking function output: " + string(lerr); + throw falco_exception(err); + } + } + else + { + throw falco_exception("No function " + m_lua_output_event + " found in lua compiler module"); + } + +} diff --git a/userspace/falco/falco_outputs.h b/userspace/falco/falco_outputs.h new file mode 100644 index 00000000000..938dbb94c80 --- /dev/null +++ b/userspace/falco/falco_outputs.h @@ -0,0 +1,41 @@ +#pragma once + +#include "config_falco.h" + +#include "falco_common.h" + +// +// This class acts as the primary interface between a program and the +// falco output engine. The falco rules engine is implemented by a +// separate class falco_engine. +// + +class falco_outputs : public falco_common +{ +public: + falco_outputs(); + virtual ~falco_outputs(); + + // The way to refer to an output (file, syslog, stdout, + // etc). An output has a name and set of options. + struct output_config + { + std::string name; + std::map options; + }; + + void init(bool json_output); + + void add_output(output_config oc); + + // + // ev is an event that has matched some rule. Pass the event + // to all configured outputs. + // + void handle_event(sinsp_evt *ev, std::string &level, std::string &priority, std::string &format); + +private: + std::string m_lua_add_output = "add_output"; + std::string m_lua_output_event = "output_event"; + std::string m_lua_main_filename = "output.lua"; +}; diff --git a/userspace/falco/fields.cpp b/userspace/falco/fields.cpp deleted file mode 100644 index 9349fa0cd7d..00000000000 --- a/userspace/falco/fields.cpp +++ /dev/null @@ -1,76 +0,0 @@ -#include "fields.h" -#include "chisel_api.h" -#include "filterchecks.h" - - -extern sinsp_filter_check_list g_filterlist; - -const static struct luaL_reg ll_falco [] = -{ - {"field", &falco_fields::field}, - {NULL,NULL} -}; - -sinsp* falco_fields::s_inspector = NULL; - -std::map falco_fields::s_fieldname_map; - - -void falco_fields::init(sinsp* inspector, lua_State *ls) -{ - s_inspector = inspector; - - luaL_openlib(ls, "falco", ll_falco, 0); -} - -int falco_fields::field(lua_State *ls) -{ - - sinsp_filter_check* chk=NULL; - - if (!lua_islightuserdata(ls, 1)) - { - string err = "invalid argument passed to falco.field()"; - fprintf(stderr, "%s\n", err.c_str()); - throw sinsp_exception("falco.field() error"); - } - sinsp_evt* evt = (sinsp_evt*)lua_topointer(ls, 1); - - string fieldname = luaL_checkstring(ls, 2); - - if (s_fieldname_map.count(fieldname) == 0) - { - - chk = g_filterlist.new_filter_check_from_fldname(fieldname, - s_inspector, - false); - - if(chk == NULL) - { - string err = "nonexistent fieldname passed to falco.field(): " + string(fieldname); - fprintf(stderr, "%s\n", err.c_str()); - throw sinsp_exception("falco.field() error"); - } - - chk->parse_field_name(fieldname.c_str(), true); - s_fieldname_map[fieldname] = chk; - } - else - { - chk = s_fieldname_map[fieldname]; - } - - uint32_t vlen; - uint8_t* rawval = chk->extract(evt, &vlen); - - if(rawval != NULL) - { - return lua_cbacks::rawval_to_lua_stack(ls, rawval, chk->get_field_info(), vlen); - } - else - { - lua_pushnil(ls); - return 1; - } -} - diff --git a/userspace/falco/fields.h b/userspace/falco/fields.h deleted file mode 100644 index ff69c52d03e..00000000000 --- a/userspace/falco/fields.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include "sinsp.h" - -extern "C" { -#include "lua.h" -#include "lualib.h" -#include "lauxlib.h" -} - -class falco_fields -{ - public: - static void init(sinsp* inspector, lua_State *ls); - - // value = falco.field(evt, fieldname) - static int field(lua_State *ls); - - static sinsp* s_inspector; - static std::map s_fieldname_map; -}; diff --git a/userspace/falco/formats.cpp b/userspace/falco/formats.cpp index 142df600003..48b9a1a0af6 100644 --- a/userspace/falco/formats.cpp +++ b/userspace/falco/formats.cpp @@ -2,6 +2,7 @@ #include "formats.h" #include "logger.h" +#include "falco_engine.h" sinsp* falco_formats::s_inspector = NULL; @@ -10,6 +11,7 @@ bool s_json_output = false; const static struct luaL_reg ll_falco [] = { {"formatter", &falco_formats::formatter}, + {"free_formatter", &falco_formats::free_formatter}, {"format_event", &falco_formats::format_event}, {NULL,NULL} }; @@ -32,9 +34,7 @@ int falco_formats::formatter(lua_State *ls) } catch(sinsp_exception& e) { - falco_logger::log(LOG_ERR, "Invalid output format '" + format + "'.\n"); - - throw sinsp_exception("set_formatter error"); + throw falco_exception("Invalid output format '" + format + "'.\n"); } lua_pushlightuserdata(ls, formatter); @@ -42,6 +42,20 @@ int falco_formats::formatter(lua_State *ls) return 1; } +int falco_formats::free_formatter(lua_State *ls) +{ + if (!lua_islightuserdata(ls, -1)) + { + throw falco_exception("Invalid argument passed to free_formatter"); + } + + sinsp_evt_formatter *formatter = (sinsp_evt_formatter *) lua_topointer(ls, 1); + + delete(formatter); + + return 1; +} + int falco_formats::format_event (lua_State *ls) { string line; @@ -50,8 +64,7 @@ int falco_formats::format_event (lua_State *ls) !lua_isstring(ls, -2) || !lua_isstring(ls, -3) || !lua_islightuserdata(ls, -4)) { - falco_logger::log(LOG_ERR, "Invalid arguments passed to format_event()\n"); - throw sinsp_exception("format_event error"); + throw falco_exception("Invalid arguments passed to format_event()\n"); } sinsp_evt* evt = (sinsp_evt*)lua_topointer(ls, 1); const char *rule = (char *) lua_tostring(ls, 2); diff --git a/userspace/falco/formats.h b/userspace/falco/formats.h index 6f369bf3e48..4a71c926ec3 100644 --- a/userspace/falco/formats.h +++ b/userspace/falco/formats.h @@ -18,6 +18,9 @@ class falco_formats // formatter = falco.formatter(format_string) static int formatter(lua_State *ls); + // falco.free_formatter(formatter) + static int free_formatter(lua_State *ls); + // formatted_string = falco.format_event(evt, formatter) static int format_event(lua_State *ls); diff --git a/userspace/falco/logger.cpp b/userspace/falco/logger.cpp index 7c4bdc5b5b4..86af8207df9 100644 --- a/userspace/falco/logger.cpp +++ b/userspace/falco/logger.cpp @@ -1,9 +1,6 @@ #include #include "logger.h" #include "chisel_api.h" -#include "filterchecks.h" - - const static struct luaL_reg ll_falco [] = { diff --git a/userspace/falco/lua/output.lua b/userspace/falco/lua/output.lua index d35a8340861..158d7fbc6cc 100644 --- a/userspace/falco/lua/output.lua +++ b/userspace/falco/lua/output.lua @@ -6,10 +6,7 @@ mod.levels = levels local outputs = {} -function mod.stdout(evt, rule, level, format) - format = "*%evt.time: "..levels[level+1].." "..format - formatter = falco.formatter(format) - msg = falco.format_event(evt, rule, levels[level+1], formatter) +function mod.stdout(level, msg) print (msg) end @@ -26,29 +23,17 @@ function mod.file_validate(options) end -function mod.file(evt, rule, level, format, options) - format = "*%evt.time: "..levels[level+1].." "..format - formatter = falco.formatter(format) - msg = falco.format_event(evt, rule, levels[level+1], formatter) - +function mod.file(level, msg) file = io.open(options.filename, "a+") file:write(msg, "\n") file:close() end -function mod.syslog(evt, rule, level, format) - - formatter = falco.formatter(format) - msg = falco.format_event(evt, rule, levels[level+1], formatter) +function mod.syslog(level, msg) falco.syslog(level, msg) end -function mod.program(evt, rule, level, format, options) - - format = "*%evt.time: "..levels[level+1].." "..format - formatter = falco.formatter(format) - msg = falco.format_event(evt, rule, levels[level+1], formatter) - +function mod.program(level, msg) -- 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. @@ -59,10 +44,27 @@ function mod.program(evt, rule, level, format, options) file:close() end -function mod.event(event, rule, level, format) +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) + format = "*%evt.time: "..levels[level+1].." "..format + formatter = falco.formatter(format) + msg = falco.format_event(event, rule, levels[level+1], formatter) + for index,o in ipairs(outputs) do - o.output(event, rule, level, format, o.config) + o.output(level, msg) end + + falco.free_formatter(formatter) end function add_output(output_name, config) diff --git a/userspace/falco/lua/rule_loader.lua b/userspace/falco/lua/rule_loader.lua index e15b85c0817..f0885dfddf5 100644 --- a/userspace/falco/lua/rule_loader.lua +++ b/userspace/falco/lua/rule_loader.lua @@ -5,7 +5,6 @@ --]] -local output = require('output') local compiler = require "compiler" local yaml = require"lyaml" @@ -101,31 +100,23 @@ function set_output(output_format, state) end end -local function priority(s) - s = string.lower(s) - for i,v in ipairs(output.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 - -- Note that the rules_by_name and rules_by_idx refer to the same rule -- object. The by_name index is used for things like describing rules, -- and the by_idx index is used to map the relational node index back -- to a rule. local state = {macros={}, lists={}, filter_ast=nil, rules_by_name={}, n_rules=0, rules_by_idx={}} -function load_rules(filename, rules_mgr, verbose, all_events) +function load_rules(rules_content, rules_mgr, verbose, all_events) compiler.set_verbose(verbose) compiler.set_all_events(all_events) - local f = assert(io.open(filename, "r")) - local s = f:read("*all") - f:close() - local rules = yaml.load(s) + local rules = yaml.load(rules_content) + + if rules == nil then + -- An empty rules file is acceptable + return + end for i,v in ipairs(rules) do -- iterate over yaml list @@ -168,8 +159,6 @@ function load_rules(filename, rules_mgr, verbose, all_events) end end - -- Convert the priority as a string to a level now - v['level'] = priority(v['priority']) state.rules_by_name[v['rule']] = v local filter_ast, evttypes = compiler.compile_filter(v['rule'], v['condition'], @@ -190,7 +179,7 @@ function load_rules(filename, rules_mgr, verbose, all_events) install_filter(filter_ast.filter.value) -- Pass the filter and event types back up - falco_rules.add_filter(rules_mgr, evttypes) + falco_rules.add_filter(rules_mgr, v['rule'], evttypes) -- Rule ASTs are merged together into one big AST, with "OR" between each -- rule. @@ -256,11 +245,7 @@ function describe_rule(name) end end -local rule_output_counts = {total=0, by_level={}, by_name={}} - -for idx=0,table.getn(output.levels)-1,1 do - rule_output_counts.by_level[idx] = 0 -end +local rule_output_counts = {total=0, by_priority={}, by_name={}} function on_event(evt_, rule_id) @@ -271,10 +256,10 @@ function on_event(evt_, rule_id) rule_output_counts.total = rule_output_counts.total + 1 local rule = state.rules_by_idx[rule_id] - if rule_output_counts.by_level[rule.level] == nil then - rule_output_counts.by_level[rule.level] = 1 + if rule_output_counts.by_priority[rule.priority] == nil then + rule_output_counts.by_priority[rule.priority] = 1 else - rule_output_counts.by_level[rule.level] = rule_output_counts.by_level[rule.level] + 1 + rule_output_counts.by_priority[rule.priority] = rule_output_counts.by_priority[rule.priority] + 1 end if rule_output_counts.by_name[rule.rule] == nil then @@ -283,17 +268,14 @@ function on_event(evt_, rule_id) rule_output_counts.by_name[rule.rule] = rule_output_counts.by_name[rule.rule] + 1 end - output.event(evt_, rule.rule, rule.level, rule.output) + return rule.rule, rule.priority, rule.output end function print_stats() print("Events detected: "..rule_output_counts.total) print("Rule counts by severity:") - for idx, level in ipairs(output.levels) do - -- To keep the output concise, we only print 0 counts for error, warning, and info levels - if rule_output_counts.by_level[idx-1] > 0 or level == "Error" or level == "Warning" or level == "Informational" then - print (" "..level..": "..rule_output_counts.by_level[idx-1]) - end + for priority, count in pairs(rule_output_counts.by_priority) do + print (" "..priority..": "..count) end print("Triggered rules by rule name:") diff --git a/userspace/falco/rules.cpp b/userspace/falco/rules.cpp index ce09ab16c00..04078ba0700 100644 --- a/userspace/falco/rules.cpp +++ b/userspace/falco/rules.cpp @@ -7,20 +7,17 @@ extern "C" { #include "lauxlib.h" } +#include "falco_engine.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) +falco_rules::falco_rules(sinsp* inspector, falco_engine *engine, lua_State *ls) + : m_inspector(inspector), m_engine(engine), m_ls(ls) { - m_inspector = inspector; - m_ls = ls; - m_lua_parser = new lua_parser(inspector, m_ls); - - load_compiler(lua_main_filename); } void falco_rules::init(lua_State *ls) @@ -30,14 +27,15 @@ void falco_rules::init(lua_State *ls) int falco_rules::add_filter(lua_State *ls) { - if (! lua_islightuserdata(ls, -2) || + if (! lua_islightuserdata(ls, -3) || + ! lua_isstring(ls, -2) || ! lua_istable(ls, -1)) { - falco_logger::log(LOG_ERR, "Invalid arguments passed to add_filter()\n"); - throw sinsp_exception("add_filter error"); + throw falco_exception("Invalid arguments passed to add_filter()\n"); } - falco_rules *rules = (falco_rules *) lua_topointer(ls, -2); + falco_rules *rules = (falco_rules *) lua_topointer(ls, -3); + const char *rulec = lua_tostring(ls, -2); list evttypes; @@ -51,44 +49,23 @@ int falco_rules::add_filter(lua_State *ls) lua_pop(ls, 1); } - rules->add_filter(evttypes); + std::string rule = rulec; + rules->add_filter(rule, evttypes); return 0; } -void falco_rules::add_filter(list &evttypes) +void falco_rules::add_filter(string &rule, 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. + // and pass it to the engine. 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) -{ - ifstream is; - is.open(lua_main_filename); - if(!is.is_open()) - { - throw sinsp_exception("can't open file " + lua_main_filename); - } - - string scriptstr((istreambuf_iterator(is)), - istreambuf_iterator()); - - // - // Load the compiler script - // - if(luaL_loadstring(m_ls, scriptstr.c_str()) || lua_pcall(m_ls, 0, 0, 0)) - { - throw sinsp_exception("Failed to load script " + - lua_main_filename + ": " + lua_tostring(m_ls, -1)); - } + m_engine->add_evttype_filter(rule, evttypes, filter); } -void falco_rules::load_rules(string rules_filename, bool verbose, bool all_events) +void falco_rules::load_rules(const string &rules_content, bool verbose, bool all_events) { lua_getglobal(m_ls, m_lua_load_rules.c_str()); if(lua_isfunction(m_ls, -1)) @@ -158,7 +135,7 @@ void falco_rules::load_rules(string rules_filename, bool verbose, bool all_event lua_setglobal(m_ls, m_lua_ignored_syscalls.c_str()); - lua_pushstring(m_ls, rules_filename.c_str()); + lua_pushstring(m_ls, rules_content.c_str()); lua_pushlightuserdata(m_ls, this); lua_pushboolean(m_ls, (verbose ? 1 : 0)); lua_pushboolean(m_ls, (all_events ? 1 : 0)); @@ -166,10 +143,10 @@ void falco_rules::load_rules(string rules_filename, bool verbose, bool all_event { const char* lerr = lua_tostring(m_ls, -1); string err = "Error loading rules:" + string(lerr); - throw sinsp_exception(err); + throw falco_exception(err); } } else { - throw sinsp_exception("No function " + m_lua_load_rules + " found in lua rule module"); + throw falco_exception("No function " + m_lua_load_rules + " found in lua rule module"); } } @@ -189,19 +166,14 @@ void falco_rules::describe_rule(std::string *rule) { const char* lerr = lua_tostring(m_ls, -1); string err = "Could not describe " + (rule == NULL ? "all rules" : "rule " + *rule) + ": " + string(lerr); - throw sinsp_exception(err); + throw falco_exception(err); } } else { - throw sinsp_exception("No function " + m_lua_describe_rule + " found in lua rule module"); + throw falco_exception("No function " + m_lua_describe_rule + " found in lua rule module"); } } -sinsp_filter* falco_rules::get_filter() -{ - return m_lua_parser->get_filter(); -} - falco_rules::~falco_rules() { delete m_lua_parser; diff --git a/userspace/falco/rules.h b/userspace/falco/rules.h index b049a8277c9..8f2ef6d8382 100644 --- a/userspace/falco/rules.h +++ b/userspace/falco/rules.h @@ -3,33 +3,33 @@ #include #include "sinsp.h" + #include "lua_parser.h" +class falco_engine; + class falco_rules { public: - falco_rules(sinsp* inspector, lua_State *ls, string lua_main_filename); + falco_rules(sinsp* inspector, falco_engine *engine, lua_State *ls); ~falco_rules(); - void load_rules(string rules_filename, bool verbose, bool all_events); + void load_rules(const string &rules_content, bool verbose, bool all_events); 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); + void add_filter(string &rule, list &evttypes); lua_parser* m_lua_parser; sinsp* m_inspector; + falco_engine *m_engine; lua_State* m_ls; 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 b1857eff354e683500a5e85e774403b21d09ff54 Mon Sep 17 00:00:00 2001 From: Mark Stemm Date: Wed, 20 Jul 2016 15:31:34 -0700 Subject: [PATCH 3/5] Move falco engine to its own library. Move the c++ and lua code implementing falco engine/falco common to its own directory userspace/engine. It's compiled as a static library libfalco_engine.a, and has its own CMakeLists.txt so it can be included by other projects. The engine's CMakeLists.txt has a add_subdirectory for the falco rules directory, so including the engine also builds the rules. The variables you need to set to use the engine's CMakeLists.txt are: - CMAKE_INSTALL_PREFIX: the root directory below which everything is installed. - FALCO_ETC_DIR: where to install the rules file. - FALCO_SHARE_DIR: where to install lua code, relative to the - install/package root. - LUAJIT_INCLUDE: where to find header files for lua. - FALCO_SINSP_LIBRARY: the library containing sinsp code. It will be - considered a dependency of the engine. - LPEG_LIB/LYAML_LIB/LIBYAML_LIB: locations for third-party libraries. - FALCO_COMPONENT: if set, will be included as a part of any install() commands. Instead of specifying /usr/share/falco in config_falco_*.h.in, use CMAKE_INSTALL_PREFIX and FALCO_SHARE_DIR. The lua code for the engine has also moved, so the two lua source directories (userspace/engine/lua and userspace/falco/lua) need to be available separately via falco_common, so make it an argument to falco_common::init. As a part of making it easy to include in another project, also clean up LPEG build/defs. Modify build-lpeg to add a PREFIX argument to allow for object files/libraries being in an alternate location, and when building lpeg, put object files in a build/ subdirectory. --- CMakeLists.txt | 16 ++++++---- rules/CMakeLists.txt | 12 ++++++- scripts/build-lpeg.sh | 24 ++++++++++---- userspace/engine/CMakeLists.txt | 31 +++++++++++++++++++ userspace/engine/config_falco_engine.h.in | 4 +++ userspace/{falco => engine}/falco_common.cpp | 12 +++---- userspace/{falco => engine}/falco_common.h | 2 +- userspace/{falco => engine}/falco_engine.cpp | 4 ++- userspace/{falco => engine}/falco_engine.h | 1 - userspace/{falco => engine}/lpeg.h | 0 userspace/{falco => engine}/lua/README.md | 0 userspace/{falco => engine}/lua/compiler.lua | 0 .../{falco => engine}/lua/parser-smoke.sh | 0 userspace/{falco => engine}/lua/parser.lua | 0 .../{falco => engine}/lua/rule_loader.lua | 0 userspace/{falco => engine}/lyaml.h | 0 userspace/{falco => engine}/rules.cpp | 0 userspace/{falco => engine}/rules.h | 0 userspace/falco/CMakeLists.txt | 7 ++--- userspace/falco/config_falco.h.in | 2 +- userspace/falco/falco.cpp | 1 + userspace/falco/falco_outputs.cpp | 5 ++- userspace/falco/falco_outputs.h | 2 -- 23 files changed, 93 insertions(+), 30 deletions(-) create mode 100644 userspace/engine/CMakeLists.txt create mode 100644 userspace/engine/config_falco_engine.h.in rename userspace/{falco => engine}/falco_common.cpp (84%) rename userspace/{falco => engine}/falco_common.h (93%) rename userspace/{falco => engine}/falco_engine.cpp (95%) rename userspace/{falco => engine}/falco_engine.h (98%) rename userspace/{falco => engine}/lpeg.h (100%) rename userspace/{falco => engine}/lua/README.md (100%) rename userspace/{falco => engine}/lua/compiler.lua (100%) rename userspace/{falco => engine}/lua/parser-smoke.sh (100%) rename userspace/{falco => engine}/lua/parser.lua (100%) rename userspace/{falco => engine}/lua/rule_loader.lua (100%) rename userspace/{falco => engine}/lyaml.h (100%) rename userspace/{falco => engine}/rules.cpp (100%) rename userspace/{falco => engine}/rules.h (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index b0809137273..730f559fcdf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,8 +6,8 @@ if(NOT DEFINED FALCO_VERSION) set(FALCO_VERSION "0.1.1dev") endif() -if(NOT DEFINED DIR_ETC) - set(DIR_ETC "/etc") +if(NOT DEFINED FALCO_ETC_DIR) + set(FALCO_ETC_DIR "/etc") endif() if(NOT CMAKE_BUILD_TYPE) @@ -39,6 +39,7 @@ set(PACKAGE_NAME "falco") set(PROBE_VERSION "${FALCO_VERSION}") set(PROBE_NAME "sysdig-probe") set(PROBE_DEVICE_NAME "sysdig") +set(CMAKE_INSTALL_PREFIX /usr) set(CMD_MAKE make) @@ -160,11 +161,12 @@ ExternalProject_Add(luajit INSTALL_COMMAND "") set (LPEG_SRC "${PROJECT_BINARY_DIR}/lpeg-prefix/src/lpeg") +set (LPEG_LIB "${PROJECT_BINARY_DIR}/lpeg-prefix/src/lpeg/build/lpeg.a") ExternalProject_Add(lpeg DEPENDS luajit URL "http://s3.amazonaws.com/download.draios.com/dependencies/lpeg-1.0.0.tar.gz" URL_MD5 "0aec64ccd13996202ad0c099e2877ece" - BUILD_COMMAND LUA_INCLUDE=${LUAJIT_INCLUDE} "${PROJECT_SOURCE_DIR}/scripts/build-lpeg.sh" + BUILD_COMMAND LUA_INCLUDE=${LUAJIT_INCLUDE} "${PROJECT_SOURCE_DIR}/scripts/build-lpeg.sh" "${LPEG_SRC}/build" BUILD_IN_SOURCE 1 CONFIGURE_COMMAND "" INSTALL_COMMAND "") @@ -188,17 +190,19 @@ ExternalProject_Add(lyaml BUILD_COMMAND ${CMD_MAKE} BUILD_IN_SOURCE 1 CONFIGURE_COMMAND ./configure --enable-static LIBS=-L../../../libyaml-prefix/src/libyaml/src/.libs CFLAGS=-I../../../libyaml-prefix/src/libyaml/include CPPFLAGS=-I../../../libyaml-prefix/src/libyaml/include LUA_INCLUDE=-I../../../luajit-prefix/src/luajit/src LUA=../../../luajit-prefix/src/luajit/src/luajit - INSTALL_COMMAND sh -c "cp -R ${PROJECT_BINARY_DIR}/lyaml-prefix/src/lyaml/lib/* ${PROJECT_SOURCE_DIR}/userspace/falco/lua") + INSTALL_COMMAND sh -c "cp -R ${PROJECT_BINARY_DIR}/lyaml-prefix/src/lyaml/lib/* ${PROJECT_SOURCE_DIR}/userspace/engine/lua") install(FILES falco.yaml - DESTINATION "${DIR_ETC}") + DESTINATION "${FALCO_ETC_DIR}") add_subdirectory("${SYSDIG_DIR}/driver" "${PROJECT_BINARY_DIR}/driver") add_subdirectory("${SYSDIG_DIR}/userspace/libscap" "${PROJECT_BINARY_DIR}/userspace/libscap") add_subdirectory("${SYSDIG_DIR}/userspace/libsinsp" "${PROJECT_BINARY_DIR}/userspace/libsinsp") -add_subdirectory(rules) add_subdirectory(scripts) +set(FALCO_SINSP_LIBRARY sinsp) +set(FALCO_SHARE_DIR share/falco) +add_subdirectory(userspace/engine) add_subdirectory(userspace/falco) diff --git a/rules/CMakeLists.txt b/rules/CMakeLists.txt index 8e7bfb68364..916f5f8f40e 100644 --- a/rules/CMakeLists.txt +++ b/rules/CMakeLists.txt @@ -1,3 +1,13 @@ +if(NOT DEFINED FALCO_ETC_DIR) + set(FALCO_ETC_DIR "/etc") +endif() + +if(DEFINED FALCO_COMPONENT) +install(FILES falco_rules.yaml + COMPONENT "${FALCO_COMPONENT}" + DESTINATION "${FALCO_ETC_DIR}") +else() install(FILES falco_rules.yaml - DESTINATION "${DIR_ETC}") + DESTINATION "${FALCO_ETC_DIR}") +endif() diff --git a/scripts/build-lpeg.sh b/scripts/build-lpeg.sh index 6a8db3fd614..ba77159f648 100755 --- a/scripts/build-lpeg.sh +++ b/scripts/build-lpeg.sh @@ -1,17 +1,29 @@ -#!/bin/sh +#!/bin/bash -gcc -O2 -fPIC -I$LUA_INCLUDE -c lpcap.c -o lpcap.o -gcc -O2 -fPIC -I$LUA_INCLUDE -c lpcode.c -o lpcode.o -gcc -O2 -fPIC -I$LUA_INCLUDE -c lpprint.c -o lpprint.o -gcc -O2 -fPIC -I$LUA_INCLUDE -c lptree.c -o lptree.o -gcc -O2 -fPIC -I$LUA_INCLUDE -c lpvm.c -o lpvm.o +set -ex + +PREFIX=$1 + +if [ -z $PREFIX ]; then + PREFIX=. +fi + +mkdir -p $PREFIX + +gcc -O2 -fPIC -I$LUA_INCLUDE -c lpcap.c -o $PREFIX/lpcap.o +gcc -O2 -fPIC -I$LUA_INCLUDE -c lpcode.c -o $PREFIX/lpcode.o +gcc -O2 -fPIC -I$LUA_INCLUDE -c lpprint.c -o $PREFIX/lpprint.o +gcc -O2 -fPIC -I$LUA_INCLUDE -c lptree.c -o $PREFIX/lptree.o +gcc -O2 -fPIC -I$LUA_INCLUDE -c lpvm.c -o $PREFIX/lpvm.o # For building lpeg.so, which we don't need now that we're statically linking lpeg.a into falco #gcc -shared -o lpeg.so -L/usr/local/lib lpcap.o lpcode.o lpprint.o lptree.o lpvm.o #gcc -shared -o lpeg.so -L/usr/local/lib lpcap.o lpcode.o lpprint.o lptree.o lpvm.o +pushd $PREFIX /usr/bin/ar cr lpeg.a lpcap.o lpcode.o lpprint.o lptree.o lpvm.o /usr/bin/ranlib lpeg.a +popd chmod ug+w re.lua diff --git a/userspace/engine/CMakeLists.txt b/userspace/engine/CMakeLists.txt new file mode 100644 index 00000000000..dfc85495362 --- /dev/null +++ b/userspace/engine/CMakeLists.txt @@ -0,0 +1,31 @@ +include_directories("${PROJECT_SOURCE_DIR}/../sysdig/userspace/libsinsp/third-party/jsoncpp") +include_directories("${PROJECT_SOURCE_DIR}/../sysdig/userspace/libscap") +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) + +target_include_directories(falco_engine PUBLIC + "${LUAJIT_INCLUDE}") + +target_link_libraries(falco_engine + "${FALCO_SINSP_LIBRARY}" + "${LPEG_LIB}" + "${LYAML_LIB}" + "${LIBYAML_LIB}") + +configure_file(config_falco_engine.h.in config_falco_engine.h) + +if(DEFINED FALCO_COMPONENT) +install(DIRECTORY lua + DESTINATION "${FALCO_SHARE_DIR}" + COMPONENT "${FALCO_COMPONENT}" + FILES_MATCHING PATTERN *.lua) +else() +install(DIRECTORY lua + DESTINATION "${FALCO_SHARE_DIR}" + FILES_MATCHING PATTERN *.lua) +endif() + +add_subdirectory("${PROJECT_SOURCE_DIR}/../falco/rules" "${PROJECT_BINARY_DIR}/rules") diff --git a/userspace/engine/config_falco_engine.h.in b/userspace/engine/config_falco_engine.h.in new file mode 100644 index 00000000000..a0481911191 --- /dev/null +++ b/userspace/engine/config_falco_engine.h.in @@ -0,0 +1,4 @@ +#pragma once + +#define FALCO_ENGINE_LUA_DIR "${CMAKE_INSTALL_PREFIX}/${FALCO_SHARE_DIR}/lua/" +#define FALCO_ENGINE_SOURCE_LUA_DIR "${PROJECT_SOURCE_DIR}/../falco/userspace/engine/lua/" diff --git a/userspace/falco/falco_common.cpp b/userspace/engine/falco_common.cpp similarity index 84% rename from userspace/falco/falco_common.cpp rename to userspace/engine/falco_common.cpp index 47874180e4d..1e2361ec186 100644 --- a/userspace/falco/falco_common.cpp +++ b/userspace/engine/falco_common.cpp @@ -1,6 +1,6 @@ #include -#include "config_falco.h" +#include "config_falco_engine.h" #include "falco_common.h" falco_common::falco_common() @@ -22,24 +22,24 @@ void falco_common::set_inspector(sinsp *inspector) m_inspector = inspector; } -void falco_common::init(string &lua_main_filename) +void falco_common::init(const char *lua_main_filename, const char *source_dir) { ifstream is; - string lua_dir = FALCO_LUA_DIR; + string lua_dir = FALCO_ENGINE_LUA_DIR; string lua_main_path = lua_dir + lua_main_filename; is.open(lua_main_path); if (!is.is_open()) { - lua_dir = FALCO_SOURCE_LUA_DIR; + lua_dir = source_dir; lua_main_path = lua_dir + lua_main_filename; is.open(lua_main_path); if (!is.is_open()) { throw falco_exception("Could not find Falco Lua entrypoint (tried " + - string(FALCO_LUA_DIR) + lua_main_filename + ", " + - string(FALCO_SOURCE_LUA_DIR) + lua_main_filename + ")"); + string(FALCO_ENGINE_LUA_DIR) + lua_main_filename + ", " + + string(source_dir) + lua_main_filename + ")"); } } diff --git a/userspace/falco/falco_common.h b/userspace/engine/falco_common.h similarity index 93% rename from userspace/falco/falco_common.h rename to userspace/engine/falco_common.h index b3c49e06548..d08a274da39 100644 --- a/userspace/falco/falco_common.h +++ b/userspace/engine/falco_common.h @@ -52,7 +52,7 @@ class falco_common falco_common(); virtual ~falco_common(); - void init(std::string &lua_main_filename); + void init(const char *lua_main_filename, const char *source_dir); void set_inspector(sinsp *inspector); diff --git a/userspace/falco/falco_engine.cpp b/userspace/engine/falco_engine.cpp similarity index 95% rename from userspace/falco/falco_engine.cpp rename to userspace/engine/falco_engine.cpp index f144721e163..c4dcb7714bd 100644 --- a/userspace/falco/falco_engine.cpp +++ b/userspace/engine/falco_engine.cpp @@ -2,6 +2,7 @@ #include #include "falco_engine.h" +#include "config_falco_engine.h" extern "C" { #include "lpeg.h" @@ -17,11 +18,12 @@ string lua_print_stats = "print_stats"; using namespace std; falco_engine::falco_engine() + : m_rules(NULL) { luaopen_lpeg(m_ls); luaopen_yaml(m_ls); - falco_common::init(m_lua_main_filename); + falco_common::init(m_lua_main_filename.c_str(), FALCO_ENGINE_SOURCE_LUA_DIR); falco_rules::init(m_ls); } diff --git a/userspace/falco/falco_engine.h b/userspace/engine/falco_engine.h similarity index 98% rename from userspace/falco/falco_engine.h rename to userspace/engine/falco_engine.h index 63675af9f73..38661a06b95 100644 --- a/userspace/falco/falco_engine.h +++ b/userspace/engine/falco_engine.h @@ -7,7 +7,6 @@ #include "rules.h" -#include "config_falco.h" #include "falco_common.h" // diff --git a/userspace/falco/lpeg.h b/userspace/engine/lpeg.h similarity index 100% rename from userspace/falco/lpeg.h rename to userspace/engine/lpeg.h diff --git a/userspace/falco/lua/README.md b/userspace/engine/lua/README.md similarity index 100% rename from userspace/falco/lua/README.md rename to userspace/engine/lua/README.md diff --git a/userspace/falco/lua/compiler.lua b/userspace/engine/lua/compiler.lua similarity index 100% rename from userspace/falco/lua/compiler.lua rename to userspace/engine/lua/compiler.lua diff --git a/userspace/falco/lua/parser-smoke.sh b/userspace/engine/lua/parser-smoke.sh similarity index 100% rename from userspace/falco/lua/parser-smoke.sh rename to userspace/engine/lua/parser-smoke.sh diff --git a/userspace/falco/lua/parser.lua b/userspace/engine/lua/parser.lua similarity index 100% rename from userspace/falco/lua/parser.lua rename to userspace/engine/lua/parser.lua diff --git a/userspace/falco/lua/rule_loader.lua b/userspace/engine/lua/rule_loader.lua similarity index 100% rename from userspace/falco/lua/rule_loader.lua rename to userspace/engine/lua/rule_loader.lua diff --git a/userspace/falco/lyaml.h b/userspace/engine/lyaml.h similarity index 100% rename from userspace/falco/lyaml.h rename to userspace/engine/lyaml.h diff --git a/userspace/falco/rules.cpp b/userspace/engine/rules.cpp similarity index 100% rename from userspace/falco/rules.cpp rename to userspace/engine/rules.cpp diff --git a/userspace/falco/rules.h b/userspace/engine/rules.h similarity index 100% rename from userspace/falco/rules.h rename to userspace/engine/rules.h diff --git a/userspace/falco/CMakeLists.txt b/userspace/falco/CMakeLists.txt index 510c0b54a4f..9111bcfada5 100644 --- a/userspace/falco/CMakeLists.txt +++ b/userspace/falco/CMakeLists.txt @@ -3,17 +3,16 @@ include_directories("${LUAJIT_INCLUDE}") include_directories("${PROJECT_SOURCE_DIR}/../sysdig/userspace/libscap") include_directories("${PROJECT_SOURCE_DIR}/../sysdig/userspace/libsinsp") +include_directories("${PROJECT_SOURCE_DIR}/userspace/engine") include_directories("${PROJECT_BINARY_DIR}/userspace/falco") 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 rules.cpp logger.cpp falco_common.cpp falco_engine.cpp falco_outputs.cpp falco.cpp) +add_executable(falco configuration.cpp formats.cpp logger.cpp falco_outputs.cpp falco.cpp) -target_link_libraries(falco sinsp) +target_link_libraries(falco falco_engine sinsp) target_link_libraries(falco - "${LPEG_SRC}/lpeg.a" - "${LYAML_LIB}" "${LIBYAML_LIB}" "${YAMLCPP_LIB}") diff --git a/userspace/falco/config_falco.h.in b/userspace/falco/config_falco.h.in index 0f0ab12413d..a977dbb033c 100644 --- a/userspace/falco/config_falco.h.in +++ b/userspace/falco/config_falco.h.in @@ -2,7 +2,7 @@ #define FALCO_VERSION "${FALCO_VERSION}" -#define FALCO_LUA_DIR "/usr/share/falco/lua/" +#define FALCO_LUA_DIR "${CMAKE_INSTALL_PREFIX}/${FALCO_SHARE_DIR}/lua/" #define FALCO_SOURCE_DIR "${PROJECT_SOURCE_DIR}" #define FALCO_SOURCE_CONF_FILE "${PROJECT_SOURCE_DIR}/falco.yaml" #define FALCO_INSTALL_CONF_FILE "/etc/falco.yaml" diff --git a/userspace/falco/falco.cpp b/userspace/falco/falco.cpp index 8359c2a2d7c..e7ebc1b2dbc 100644 --- a/userspace/falco/falco.cpp +++ b/userspace/falco/falco.cpp @@ -14,6 +14,7 @@ #include "configuration.h" #include "falco_engine.h" +#include "config_falco.h" bool g_terminate = false; // diff --git a/userspace/falco/falco_outputs.cpp b/userspace/falco/falco_outputs.cpp index 7929f1c14b0..d16cbdda1a6 100644 --- a/userspace/falco/falco_outputs.cpp +++ b/userspace/falco/falco_outputs.cpp @@ -1,6 +1,9 @@ #include "falco_outputs.h" +#include "config_falco.h" + + #include "formats.h" #include "logger.h" @@ -24,7 +27,7 @@ void falco_outputs::init(bool json_output) throw falco_exception("No inspector provided"); } - falco_common::init(m_lua_main_filename); + falco_common::init(m_lua_main_filename.c_str(), FALCO_SOURCE_LUA_DIR); falco_formats::init(m_inspector, m_ls, json_output); diff --git a/userspace/falco/falco_outputs.h b/userspace/falco/falco_outputs.h index 938dbb94c80..28da94d671e 100644 --- a/userspace/falco/falco_outputs.h +++ b/userspace/falco/falco_outputs.h @@ -1,7 +1,5 @@ #pragma once -#include "config_falco.h" - #include "falco_common.h" // From 09405e4fad177f926a85325353cb5fb39cfc2ccd Mon Sep 17 00:00:00 2001 From: Mark Stemm Date: Wed, 27 Jul 2016 15:18:37 -0700 Subject: [PATCH 4/5] Add configurable event dropping for falco engine. Add the ability to drop events at the falco engine level in a way that can scale with the dropping that already occurs at the kernel/inspector level. New inline function should_drop_evt() controls whether or not events are matched against the set of rules, and is controlled by two values--sampling ratio and sampling multiplier. Here's how the sampling ratio and multiplier influence whether or not an event is dropped in should_drop_evt(). The intent is that m_sampling_ratio is generally changing external to the engine e.g. in the main inspector class based on how busy the inspector is. A sampling ratio implies no dropping. Values > 1 imply increasing levels of dropping. External to the engine, the sampling ratio results in events being dropped at the kernel/inspector interface. The sampling multiplier is an amplification to the sampling factor in m_sampling_ratio. If 0, no additional events are dropped other than those that might be dropped by the kernel/inspector interface. If 1, events that make it past the kernel module are subject to an additional level of dropping at the falco engine, scaling with the sampling ratio in m_sampling_ratio. Unlike the dropping that occurs at the kernel level, where the events in the first part of each second are dropped, this dropping is random. --- userspace/engine/falco_engine.cpp | 41 ++++++++++++++++++++++++++-- userspace/engine/falco_engine.h | 45 ++++++++++++++++++++++++++++++- 2 files changed, 83 insertions(+), 3 deletions(-) diff --git a/userspace/engine/falco_engine.cpp b/userspace/engine/falco_engine.cpp index c4dcb7714bd..15aa11d441b 100644 --- a/userspace/engine/falco_engine.cpp +++ b/userspace/engine/falco_engine.cpp @@ -1,3 +1,5 @@ +#include +#include #include #include @@ -17,14 +19,19 @@ string lua_print_stats = "print_stats"; using namespace std; -falco_engine::falco_engine() - : m_rules(NULL) +falco_engine::falco_engine(bool seed_rng) + : m_rules(NULL), m_sampling_ratio(1), m_sampling_multiplier(0) { luaopen_lpeg(m_ls); luaopen_yaml(m_ls); falco_common::init(m_lua_main_filename.c_str(), FALCO_ENGINE_SOURCE_LUA_DIR); falco_rules::init(m_ls); + + if(seed_rng) + { + srandom((unsigned) getpid()); + } } falco_engine::~falco_engine() @@ -75,6 +82,12 @@ void falco_engine::enable_rule(string &pattern, bool enabled) falco_engine::rule_result *falco_engine::process_event(sinsp_evt *ev) { + + if(should_drop_evt()) + { + return NULL; + } + if(!m_evttype_filter.run(ev)) { return NULL; @@ -142,4 +155,28 @@ void falco_engine::add_evttype_filter(string &rule, m_evttype_filter.add(rule, evttypes, filter); } +void falco_engine::set_sampling_ratio(uint32_t sampling_ratio) +{ + m_sampling_ratio = sampling_ratio; +} +void falco_engine::set_sampling_multiplier(double sampling_multiplier) +{ + m_sampling_multiplier = sampling_multiplier; +} + +inline bool falco_engine::should_drop_evt() +{ + if(m_sampling_multiplier == 0) + { + return false; + } + + if(m_sampling_ratio == 1) + { + return false; + } + + double coin = (random() * (1.0/RAND_MAX)); + return (coin >= (1.0/(m_sampling_multiplier * m_sampling_ratio))); +} diff --git a/userspace/engine/falco_engine.h b/userspace/engine/falco_engine.h index 38661a06b95..30223c99ffc 100644 --- a/userspace/engine/falco_engine.h +++ b/userspace/engine/falco_engine.h @@ -18,7 +18,7 @@ class falco_engine : public falco_common { public: - falco_engine(); + falco_engine(bool seed_rng=true); virtual ~falco_engine(); // @@ -66,10 +66,53 @@ class falco_engine : public falco_common list &evttypes, sinsp_filter* filter); + // + // Set the sampling ratio, which can affect which events are + // matched against the set of rules. + // + void set_sampling_ratio(uint32_t sampling_ratio); + + // + // Set the sampling ratio multiplier, which can affect which + // events are matched against the set of rules. + // + void set_sampling_multiplier(double sampling_multiplier); + private: + + // + // Determine whether the given event should be matched at all + // against the set of rules, given the current sampling + // ratio/multiplier. + // + inline bool should_drop_evt(); + falco_rules *m_rules; sinsp_evttype_filter m_evttype_filter; + // + // Here's how the sampling ratio and multiplier influence + // whether or not an event is dropped in + // should_drop_evt(). The intent is that m_sampling_ratio is + // generally changing external to the engine e.g. in the main + // inspector class based on how busy the inspector is. A + // sampling ratio implies no dropping. Values > 1 imply + // increasing levels of dropping. External to the engine, the + // sampling ratio results in events being dropped at the + // kernel/inspector interface. + // + // The sampling multiplier is an amplification to the sampling + // factor in m_sampling_ratio. If 0, no additional events are + // dropped other than those that might be dropped by the + // kernel/inspector interface. If 1, events that make it past + // the kernel module are subject to an additional level of + // dropping at the falco engine, scaling with the sampling + // ratio in m_sampling_ratio. + // + + uint32_t m_sampling_ratio; + double m_sampling_multiplier; + std::string m_lua_main_filename = "rule_loader.lua"; }; From f1748060c57cbb90180cbc4d179f2fec7b8201c0 Mon Sep 17 00:00:00 2001 From: Mark Stemm Date: Thu, 4 Aug 2016 12:01:54 -0700 Subject: [PATCH 5/5] Add tests for multiple files, disabled rules. Add test that cover reading from multiple sets of rule files and disabling rules. Specific changes: - Modify falco to allow multiple -r arguments to read from multiple files. - In the test multiplex file, add a disabled_rules attribute, containing a sequence of rules to disable. Result in -D arguments when running falco. - In the test multiplex file, 'rules_file' can be a sequence. It results in multiple -r arguments when running falco. - In the test multiplex file, 'detect_level' can be a squence of multiple severity levels. All levels will be checked for in the output. - Move all test rules files to a rules subdirectory and all trace files to a traces subdirectory. - Add a small trace file for a simple cat of /dev/null. Used by the new tests. - Add the following new tests: - Reading from multiple files, with the first file being empty. Ensure that the rules from the second file are properly loaded. - Reading from multiple files with the last being empty. Ensures that the empty file doesn't overwrite anything from the first file. - Reading from multiple files with varying severity levels for each rule. Ensures that both files are properly read. - Disabling rules from a rules file, both with full rule names and regexes. Will result in not detecting anything. --- test/falco_test.py | 46 ++++++++++++++----- test/falco_tests.yaml.in | 51 +++++++++++++++++++-- test/rules/double_rule.yaml | 13 ++++++ test/rules/empty_rules.yaml | 0 test/{ => rules}/falco_rules_warnings.yaml | 0 test/rules/single_rule.yaml | 8 ++++ test/trace_files/cat_write.scap | Bin 0 -> 12532 bytes test/{ => trace_files}/empty.scap | Bin userspace/falco/configuration.cpp | 2 +- userspace/falco/configuration.h | 2 +- userspace/falco/falco.cpp | 20 +++++--- 11 files changed, 119 insertions(+), 23 deletions(-) create mode 100644 test/rules/double_rule.yaml create mode 100644 test/rules/empty_rules.yaml rename test/{ => rules}/falco_rules_warnings.yaml (100%) create mode 100644 test/rules/single_rule.yaml create mode 100644 test/trace_files/cat_write.scap rename test/{ => trace_files}/empty.scap (100%) diff --git a/test/falco_test.py b/test/falco_test.py index 7b72d5eef8a..66eff5856f0 100644 --- a/test/falco_test.py +++ b/test/falco_test.py @@ -26,8 +26,28 @@ def setUp(self): 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) + if not isinstance(self.rules_file, list): + self.rules_file = [self.rules_file] + + self.rules_args = "" + + for file in self.rules_file: + if not os.path.isabs(file): + file = os.path.join(self.basedir, file) + self.rules_args = self.rules_args + "-r " + file + " " + + self.disabled_rules = self.params.get('disabled_rules', '*', default='') + + if self.disabled_rules == '': + self.disabled_rules = [] + + if not isinstance(self.disabled_rules, list): + self.disabled_rules = [self.disabled_rules] + + self.disabled_args = "" + + for rule in self.disabled_rules: + self.disabled_args = self.disabled_args + "-D " + rule + " " self.rules_warning = self.params.get('rules_warning', '*', default=False) if self.rules_warning == False: @@ -49,6 +69,9 @@ def setUp(self): if self.should_detect: self.detect_level = self.params.get('detect_level', '*') + if not isinstance(self.detect_level, list): + self.detect_level = [self.detect_level] + # Doing this in 2 steps instead of simply using # module_is_loaded to avoid logging lsmod output to the log. lsmod_output = process.system_output("lsmod", verbose=False) @@ -105,16 +128,17 @@ def check_detections(self, res): if events_detected == 0: self.fail("Detected {} events when should have detected > 0".format(events_detected)) - level_line = '(?i){}: (\d+)'.format(self.detect_level) - match = re.search(level_line, res.stdout) + for level in self.detect_level: + level_line = '(?i){}: (\d+)'.format(level) + match = re.search(level_line, res.stdout) - if match is None: - self.fail("Could not find a line '{}: ' in falco output".format(self.detect_level)) + if match is None: + self.fail("Could not find a line '{}: ' in falco output".format(level)) - events_detected = int(match.group(1)) + events_detected = int(match.group(1)) - if not events_detected > 0: - self.fail("Detected {} events at level {} when should have detected > 0".format(events_detected, self.detect_level)) + if not events_detected > 0: + self.fail("Detected {} events at level {} when should have detected > 0".format(events_detected, level)) def check_json_output(self, res): if self.json_output: @@ -131,8 +155,8 @@ 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) + cmd = '{}/userspace/falco/falco {} {} -c {}/../falco.yaml -e {} -o json_output={} -v'.format( + self.falcodir, self.rules_args, self.disabled_args, self.falcodir, self.trace_file, self.json_output) self.falco_proc = process.SubProcess(cmd) diff --git a/test/falco_tests.yaml.in b/test/falco_tests.yaml.in index bb1eb511faf..17e61f24585 100644 --- a/test/falco_tests.yaml.in +++ b/test/falco_tests.yaml.in @@ -1,13 +1,13 @@ trace_files: !mux builtin_rules_no_warnings: detect: False - trace_file: empty.scap + trace_file: trace_files/empty.scap rules_warning: False test_warnings: detect: False - trace_file: empty.scap - rules_file: falco_rules_warnings.yaml + trace_file: trace_files/empty.scap + rules_file: rules/falco_rules_warnings.yaml rules_warning: - no_evttype - evttype_not_equals @@ -60,3 +60,48 @@ trace_files: !mux - repeated_evttypes_with_in: [open] - repeated_evttypes_with_separate_in: [open] - repeated_evttypes_with_mix: [open] + + multiple_rules_first_empty: + detect: True + detect_level: WARNING + rules_file: + - rules/empty_rules.yaml + - rules/single_rule.yaml + trace_file: trace_files/cat_write.scap + + multiple_rules_last_empty: + detect: True + detect_level: WARNING + rules_file: + - rules/single_rule.yaml + - rules/empty_rules.yaml + trace_file: trace_files/cat_write.scap + + multiple_rules: + detect: True + detect_level: + - WARNING + - INFO + - ERROR + rules_file: + - rules/single_rule.yaml + - rules/double_rule.yaml + trace_file: trace_files/cat_write.scap + + disabled_rules: + detect: False + rules_file: + - rules/empty_rules.yaml + - rules/single_rule.yaml + disabled_rules: + - open_from_cat + trace_file: trace_files/cat_write.scap + + disabled_rules_using_regex: + detect: False + rules_file: + - rules/empty_rules.yaml + - rules/single_rule.yaml + disabled_rules: + - "open.*" + trace_file: trace_files/cat_write.scap diff --git a/test/rules/double_rule.yaml b/test/rules/double_rule.yaml new file mode 100644 index 00000000000..4633a55b65c --- /dev/null +++ b/test/rules/double_rule.yaml @@ -0,0 +1,13 @@ +# This ruleset depends on the is_cat macro defined in single_rule.yaml + +- rule: exec_from_cat + desc: A process named cat does execve + condition: evt.type=execve and is_cat + output: "An exec was seen (command=%proc.cmdline)" + priority: ERROR + +- rule: access_from_cat + desc: A process named cat does an access + condition: evt.type=access and is_cat + output: "An access was seen (command=%proc.cmdline)" + priority: INFO \ No newline at end of file diff --git a/test/rules/empty_rules.yaml b/test/rules/empty_rules.yaml new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/falco_rules_warnings.yaml b/test/rules/falco_rules_warnings.yaml similarity index 100% rename from test/falco_rules_warnings.yaml rename to test/rules/falco_rules_warnings.yaml diff --git a/test/rules/single_rule.yaml b/test/rules/single_rule.yaml new file mode 100644 index 00000000000..3044c6b836e --- /dev/null +++ b/test/rules/single_rule.yaml @@ -0,0 +1,8 @@ +- macro: is_cat + condition: proc.name=cat + +- rule: open_from_cat + desc: A process named cat does an open + condition: evt.type=open and is_cat + output: "An open was seen (command=%proc.cmdline)" + priority: WARNING \ No newline at end of file diff --git a/test/trace_files/cat_write.scap b/test/trace_files/cat_write.scap new file mode 100644 index 0000000000000000000000000000000000000000..c4cf36b9cfa11aa1cd8ce229fc29b7ac1263d86b GIT binary patch literal 12532 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 &cmdline_optio init_cmdline_options(cmdline_options); - m_rules_filename = m_config->get_scalar("rules_file", "/etc/falco_rules.yaml"); + m_rules_filenames.push_back(m_config->get_scalar("rules_file", "/etc/falco_rules.yaml")); m_json_output = m_config->get_scalar("json_output", false); falco_outputs::output_config file_output; diff --git a/userspace/falco/configuration.h b/userspace/falco/configuration.h index 8e13fd9474d..2076b5aa60a 100644 --- a/userspace/falco/configuration.h +++ b/userspace/falco/configuration.h @@ -123,7 +123,7 @@ class falco_configuration void init(std::string conf_filename, std::list &cmdline_options); void init(std::list &cmdline_options); - std::string m_rules_filename; + std::list m_rules_filenames; bool m_json_output; std::vector m_outputs; private: diff --git a/userspace/falco/falco.cpp b/userspace/falco/falco.cpp index e7ebc1b2dbc..7e9864875aa 100644 --- a/userspace/falco/falco.cpp +++ b/userspace/falco/falco.cpp @@ -2,6 +2,9 @@ #include #include +#include +#include +#include #include #include #include @@ -41,6 +44,7 @@ static void usage() " -p, --pidfile When run as a daemon, write pid to specified file\n" " -e Read the events from (in .scap format) instead of tapping into live.\n" " -r Rules file (defaults to value set in configuration file, or /etc/falco_rules.yaml).\n" + " Can be specified multiple times to read from multiple files.\n" " -D Disable any rules matching the regex . Can be specified multiple times.\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" @@ -140,7 +144,7 @@ int falco_init(int argc, char **argv) int long_index = 0; string scap_filename; string conf_filename; - string rules_filename; + list rules_filenames; bool daemon = false; string pidfilename = "/var/run/falco.pid"; bool describe_all_rules = false; @@ -192,7 +196,7 @@ int falco_init(int argc, char **argv) scap_filename = optarg; break; case 'r': - rules_filename = optarg; + rules_filenames.push_back(optarg); break; case 'D': pattern = optarg; @@ -273,14 +277,16 @@ int falco_init(int argc, char **argv) falco_logger::log(LOG_INFO, "Falco initialized. No configuration file found, proceeding with defaults\n"); } - if (rules_filename.size()) + if (rules_filenames.size()) { - config.m_rules_filename = rules_filename; + config.m_rules_filenames = rules_filenames; } - engine->load_rules_file(rules_filename, verbose, all_events); - - falco_logger::log(LOG_INFO, "Parsed rules from file " + rules_filename + "\n"); + for (auto filename : config.m_rules_filenames) + { + engine->load_rules_file(filename, verbose, all_events); + falco_logger::log(LOG_INFO, "Parsed rules from file " + filename + "\n"); + } for (auto pattern : disabled_rule_patterns) {