diff --git a/cpack/debian/conffiles b/cpack/debian/conffiles index d2424c8d..98c4d5ad 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 b19e5b7f..3a3c29c5 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 4766d3f6..919ac21b 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/test/falco_tests.yaml b/test/falco_tests.yaml index cf26a8fa..98448907 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 00000000..ccba5ea9 --- /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 00000000..4633a55b --- /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 diff --git a/userspace/falco/configuration.cpp b/userspace/falco/configuration.cpp index a3a9098f..babdb114 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 40d54f79..965203c1 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 ae75bf0b..2aeeb75c 100644 --- a/userspace/falco/falco.cpp +++ b/userspace/falco/falco.cpp @@ -106,8 +106,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" @@ -391,7 +392,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; @@ -516,13 +517,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