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