From e99bec6df5a33f15c59aef494946b10e553573a6 Mon Sep 17 00:00:00 2001 From: Mark Stemm Date: Thu, 5 Apr 2018 15:25:12 -0700 Subject: [PATCH 1/2] Add ability to read rules files from directories When the argument to -r or an entry in falco.yaml's rules_file list is a directory, read all files in the directory and add them to the rules file list. The files in the directory are sorted alphabetically before being added to the list. The installed falco adds directories /etc/falco/rules.available and /etc/falco/rules.d and moves /etc/falco/application_rules.yaml to /etc/falco/rules.available. /etc/falco/rules.d is empty, but the idea is that admins can symlink to /etc/falco/rules.available for applications they want to enable. This will make it easier to add application-specific rulesets that admins can opt-in to. --- cpack/debian/conffiles | 2 +- falco.yaml | 6 ++- rules/CMakeLists.txt | 4 +- userspace/falco/configuration.cpp | 73 ++++++++++++++++++++++++++++++- userspace/falco/configuration.h | 2 + userspace/falco/falco.cpp | 17 ++++--- 6 files changed, 95 insertions(+), 9 deletions(-) diff --git a/cpack/debian/conffiles b/cpack/debian/conffiles index d2424c8da37..98c4d5ad42e 100644 --- a/cpack/debian/conffiles +++ b/cpack/debian/conffiles @@ -1,4 +1,4 @@ /etc/falco/falco.yaml /etc/falco/falco_rules.yaml -/etc/falco/application_rules.yaml +/etc/falco/rules.available/application_rules.yaml /etc/falco/falco_rules.local.yaml diff --git a/falco.yaml b/falco.yaml index 78c1355f6e9..3592ce682de 100644 --- a/falco.yaml +++ b/falco.yaml @@ -1,4 +1,7 @@ -# File(s) containing Falco rules, loaded at startup. +# File(s) or Directories containing Falco rules, loaded at startup. +# The name "rules_file" is only for backwards compatibility. +# If the entry is a file, it will be read directly. If the entry is a directory, +# every file in that directory will be read, in alphabetical order. # # falco_rules.yaml ships with the falco package and is overridden with # every new software version. falco_rules.local.yaml is only created @@ -10,6 +13,7 @@ rules_file: - /etc/falco/falco_rules.yaml - /etc/falco/falco_rules.local.yaml + - /etc/falco/rules.d # Whether to output events in json or text json_output: false diff --git a/rules/CMakeLists.txt b/rules/CMakeLists.txt index 4766d3f6e28..919ac21bfbd 100644 --- a/rules/CMakeLists.txt +++ b/rules/CMakeLists.txt @@ -31,7 +31,9 @@ install(FILES falco_rules.local.yaml RENAME "${FALCO_LOCAL_RULES_DEST_FILENAME}") install(FILES application_rules.yaml - DESTINATION "${FALCO_ETC_DIR}" + DESTINATION "/etc/falco/rules.available" RENAME "${FALCO_APP_RULES_DEST_FILENAME}") + +install(DIRECTORY DESTINATION "/etc/falco/rules.d") endif() diff --git a/userspace/falco/configuration.cpp b/userspace/falco/configuration.cpp index a3a9098fd38..babdb114853 100644 --- a/userspace/falco/configuration.cpp +++ b/userspace/falco/configuration.cpp @@ -16,6 +16,13 @@ You should have received a copy of the GNU General Public License along with falco. If not, see . */ +#include + +#include +#include +#include +#include + #include "configuration.h" #include "logger.h" @@ -62,7 +69,7 @@ void falco_configuration::init(string conf_filename, list &cmdline_optio struct stat buffer; if(stat(file.c_str(), &buffer) == 0) { - m_rules_filenames.push_back(file); + read_rules_file_directory(file, m_rules_filenames); } } @@ -150,6 +157,70 @@ void falco_configuration::init(string conf_filename, list &cmdline_optio falco_logger::log_syslog = m_config->get_scalar("log_syslog", true); } +void falco_configuration::read_rules_file_directory(const string &path, list &rules_filenames) +{ + struct stat st; + + int rc = stat(path.c_str(), &st); + + if(rc != 0) + { + std::cerr << "Could not get info on rules file " << path << ": " << strerror(errno) << std::endl; + exit(-1); + } + + if(st.st_mode & S_IFDIR) + { + // It's a directory. Read the contents, sort + // alphabetically, and add every path to + // rules_filenames + vector dir_filenames; + + DIR *dir = opendir(path.c_str()); + + if(!dir) + { + std::cerr << "Could not get read contents of directory " << path << ": " << strerror(errno) << std::endl; + exit(-1); + } + + for (struct dirent *ent = readdir(dir); ent; ent = readdir(dir)) + { + string efile = path + "/" + ent->d_name; + + rc = stat(efile.c_str(), &st); + + if(rc != 0) + { + std::cerr << "Could not get info on rules file " << efile << ": " << strerror(errno) << std::endl; + exit(-1); + } + + if(st.st_mode & S_IFREG) + { + dir_filenames.push_back(efile); + } + } + + closedir(dir); + + std::sort(dir_filenames.begin(), + dir_filenames.end()); + + for (string &ent : dir_filenames) + { + rules_filenames.push_back(ent); + } + } + else + { + // Assume it's a file and just add to + // rules_filenames. If it can't be opened/etc that + // will be reported later.. + rules_filenames.push_back(path); + } +} + static bool split(const string &str, char delim, pair &parts) { size_t pos; diff --git a/userspace/falco/configuration.h b/userspace/falco/configuration.h index 40d54f790b9..965203c1225 100644 --- a/userspace/falco/configuration.h +++ b/userspace/falco/configuration.h @@ -165,6 +165,8 @@ class falco_configuration void init(std::string conf_filename, std::list &cmdline_options); void init(std::list &cmdline_options); + static void read_rules_file_directory(const string &path, list &rules_filenames); + std::list m_rules_filenames; bool m_json_output; bool m_json_include_output_property; diff --git a/userspace/falco/falco.cpp b/userspace/falco/falco.cpp index aa2253b699c..b3cf2d6622a 100644 --- a/userspace/falco/falco.cpp +++ b/userspace/falco/falco.cpp @@ -99,8 +99,9 @@ static void usage() " of %%container.info in rule output fields\n" " See the examples section below for more info.\n" " -P, --pidfile When run as a daemon, write pid to specified file\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" + " -r Rules file/directory (defaults to value set in configuration file,\n" + " or /etc/falco_rules.yaml). Can be specified multiple times to read\n" + " from multiple files/directories.\n" " -s If specified, write statistics related to falco's reading/processing of events\n" " to this file. (Only useful in live mode).\n" " -T Disable any rules with a tag=. Can be specified multiple times.\n" @@ -378,7 +379,7 @@ int falco_init(int argc, char **argv) } break; case 'r': - rules_filenames.push_back(optarg); + falco_configuration::read_rules_file_directory(string(optarg), rules_filenames); break; case 's': stats_filename = optarg; @@ -503,13 +504,19 @@ int falco_init(int argc, char **argv) if(config.m_rules_filenames.size() == 0) { - throw std::invalid_argument("You must specify at least one rules file via -r or a rules_file entry in falco.yaml"); + throw std::invalid_argument("You must specify at least one rules file/directory via -r or a rules_file entry in falco.yaml"); } + falco_logger::log(LOG_DEBUG, "Configured rules filenames:\n"); for (auto filename : config.m_rules_filenames) { + falco_logger::log(LOG_DEBUG, string(" ") + filename + "\n"); + } + + for (auto filename : config.m_rules_filenames) + { + falco_logger::log(LOG_INFO, "Loading rules from file " + filename + ":\n"); engine->load_rules_file(filename, verbose, all_events); - falco_logger::log(LOG_INFO, "Parsed rules from file " + filename + "\n"); } // You can't both disable and enable rules From e42020eb4e8d74b4379fc37002435fe5b0a66bc6 Mon Sep 17 00:00:00 2001 From: Mark Stemm Date: Thu, 5 Apr 2018 15:41:08 -0700 Subject: [PATCH 2/2] Unit test for reading rules from directory Copy the rules/trace file from the test multiple_rules to a new test rules_directory. The rules files are in rules/rules_dir/{000,001}*.yaml, and the test uses a rules_file argument of rules_dir. Ensure that the same events are detected. --- test/falco_tests.yaml | 10 ++++++++++ test/rules/rules_dir/000-single_rule.yaml | 14 ++++++++++++++ test/rules/rules_dir/001-double_rule.yaml | 13 +++++++++++++ 3 files changed, 37 insertions(+) create mode 100644 test/rules/rules_dir/000-single_rule.yaml create mode 100644 test/rules/rules_dir/001-double_rule.yaml diff --git a/test/falco_tests.yaml b/test/falco_tests.yaml index cf26a8fac88..98448907c3f 100644 --- a/test/falco_tests.yaml +++ b/test/falco_tests.yaml @@ -129,6 +129,16 @@ trace_files: !mux - rules/double_rule.yaml trace_file: trace_files/cat_write.scap + rules_directory: + detect: True + detect_level: + - WARNING + - INFO + - ERROR + rules_file: + - rules/rules_dir + trace_file: trace_files/cat_write.scap + multiple_rules_suppress_info: detect: True detect_level: diff --git a/test/rules/rules_dir/000-single_rule.yaml b/test/rules/rules_dir/000-single_rule.yaml new file mode 100644 index 00000000000..ccba5ea943c --- /dev/null +++ b/test/rules/rules_dir/000-single_rule.yaml @@ -0,0 +1,14 @@ +- list: cat_binaries + items: [cat] + +- list: cat_capable_binaries + items: [cat_binaries] + +- macro: is_cat + condition: proc.name in (cat_capable_binaries) + +- 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/rules/rules_dir/001-double_rule.yaml b/test/rules/rules_dir/001-double_rule.yaml new file mode 100644 index 00000000000..4633a55b65c --- /dev/null +++ b/test/rules/rules_dir/001-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