From ec5adfe8922eb39eb70985ead3fbfb49deb0512c Mon Sep 17 00:00:00 2001 From: Mark Stemm Date: Mon, 20 Mar 2017 15:36:03 -0700 Subject: [PATCH 1/3] Build and package standalone falco kernel module Start packaging (and building when necessary) a falco-specific kernel module in falco releases. Previously, falco would depend on sysdig and use its kernel module instead. The kernel module was already templated to some degree in various places, so we just had to change the templated name from sysdig/sysdig-probe to falco/falco-probe. In containers, run falco-probe-loader instead of sysdig-probe-loader. This is actually a script in the sysdig repository which is modified in https://github.com/draios/sysdig/pull/789, and uses the filename to indicate what kernel module to build and/or load. For the falco package itself, don't depend on sysdig any longer but instead depend on dkms and its dependencies, using sysdig as a guide on the set of required packages. Additionally, for the package pre-install/post-install scripts start running falco-probe-loader. Finally, add a --version argument to falco so it can pass the desired version string to falco-probe-loader. --- CMakeLists.txt | 10 +++++----- docker/dev/docker-entrypoint.sh | 2 +- docker/local/Dockerfile | 2 +- docker/local/docker-entrypoint.sh | 2 +- docker/stable/docker-entrypoint.sh | 2 +- scripts/CMakeLists.txt | 7 +++++++ scripts/debian/postinst | 9 --------- scripts/debian/postinst.in | 32 ++++++++++++++++++++++++++++++ scripts/debian/prerm | 13 ------------ scripts/debian/prerm.in | 23 +++++++++++++++++++++ scripts/rpm/postinstall | 14 +++++++++++++ scripts/rpm/preuninstall | 2 ++ test/falco_test.py | 6 +++--- userspace/falco/falco.cpp | 10 ++++++++++ 14 files changed, 100 insertions(+), 34 deletions(-) delete mode 100755 scripts/debian/postinst create mode 100755 scripts/debian/postinst.in delete mode 100755 scripts/debian/prerm create mode 100755 scripts/debian/prerm.in diff --git a/CMakeLists.txt b/CMakeLists.txt index 343bfb3b..9c815b1c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,8 +41,8 @@ endif() set(PACKAGE_NAME "falco") set(PROBE_VERSION "${FALCO_VERSION}") -set(PROBE_NAME "sysdig-probe") -set(PROBE_DEVICE_NAME "sysdig") +set(PROBE_NAME "falco-probe") +set(PROBE_DEVICE_NAME "falco") set(CMAKE_INSTALL_PREFIX /usr) set(CMD_MAKE make) @@ -415,12 +415,12 @@ set(CPACK_GENERATOR DEB RPM TGZ) set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Sysdig ") set(CPACK_DEBIAN_PACKAGE_SECTION "utils") set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "http://www.sysdig.org") -set(CPACK_DEBIAN_PACKAGE_DEPENDS "sysdig") -set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA "${PROJECT_SOURCE_DIR}/scripts/debian/postinst;${PROJECT_SOURCE_DIR}/scripts/debian/prerm;${PROJECT_SOURCE_DIR}/scripts/debian/postrm") +set(CPACK_DEBIAN_PACKAGE_DEPENDS "dkms (>= 2.1.0.0)") +set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA "${CMAKE_BINARY_DIR}/scripts/debian/postinst;${CMAKE_BINARY_DIR}/scripts/debian/prerm;${PROJECT_SOURCE_DIR}/scripts/debian/postrm") set(CPACK_RPM_PACKAGE_LICENSE "GPLv2") set(CPACK_RPM_PACKAGE_URL "http://www.sysdig.org") -set(CPACK_RPM_PACKAGE_REQUIRES "sysdig") +set(CPACK_RPM_PACKAGE_REQUIRES "dkms, gcc, make, kernel-devel, perl") set(CPACK_RPM_POST_INSTALL_SCRIPT_FILE "${PROJECT_SOURCE_DIR}/scripts/rpm/postinstall") set(CPACK_RPM_PRE_UNINSTALL_SCRIPT_FILE "${PROJECT_SOURCE_DIR}/scripts/rpm/preuninstall") set(CPACK_RPM_POST_UNINSTALL_SCRIPT_FILE "${PROJECT_SOURCE_DIR}/scripts/rpm/postuninstall") diff --git a/docker/dev/docker-entrypoint.sh b/docker/dev/docker-entrypoint.sh index f0d6e32c..ffa7e671 100755 --- a/docker/dev/docker-entrypoint.sh +++ b/docker/dev/docker-entrypoint.sh @@ -11,7 +11,7 @@ if [[ -z "${SYSDIG_SKIP_LOAD}" ]]; then ln -s $SYSDIG_HOST_ROOT/usr/src/$i /usr/src/$i done - /usr/bin/sysdig-probe-loader + /usr/bin/falco-probe-loader fi exec "$@" diff --git a/docker/local/Dockerfile b/docker/local/Dockerfile index acf3ac1e..630196d6 100644 --- a/docker/local/Dockerfile +++ b/docker/local/Dockerfile @@ -25,7 +25,7 @@ RUN echo "deb http://httpredir.debian.org/debian jessie main" > /etc/apt/sources gcc \ gcc-5 \ gcc-4.9 \ - sysdig && rm -rf /var/lib/apt/lists/* + dkms && rm -rf /var/lib/apt/lists/* # Since our base Debian image ships with GCC 5.0 which breaks older kernels, revert the # default to gcc-4.9. Also, since some customers use some very old distributions whose kernel diff --git a/docker/local/docker-entrypoint.sh b/docker/local/docker-entrypoint.sh index f0d6e32c..ffa7e671 100755 --- a/docker/local/docker-entrypoint.sh +++ b/docker/local/docker-entrypoint.sh @@ -11,7 +11,7 @@ if [[ -z "${SYSDIG_SKIP_LOAD}" ]]; then ln -s $SYSDIG_HOST_ROOT/usr/src/$i /usr/src/$i done - /usr/bin/sysdig-probe-loader + /usr/bin/falco-probe-loader fi exec "$@" diff --git a/docker/stable/docker-entrypoint.sh b/docker/stable/docker-entrypoint.sh index f0d6e32c..ffa7e671 100755 --- a/docker/stable/docker-entrypoint.sh +++ b/docker/stable/docker-entrypoint.sh @@ -11,7 +11,7 @@ if [[ -z "${SYSDIG_SKIP_LOAD}" ]]; then ln -s $SYSDIG_HOST_ROOT/usr/src/$i /usr/src/$i done - /usr/bin/sysdig-probe-loader + /usr/bin/falco-probe-loader fi exec "$@" diff --git a/scripts/CMakeLists.txt b/scripts/CMakeLists.txt index b8807b81..823b8d17 100644 --- a/scripts/CMakeLists.txt +++ b/scripts/CMakeLists.txt @@ -1,5 +1,12 @@ +configure_file(debian/postinst.in debian/postinst) +configure_file(debian/prerm.in debian/prerm) + file(COPY "${PROJECT_SOURCE_DIR}/scripts/debian/falco" DESTINATION "${PROJECT_BINARY_DIR}/scripts/debian") file(COPY "${PROJECT_SOURCE_DIR}/scripts/rpm/falco" DESTINATION "${PROJECT_BINARY_DIR}/scripts/rpm") + +install(PROGRAMS ${SYSDIG_DIR}/scripts/sysdig-probe-loader + DESTINATION bin + RENAME falco-probe-loader) diff --git a/scripts/debian/postinst b/scripts/debian/postinst deleted file mode 100755 index 0084f213..00000000 --- a/scripts/debian/postinst +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/sh -set -e - -NAME=falco - -if [ -x "/etc/init.d/$NAME" ]; then - update-rc.d $NAME defaults >/dev/null -fi - diff --git a/scripts/debian/postinst.in b/scripts/debian/postinst.in new file mode 100755 index 00000000..04d0d85b --- /dev/null +++ b/scripts/debian/postinst.in @@ -0,0 +1,32 @@ +#!/bin/sh +set -e + +DKMS_PACKAGE_NAME="@PACKAGE_NAME@" +DKMS_VERSION="@PROBE_VERSION@" +NAME="@PACKAGE_NAME@" + +postinst_found=0 + +case "$1" in + configure) + for DKMS_POSTINST in /usr/lib/dkms/common.postinst /usr/share/$DKMS_PACKAGE_NAME/postinst; do + if [ -f $DKMS_POSTINST ]; then + $DKMS_POSTINST $DKMS_PACKAGE_NAME $DKMS_VERSION /usr/share/$DKMS_PACKAGE_NAME "" $2 + postinst_found=1 + break + fi + done + if [ "$postinst_found" -eq 0 ]; then + echo "ERROR: DKMS version is too old and $DKMS_PACKAGE_NAME was not" + echo "built with legacy DKMS support." + echo "You must either rebuild $DKMS_PACKAGE_NAME with legacy postinst" + echo "support or upgrade DKMS to a more current version." + exit 1 + fi + ;; +esac + +if [ -x "/etc/init.d/$NAME" ]; then + update-rc.d $NAME defaults >/dev/null +fi + diff --git a/scripts/debian/prerm b/scripts/debian/prerm deleted file mode 100755 index 5270e189..00000000 --- a/scripts/debian/prerm +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/sh -set -e - -NAME=falco - -if [ -x "/etc/init.d/$NAME" ]; then - if [ -x "`which invoke-rc.d 2>/dev/null`" ]; then - invoke-rc.d $NAME stop || exit $? - else - /etc/init.d/$NAME stop || exit $? - fi -fi - diff --git a/scripts/debian/prerm.in b/scripts/debian/prerm.in new file mode 100755 index 00000000..fd353e7d --- /dev/null +++ b/scripts/debian/prerm.in @@ -0,0 +1,23 @@ +#!/bin/sh +set -e + +NAME="@PACKAGE_NAME@" + +if [ -x "/etc/init.d/$NAME" ]; then + if [ -x "`which invoke-rc.d 2>/dev/null`" ]; then + invoke-rc.d $NAME stop || exit $? + else + /etc/init.d/$NAME stop || exit $? + fi +fi + +DKMS_PACKAGE_NAME="@PACKAGE_NAME@" +DKMS_VERSION="@PROBE_VERSION@" + +case "$1" in + remove|upgrade|deconfigure) + if [ "$(dkms status -m $DKMS_PACKAGE_NAME -v $DKMS_VERSION)" ]; then + dkms remove -m $DKMS_PACKAGE_NAME -v $DKMS_VERSION --all + fi + ;; +esac diff --git a/scripts/rpm/postinstall b/scripts/rpm/postinstall index 56e0be45..e24af669 100755 --- a/scripts/rpm/postinstall +++ b/scripts/rpm/postinstall @@ -1 +1,15 @@ +dkms add -m falco -v %{version} --rpm_safe_upgrade +if [ `uname -r | grep -c "BOOT"` -eq 0 ] && [ -e /lib/modules/`uname -r`/build/include ]; then + dkms build -m falco -v %{version} + dkms install --force -m falco -v %{version} +elif [ `uname -r | grep -c "BOOT"` -gt 0 ]; then + echo -e "" + echo -e "Module build for the currently running kernel was skipped since you" + echo -e "are running a BOOT variant of the kernel." +else + echo -e "" + echo -e "Module build for the currently running kernel was skipped since the" + echo -e "kernel source for this kernel does not seem to be installed." +fi + /sbin/chkconfig --add falco diff --git a/scripts/rpm/preuninstall b/scripts/rpm/preuninstall index 10d3b98f..ce357b38 100755 --- a/scripts/rpm/preuninstall +++ b/scripts/rpm/preuninstall @@ -2,3 +2,5 @@ if [ $1 = 0 ]; then /sbin/service falco stop > /dev/null 2>&1 /sbin/chkconfig --del falco fi + +dkms remove -m falco -v %{version} --all --rpm_safe_upgrade diff --git a/test/falco_test.py b/test/falco_test.py index a0a51bdc..9594f007 100644 --- a/test/falco_test.py +++ b/test/falco_test.py @@ -93,9 +93,9 @@ class FalcoTest(Test): # 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)) + if linux_modules.parse_lsmod_for_module(lsmod_output, 'falco_probe') == {}: + self.log.debug("Loading falco kernel module") + process.run('sudo insmod {}/driver/falco-probe.ko'.format(self.falcodir)) self.str_variant = self.trace_file diff --git a/userspace/falco/falco.cpp b/userspace/falco/falco.cpp index 455ab3a1..8fad34bb 100644 --- a/userspace/falco/falco.cpp +++ b/userspace/falco/falco.cpp @@ -53,6 +53,7 @@ static void signal_callback(int signal) static void usage() { printf( + "falco version " FALCO_VERSION "\n" "Usage: falco [options]\n\n" "Options:\n" " -h, --help Print this page\n" @@ -106,6 +107,7 @@ static void usage() " -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" + " --version Print version number.\n" "\n" ); } @@ -255,6 +257,7 @@ int falco_init(int argc, char **argv) {"option", required_argument, 0, 'o'}, {"print", required_argument, 0, 'p' }, {"pidfile", required_argument, 0, 'P' }, + {"version", no_argument, 0, 0 }, {"writefile", required_argument, 0, 'w' }, {0, 0, 0, 0} @@ -368,6 +371,13 @@ int falco_init(int argc, char **argv) } + if(string(long_options[long_index].name) == "version") + { + printf("falco version %s\n", FALCO_VERSION); + return EXIT_SUCCESS; + } + + inspector = new sinsp(); engine = new falco_engine(); engine->set_inspector(inspector); From 52b006e8755eecd8a78311c5a24ef4768fea8bb7 Mon Sep 17 00:00:00 2001 From: Mark Stemm Date: Wed, 22 Mar 2017 13:06:23 -0700 Subject: [PATCH 2/3] Add ability to run live for specific duration Use -M (same as sysdig) to run falco for a specific duration and exit. --- userspace/falco/falco.cpp | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/userspace/falco/falco.cpp b/userspace/falco/falco.cpp index 8fad34bb..690271ab 100644 --- a/userspace/falco/falco.cpp +++ b/userspace/falco/falco.cpp @@ -87,6 +87,7 @@ static void usage() " Marathon url is optional and defaults to Mesos address, port 8080.\n" " The API servers can also be specified via the environment variable\n" " FALCO_MESOS_API.\n" + " -M Stop collecting after reached.\n" " -o, --option = Set the value of option to . Overrides values in configuration file.\n" " can be a two-part .\n" " -p , --print=\n" @@ -135,12 +136,14 @@ std::list cmdline_options; uint64_t do_inspect(falco_engine *engine, falco_outputs *outputs, sinsp* inspector, + uint64_t duration_to_tot_ns, string &stats_filename) { uint64_t num_evts = 0; int32_t res; sinsp_evt* ev; StatsFileWriter writer; + uint64_t duration_start = 0; if (stats_filename != "") { @@ -184,6 +187,17 @@ uint64_t do_inspect(falco_engine *engine, throw sinsp_exception(inspector->getlasterr().c_str()); } + if (duration_start == 0) + { + duration_start = ev->get_ts(); + } else if(duration_to_tot_ns > 0) + { + if(ev->get_ts() - duration_start >= duration_to_tot_ns) + { + break; + } + } + if(!inspector->is_debug_enabled() && ev->get_category() & EC_INTERNAL) { @@ -234,6 +248,7 @@ int falco_init(int argc, char **argv) string* mesos_api = 0; string output_format = ""; bool replace_container_info = false; + int duration_to_tot = 0; // Used for writing trace files int duration_seconds = 0; @@ -275,7 +290,7 @@ int falco_init(int argc, char **argv) // Parse the args // while((op = getopt_long(argc, argv, - "hc:AdD:e:k:K:Ll:m:o:P:p:r:s:T:t:vw:", + "hc:AdD:e:k:K:Ll:m:M:o:P:p:r:s:T:t:vw:", long_options, &long_index)) != -1) { switch(op) @@ -316,6 +331,13 @@ int falco_init(int argc, char **argv) case 'm': mesos_api = new string(optarg); break; + case 'M': + duration_to_tot = atoi(optarg); + if(duration_to_tot <= 0) + { + throw sinsp_exception(string("invalid duration") + optarg); + } + break; case 'o': cmdline_options.push_back(optarg); break; @@ -662,6 +684,7 @@ int falco_init(int argc, char **argv) num_evts = do_inspect(engine, outputs, inspector, + uint64_t(duration_to_tot*ONE_SECOND_IN_NS), stats_filename); duration = ((double)clock()) / CLOCKS_PER_SEC - duration; From 73fbbdb5774d2432f5c0c9d32a15691ac98d3e18 Mon Sep 17 00:00:00 2001 From: Mark Stemm Date: Mon, 20 Mar 2017 16:54:45 -0700 Subject: [PATCH 3/3] Add automated tests for packages/driver installs Add automated tests for running falco from a package and container. As a result, this will also test building the kernel module as well as runnning falco-probe-loader as a backup. In travis.yml, switch to the docker-enabled vm and install dkms. This changed the environment slightly, so change how avocado's python dependencies are installed. After building falco, copy the .deb package to docker/local and build a local docker image based on that package. Add the following new tests: - docker_package: this uses "docker run" to run the image created in travis.yml. This includes using dkms to build the kernel module and load it. In addition, the conf directory is mounted to /host/conf, the rules directory is mounted to /host/rules, and the traces directory is mounted to /host/traces. - docker_package_local_driver: this disables dkms via a volume mount that maps /dev/null to /usr/sbin/dkms and copies the kernel module by hand into the container to /root/.sysdig/falco-probe-....ko. As a result, falco-probe-loader will use the local kernel module instead of building one itself. - debian_package: this installs the .deb package and runs the installed version of falco. Ideally, there'd also be a test for downloading the driver, but since the driver depends on the kernel as well as the falco version string, you can't put a single driver on download.draios.com that will work long-term. These tests depend on the following new test attributes: - package: if present, this points to the docker image/debian package to install. - addl_docker_run_args: if present, will be added to the docker run command. - copy_local_driver: if present, will copy the built kernel module to ~/.sysdig. ~/.sysdig/* is always cleared out before each test. - run_duration: maps to falco's -M flag - trace_file is now optional. Also add some misc general test changes: - Clean up our use of process.run. By default it will fail a test if the run program returns non-zero, so we don't have to grab the exit status. In addition, get rid of sudo in the command lines and use the sudo attribute instead. - Fix some tests that were writing to files below /tmp/falco_outputs by creating the directory first. Useful when running avocado directly. --- .travis.yml | 12 +++- test/falco_test.py | 139 +++++++++++++++++++++++++++++++++++---- test/falco_tests.yaml.in | 34 ++++++++++ 3 files changed, 170 insertions(+), 15 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7655a564..2b3709f9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,9 @@ language: c env: - BUILD_TYPE=Debug - BUILD_TYPE=Release +sudo: required +services: + - docker before_install: - sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test - sudo apt-get update @@ -9,12 +12,12 @@ 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 + - sudo apt-get install -y python-pip libvirt-dev jq dkms - 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 -H pip install -r requirements.txt - sudo python setup.py install - cd ../falco before_script: @@ -35,7 +38,10 @@ script: - cmake .. -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DDRAIOS_DEBUG_FLAGS="-D_DEBUG -DNDEBUG" - make VERBOSE=1 - make package - - cd .. + - cp falco*.deb ../docker/local + - cd ../docker/local + - docker build -t sysdig/falco:test . + - cd ../.. - sudo test/run_regression_tests.sh $TRAVIS_BRANCH notifications: webhooks: diff --git a/test/falco_test.py b/test/falco_test.py index 9594f007..7aaf3595 100644 --- a/test/falco_test.py +++ b/test/falco_test.py @@ -4,6 +4,9 @@ import os import re import json import sets +import glob +import shutil +import subprocess from avocado import Test from avocado.utils import process @@ -21,9 +24,9 @@ class FalcoTest(Test): self.stderr_contains = self.params.get('stderr_contains', '*', default='') self.exit_status = self.params.get('exit_status', '*', default=0) self.should_detect = self.params.get('detect', '*', default=False) - self.trace_file = self.params.get('trace_file', '*') + self.trace_file = self.params.get('trace_file', '*', default='') - if not os.path.isabs(self.trace_file): + if self.trace_file and not os.path.isabs(self.trace_file): self.trace_file = os.path.join(self.basedir, self.trace_file) self.json_output = self.params.get('json_output', '*', default=False) @@ -43,6 +46,8 @@ class FalcoTest(Test): if not os.path.isabs(self.conf_file): self.conf_file = os.path.join(self.basedir, self.conf_file) + self.run_duration = self.params.get('run_duration', '*', default='') + self.disabled_rules = self.params.get('disabled_rules', '*', default='') if self.disabled_rules == '': @@ -89,15 +94,23 @@ class FalcoTest(Test): 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) + self.package = self.params.get('package', '*', default='None') - if linux_modules.parse_lsmod_for_module(lsmod_output, 'falco_probe') == {}: - self.log.debug("Loading falco kernel module") - process.run('sudo insmod {}/driver/falco-probe.ko'.format(self.falcodir)) + if self.package == 'None': + # 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) - self.str_variant = self.trace_file + if linux_modules.parse_lsmod_for_module(lsmod_output, 'falco_probe') == {}: + self.log.debug("Loading falco kernel module") + process.run('insmod {}/driver/falco-probe.ko'.format(self.falcodir), sudo=True) + + self.addl_docker_run_args = self.params.get('addl_docker_run_args', '*', default='') + + self.copy_local_driver = self.params.get('copy_local_driver', '*', default=False) + + # Used by possibly_copy_local_driver as well as docker run + self.module_dir = os.path.expanduser("~/.sysdig") self.outputs = self.params.get('outputs', '*', default='') @@ -111,6 +124,10 @@ class FalcoTest(Test): output['file'] = item2[0] output['line'] = item2[1] outputs.append(output) + filedir = os.path.dirname(output['file']) + # Create the parent directory for the trace file if it doesn't exist. + if not os.path.isdir(filedir): + os.makedirs(filedir) self.outputs = outputs self.disable_tags = self.params.get('disable_tags', '*', default='') @@ -123,6 +140,10 @@ class FalcoTest(Test): if self.run_tags == '': self.run_tags=[] + def tearDown(self): + if self.package != 'None': + self.uninstall_package() + def check_rules_warnings(self, res): found_warning = sets.Set() @@ -231,12 +252,103 @@ class FalcoTest(Test): if not attr in obj: self.fail("Falco JSON object {} does not contain property \"{}\"".format(line, attr)) + def install_package(self): + + if self.package.startswith("docker:"): + + image = self.package.split(":", 1)[1] + # Remove an existing falco-test container first. Note we don't check the output--docker rm + # doesn't have an -i equivalent. + res = process.run("docker rm falco-test", ignore_status=True) + rules_dir = os.path.abspath(os.path.join(self.basedir, "./rules")) + conf_dir = os.path.abspath(os.path.join(self.basedir, "../")) + traces_dir = os.path.abspath(os.path.join(self.basedir, "./trace_files")) + self.falco_binary_path = "docker run -i -t --name falco-test --privileged " \ + "-v {}:/host/rules -v {}:/host/conf -v {}:/host/traces " \ + "-v /var/run/docker.sock:/host/var/run/docker.sock " \ + "-v /dev:/host/dev -v /proc:/host/proc:ro -v /boot:/host/boot:ro " \ + "-v /lib/modules:/host/lib/modules:ro -v {}:/root/.sysdig:ro -v " \ + "/usr:/host/usr:ro {} {} falco".format( + rules_dir, conf_dir, traces_dir, + self.module_dir, self.addl_docker_run_args, image) + + elif self.package.endswith(".deb"): + self.falco_binary_path = '/usr/bin/falco'; + + package_glob = "{}/{}".format(self.falcodir, self.package) + + matches = glob.glob(package_glob) + + if len(matches) != 1: + self.fail("Package path {} did not match exactly 1 file. Instead it matched: {}", package_glob, ",".join(matches)) + + package_path = matches[0] + + cmdline = "dpkg -i {}".format(package_path) + self.log.debug("Installing debian package via \"{}\"".format(cmdline)) + res = process.run(cmdline, timeout=120, sudo=True) + + def uninstall_package(self): + + if self.package.startswith("docker:"): + # Remove the falco-test image. Here we *do* check the return value + res = process.run("docker rm falco-test") + + elif self.package.endswith(".deb"): + cmdline = "dpkg -r falco" + self.log.debug("Uninstalling debian package via \"{}\"".format(cmdline)) + res = process.run(cmdline, timeout=120, sudo=True) + + def possibly_copy_driver(self): + # Remove the contents of ~/.sysdig regardless of + # copy_local_driver. + self.log.debug("Checking for module dir {}".format(self.module_dir)) + if os.path.isdir(self.module_dir): + self.log.info("Removing files below directory {}".format(self.module_dir)) + for rmfile in glob.glob(self.module_dir + "/*"): + self.log.debug("Removing file {}".format(rmfile)) + os.remove(rmfile) + + if self.copy_local_driver: + verstr = subprocess.check_output([self.falco_binary_path, "--version"]).rstrip() + self.log.info("verstr {}".format(verstr)) + falco_version = verstr.split(" ")[2] + self.log.info("falco_version {}".format(falco_version)) + arch = subprocess.check_output(["uname", "-m"]).rstrip() + self.log.info("arch {}".format(arch)) + kernel_release = subprocess.check_output(["uname", "-r"]).rstrip() + self.log.info("kernel release {}".format(kernel_release)) + + # sysdig-probe-loader has a more comprehensive set of ways to + # find the config hash. We only look at /boot/config- + md5_output = subprocess.check_output(["md5sum", "/boot/config-{}".format(kernel_release)]).rstrip() + config_hash = md5_output.split(" ")[0] + + probe_filename = "falco-probe-{}-{}-{}-{}.ko".format(falco_version, arch, kernel_release, config_hash) + driver_path = os.path.join(self.falcodir, "driver", "falco-probe.ko") + module_path = os.path.join(self.module_dir, probe_filename) + self.log.debug("Copying {} to {}".format(driver_path, module_path)) + shutil.copyfile(driver_path, module_path) + def test(self): self.log.info("Trace file %s", self.trace_file) - # Run the provided trace file though falco - 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) + self.falco_binary_path = '{}/userspace/falco/falco'.format(self.falcodir) + + self.possibly_copy_driver() + + if self.package != 'None': + # This sets falco_binary_path as a side-effect. + self.install_package() + + trace_arg = self.trace_file + + if self.trace_file: + trace_arg = "-e {}".format(self.trace_file) + + # Run falco + cmd = '{} {} {} -c {} {} -o json_output={} -v'.format( + self.falco_binary_path, self.rules_args, self.disabled_args, self.conf_file, trace_arg, self.json_output) for tag in self.disable_tags: cmd += ' -T {}'.format(tag) @@ -244,6 +356,9 @@ class FalcoTest(Test): for tag in self.run_tags: cmd += ' -t {}'.format(tag) + if self.run_duration: + cmd += ' -M {}'.format(self.run_duration) + 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 1453dccb..35e89628 100644 --- a/test/falco_tests.yaml.in +++ b/test/falco_tests.yaml.in @@ -1,4 +1,38 @@ trace_files: !mux + + docker_package: + package: docker:sysdig/falco:test + detect: True + detect_level: WARNING + rules_file: /host/rules/rule_names_with_spaces.yaml + trace_file: /host/traces/cat_write.scap + conf_file: /host/conf/falco.yaml + + # This uses a volume mount to overwrite and prevent /usr/sbin/dkms + # from being run. As a result, it will force falco-probe-loader to + # fall back to loading the driver from ~/.sysdig. Setting + # copy_local_driver to True copied the driver to ~/.sysdig, so it + # will be available. In this case, we're running live for 5 seconds + # just to see if falco can load the driver. + + docker_package_local_driver: + package: docker:sysdig/falco:test + addl_docker_run_args: -v /dev/null:/usr/sbin/dkms + copy_local_driver: True + detect: False + detect_level: WARNING + rules_file: /host/rules/tagged_rules.yaml + conf_file: /host/conf/falco.yaml + run_duration: 5 + + debian_package: + package: falco*.deb + detect: True + detect_level: WARNING + rules_file: + - rules/rule_names_with_spaces.yaml + trace_file: trace_files/cat_write.scap + builtin_rules_no_warnings: detect: False trace_file: trace_files/empty.scap