diff --git a/test/falco_test.py b/test/falco_test.py index 7b72d5ee..66eff585 100644 --- a/test/falco_test.py +++ b/test/falco_test.py @@ -26,8 +26,28 @@ class FalcoTest(Test): 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 @@ class FalcoTest(Test): 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 @@ class FalcoTest(Test): 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 @@ class FalcoTest(Test): 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 bb1eb511..17e61f24 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 00000000..4633a55b --- /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 00000000..e69de29b 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 00000000..3044c6b8 --- /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 00000000..c4cf36b9 Binary files /dev/null and b/test/trace_files/cat_write.scap differ diff --git a/test/empty.scap b/test/trace_files/empty.scap similarity index 100% rename from test/empty.scap rename to test/trace_files/empty.scap diff --git a/userspace/falco/configuration.cpp b/userspace/falco/configuration.cpp index 9e88b9a3..e41a9cda 100644 --- a/userspace/falco/configuration.cpp +++ b/userspace/falco/configuration.cpp @@ -33,7 +33,7 @@ void falco_configuration::init(string conf_filename, list &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 8e13fd94..2076b5aa 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 e7ebc1b2..7e986487 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) {