mirror of
https://github.com/falcosecurity/falco.git
synced 2025-08-31 14:20:04 +00:00
Merge pull request #83 from draios/add-correctness-tests
Add correctness tests
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,4 +1,9 @@
|
||||
/build*
|
||||
*~
|
||||
test/falco_test.pyc
|
||||
test/falco_tests.yaml
|
||||
test/traces-negative
|
||||
test/traces-positive
|
||||
|
||||
userspace/falco/lua/re.lua
|
||||
userspace/falco/lua/lpeg.so
|
||||
|
10
.travis.yml
10
.travis.yml
@@ -9,6 +9,14 @@ install:
|
||||
- sudo apt-get --force-yes install g++-4.8
|
||||
- sudo apt-get install rpm linux-headers-$(uname -r)
|
||||
- git clone https://github.com/draios/sysdig.git ../sysdig
|
||||
- sudo apt-get install -y python-pip libvirt-dev jq
|
||||
- cd ..
|
||||
- curl -Lo avocado-36.0-tar.gz https://github.com/avocado-framework/avocado/archive/36.0lts.tar.gz
|
||||
- tar -zxvf avocado-36.0-tar.gz
|
||||
- cd avocado-36.0lts
|
||||
- sudo pip install -r requirements-travis.txt
|
||||
- sudo python setup.py install
|
||||
- cd ../falco
|
||||
before_script:
|
||||
- export KERNELDIR=/lib/modules/$(ls /lib/modules | sort | head -1)/build
|
||||
script:
|
||||
@@ -28,7 +36,7 @@ script:
|
||||
- make VERBOSE=1
|
||||
- make package
|
||||
- cd ..
|
||||
- sudo test/falco_trace_regression.sh build/userspace/falco/falco
|
||||
- sudo test/run_regression_tests.sh
|
||||
notifications:
|
||||
webhooks:
|
||||
urls:
|
||||
|
@@ -38,12 +38,16 @@
|
||||
- macro: modify
|
||||
condition: rename or remove
|
||||
|
||||
- macro: spawn_process
|
||||
condition: syscall.type = execve and evt.dir=<
|
||||
- macro: spawned_process
|
||||
condition: evt.type = execve and evt.dir=<
|
||||
|
||||
# File categories
|
||||
- macro: terminal_file_fd
|
||||
condition: fd.name=/dev/ptmx or fd.directory=/dev/pts
|
||||
|
||||
# This really should be testing that the directory begins with these
|
||||
# prefixes but sysdig's filter doesn't have a "starts with" operator
|
||||
# (yet).
|
||||
- macro: bin_dir
|
||||
condition: fd.directory in (/bin, /sbin, /usr/bin, /usr/sbin)
|
||||
|
||||
@@ -52,6 +56,8 @@
|
||||
- macro: bin_dir_rename
|
||||
condition: evt.arg[1] contains /bin/ or evt.arg[1] contains /sbin/ or evt.arg[1] contains /usr/bin/ or evt.arg[1] contains /usr/sbin/
|
||||
|
||||
# This really should be testing that the directory begins with /etc,
|
||||
# but sysdig's filter doesn't have a "starts with" operator (yet).
|
||||
- macro: etc_dir
|
||||
condition: fd.directory contains /etc
|
||||
|
||||
@@ -74,25 +80,31 @@
|
||||
tac, link, chroot, vdir, chown, touch, ls, dd, uname, true, pwd, date,
|
||||
chgrp, chmod, mktemp, cat, mknod, sync, ln, false, rm, mv, cp, echo,
|
||||
readlink, sleep, stty, mkdir, df, dir, rmdir, touch)
|
||||
- macro: adduser_binaries
|
||||
condition: proc.name in (adduser, deluser, addgroup, delgroup)
|
||||
- macro: login_binaries
|
||||
condition: proc.name in (bin, login, su, sbin, nologin, bin, faillog, lastlog, newgrp, sg)
|
||||
|
||||
# dpkg -L passwd | grep bin | xargs -L 1 basename | tr "\\n" ","
|
||||
# dpkg -L login | grep bin | xargs ls -ld | grep -v '^d' | awk '{print $9}' | xargs -L 1 basename | tr "\\n" ","
|
||||
- macro: login_binaries
|
||||
condition: proc.name in (login, systemd-logind, su, nologin, faillog, lastlog, newgrp, sg)
|
||||
|
||||
# dpkg -L passwd | grep bin | xargs ls -ld | grep -v '^d' | awk '{print $9}' | xargs -L 1 basename | tr "\\n" ","
|
||||
- macro: passwd_binaries
|
||||
condition: >
|
||||
proc.name in (sbin, shadowconfig, sbin, grpck, pwunconv, grpconv, pwck,
|
||||
proc.name in (shadowconfig, grpck, pwunconv, grpconv, pwck,
|
||||
groupmod, vipw, pwconv, useradd, newusers, cppw, chpasswd, usermod,
|
||||
groupadd, groupdel, grpunconv, chgpasswd, userdel, bin, chage, chsh,
|
||||
groupadd, groupdel, grpunconv, chgpasswd, userdel, chage, chsh,
|
||||
gpasswd, chfn, expiry, passwd, vigr, cpgr)
|
||||
|
||||
# repoquery -l shadow-utils | grep bin | xargs -L 1 basename | tr "\\n" ","
|
||||
# repoquery -l shadow-utils | grep bin | xargs ls -ld | grep -v '^d' | awk '{print $9}' | xargs -L 1 basename | tr "\\n" ","
|
||||
- macro: shadowutils_binaries
|
||||
condition: >
|
||||
proc.name in (chage, gpasswd, lastlog, newgrp, sg, adduser, chpasswd,
|
||||
groupadd, groupdel, groupmems, groupmod, grpck, grpconv, grpunconv,
|
||||
newusers, pwck, pwconv, pwunconv, useradd, userdel, usermod, vigr, vipw)
|
||||
proc.name in (chage, gpasswd, lastlog, newgrp, sg, adduser, deluser, chpasswd,
|
||||
groupadd, groupdel, addgroup, delgroup, groupmems, groupmod, grpck, grpconv, grpunconv,
|
||||
newusers, pwck, pwconv, pwunconv, useradd, userdel, usermod, vigr, vipw, unix_chkpwd)
|
||||
|
||||
- macro: sysdigcloud_binaries
|
||||
condition: proc.name in (setup-backend, dragent)
|
||||
|
||||
- macro: sysdigcloud_binaries_parent
|
||||
condition: proc.pname in (setup-backend, dragent)
|
||||
|
||||
- macro: docker_binaries
|
||||
condition: proc.name in (docker, exe)
|
||||
@@ -103,25 +115,33 @@
|
||||
- macro: db_server_binaries
|
||||
condition: proc.name in (mysqld)
|
||||
|
||||
- macro: server_binaries
|
||||
condition: http_server_binaries or db_server_binaries or docker_binaries or proc.name in (sshd)
|
||||
- macro: db_server_binaries_parent
|
||||
condition: proc.pname in (mysqld)
|
||||
|
||||
- macro: server_binaries
|
||||
condition: (http_server_binaries or db_server_binaries or docker_binaries or proc.name in (sshd))
|
||||
|
||||
# The truncated dpkg-preconfigu is intentional, process names are
|
||||
# truncated at the sysdig level.
|
||||
- macro: package_mgmt_binaries
|
||||
condition: proc.name in (dpkg, rpm)
|
||||
condition: proc.name in (dpkg, dpkg-preconfigu, rpm, yum)
|
||||
|
||||
# A canonical set of processes that run other programs with different
|
||||
# privileges or as a different user.
|
||||
- macro: userexec_binaries
|
||||
condition: proc.name in (sudo, su)
|
||||
|
||||
- macro: user_mgmt_binaries
|
||||
condition: (login_binaries or passwd_binaries or shadowutils_binaries)
|
||||
|
||||
- macro: system_binaries
|
||||
condition: coreutils_binaries or adduser_binaries or login_binaries or passwd_binaries or shadowutils_binaries
|
||||
condition: (coreutils_binaries or user_mgmt_binaries)
|
||||
|
||||
- macro: mail_binaries
|
||||
condition: proc.name in (sendmail, postfix, procmail)
|
||||
condition: proc.name in (sendmail, sendmail-msp, postfix, procmail)
|
||||
|
||||
- macro: sensitive_files
|
||||
condition: fd.name contains /etc/shadow or fd.name = /etc/sudoers or fd.directory = /etc/sudoers.d or fd.directory = /etc/pam.d or fd.name = /etc/pam.conf
|
||||
condition: (fd.name contains /etc/shadow or fd.name = /etc/sudoers or fd.directory = /etc/sudoers.d or fd.directory = /etc/pam.d or fd.name = /etc/pam.conf)
|
||||
|
||||
# Indicates that the process is new. Currently detected using time
|
||||
# since process was started, using a threshold of 5 seconds.
|
||||
@@ -130,7 +150,7 @@
|
||||
|
||||
# Network
|
||||
- macro: inbound
|
||||
condition: (syscall.type=listen and evt.dir=>) or (syscall.type=accept and evt.dir=<)
|
||||
condition: ((syscall.type=listen and evt.dir=>) or (syscall.type=accept and evt.dir=<))
|
||||
|
||||
# Currently sendto is an ignored syscall, otherwise this could also check for (syscall.type=sendto and evt.dir=>)
|
||||
- macro: outbound
|
||||
@@ -141,7 +161,7 @@
|
||||
|
||||
# Ssh
|
||||
- macro: ssh_error_message
|
||||
condition: evt.arg.data contains "Invalid user" or evt.arg.data contains "preauth"
|
||||
condition: (evt.arg.data contains "Invalid user" or evt.arg.data contains "preauth" or evt.arg.data contains "Failed password")
|
||||
|
||||
# System
|
||||
- macro: modules
|
||||
@@ -149,9 +169,9 @@
|
||||
- macro: container
|
||||
condition: container.id != host
|
||||
- macro: interactive
|
||||
condition: (proc.aname=sshd and proc.name != sshd) or proc.name=systemd-logind
|
||||
condition: ((proc.aname=sshd and proc.name != sshd) or proc.name=systemd-logind)
|
||||
- macro: syslog
|
||||
condition: fd.name = /dev/log
|
||||
condition: fd.name in (/dev/log, /run/systemd/journal/syslog)
|
||||
- macro: cron
|
||||
condition: proc.name in (cron, crond)
|
||||
- macro: parent_cron
|
||||
@@ -169,32 +189,32 @@
|
||||
|
||||
- rule: write_binary_dir
|
||||
desc: an attempt to write to any file below a set of binary directories
|
||||
condition: evt.dir = > and open_write and bin_dir
|
||||
condition: evt.dir = < and open_write and not package_mgmt_binaries and bin_dir
|
||||
output: "File below a known binary directory opened for writing (user=%user.name command=%proc.cmdline file=%fd.name)"
|
||||
priority: WARNING
|
||||
|
||||
- rule: write_etc
|
||||
desc: an attempt to write to any file below /etc
|
||||
condition: evt.dir = > and open_write and etc_dir
|
||||
condition: evt.dir = < and open_write and not shadowutils_binaries and not sysdigcloud_binaries_parent and not package_mgmt_binaries and etc_dir
|
||||
output: "File below /etc opened for writing (user=%user.name command=%proc.cmdline file=%fd.name)"
|
||||
priority: WARNING
|
||||
|
||||
- rule: read_sensitive_file_untrusted
|
||||
desc: an attempt to read any sensitive file (e.g. files containing user/password/authentication information). Exceptions are made for known trusted programs.
|
||||
condition: open_read and not server_binaries and not userexec_binaries and not proc.name in (iptables, ps, systemd-logind, lsb_release, check-new-relea, dumpe2fs, accounts-daemon, bash) and not cron and sensitive_files
|
||||
condition: open_read and not user_mgmt_binaries and not userexec_binaries and not proc.name in (iptables, ps, lsb_release, check-new-relea, dumpe2fs, accounts-daemon, bash, sshd) and not cron and sensitive_files
|
||||
output: "Sensitive file opened for reading by non-trusted program (user=%user.name command=%proc.cmdline file=%fd.name)"
|
||||
priority: WARNING
|
||||
|
||||
- rule: read_sensitive_file_trusted_after_startup
|
||||
desc: an attempt to read any sensitive file (e.g. files containing user/password/authentication information) by a trusted program after startup. Trusted programs might read these files at startup to load initial state, but not afterwards.
|
||||
condition: open_read and server_binaries and not proc_is_new and sensitive_files
|
||||
condition: open_read and server_binaries and not proc_is_new and sensitive_files 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
|
||||
|
||||
- rule: db_program_spawn_process
|
||||
desc: a database-server related program spawning a new process after startup. This shouldn\'t occur and is a follow on from some SQL injection attacks.
|
||||
condition: db_server_binaries and not proc_is_new and spawn_process
|
||||
output: "Database-related program spawned new process after startup (user=%user.name command=%proc.cmdline)"
|
||||
- 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: db_server_binaries_parent and not db_server_binaries and spawned_process
|
||||
output: "Database-related program spawned process other than itself (user=%user.name program=%proc.cmdline parent=%proc.pname)"
|
||||
priority: WARNING
|
||||
|
||||
- rule: modify_binary_dirs
|
||||
@@ -218,11 +238,12 @@
|
||||
# output: "Loaded .so from unexpected dir (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)"
|
||||
# priority: WARNING
|
||||
|
||||
- rule: syscall_returns_eaccess
|
||||
desc: any system call that returns EACCESS. This is not always a strong indication of a problem, hence the INFO priority.
|
||||
condition: evt.res = EACCESS
|
||||
output: "System call returned EACCESS (user=%user.name command=%proc.cmdline syscall=%evt.type args=%evt.args)"
|
||||
priority: INFO
|
||||
# Temporarily disabling this rule as it's tripping over https://github.com/draios/sysdig/issues/598
|
||||
# - rule: syscall_returns_eaccess
|
||||
# desc: any system call that returns EACCESS. This is not always a strong indication of a problem, hence the INFO priority.
|
||||
# condition: evt.res = EACCESS
|
||||
# output: "System call returned EACCESS (user=%user.name command=%proc.cmdline syscall=%evt.type args=%evt.args)"
|
||||
# priority: INFO
|
||||
|
||||
- rule: change_thread_namespace
|
||||
desc: an attempt to change a program/thread\'s namespace (commonly done as a part of creating a container) by calling setns.
|
||||
@@ -232,7 +253,7 @@
|
||||
|
||||
- rule: run_shell_untrusted
|
||||
desc: an attempt to spawn a shell by a non-shell program. Exceptions are made for trusted binaries.
|
||||
condition: proc.name = bash and evt.dir=< and evt.type=execve and proc.pname exists and not parent_cron and not proc.pname in (bash, sshd, sudo, docker, su, tmux, screen, emacs, systemd, flock, fs-bash, nginx, monit, supervisord)
|
||||
condition: not container and proc.name = bash and spawned_process and proc.pname exists and not parent_cron and not proc.pname in (bash, sshd, sudo, docker, su, tmux, screen, emacs, systemd, login, flock, fbash, nginx, monit, supervisord, dragent)
|
||||
output: "Shell spawned by untrusted binary (user=%user.name shell=%proc.name parent=%proc.pname cmdline=%proc.cmdline)"
|
||||
priority: WARNING
|
||||
|
||||
@@ -243,13 +264,13 @@
|
||||
|
||||
- rule: system_user_interactive
|
||||
desc: an attempt to run interactive commands by a system (i.e. non-login) user
|
||||
condition: spawn_process and system_users and interactive
|
||||
condition: spawned_process and system_users and interactive
|
||||
output: "System user ran an interactive command (user=%user.name command=%proc.cmdline)"
|
||||
priority: WARNING
|
||||
|
||||
- rule: run_shell_in_container
|
||||
desc: an attempt to spawn a shell by a non-shell program in a container. Container entrypoints are excluded.
|
||||
condition: container and proc.name = bash and evt.dir=< and evt.type=execve and proc.pname exists and not proc.pname in (bash, docker)
|
||||
desc: a shell was spawned by a non-shell program in a container. Container entrypoints are excluded.
|
||||
condition: container and proc.name = bash and spawned_process and proc.pname exists and not proc.pname in (bash, docker)
|
||||
output: "Shell spawned in a container other than entrypoint (user=%user.name container_id=%container.id container_name=%container.name shell=%proc.name parent=%proc.pname cmdline=%proc.cmdline)"
|
||||
priority: WARNING
|
||||
|
||||
@@ -260,22 +281,26 @@
|
||||
output: "Known system binary sent/received network traffic (user=%user.name command=%proc.cmdline connection=%fd.name)"
|
||||
priority: WARNING
|
||||
|
||||
- rule: ssh_error_syslog
|
||||
desc: any ssh errors (failed logins, disconnects, ...) sent to syslog
|
||||
condition: syslog and ssh_error_message and evt.dir = <
|
||||
output: "sshd sent error message to syslog (error=%evt.buffer)"
|
||||
priority: WARNING
|
||||
# With the current restriction on system calls handled by falco
|
||||
# (e.g. excluding read/write/sendto/recvfrom/etc, this rule won't
|
||||
# trigger).
|
||||
# - rule: ssh_error_syslog
|
||||
# desc: any ssh errors (failed logins, disconnects, ...) sent to syslog
|
||||
# condition: syslog and ssh_error_message and evt.dir = <
|
||||
# output: "sshd sent error message to syslog (error=%evt.buffer)"
|
||||
# priority: WARNING
|
||||
|
||||
# sshd, sendmail-msp, sendmail attempt to setuid to root even when running as non-root. Excluding here to avoid meaningless FPs
|
||||
- rule: non_sudo_setuid
|
||||
desc: an attempt to change users by calling setuid. sudo/su are excluded. user "root" is also excluded, as setuid calls typically involve dropping privileges.
|
||||
condition: evt.type=setuid and evt.dir=> and not user.name=root and not userexec_binaries
|
||||
condition: evt.type=setuid and evt.dir=> and not user.name=root and not userexec_binaries and not proc.name in (sshd, sendmail-msp, sendmail)
|
||||
output: "Unexpected setuid call by non-sudo, non-root program (user=%user.name command=%proc.cmdline uid=%evt.arg.uid)"
|
||||
priority: WARNING
|
||||
|
||||
- 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: spawn_process and not proc.name in (su, sudo) and not container and (adduser_binaries or login_binaries or passwd_binaries or shadowutils_binaries)
|
||||
output: "User management binary command run outside of container (user=%user.name command=%proc.cmdline)"
|
||||
condition: spawned_process and not proc.name in (su, sudo) and not container and user_mgmt_binaries and not parent_cron and not proc.pname in (systemd, run-parts)
|
||||
output: "User management binary command run outside of container (user=%user.name command=%proc.cmdline parent=%proc.pname)"
|
||||
priority: WARNING
|
||||
|
||||
# (we may need to add additional checks against false positives, see: https://bugs.launchpad.net/ubuntu/+source/rkhunter/+bug/86153)
|
||||
@@ -285,17 +310,17 @@
|
||||
output: "File created below /dev by untrusted program (user=%user.name command=%proc.cmdline file=%fd.name)"
|
||||
priority: WARNING
|
||||
|
||||
# fs-bash is a restricted version of bash suitable for use in curl <curl> | sh installers.
|
||||
# fbash is a small shell script that runs bash, and is suitable for use in curl <curl> | fbash installers.
|
||||
- rule: installer_bash_starts_network_server
|
||||
desc: an attempt by any program that is a child of fs-bash to start listening for network connections
|
||||
condition: evt.type=listen and proc.aname=fs-bash
|
||||
output: "Unexpected listen call by a child process of fs-bash (command=%proc.cmdline)"
|
||||
desc: an attempt by any program that is a child of fbash to start listening for network connections
|
||||
condition: evt.type=listen and proc.aname=fbash
|
||||
output: "Unexpected listen call by a child process of fbash (command=%proc.cmdline)"
|
||||
priority: WARNING
|
||||
|
||||
- rule: installer_bash_starts_session
|
||||
desc: an attempt by any program that is a child of fs-bash to start a new session (process group)
|
||||
condition: evt.type=setsid and proc.aname=fs-bash
|
||||
output: "Unexpected setsid call by a child process of fs-bash (command=%proc.cmdline)"
|
||||
desc: an attempt by any program that is a child of fbash to start a new session (process group)
|
||||
condition: evt.type=setsid and proc.aname=fbash
|
||||
output: "Unexpected setsid call by a child process of fbash (command=%proc.cmdline)"
|
||||
priority: WARNING
|
||||
|
||||
###########################
|
||||
|
63
test/falco_test.py
Normal file
63
test/falco_test.py
Normal file
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
from avocado import Test
|
||||
from avocado.utils import process
|
||||
from avocado.utils import linux_modules
|
||||
|
||||
class FalcoTest(Test):
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
Load the sysdig kernel module if not already loaded.
|
||||
"""
|
||||
self.falcodir = self.params.get('falcodir', '/', default=os.path.join(self.basedir, '../build'))
|
||||
|
||||
self.should_detect = self.params.get('detect', '*')
|
||||
self.trace_file = self.params.get('trace_file', '*')
|
||||
|
||||
# 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)
|
||||
|
||||
if linux_modules.parse_lsmod_for_module(lsmod_output, 'sysdig_probe') == {}:
|
||||
self.log.debug("Loading sysdig kernel module")
|
||||
process.run('sudo insmod {}/driver/sysdig-probe.ko'.format(self.falcodir))
|
||||
|
||||
self.str_variant = self.trace_file
|
||||
|
||||
def test(self):
|
||||
self.log.info("Trace file %s", self.trace_file)
|
||||
|
||||
# Run the provided trace file though falco
|
||||
cmd = '{}/userspace/falco/falco -r {}/../rules/falco_rules.yaml -c {}/../falco.yaml -e {}'.format(
|
||||
self.falcodir, self.falcodir, self.falcodir, self.trace_file)
|
||||
|
||||
self.falco_proc = process.SubProcess(cmd)
|
||||
|
||||
res = self.falco_proc.run(timeout=60, sig=9)
|
||||
|
||||
if res.exit_status != 0:
|
||||
self.error("Falco command \"{}\" exited with non-zero return value {}".format(
|
||||
cmd, res.exit_status))
|
||||
|
||||
# Get the number of events detected.
|
||||
res = re.search('Events detected: (\d+)', res.stdout)
|
||||
if res is None:
|
||||
self.fail("Could not find a line 'Events detected: <count>' in falco output")
|
||||
|
||||
events_detected = int(res.group(1))
|
||||
|
||||
if not self.should_detect and events_detected > 0:
|
||||
self.fail("Detected {} events when should have detected none".format(events_detected))
|
||||
|
||||
if self.should_detect and events_detected == 0:
|
||||
self.fail("Detected {} events when should have detected > 0".format(events_detected))
|
||||
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@@ -1,30 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -eu
|
||||
|
||||
SCRIPT=$(readlink -f $0)
|
||||
BASEDIR=$(dirname $SCRIPT)
|
||||
|
||||
FALCO=$1
|
||||
BUILDDIR=$(dirname $FALCO)
|
||||
|
||||
# Load the built kernel module by hand
|
||||
insmod $BUILDDIR/../../driver/sysdig-probe.ko
|
||||
|
||||
# For now, simply ensure that falco can run without errors.
|
||||
FALCO_CMDLINE="$FALCO -c $BASEDIR/../falco.yaml -r $BASEDIR/../rules/falco_rules.yaml"
|
||||
echo "Running falco: $FALCO_CMDLINE"
|
||||
$FALCO_CMDLINE > $BASEDIR/falco.log 2>&1 &
|
||||
FALCO_PID=$!
|
||||
echo "Falco started, pid $FALCO_PID"
|
||||
sleep 10
|
||||
if kill -0 $FALCO_PID > /dev/null 2>&1; then
|
||||
echo "Falco ran successfully"
|
||||
kill $FALCO_PID
|
||||
ret=0
|
||||
else
|
||||
echo "Falco did not start successfully. Full program output:"
|
||||
cat $BASEDIR/falco.log
|
||||
ret=1
|
||||
fi
|
||||
|
||||
exit $ret
|
62
test/run_regression_tests.sh
Executable file
62
test/run_regression_tests.sh
Executable file
@@ -0,0 +1,62 @@
|
||||
#!/bin/bash
|
||||
|
||||
SCRIPT=$(readlink -f $0)
|
||||
SCRIPTDIR=$(dirname $SCRIPT)
|
||||
MULT_FILE=$SCRIPTDIR/falco_tests.yaml
|
||||
|
||||
function download_trace_files() {
|
||||
for TRACE in traces-positive traces-negative ; do
|
||||
curl -so $SCRIPTDIR/$TRACE.zip https://s3.amazonaws.com/download.draios.com/falco-tests/$TRACE.zip &&
|
||||
unzip -d $SCRIPTDIR $SCRIPTDIR/$TRACE.zip &&
|
||||
rm -rf $SCRIPTDIR/$TRACE.zip
|
||||
done
|
||||
}
|
||||
|
||||
function prepare_multiplex_file() {
|
||||
echo "trace_files: !mux" > $MULT_FILE
|
||||
|
||||
for trace in $SCRIPTDIR/traces-positive/*.scap ; do
|
||||
[ -e "$trace" ] || continue
|
||||
NAME=`basename $trace .scap`
|
||||
cat << EOF >> $MULT_FILE
|
||||
$NAME:
|
||||
detect: True
|
||||
trace_file: $trace
|
||||
EOF
|
||||
done
|
||||
|
||||
for trace in $SCRIPTDIR/traces-negative/*.scap ; do
|
||||
[ -e "$trace" ] || continue
|
||||
NAME=`basename $trace .scap`
|
||||
cat << EOF >> $MULT_FILE
|
||||
$NAME:
|
||||
detect: False
|
||||
trace_file: $trace
|
||||
EOF
|
||||
done
|
||||
|
||||
echo "Contents of $MULT_FILE:"
|
||||
cat $MULT_FILE
|
||||
}
|
||||
|
||||
function run_tests() {
|
||||
CMD="avocado run --multiplex $MULT_FILE --job-results-dir $SCRIPTDIR/job-results -- $SCRIPTDIR/falco_test.py"
|
||||
echo "Running: $CMD"
|
||||
$CMD
|
||||
TEST_RC=$?
|
||||
}
|
||||
|
||||
|
||||
function print_test_failure_details() {
|
||||
echo "Showing full job logs for any tests that failed:"
|
||||
jq '.tests[] | select(.status != "PASS") | .logfile' $SCRIPTDIR/job-results/latest/results.json | xargs cat
|
||||
}
|
||||
|
||||
download_trace_files
|
||||
prepare_multiplex_file
|
||||
run_tests
|
||||
if [ $TEST_RC -ne 0 ]; then
|
||||
print_test_failure_details
|
||||
fi
|
||||
|
||||
exit $TEST_RC
|
9
test/utils/run_sysdig.sh
Normal file
9
test/utils/run_sysdig.sh
Normal file
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Run sysdig excluding all events that aren't used by falco and also
|
||||
# excluding other high-volume events that aren't essential. This
|
||||
# results in smaller trace files.
|
||||
|
||||
# The remaining arguments are taken from the command line.
|
||||
|
||||
exec sudo sysdig not evt.type in '(mprotect,brk,mq_timedreceive,mq_receive,mq_timedsend,mq_send,getrusage,procinfo,rt_sigprocmask,rt_sigaction,ioctl,clock_getres,clock_gettime,clock_nanosleep,clock_settime,close,epoll_create,epoll_create1,epoll_ctl,epoll_pwait,epoll_wait,eventfd,fcntl,fcntl64,fstat,fstat64,fstatat64,fstatfs,fstatfs64,futex,getitimer,gettimeofday,ioprio_get,ioprio_set,llseek,lseek,lstat,lstat64,mmap,mmap2,munmap,nanosleep,poll,ppoll,pread,pread64,preadv,procinfo,pselect6,pwrite,pwrite64,pwritev,read,readv,recv,recvfrom,recvmmsg,recvmsg,sched_yield,select,send,sendfile,sendfile64,sendmmsg,sendmsg,sendto,setitimer,settimeofday,shutdown,splice,stat,stat64,statfs,statfs64,switch,tee,timer_create,timer_delete,timerfd_create,timerfd_gettime,timerfd_settime,timer_getoverrun,timer_gettime,timer_settime,wait4,write,writev) and user.name!=ec2-user' $@
|
@@ -28,6 +28,14 @@ extern "C" {
|
||||
#include "utils.h"
|
||||
#include <yaml-cpp/yaml.h>
|
||||
|
||||
bool g_terminate = false;
|
||||
//
|
||||
// Helper functions
|
||||
//
|
||||
static void signal_callback(int signal)
|
||||
{
|
||||
g_terminate = true;
|
||||
}
|
||||
|
||||
//
|
||||
// Program help
|
||||
@@ -67,6 +75,7 @@ static void display_fatal_err(const string &msg, bool daemon)
|
||||
|
||||
string lua_on_event = "on_event";
|
||||
string lua_add_output = "add_output";
|
||||
string lua_print_stats = "print_stats";
|
||||
|
||||
// Splitting into key=value or key.subkey=value will be handled by configuration class.
|
||||
std::list<string> cmdline_options;
|
||||
@@ -90,7 +99,11 @@ void do_inspect(sinsp* inspector,
|
||||
|
||||
res = inspector->next(&ev);
|
||||
|
||||
if(res == SCAP_TIMEOUT)
|
||||
if (g_terminate)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if(res == SCAP_TIMEOUT)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -199,6 +212,26 @@ void add_output(lua_State *ls, output_config oc)
|
||||
|
||||
}
|
||||
|
||||
// Print statistics on the the rules that triggered
|
||||
void print_stats(lua_State *ls)
|
||||
{
|
||||
lua_getglobal(ls, lua_print_stats.c_str());
|
||||
|
||||
if(lua_isfunction(ls, -1))
|
||||
{
|
||||
if(lua_pcall(ls, 0, 0, 0) != 0)
|
||||
{
|
||||
const char* lerr = lua_tostring(ls, -1);
|
||||
string err = "Error invoking function print_stats: " + string(lerr);
|
||||
throw sinsp_exception(err);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw sinsp_exception("No function " + lua_print_stats + " found in lua rule loader module");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
// ARGUMENT PARSING AND PROGRAM SETUP
|
||||
@@ -398,6 +431,20 @@ int falco_init(int argc, char **argv)
|
||||
add_output(ls, *it);
|
||||
}
|
||||
|
||||
if(signal(SIGINT, signal_callback) == SIG_ERR)
|
||||
{
|
||||
fprintf(stderr, "An error occurred while setting SIGINT signal handler.\n");
|
||||
result = EXIT_FAILURE;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if(signal(SIGTERM, signal_callback) == SIG_ERR)
|
||||
{
|
||||
fprintf(stderr, "An error occurred while setting SIGTERM signal handler.\n");
|
||||
result = EXIT_FAILURE;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (scap_filename.size())
|
||||
{
|
||||
inspector->open(scap_filename);
|
||||
@@ -406,7 +453,7 @@ int falco_init(int argc, char **argv)
|
||||
{
|
||||
try
|
||||
{
|
||||
inspector->open();
|
||||
inspector->open(200);
|
||||
}
|
||||
catch(sinsp_exception e)
|
||||
{
|
||||
@@ -478,6 +525,8 @@ int falco_init(int argc, char **argv)
|
||||
ls);
|
||||
|
||||
inspector->close();
|
||||
|
||||
print_stats(ls);
|
||||
}
|
||||
catch(sinsp_exception& e)
|
||||
{
|
||||
|
@@ -2,6 +2,8 @@ local mod = {}
|
||||
|
||||
levels = {"Emergency", "Alert", "Critical", "Error", "Warning", "Notice", "Informational", "Debug"}
|
||||
|
||||
mod.levels = levels
|
||||
|
||||
local outputs = {}
|
||||
|
||||
function mod.stdout(evt, level, format)
|
||||
|
@@ -230,12 +230,51 @@ function describe_rule(name)
|
||||
end
|
||||
end
|
||||
|
||||
local rule_output_counts = {total=0, by_level={}, by_name={}}
|
||||
|
||||
for idx, level in ipairs(output.levels) do
|
||||
rule_output_counts[level] = 0
|
||||
end
|
||||
|
||||
function on_event(evt_, rule_id)
|
||||
|
||||
if state.rules_by_idx[rule_id] == nil then
|
||||
error ("rule_loader.on_event(): event with invalid rule_id: ", rule_id)
|
||||
end
|
||||
|
||||
output.event(evt_, state.rules_by_idx[rule_id].level, state.rules_by_idx[rule_id].output)
|
||||
rule_output_counts.total = rule_output_counts.total + 1
|
||||
local rule = state.rules_by_idx[rule_id]
|
||||
|
||||
if rule_output_counts.by_level[rule.level] == nil then
|
||||
rule_output_counts.by_level[rule.level] = 1
|
||||
else
|
||||
rule_output_counts.by_level[rule.level] = rule_output_counts.by_level[rule.level] + 1
|
||||
end
|
||||
|
||||
if rule_output_counts.by_name[rule.rule] == nil then
|
||||
rule_output_counts.by_name[rule.rule] = 1
|
||||
else
|
||||
rule_output_counts.by_name[rule.rule] = rule_output_counts.by_name[rule.rule] + 1
|
||||
end
|
||||
|
||||
output.event(evt_, rule.level, rule.output)
|
||||
end
|
||||
|
||||
function print_stats()
|
||||
print("Events detected: "..rule_output_counts.total)
|
||||
print("Rule counts by severity:")
|
||||
for idx, level in ipairs(output.levels) do
|
||||
-- To keep the output concise, we only print 0 counts for error, warning, and info levels
|
||||
if rule_output_counts[level] > 0 or level == "Error" or level == "Warning" or level == "Informational" then
|
||||
print (" "..level..": "..rule_output_counts[level])
|
||||
end
|
||||
end
|
||||
|
||||
print("Triggered rules by rule name:")
|
||||
for name, count in pairs(rule_output_counts.by_name) do
|
||||
print (" "..name..": "..count)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user