diff --git a/.gitignore b/.gitignore index b3abef09..b2e74fc5 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,7 @@ test/results*.json.* userspace/falco/lua/re.lua userspace/falco/lua/lpeg.so -docker/event-generator/event-generator +docker/event-generator/event_generator docker/event-generator/mysqld docker/event-generator/httpd docker/event-generator/sha1sum diff --git a/rules/falco_rules.yaml b/rules/falco_rules.yaml index 175977c6..3fba1462 100644 --- a/rules/falco_rules.yaml +++ b/rules/falco_rules.yaml @@ -247,6 +247,7 @@ condition: bin_dir and evt.dir = < and open_write and not package_mgmt_procs output: "File below a known binary directory opened for writing (user=%user.name command=%proc.cmdline file=%fd.name)" priority: WARNING + tags: [filesystem] - macro: write_etc_common condition: > @@ -266,6 +267,7 @@ condition: write_etc_common and not proc.sname=fbash output: "File below /etc opened for writing (user=%user.name command=%proc.cmdline file=%fd.name)" priority: WARNING + tags: [filesystem] # Within a fbash session, the severity is lowered to INFO - rule: Write below etc in installer @@ -273,6 +275,7 @@ condition: write_etc_common and proc.sname=fbash output: "File below /etc opened for writing (user=%user.name command=%proc.cmdline file=%fd.name) within pipe installer session" priority: INFO + tags: [filesystem] - macro: cmp_cp_by_passwd condition: proc.name in (cmp, cp) and proc.pname=passwd @@ -282,6 +285,7 @@ condition: sensitive_files and open_read and server_procs and not proc_is_new and proc.name!="sshd" output: "Sensitive file opened for reading by trusted program after startup (user=%user.name command=%proc.cmdline file=%fd.name)" priority: WARNING + tags: [filesystem] - list: read_sensitive_file_binaries items: [iptables, ps, lsb_release, check-new-relea, dumpe2fs, accounts-daemon, sshd, vsftpd, systemd] @@ -296,6 +300,7 @@ and not proc.cmdline contains /usr/bin/mandb output: "Sensitive file opened for reading by non-trusted program (user=%user.name name=%proc.name command=%proc.cmdline file=%fd.name)" priority: WARNING + tags: [filesystem] # Only let rpm-related programs write to the rpm database - rule: Write below rpm database @@ -303,24 +308,28 @@ condition: fd.name startswith /var/lib/rpm and open_write and not proc.name in (dnf,rpm,rpmkey,yum) and not ansible_running_python output: "Rpm database opened for writing by a non-rpm program (command=%proc.cmdline file=%fd.name)" priority: WARNING + tags: [filesystem, software_mgmt] - rule: DB program spawned process desc: a database-server related program spawned a new process other than itself. This shouldn\'t occur and is a follow on from some SQL injection attacks. condition: proc.pname in (db_server_binaries) and spawned_process and not proc.name in (db_server_binaries) output: "Database-related program spawned process other than itself (user=%user.name program=%proc.cmdline parent=%proc.pname)" priority: WARNING + tags: [process, database] - rule: Modify binary dirs desc: an attempt to modify any file below a set of binary directories. condition: bin_dir_rename and modify and not package_mgmt_procs output: "File below known binary directory renamed/removed (user=%user.name command=%proc.cmdline operation=%evt.type file=%fd.name %evt.args)" priority: WARNING + tags: [filesystem] - rule: Mkdir binary dirs desc: an attempt to create a directory below a set of binary directories. condition: mkdir and bin_dir_mkdir and not package_mgmt_procs output: "Directory below known binary directory created (user=%user.name command=%proc.cmdline directory=%evt.arg.path)" priority: WARNING + tags: [filesystem] # Don't load shared objects coming from unexpected places # Commenting this out for now--there are lots of shared library @@ -346,6 +355,7 @@ and not proc.pname in (sysdigcloud_binaries) output: "Namespace change (setns) by unexpected program (user=%user.name command=%proc.cmdline parent=%proc.pname %container.info)" priority: WARNING + tags: [process] - list: known_shell_spawn_binaries items: [ @@ -373,6 +383,7 @@ and not parent_linux_image_upgrade_script output: "Shell spawned by untrusted binary (user=%user.name shell=%proc.name parent=%proc.pname cmdline=%proc.cmdline pcmdline=%proc.pcmdline)" priority: WARNING + tags: [host, shell] - macro: trusted_containers condition: (container.image startswith sysdig/agent or @@ -388,6 +399,7 @@ condition: (open_read or open_write) and container and container.privileged=true and not trusted_containers output: File opened for read/write by privileged container (user=%user.name command=%proc.cmdline %container.info file=%fd.name) priority: WARNING + tags: [container, cis] - macro: sensitive_mount condition: (container.mount.dest[/proc*] != "N/A") @@ -397,6 +409,7 @@ condition: (open_read or open_write) and container and sensitive_mount and not trusted_containers output: File opened for read/write by container mounting sensitive directory (user=%user.name command=%proc.cmdline %container.info file=%fd.name) priority: WARNING + tags: [container, cis] # Anything run interactively by root # - condition: evt.type != switch and user.name = root and proc.name != sshd and interactive @@ -408,6 +421,7 @@ condition: spawned_process and system_users and interactive output: "System user ran an interactive command (user=%user.name command=%proc.cmdline)" priority: WARNING + tags: [users] - rule: Run shell in container desc: a shell was spawned by a non-shell program in a container. Container entrypoints are excluded. @@ -420,6 +434,7 @@ and not trusted_containers output: "Shell spawned in a container other than entrypoint (user=%user.name %container.info shell=%proc.name parent=%proc.pname cmdline=%proc.cmdline)" priority: WARNING + tags: [container, shell] # sockfamily ip is to exclude certain processes (like 'groups') that communicate on unix-domain sockets - rule: System procs network activity @@ -427,6 +442,7 @@ condition: (fd.sockfamily = ip and system_procs) and (inbound or outbound) output: "Known system binary sent/received network traffic (user=%user.name command=%proc.cmdline connection=%fd.name)" priority: WARNING + tags: [network] # With the current restriction on system calls handled by falco # (e.g. excluding read/write/sendto/recvfrom/etc, this rule won't @@ -443,12 +459,14 @@ condition: evt.type=setuid and evt.dir=> and not user.name=root and not proc.name in (userexec_binaries, mail_binaries, sshd, dbus-daemon-lau, ping, ping6, critical-stack-) output: "Unexpected setuid call by non-sudo, non-root program (user=%user.name parent=%proc.pname command=%proc.cmdline uid=%evt.arg.uid)" priority: WARNING + tags: [users] - rule: User mgmt binaries desc: activity by any programs that can manage users, passwords, or permissions. sudo and su are excluded. Activity in containers is also excluded--some containers create custom users on top of a base linux distribution at startup. condition: spawned_process and proc.name in (user_mgmt_binaries) and not proc.name in (su, sudo) and not container and not proc.pname in (cron_binaries, systemd, run-parts) output: "User management binary command run outside of container (user=%user.name command=%proc.cmdline parent=%proc.pname)" priority: WARNING + tags: [host, users] - list: allowed_dev_files items: [/dev/null, /dev/stdin, /dev/stdout, /dev/stderr, /dev/tty, /dev/random, /dev/urandom, /dev/console] @@ -463,6 +481,7 @@ and not fd.name in (allowed_dev_files) output: "File created below /dev by untrusted program (user=%user.name command=%proc.cmdline file=%fd.name)" priority: WARNING + tags: [filesystem] # fbash is a small shell script that runs bash, and is suitable for use in curl | fbash installers. - rule: Installer bash starts network server @@ -470,18 +489,21 @@ condition: evt.type=listen and proc.sname=fbash output: "Unexpected listen call by a process in a fbash session (command=%proc.cmdline)" priority: WARNING + tags: [network] - rule: Installer bash starts session desc: an attempt by a program in a pipe installer session to start a new session condition: evt.type=setsid and proc.sname=fbash output: "Unexpected setsid call by a process in fbash session (command=%proc.cmdline)" priority: WARNING + tags: [process] - rule: Installer bash non https connection desc: an attempt by a program in a pipe installer session to make an outgoing connection on a non-http(s) port condition: proc.sname=fbash and outbound and not fd.sport in (80, 443, 53) output: "Outbound connection on non-http(s) port by a process in a fbash session (command=%proc.cmdline connection=%fd.name)" priority: WARNING + tags: [network] # It'd be nice if we could warn when processes in a fbash session try # to download from any nonstandard location? This is probably blocked @@ -495,6 +517,7 @@ condition: evt.type=execve and proc.name in (chkconfig, systemctl) and proc.sname=fbash output: "Service management program run by process in a fbash session (command=%proc.cmdline)" priority: INFO + tags: [software_mgmt] # Notice when processes try to run any package management binary within a fbash session. # Note: this is not a WARNING, as you'd expect some package management @@ -504,6 +527,7 @@ condition: evt.type=execve and package_mgmt_procs and proc.sname=fbash output: "Package management program run by process in a fbash session (command=%proc.cmdline)" priority: INFO + tags: [software_mgmt] ########################### # Application-Related Rules diff --git a/test/falco_test.py b/test/falco_test.py index afb7c4f8..a0a51bdc 100644 --- a/test/falco_test.py +++ b/test/falco_test.py @@ -113,6 +113,16 @@ class FalcoTest(Test): outputs.append(output) self.outputs = outputs + self.disable_tags = self.params.get('disable_tags', '*', default='') + + if self.disable_tags == '': + self.disable_tags=[] + + self.run_tags = self.params.get('run_tags', '*', default='') + + if self.run_tags == '': + self.run_tags=[] + def check_rules_warnings(self, res): found_warning = sets.Set() @@ -180,13 +190,18 @@ class FalcoTest(Test): triggered_rules = match.group(1) for rule, count in self.detect_counts.iteritems(): - expected_line = '{}: {}'.format(rule, count) - match = re.search(expected_line, triggered_rules) + expected = '{}: (\d+)'.format(rule) + match = re.search(expected, triggered_rules) if match is None: - self.fail("Could not find a line '{}' in triggered rule counts '{}'".format(expected_line, triggered_rules)) + actual_count = 0 else: - self.log.debug("Found expected count for {}: {}".format(rule, match.group())) + actual_count = int(match.group(1)) + + if actual_count != count: + self.fail("Different counts for rule {}: expected={}, actual={}".format(rule, count, actual_count)) + else: + self.log.debug("Found expected count for rule {}: {}".format(rule, count)) def check_outputs(self): for output in self.outputs: @@ -223,6 +238,12 @@ class FalcoTest(Test): cmd = '{}/userspace/falco/falco {} {} -c {} -e {} -o json_output={} -v'.format( self.falcodir, self.rules_args, self.disabled_args, self.conf_file, self.trace_file, self.json_output) + for tag in self.disable_tags: + cmd += ' -T {}'.format(tag) + + for tag in self.run_tags: + cmd += ' -t {}'.format(tag) + self.falco_proc = process.SubProcess(cmd) res = self.falco_proc.run(timeout=180, sig=9) diff --git a/test/falco_tests.yaml.in b/test/falco_tests.yaml.in index 977b7d4c..1453dccb 100644 --- a/test/falco_tests.yaml.in +++ b/test/falco_tests.yaml.in @@ -154,6 +154,25 @@ trace_files: !mux - rules/single_rule_enabled_flag.yaml trace_file: trace_files/cat_write.scap + disabled_and_enabled_rules_1: + exit_status: 1 + stderr_contains: "Runtime error: You can not specify both disabled .-D/-T. and enabled .-t. rules. Exiting." + disable_tags: [a] + run_tags: [a] + rules_file: + - rules/single_rule.yaml + trace_file: trace_files/cat_write.scap + + disabled_and_enabled_rules_2: + exit_status: 1 + stderr_contains: "Runtime error: You can not specify both disabled .-D/-T. and enabled .-t. rules. Exiting." + disabled_rules: + - "open.*" + run_tags: [a] + rules_file: + - rules/single_rule.yaml + trace_file: trace_files/cat_write.scap + null_output_field: detect: True detect_level: WARNING @@ -200,3 +219,267 @@ trace_files: !mux - "Create files below dev": 1 - "Modify binary dirs": 2 - "Change thread namespace": 2 + + disabled_tags_a: + detect: True + detect_level: WARNING + rules_file: + - rules/tagged_rules.yaml + trace_file: trace_files/open-multiple-files.scap + disable_tags: [a] + detect_counts: + - open_1: 0 + - open_2: 1 + - open_3: 1 + - open_4: 0 + - open_5: 0 + - open_6: 1 + - open_7: 0 + - open_8: 0 + - open_9: 0 + - open_10: 0 + - open_11: 1 + - open_12: 1 + - open_13: 1 + + disabled_tags_b: + detect: True + detect_level: WARNING + rules_file: + - rules/tagged_rules.yaml + trace_file: trace_files/open-multiple-files.scap + disable_tags: [b] + detect_counts: + - open_1: 1 + - open_2: 0 + - open_3: 1 + - open_4: 0 + - open_5: 1 + - open_6: 0 + - open_7: 0 + - open_8: 0 + - open_9: 1 + - open_10: 0 + - open_11: 1 + - open_12: 1 + - open_13: 1 + + disabled_tags_c: + detect: True + detect_level: WARNING + rules_file: + - rules/tagged_rules.yaml + trace_file: trace_files/open-multiple-files.scap + disable_tags: [c] + detect_counts: + - open_1: 1 + - open_2: 1 + - open_3: 0 + - open_4: 1 + - open_5: 0 + - open_6: 0 + - open_7: 0 + - open_8: 1 + - open_9: 0 + - open_10: 0 + - open_11: 1 + - open_12: 1 + - open_13: 1 + + disabled_tags_ab: + detect: True + detect_level: WARNING + rules_file: + - rules/tagged_rules.yaml + trace_file: trace_files/open-multiple-files.scap + disable_tags: [a, b] + detect_counts: + - open_1: 0 + - open_2: 0 + - open_3: 1 + - open_4: 0 + - open_5: 0 + - open_6: 0 + - open_7: 0 + - open_8: 0 + - open_9: 0 + - open_10: 0 + - open_11: 1 + - open_12: 1 + - open_13: 1 + + disabled_tags_abc: + detect: True + detect_level: WARNING + rules_file: + - rules/tagged_rules.yaml + trace_file: trace_files/open-multiple-files.scap + disable_tags: [a, b, c] + detect_counts: + - open_1: 0 + - open_2: 0 + - open_3: 0 + - open_4: 0 + - open_5: 0 + - open_6: 0 + - open_7: 0 + - open_8: 0 + - open_9: 0 + - open_10: 0 + - open_11: 1 + - open_12: 1 + - open_13: 1 + + run_tags_a: + detect: True + detect_level: WARNING + rules_file: + - rules/tagged_rules.yaml + trace_file: trace_files/open-multiple-files.scap + run_tags: [a] + detect_counts: + - open_1: 1 + - open_2: 0 + - open_3: 0 + - open_4: 1 + - open_5: 1 + - open_6: 0 + - open_7: 1 + - open_8: 1 + - open_9: 1 + - open_10: 1 + - open_11: 0 + - open_12: 0 + - open_13: 0 + + run_tags_b: + detect: True + detect_level: WARNING + rules_file: + - rules/tagged_rules.yaml + trace_file: trace_files/open-multiple-files.scap + run_tags: [b] + detect_counts: + - open_1: 0 + - open_2: 1 + - open_3: 0 + - open_4: 1 + - open_5: 0 + - open_6: 1 + - open_7: 1 + - open_8: 1 + - open_9: 0 + - open_10: 1 + - open_11: 0 + - open_12: 0 + - open_13: 0 + + run_tags_c: + detect: True + detect_level: WARNING + rules_file: + - rules/tagged_rules.yaml + trace_file: trace_files/open-multiple-files.scap + run_tags: [c] + detect_counts: + - open_1: 0 + - open_2: 0 + - open_3: 1 + - open_4: 0 + - open_5: 1 + - open_6: 1 + - open_7: 1 + - open_8: 0 + - open_9: 1 + - open_10: 1 + - open_11: 0 + - open_12: 0 + - open_13: 0 + + run_tags_ab: + detect: True + detect_level: WARNING + rules_file: + - rules/tagged_rules.yaml + trace_file: trace_files/open-multiple-files.scap + run_tags: [a, b] + detect_counts: + - open_1: 1 + - open_2: 1 + - open_3: 0 + - open_4: 1 + - open_5: 1 + - open_6: 1 + - open_7: 1 + - open_8: 1 + - open_9: 1 + - open_10: 1 + - open_11: 0 + - open_12: 0 + - open_13: 0 + + run_tags_bc: + detect: True + detect_level: WARNING + rules_file: + - rules/tagged_rules.yaml + trace_file: trace_files/open-multiple-files.scap + run_tags: [b, c] + detect_counts: + - open_1: 0 + - open_2: 1 + - open_3: 1 + - open_4: 1 + - open_5: 1 + - open_6: 1 + - open_7: 1 + - open_8: 1 + - open_9: 1 + - open_10: 1 + - open_11: 0 + - open_12: 0 + - open_13: 0 + + run_tags_abc: + detect: True + detect_level: WARNING + rules_file: + - rules/tagged_rules.yaml + trace_file: trace_files/open-multiple-files.scap + run_tags: [a, b, c] + detect_counts: + - open_1: 1 + - open_2: 1 + - open_3: 1 + - open_4: 1 + - open_5: 1 + - open_6: 1 + - open_7: 1 + - open_8: 1 + - open_9: 1 + - open_10: 1 + - open_11: 0 + - open_12: 0 + - open_13: 0 + + run_tags_d: + detect: True + detect_level: WARNING + rules_file: + - rules/tagged_rules.yaml + trace_file: trace_files/open-multiple-files.scap + run_tags: [d] + detect_counts: + - open_1: 0 + - open_2: 0 + - open_3: 0 + - open_4: 0 + - open_5: 0 + - open_6: 0 + - open_7: 0 + - open_8: 0 + - open_9: 0 + - open_10: 0 + - open_11: 1 + - open_12: 0 + - open_13: 0 diff --git a/test/rules/tagged_rules.yaml b/test/rules/tagged_rules.yaml new file mode 100644 index 00000000..09d08b74 --- /dev/null +++ b/test/rules/tagged_rules.yaml @@ -0,0 +1,93 @@ +- macro: open_read + condition: (evt.type=open or evt.type=openat) and evt.is_open_read=true and fd.typechar='f' + +- rule: open_1 + desc: open one + condition: open_read and fd.name=/tmp/file-1 + output: Open one (file=%fd.name) + priority: WARNING + tags: [a] + +- rule: open_2 + desc: open two + condition: open_read and fd.name=/tmp/file-2 + output: Open two (file=%fd.name) + priority: WARNING + tags: [b] + +- rule: open_3 + desc: open three + condition: open_read and fd.name=/tmp/file-3 + output: Open three (file=%fd.name) + priority: WARNING + tags: [c] + +- rule: open_4 + desc: open four + condition: open_read and fd.name=/tmp/file-4 + output: Open four (file=%fd.name) + priority: WARNING + tags: [a, b] + +- rule: open_5 + desc: open file + condition: open_read and fd.name=/tmp/file-5 + output: Open file (file=%fd.name) + priority: WARNING + tags: [a, c] + +- rule: open_6 + desc: open six + condition: open_read and fd.name=/tmp/file-6 + output: Open six (file=%fd.name) + priority: WARNING + tags: [b, c] + +- rule: open_7 + desc: open seven + condition: open_read and fd.name=/tmp/file-7 + output: Open seven (file=%fd.name) + priority: WARNING + tags: [a, b, c] + +- rule: open_8 + desc: open eight + condition: open_read and fd.name=/tmp/file-8 + output: Open eight (file=%fd.name) + priority: WARNING + tags: [b, a] + +- rule: open_9 + desc: open nine + condition: open_read and fd.name=/tmp/file-9 + output: Open nine (file=%fd.name) + priority: WARNING + tags: [c, a] + +- rule: open_10 + desc: open ten + condition: open_read and fd.name=/tmp/file-10 + output: Open ten (file=%fd.name) + priority: WARNING + tags: [b, c, a] + +- rule: open_11 + desc: open eleven + condition: open_read and fd.name=/tmp/file-11 + output: Open eleven (file=%fd.name) + priority: WARNING + tags: [d] + +- rule: open_12 + desc: open twelve + condition: open_read and fd.name=/tmp/file-12 + output: Open twelve (file=%fd.name) + priority: WARNING + tags: [] + +- rule: open_13 + desc: open thirteen + condition: open_read and fd.name=/tmp/file-13 + output: Open thirteen (file=%fd.name) + priority: WARNING + diff --git a/test/trace_files/open-multiple-files.scap b/test/trace_files/open-multiple-files.scap new file mode 100644 index 00000000..61832b17 Binary files /dev/null and b/test/trace_files/open-multiple-files.scap differ diff --git a/userspace/engine/falco_engine.cpp b/userspace/engine/falco_engine.cpp index c735710c..ea00514b 100644 --- a/userspace/engine/falco_engine.cpp +++ b/userspace/engine/falco_engine.cpp @@ -40,7 +40,8 @@ string lua_print_stats = "print_stats"; using namespace std; falco_engine::falco_engine(bool seed_rng) - : m_rules(NULL), m_sampling_ratio(1), m_sampling_multiplier(0), + : m_rules(NULL), m_next_ruleset_id(0), + m_sampling_ratio(1), m_sampling_multiplier(0), m_replace_container_info(false) { luaopen_lpeg(m_ls); @@ -55,6 +56,8 @@ falco_engine::falco_engine(bool seed_rng) { srandom((unsigned) getpid()); } + + m_default_ruleset_id = find_ruleset_id(m_default_ruleset); } falco_engine::~falco_engine() @@ -107,20 +110,52 @@ void falco_engine::load_rules_file(const string &rules_filename, bool verbose, b load_rules(rules_content, verbose, all_events); } -void falco_engine::enable_rule(string &pattern, bool enabled) +void falco_engine::enable_rule(const string &pattern, bool enabled, const string &ruleset) { - m_evttype_filter->enable(pattern, enabled); + uint16_t ruleset_id = find_ruleset_id(ruleset); + + m_evttype_filter->enable(pattern, enabled, ruleset_id); } -unique_ptr falco_engine::process_event(sinsp_evt *ev) +void falco_engine::enable_rule(const string &pattern, bool enabled) { + enable_rule(pattern, enabled, m_default_ruleset); +} +void falco_engine::enable_rule_by_tag(const set &tags, bool enabled, const string &ruleset) +{ + uint16_t ruleset_id = find_ruleset_id(ruleset); + + m_evttype_filter->enable_tags(tags, enabled, ruleset_id); +} + +void falco_engine::enable_rule_by_tag(const set &tags, bool enabled) +{ + enable_rule_by_tag(tags, enabled, m_default_ruleset); +} + +uint16_t falco_engine::find_ruleset_id(const std::string &ruleset) +{ + auto it = m_known_rulesets.lower_bound(ruleset); + + if(it == m_known_rulesets.end() || + it->first != ruleset) + { + it = m_known_rulesets.emplace_hint(it, + std::make_pair(ruleset, m_next_ruleset_id++)); + } + + return it->second; +} + +unique_ptr falco_engine::process_event(sinsp_evt *ev, uint16_t ruleset_id) +{ if(should_drop_evt()) { return unique_ptr(); } - if(!m_evttype_filter->run(ev)) + if(!m_evttype_filter->run(ev, ruleset_id)) { return unique_ptr(); } @@ -155,6 +190,11 @@ unique_ptr falco_engine::process_event(sinsp_evt *ev) return res; } +unique_ptr falco_engine::process_event(sinsp_evt *ev) +{ + return process_event(ev, m_default_ruleset_id); +} + void falco_engine::describe_rule(string *rule) { return m_rules->describe_rule(rule); @@ -182,10 +222,11 @@ void falco_engine::print_stats() } void falco_engine::add_evttype_filter(string &rule, - list &evttypes, + set &evttypes, + set &tags, sinsp_filter* filter) { - m_evttype_filter->add(rule, evttypes, filter); + m_evttype_filter->add(rule, evttypes, tags, filter); } void falco_engine::clear_filters() diff --git a/userspace/engine/falco_engine.h b/userspace/engine/falco_engine.h index 2a423f10..d06c45e7 100644 --- a/userspace/engine/falco_engine.h +++ b/userspace/engine/falco_engine.h @@ -20,6 +20,7 @@ along with falco. If not, see . #include #include +#include #include "sinsp.h" #include "filter.h" @@ -47,9 +48,24 @@ public: void load_rules(const std::string &rules_content, bool verbose, bool all_events); // - // Enable/Disable any rules matching the provided pattern (regex). + // Enable/Disable any rules matching the provided pattern + // (regex). When provided, enable/disable these rules in the + // context of the provided ruleset. The ruleset (id) can later + // be passed as an argument to process_event(). This allows + // for different sets of rules being active at once. // - void enable_rule(std::string &pattern, bool enabled); + void enable_rule(const std::string &pattern, bool enabled, const std::string &ruleset); + + // Wrapper that assumes the default ruleset + void enable_rule(const std::string &pattern, bool enabled); + + // + // Enable/Disable any rules with any of the provided tags (set, exact matches only) + // + void enable_rule_by_tag(const std::set &tags, bool enabled, const std::string &ruleset); + + // Wrapper that assumes the default ruleset + void enable_rule_by_tag(const std::set &tags, bool enabled); struct rule_result { sinsp_evt *evt; @@ -58,12 +74,30 @@ public: std::string format; }; + // + // Return the ruleset id corresponding to this ruleset name, + // creating a new one if necessary. If you provide any ruleset + // to enable_rule/enable_rule_by_tag(), you should look up the + // ruleset id and pass it to process_event(). + // + uint16_t find_ruleset_id(const std::string &ruleset); + // // 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. + // When ruleset_id is provided, use the enabled/disabled status + // associated with the provided ruleset. This is only useful + // when you have previously called enable_rule/enable_rule_by_tag + // with a ruleset string. + // + // the returned rule_result is allocated and must be delete()d. + std::unique_ptr process_event(sinsp_evt *ev, uint16_t ruleset_id); + + // + // Wrapper assuming the default ruleset + // std::unique_ptr process_event(sinsp_evt *ev); // @@ -78,11 +112,12 @@ public: void print_stats(); // - // Add a filter, which is related to the specified list of + // Add a filter, which is related to the specified set of // event types, to the engine. // void add_evttype_filter(std::string &rule, - list &evttypes, + std::set &evttypes, + std::set &tags, sinsp_filter* filter); // Clear all existing filters. @@ -120,6 +155,8 @@ private: inline bool should_drop_evt(); falco_rules *m_rules; + uint16_t m_next_ruleset_id; + std::map m_known_rulesets; std::unique_ptr m_evttype_filter; // @@ -146,6 +183,8 @@ private: double m_sampling_multiplier; std::string m_lua_main_filename = "rule_loader.lua"; + std::string m_default_ruleset = "falco-default-ruleset"; + uint32_t m_default_ruleset_id; std::string m_extra; bool m_replace_container_info; diff --git a/userspace/engine/lua/rule_loader.lua b/userspace/engine/lua/rule_loader.lua index 4e18ede8..99be9a4f 100644 --- a/userspace/engine/lua/rule_loader.lua +++ b/userspace/engine/lua/rule_loader.lua @@ -308,8 +308,12 @@ function load_rules(rules_content, rules_mgr, verbose, all_events, extra, replac install_filter(filter_ast.filter.value) + if (v['tags'] == nil) then + v['tags'] = {} + end + -- Pass the filter and event types back up - falco_rules.add_filter(rules_mgr, v['rule'], evttypes) + falco_rules.add_filter(rules_mgr, v['rule'], evttypes, v['tags']) -- Rule ASTs are merged together into one big AST, with "OR" between each -- rule. diff --git a/userspace/engine/rules.cpp b/userspace/engine/rules.cpp index e0a78d1e..63b9b416 100644 --- a/userspace/engine/rules.cpp +++ b/userspace/engine/rules.cpp @@ -65,42 +65,55 @@ void falco_rules::clear_filters() int falco_rules::add_filter(lua_State *ls) { - if (! lua_islightuserdata(ls, -3) || - ! lua_isstring(ls, -2) || + if (! lua_islightuserdata(ls, -4) || + ! lua_isstring(ls, -3) || + ! lua_istable(ls, -2) || ! lua_istable(ls, -1)) { throw falco_exception("Invalid arguments passed to add_filter()\n"); } - falco_rules *rules = (falco_rules *) lua_topointer(ls, -3); - const char *rulec = lua_tostring(ls, -2); + falco_rules *rules = (falco_rules *) lua_topointer(ls, -4); + const char *rulec = lua_tostring(ls, -3); - list evttypes; + set evttypes; + + lua_pushnil(ls); /* first key */ + while (lua_next(ls, -3) != 0) { + // key is at index -2, value is at index + // -1. We want the keys. + evttypes.insert(luaL_checknumber(ls, -2)); + + // Remove value, keep key for next iteration + lua_pop(ls, 1); + } + + set tags; lua_pushnil(ls); /* first key */ while (lua_next(ls, -2) != 0) { // key is at index -2, value is at index // -1. We want the keys. - evttypes.push_back(luaL_checknumber(ls, -2)); + tags.insert(lua_tostring(ls, -1)); // Remove value, keep key for next iteration lua_pop(ls, 1); } std::string rule = rulec; - rules->add_filter(rule, evttypes); + rules->add_filter(rule, evttypes, tags); return 0; } -void falco_rules::add_filter(string &rule, list &evttypes) +void falco_rules::add_filter(string &rule, set &evttypes, set &tags) { // 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 engine. sinsp_filter *filter = m_lua_parser->get_filter(true); - m_engine->add_evttype_filter(rule, evttypes, filter); + m_engine->add_evttype_filter(rule, evttypes, tags, filter); } int falco_rules::enable_rule(lua_State *ls) diff --git a/userspace/engine/rules.h b/userspace/engine/rules.h index 3cd06b08..1770aacc 100644 --- a/userspace/engine/rules.h +++ b/userspace/engine/rules.h @@ -18,7 +18,7 @@ along with falco. If not, see . #pragma once -#include +#include #include "sinsp.h" @@ -42,7 +42,7 @@ class falco_rules private: void clear_filters(); - void add_filter(string &rule, list &evttypes); + void add_filter(string &rule, std::set &evttypes, std::set &tags); void enable_rule(string &rule, bool enabled); lua_parser* m_lua_parser; diff --git a/userspace/falco/falco.cpp b/userspace/falco/falco.cpp index fb2fa6db..455ab3a1 100644 --- a/userspace/falco/falco.cpp +++ b/userspace/falco/falco.cpp @@ -60,6 +60,7 @@ static void usage() " -A Monitor all events, including those with EF_DROP_FALCO flag.\n" " -d, --daemon Run as a daemon\n" " -D Disable any rules matching the regex . Can be specified multiple times.\n" + " Can not be specified with -t.\n" " -e Read the events from (in .scap format) instead of tapping into live.\n" " -k , --k8s-api=\n" " Enable Kubernetes support by connecting to the API server\n" @@ -100,6 +101,10 @@ static void usage() " Can be specified multiple times to read from multiple files.\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" + " Can not be specified with -t.\n" + " -t Only run those rules with a tag=. Can be specified multiple times.\n" + " Can not be specified with -T/-D.\n" " -v Verbose output.\n" "\n" ); @@ -259,12 +264,15 @@ int falco_init(int argc, char **argv) { set disabled_rule_patterns; string pattern; + string all_rules = ".*"; + set disabled_rule_tags; + set enabled_rule_tags; // // Parse the args // while((op = getopt_long(argc, argv, - "hc:AdD:e:k:K:Ll:m:o:P:p:r:s:vw:", + "hc:AdD:e:k:K:Ll:m:o:P:p:r:s:T:t:vw:", long_options, &long_index)) != -1) { switch(op) @@ -339,6 +347,12 @@ int falco_init(int argc, char **argv) case 's': stats_filename = optarg; break; + case 'T': + disabled_rule_tags.insert(optarg); + break; + case 't': + enabled_rule_tags.insert(optarg); + break; case 'v': verbose = true; break; @@ -421,12 +435,40 @@ int falco_init(int argc, char **argv) falco_logger::log(LOG_INFO, "Parsed rules from file " + filename + "\n"); } + // You can't both disable and enable rules + if((disabled_rule_patterns.size() + disabled_rule_tags.size() > 0) && + enabled_rule_tags.size() > 0) { + throw std::invalid_argument("You can not specify both disabled (-D/-T) and enabled (-t) rules"); + } + for (auto pattern : disabled_rule_patterns) { falco_logger::log(LOG_INFO, "Disabling rules matching pattern: " + pattern + "\n"); engine->enable_rule(pattern, false); } + if(disabled_rule_tags.size() > 0) + { + for(auto tag : disabled_rule_tags) + { + falco_logger::log(LOG_INFO, "Disabling rules with tag: " + tag + "\n"); + } + engine->enable_rule_by_tag(disabled_rule_tags, false); + } + + if(enabled_rule_tags.size() > 0) + { + + // Since we only want to enable specific + // rules, first disable all rules. + engine->enable_rule(all_rules, false); + for(auto tag : enabled_rule_tags) + { + falco_logger::log(LOG_INFO, "Enabling rules with tag: " + tag + "\n"); + } + engine->enable_rule_by_tag(enabled_rule_tags, true); + } + outputs->init(config.m_json_output, config.m_notifications_rate, config.m_notifications_max_burst); if(!all_events)