Merge pull request #38 from draios/rules-yaml

Change rules file format to YAML
This commit is contained in:
Henri DF 2016-05-05 20:39:34 -07:00
commit 3d02acf3af
21 changed files with 928 additions and 869 deletions

View File

@ -152,6 +152,27 @@ ExternalProject_Add(lpeg
CONFIGURE_COMMAND ""
INSTALL_COMMAND "")
set (LIBYAML_SRC "${PROJECT_BINARY_DIR}/libyaml-prefix/src/libyaml/src")
set(LIBYAML_LIB "${LIBYAML_SRC}/.libs/libyaml.a")
ExternalProject_Add(libyaml
URL "http://download.draios.com/dependencies/libyaml-0.1.4.tar.gz"
URL_MD5 "4a4bced818da0b9ae7fc8ebc690792a7"
BUILD_COMMAND ${CMD_MAKE}
BUILD_IN_SOURCE 1
CONFIGURE_COMMAND ./bootstrap && ./configure
INSTALL_COMMAND "")
set (LYAML_SRC "${PROJECT_BINARY_DIR}/lyaml-prefix/src/lyaml/ext/yaml")
set(LYAML_LIB "${LYAML_SRC}/.libs/yaml.a")
ExternalProject_Add(lyaml
URL "http://download.draios.com/dependencies/lyaml-release-v6.0.tar.gz"
URL_MD5 "dc3494689a0dce7cf44e7a99c72b1f30"
BUILD_COMMAND ${CMD_MAKE}
BUILD_IN_SOURCE 1
CONFIGURE_COMMAND ./configure --enable-static LIBS=-L../../../libyaml-prefix/src/libyaml/src/.libs CFLAGS=-I../../../libyaml-prefix/src/libyaml/include CPPFLAGS=-I../../../libyaml-prefix/src/libyaml/include LUA_INCLUDE=-I../../../luajit-prefix/src/luajit/src LUA=../../../luajit-prefix/src/luajit/src/luajit
INSTALL_COMMAND sh -c "cp -R ${PROJECT_BINARY_DIR}/lyaml-prefix/src/lyaml/lib/* ${PROJECT_SOURCE_DIR}/userspace/falco/lua")
install(FILES falco.yaml
DESTINATION "${DIR_ETC}")

View File

@ -1,3 +1,3 @@
install(FILES falco_rules.conf
install(FILES falco_rules.yaml
DESTINATION "${DIR_ETC}")

View File

@ -1,14 +0,0 @@
# A very simple config for introductory purpose. Not for the real-world!
# Network traffic to/from standard system utilities
# These utils never communicate on the network - if they do, that is a strong indication
# that something is wrong (rootkit?)
# Note that the full rule lists all ~150 binaries from coreutils; this example only has a few.
(fd.typechar = 4 or fd.typechar = 6) and proc.name in (ls, mkdir, cat, less, ps)
# System binary is modified or new file is written to standard binary dirs
evt.type = write and fd.directory in (/bin, /sbin, /usr/bin, /usr/sbin)
# Shell running in container
container.id != host and proc.name = bash

View File

@ -0,0 +1,16 @@
# A very simple config for introductory purpose. Not for the real-world!
# Network traffic to/from standard system utilities
# These utils never communicate on the network - if they do, that is a strong indication
# that something is wrong (rootkit?)
# Note that the full rule lists all ~150 binaries from coreutils; this example only has a few.
- condition: (fd.typechar = 4 or fd.typechar = 6) and proc.name in (ls, mkdir, cat, less, ps)
output: "%evt.time: %proc.name network with %fd.l4proto"
# System binary is modified or new file is written to standard binary dirs
- condition: evt.type = open and fd.directory in (/bin, /sbin, /usr/bin, /usr/sbin)
output: "%evt.time: System binary modified (file '%fd.filename' written by process %proc.name)"
# Shell running in container
- condition: container.id != host and proc.name = bash
output: "%evt.time: Shell running in container (%proc.name, %container.id)"

View File

@ -1,17 +0,0 @@
# A very simple config for introductory purpose. Not for the real-world!
# Binary directories
bin_dir: fd.directory in (/bin, /sbin, /usr/bin, /usr/sbin)
# Core binaries
core_binaries: proc.name in (ls, mkdir, cat, less, ps)
# Network traffic to/from standard utility
(fd.typechar = 4 or fd.typechar=6) and core_binaries
# System binary is modified
evt.type = write and bin_dir
# Shell running in container
container.id != host and proc.name = bash

View File

@ -0,0 +1,22 @@
# A very simple config for introductory purpose. Not for the real-world!
# Binary directories
- macro: bin_dir
condition: fd.directory in (/bin, /sbin, /usr/bin, /usr/sbin)
# Core binaries
- macro: core_binaries
condition: proc.name in (ls, mkdir, cat, less, ps)
# Network traffic to/from standard utility
- condition: (fd.typechar = 4 or fd.typechar=6) and core_binaries
output: "%evt.time: %proc.name network with %fd.l4proto"
# System binary is modified
- condition: evt.type = write and bin_dir
output: "%evt.time: System binary modified (file '%fd.filename' written by process %proc.name)"
# Shell running in container
- condition: container.id != host and proc.name = bash
output: "%evt.time: Shell running in container (%proc.name, %container.id)"

View File

@ -1,16 +0,0 @@
# A very simple config for introductory purpose. Not for the real-world!
# Binary directories
bin_dir: fd.directory in (/bin, /sbin, /usr/bin, /usr/sbin)
# Core binaries
core_binaries: proc.name in (ls, mkdir, cat, less, ps)
# Network traffic to/from standard utility
(fd.typechar = 4 or fd.typechar=6) and core_binaries | %evt.time: %proc.name network with %fd.l4proto
# System binary is modified
evt.type = write and bin_dir | %evt.time: System binary modified (file '%fd.filename' written by process %proc.name)
# Shell running in container
container.id != host and proc.name = bash | %evt.time: Shell running in container (%proc.name, %container.id)

View File

@ -1,247 +0,0 @@
#############
# Definitions
#############
# File actions
write: (syscall.type=write and fd.type in (file, directory))
read: (syscall.type=read and evt.dir=> and fd.type in (file, directory))
rename: syscall.type = rename
mkdir: syscall.type = mkdir
remove: syscall.type in (remove, rmdir, unlink, unlink_at)
modify: rename or mkdir or remove
# File categories
terminal_file_fd: fd.name=/dev/ptmx or fd.directory=/dev/pts
bin_dir: fd.directory in (/bin, /sbin, /usr/bin, /usr/sbin)
bin_dir_mkdir: 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
bin_dir_rename: 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
etc_dir: fd.directory contains /etc
ubuntu_so_dirs: 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
centos_so_dirs: fd.directory contains /lib64 or fd.directory contains /user/lib64 or fd.directory contains /usr/libexec
coreutils_binaries: proc.name in (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, 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)
adduser_binaries: proc.name in (adduser, deluser, addgroup, delgroup)
login_binaries: proc.name in (bin, login, su, sbin, nologin, bin, faillog, lastlog, newgrp, sg)
# dpkg -L passwd | grep bin | xargs -L 1 basename | tr "\\n" ","
passwd_binaries: proc.name in (sbin, shadowconfig, sbin, grpck, pwunconv, grpconv, pwck, groupmod, vipw, pwconv, useradd, newusers, cppw, chpasswd, usermod, groupadd, groupdel, grpunconv, chgpasswd, userdel, bin, chage, chsh, gpasswd, chfn, expiry, passwd, vigr, cpgr)
# repoquery -l shadow-utils | grep bin | xargs -L 1 basename | tr "\\n" ","
shadowutils_binaries: proc.name in (chage,gpasswd,lastlog,newgrp,sg,adduser,chpasswd,groupadd,groupdel,groupmems,groupmod,grpck,grpconv,grpunconv,newusers,pwck,pwconv,pwunconv,useradd,userdel,usermod,vigr,vipw)
system_binaries: coreutils_binaries or adduser_binaries or login_binaries or passwd_binaries or shadowutils_binaries
sensitive_files: fd.name contains /etc/passwd or fd.name = /etc/sudoers or fd.directory = /etc/sudoers.d or fd.directory = /etc/pam.d or fd.name = /etc/pam.conf
# Network
inbound: (syscall.type=listen and evt.dir=>) or (syscall.type=accept and evt.dir=<)
outbound: ((syscall.type=connect and evt.dir=<) or (syscall.type=sendto and evt.dir=>)) and (fd.typechar=4 or fd.typechar=6)
ssh_port: fd.lport=22
# Ssh
ssh_error_message: evt.arg.data contains "Invalid user" or evt.arg.data contains "preauth"
# System
modules: syscall.type in (delete_module, init_module)
container: container.id != host
interactive: (proc.aname=sshd and proc.name != sshd) or proc.name=systemd-logind
syslog: fd.name = /dev/log
not_cron: proc.name != cron
# System users that should never log into a system. Consider adding your own
# service users (e.g. 'apache' or 'mysqld') here.
system_users: user.name in (bin, daemon, games, lp, mail, nobody, sshd, sync, uucp, www-data)
#######
# Rules
#######
# Don't write to binary dirs
evt.dir = > and write and bin_dir | WARNING Write to bin dir (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)
# Don't write to /etc
evt.dir = > and write and etc_dir | WARNING Write to etc dir (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)
# Don't read 'sensitive' files
read and not proc.name in (sshd, sudo, su) and not_cron and sensitive_files | WARNING Read sensitive file (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)
# Don't modify binary dirs
modify and (bin_dir_rename or bin_dir_mkdir) | WARNING Modify bin dir (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)
# Don't load shared objects coming from unexpected places
read and fd.name contains .so and not (ubuntu_so_dirs or centos_so_dirs) | WARNING Loaded .so from unexpected dir (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)
# Attempts to access things that shouldn't be
evt.res = EACCES | INFO System call returned EACCESS (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)
# Only sysdig and docker can call setns
syscall.type = setns and not proc.name in (docker, sysdig) | WARNING Unexpected setns (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)
# Shells should only be run by cron or sshd
proc.name = bash and not proc.pname in (bash, sshd, cron, sudo, su, tmux) | WARNING Unexpected shell (%user.name %proc.name %proc.pname %evt.dir %evt.type %evt.args %fd.name)
# Anything run interactively by root
# evt.type != switch and user.name = root and proc.name != sshd and interactive | WARNING Interactive root (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)
# Anything run interactively by a non-login user
system_users and interactive | WARNING Sytem user ran an interactive command (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)
# Chmod should only be run interactively (by a user)
syscall.type = chmod and not interactive | WARNING non-interactive chmod (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)
# Shells in a container
container and proc.name = bash | WARNING shell in a container (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)
# Network traffic to/from standard utils
# sockfamily ip is to exclude certain processes (like 'groups') that communicate on unix-domain sockets
fd.sockfamily = ip and system_binaries | WARNING network traffic to %proc.name (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)
# SSH errors (failed logins, disconnects, ..)
syslog and ssh_error_message and evt.dir = < | WARNING sshd error (%proc.name %evt.arg.data)
# Non-sudo setuid
evt.type=setuid and not_cron and not proc.name in (sudo, sshd) | WARNING unexpected setuid call by non-sudo (%user.name %proc.name %evt.dir %evt.type %evt.args)
# User management (su and sudo are ok)
not proc.name in (su, sudo) and (adduser_binaries or login_binaries or passwd_binaries or shadowutils_binaries) | WARNING user-management binary command run (%user.name %proc.name %evt.dir %evt.type %evt.args)
# Some rootkits hide files in /dev
# (we may need to add additional checks against false positives, see: https://bugs.launchpad.net/ubuntu/+source/rkhunter/+bug/86153)
(evt.type = creat or evt.arg.flags contains O_CREAT) and proc.name != blkid and fd.directory = /dev and fd.name != /dev/null | WARNING file created in /dev (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)
# Elasticsearch ports
elasticsearch_cluster_port: fd.sport=9300
elasticsearch_api_port: fd.sport=9200
elasticsearch_port: elasticsearch_cluster_port or elasticsearch_api_port
user.name = elasticsearch and inbound and not elasticsearch_port | WARNING Unexpected Elasticsearch inbound port (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)
user.name = elasticsearch and outbound and not elasticsearch_cluster_port | WARNING Unexpected Elasticsearch outbound port (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)
# ActiveMQ ports
activemq_cluster_port: fd.sport=61616
activemq_web_port: fd.sport=8161
activemq_port: activemq_web_port or activemq_cluster_port
user.name = activemq and inbound and not activemq_port | WARNING Unexpected ActiveMQ inbound port (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)
user.name = activemq and outbound and not activemq_cluster_port | WARNING Unexpected ActiveMQ outbound port (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)
# Cassandra ports
# https://docs.datastax.com/en/cassandra/2.0/cassandra/security/secureFireWall_r.html
cassandra_thrift_client_port: fd.sport=9160
cassandra_cql_port: fd.sport=9042
cassandra_cluster_port: fd.sport=7000
cassandra_ssl_cluster_port: fd.sport=7001
cassandra_jmx_port: fd.sport=7199
cassandra_port: cassandra_thrift_client_port or cassandra_cql_port or cassandra_cluster_port or cassandra_ssl_cluster_port or cassandra_jmx_port
user.name = cassandra and inbound and not cassandra_port | WARNING Unexpected Cassandra inbound port (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)
user.name = cassandra and outbound and not (cassandra_ssl_cluster_port or cassandra_cluster_port) | WARNING Unexpected Cassandra outbound port (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)
# Couchbase ports
# http://docs.couchbase.com/admin/admin/Install/install-networkPorts.html
# Web Administration Port
couchbase_web_port: fd.sport=8091
# Couchbase API Port
couchbase_api_port: fd.sport=8092
# Internal/External Bucket Port for SSL
couchbase_ssl_bucket_port: fd.sport=11207
# Internal Bucket Port
couchbase_bucket_port: fd.sport=11209
# Internal/External Bucket Port
couchbase_bucket_port_ie: fd.sport=11210
# Client interface (proxy)
couchbase_client_interface_port: fd.sport=11211
# Incoming SSL Proxy
couchbase_incoming_ssl: fd.sport=11214
# Internal Outgoing SSL Proxy
couchbase_outgoing_ssl: fd.sport=11215
# Internal REST HTTPS for SSL
couchbase_internal_rest_port: fd.sport=18091
# Internal CAPI HTTPS for SSL
couchbase_internal_capi_port: fd.sport=18092
# Erlang Port Mapper ( epmd )
couchbase_epmd_port: fd.sport=4369
# Node data exchange
couchbase_dataexchange_port: fd.sport>=21100 and fd.sport<=21299
couchbase_internal_port: couchbase_bucket_port or couchbase_epmd_port or couchbase_dataexchange_port
couchbase_port: couchbase_web_port or couchbase_api_port or couchbase_ssl_bucket_port or couchbase_internal_port or couchbase_bucket_port_ie or couchbase_client_interface_port or couchbase_incoming_ssl or couchbase_outgoing_ssl or couchbase_internal_rest_port or couchbase_internal_capi_port
user.name = couchbase and inbound and not couchbase_port | WARNING Unexpected Couchbase inbound port (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)
user.name = couchbase and outbound and not couchbase_internal_port | WARNING Unexpected Couchbase inbound port (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)
# Couchdb ports
# https://github.com/davisp/couchdb/blob/master/etc/couchdb/local.ini
couchdb_httpd_port: fd.sport=5984
couchdb_httpd_ssl_port: fd.sport=6984
# xxx can't tell what clustering ports are used. not writing rules for this
# yet.
# Etcd ports
etcd_client_port: fd.sport=2379
etcd_peer_port: fd.sport=2380
# need to double-check which user etcd runs as
user.name = etcd and inbound and not (etcd_client_port or etcd_peer_port) | WARNING Unexpected Etcd inbound port (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)
user.name = etcd and outbound and not couchbase_internal_port | WARNING Unexpected Etcd outbound port (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)
# Fluentd ports
fluentd_http_port: fd.sport=9880
fluentd_forward_port: fd.sport=24224
user.name = td-agent and inbound and not (fluentd_forward_port or fluentd_http_port) | WARNING Unexpected Fluentd inbound port (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)
user.name = td-agent and outbound and not fluentd_forward_port | WARNING Unexpected Fluentd outbound port (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)
# Gearman ports
# http://gearman.org/protocol/
user.name = gearman and outbound and outbound and not fd.sport = 4730 | WARNING Unexpected Gearman outbound port (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)
# Zookeeper
zookeeper_port: fd.sport = 2181
# HBase ports
# http://blog.cloudera.com/blog/2013/07/guide-to-using-apache-hbase-ports/
hbase_master_port: fd.sport = 60000
hbase_master_info_port: fd.sport = 60010
hbase_regionserver_port: fd.sport = 60020
hbase_regionserver_info_port: fd.sport = 60030
hbase_rest_port: fd.sport = 8080
hbase_rest_info_port: fd.sport = 8085
hbase_regionserver_thrift_port: fd.sport = 9090
hbase_thrift_info_port: fd.sport = 9095
# If you're not running HBase under the 'hbase' user, adjust first expression
# in each rule below
user.name = hbase and inbound and not (hbase_master_port or hbase_master_info_port or hbase_regionserver_port or hbase_regionserver_info_port or hbase_rest_port or hbase_rest_info_port or hbase_regionserver_thrift_port or hbase_thrift_info_port) | WARNING Unexpected HBase inbound port (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)
user.name = hbase and outbound and not (zookeeper_port or hbase_master_port or hbase_regionserver_port) | WARNING Unexpected HBase outbound port (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)
# Kafka ports
user.name = kafka and inbound and fd.sport != 9092 | WARNING Unexpected Kafka inbound port (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)
# Memcached ports
user.name = memcached and inbound and fd.sport != 11211 | WARNING Unexpected Memcached inbound port (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)
user.name = memcached and outbound | WARNING Unexpected Memcached outbound connection (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)
# MongoDB ports
mongodb_server_port: fd.sport = 27017
mongodb_shardserver_port: fd.sport = 27018
mongodb_configserver_port: fd.sport = 27019
mongodb_webserver_port: fd.sport = 28017
user.name = mongodb and inbound and not (mongodb_server_port or mongodb_shardserver_port or mongodb_configserver_port or mongodb_webserver_port) | WARNING Unexpected MongoDB inbound port (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)
# MySQL ports
user.name = mysql and inbound and fd.sport != 3306 | WARNING Unexpected MySQL inbound port (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)
# HTTP server
http_server: proc.name in (nginx, httpd, lighttpd)
http_server and inbound and fd.sport != 80 and fd.sport != 443 | WARNING Unexpected HTTP server inbound port (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)

438
rules/falco_rules.yaml Normal file
View File

@ -0,0 +1,438 @@
#############
# Definitions
#############
# File actions
- macro: write
condition: (syscall.type=write and fd.type in (file, directory))
- macro: read
condition: (syscall.type=read and evt.dir=> and fd.type in (file, directory))
- macro: rename
condition: syscall.type = rename
- macro: mkdir
condition: syscall.type = mkdir
- macro: remove
condition: syscall.type in (remove, rmdir, unlink, unlink_at)
- macro: modify
condition: rename or mkdir or remove
# File categories
- macro: terminal_file_fd
condition: fd.name=/dev/ptmx or fd.directory=/dev/pts
- 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
- 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
- macro: etc_dir
condition: fd.directory contains /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
- macro: centos_so_dirs
condition: fd.directory contains /lib64 or fd.directory contains /user/lib64 or fd.directory contains /usr/libexec
- macro: coreutils_binaries
condition: >
proc.name in (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,
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)
- macro: adduser_binaries
condition: proc.name in (adduser, deluser, addgroup, delgroup)
- macro: login_binaries
condition: proc.name in (bin, login, su, sbin, nologin, bin, faillog, lastlog, newgrp, sg)
# dpkg -L passwd | grep bin | xargs -L 1 basename | tr "\\n" ","
- macro: passwd_binaries
condition: >
proc.name in (sbin, shadowconfig, sbin, grpck, pwunconv, grpconv, pwck,
groupmod, vipw, pwconv, useradd, newusers, cppw, chpasswd, usermod,
groupadd, groupdel, grpunconv, chgpasswd, userdel, bin, chage, chsh,
gpasswd, chfn, expiry, passwd, vigr, cpgr)
# repoquery -l shadow-utils | grep bin | xargs -L 1 basename | tr "\\n" ","
- macro: shadowutils_binaries
condition: >
proc.name in (chage, gpasswd, lastlog, newgrp, sg, adduser, chpasswd,
groupadd, groupdel, groupmems, groupmod, grpck, grpconv, grpunconv,
newusers, pwck, pwconv, pwunconv, useradd, userdel, usermod, vigr, vipw)
- macro: system_binaries
condition: coreutils_binaries or adduser_binaries or login_binaries or passwd_binaries or shadowutils_binaries
- macro: sensitive_files
condition: fd.name contains /etc/passwd or fd.name = /etc/sudoers or fd.directory = /etc/sudoers.d or fd.directory = /etc/pam.d or fd.name = /etc/pam.conf
# Network
- macro: inbound
condition: (syscall.type=listen and evt.dir=>) or (syscall.type=accept and evt.dir=<)
- macro: outbound
condition: ((syscall.type=connect and evt.dir=<) or (syscall.type=sendto and evt.dir=>)) and (fd.typechar=4 or fd.typechar=6)
- macro: ssh_port
condition: fd.lport=22
# Ssh
- macro: ssh_error_message
condition: evt.arg.data contains "Invalid user" or evt.arg.data contains "preauth"
# System
- macro: modules
condition: syscall.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 = /dev/log
- macro: not_cron
condition: proc.name != cron
# System users that should never log into a system. Consider adding your own
# service users (e.g. 'apache' or 'mysqld') here.
- macro: system_users
condition: user.name in (bin, daemon, games, lp, mail, nobody, sshd, sync, uucp, www-data)
#######
# Rules
#######
# Don't write to binary dirs
- condition: evt.dir = > and write and bin_dir
output: "Write to bin dir (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)"
priority: WARNING
# Don't write to /etc
- condition: evt.dir = > and write and etc_dir
output: "Write to etc dir (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)"
priority: WARNING
# Don't read 'sensitive' files
- condition: read and not proc.name in (sshd, sudo, su) and not_cron and sensitive_files
output: "Read sensitive file (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)"
priority: WARNING
# Don't modify binary dirs
- condition: modify and (bin_dir_rename or bin_dir_mkdir)
output: "Modify bin dir (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)"
priority: WARNING
# Don't load shared objects coming from unexpected places
- condition: read and fd.name contains .so and not (ubuntu_so_dirs or centos_so_dirs)
output: "Loaded .so from unexpected dir (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)"
priority: WARNING
# Attempts to access things that shouldn't be
- condition: evt.res = EACCES
output: "System call returned EACCESS (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)"
priority: INFO
# Only sysdig and docker can call setns
- condition: syscall.type = setns and not proc.name in (docker, sysdig)
output: "Unexpected setns (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)"
priority: WARNING
# Shells should only be run by cron or sshd
- condition: proc.name = bash and not proc.pname in (bash, sshd, cron, sudo, su, tmux)
output: "Unexpected shell (%user.name %proc.name %proc.pname %evt.dir %evt.type %evt.args %fd.name)"
priority: WARNING
# Anything run interactively by root
# - condition: evt.type != switch and user.name = root and proc.name != sshd and interactive
# output: "Interactive root (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)"
# priority: WARNING
# Anything run interactively by a non-login user
- condition: system_users and interactive
output: "Sytem user ran an interactive command (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)"
priority: WARNING
# Chmod should only be run interactively (by a user)
- condition: syscall.type = chmod and not interactive
output: "non-interactive chmod (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)"
priority: WARNING
# Shells in a container
- condition: container and proc.name = bash
output: "shell in a container (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)"
priority: WARNING
# Network traffic to/from standard utils
# sockfamily ip is to exclude certain processes (like 'groups') that communicate on unix-domain sockets
- condition: fd.sockfamily = ip and system_binaries
output: "network traffic to %proc.name (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)"
priority: WARNING
# SSH errors (failed logins, disconnects, ..)
- condition: syslog and ssh_error_message and evt.dir = <
output: "sshd error (%proc.name %evt.arg.data)"
priority: WARNING
# Non-sudo setuid
- condition: evt.type=setuid and not_cron and not proc.name in (sudo, sshd)
output: "unexpected setuid call by non-sudo (%user.name %proc.name %evt.dir %evt.type %evt.args)"
priority: WARNING
# User management (su and sudo are ok)
- condition: not proc.name in (su, sudo) and (adduser_binaries or login_binaries or passwd_binaries or shadowutils_binaries)
output: "user-management binary command run (%user.name %proc.name %evt.dir %evt.type %evt.args)"
priority: WARNING
# Some rootkits hide files in /dev
# (we may need to add additional checks against false positives, see: https://bugs.launchpad.net/ubuntu/+source/rkhunter/+bug/86153)
- condition: (evt.type = creat or evt.arg.flags contains O_CREAT) and proc.name != blkid and fd.directory = /dev and fd.name != /dev/null
output: "file created in /dev (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)"
priority: WARNING
# Elasticsearch ports
- macro: elasticsearch_cluster_port
condition: fd.sport=9300
- macro: elasticsearch_api_port
condition: fd.sport=9200
- macro: elasticsearch_port
condition: elasticsearch_cluster_port or elasticsearch_api_port
- condition: user.name = elasticsearch and inbound and not elasticsearch_port
output: "Unexpected Elasticsearch inbound port (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)"
priority: WARNING
- condition: user.name = elasticsearch and outbound and not elasticsearch_cluster_port
output: "Unexpected Elasticsearch outbound port (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)"
priority: WARNING
# ActiveMQ ports
- macro: activemq_cluster_port
condition: fd.sport=61616
- macro: activemq_web_port
condition: fd.sport=8161
- macro: activemq_port
condition: activemq_web_port or activemq_cluster_port
- condition: user.name = activemq and inbound and not activemq_port
output: "Unexpected ActiveMQ inbound port (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)"
priority: WARNING
- condition: user.name = activemq and outbound and not activemq_cluster_port
output: "Unexpected ActiveMQ outbound port (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)"
priority: WARNING
# Cassandra ports
# https://docs.datastax.com/en/cassandra/2.0/cassandra/security/secureFireWall_r.html
- macro: cassandra_thrift_client_port
condition: fd.sport=9160
- macro: cassandra_cql_port
condition: fd.sport=9042
- macro: cassandra_cluster_port
condition: fd.sport=7000
- macro: cassandra_ssl_cluster_port
condition: fd.sport=7001
- macro: cassandra_jmx_port
condition: fd.sport=7199
- macro: cassandra_port
condition: cassandra_thrift_client_port or cassandra_cql_port or cassandra_cluster_port or cassandra_ssl_cluster_port or cassandra_jmx_port
- condition: user.name = cassandra and inbound and not cassandra_port
output: "Unexpected Cassandra inbound port (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)"
priority: WARNING
- condition: user.name = cassandra and outbound and not (cassandra_ssl_cluster_port or cassandra_cluster_port)
output: "Unexpected Cassandra outbound port (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)"
priority: WARNING
# Couchbase ports
# http://docs.couchbase.com/admin/admin/Install/install-networkPorts.html
# Web Administration Port
- macro: couchbase_web_port
condition: fd.sport=8091
# Couchbase API Port
- macro: couchbase_api_port
condition: fd.sport=8092
# Internal/External Bucket Port for SSL
- macro: couchbase_ssl_bucket_port
condition: fd.sport=11207
# Internal Bucket Port
- macro: couchbase_bucket_port
condition: fd.sport=11209
# Internal/External Bucket Port
- macro: couchbase_bucket_port_ie
condition: fd.sport=11210
# Client interface (proxy)
- macro: couchbase_client_interface_port
condition: fd.sport=11211
# Incoming SSL Proxy
- macro: couchbase_incoming_ssl
condition: fd.sport=11214
# Internal Outgoing SSL Proxy
- macro: couchbase_outgoing_ssl
condition: fd.sport=11215
# Internal REST HTTPS for SSL
- macro: couchbase_internal_rest_port
condition: fd.sport=18091
# Internal CAPI HTTPS for SSL
- macro: couchbase_internal_capi_port
condition: fd.sport=18092
# Erlang Port Mapper ( epmd )
- macro: couchbase_epmd_port
condition: fd.sport=4369
# Node data exchange
- macro: couchbase_dataexchange_port
condition: fd.sport>=21100 and fd.sport<=21299
- macro: couchbase_internal_port
condition: couchbase_bucket_port or couchbase_epmd_port or couchbase_dataexchange_port
- macro: couchbase_port
condition: >
couchbase_web_port or couchbase_api_port or couchbase_ssl_bucket_port or
couchbase_internal_port or couchbase_bucket_port_ie or
couchbase_client_interface_port or couchbase_incoming_ssl or
couchbase_outgoing_ssl or couchbase_internal_rest_port or
couchbase_internal_capi_port
- condition: user.name = couchbase and inbound and not couchbase_port
output: "Unexpected Couchbase inbound port (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)"
priority: WARNING
- condition: user.name = couchbase and outbound and not couchbase_internal_port
output: "Unexpected Couchbase inbound port (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)"
priority: WARNING
# Couchdb ports
# https://github.com/davisp/couchdb/blob/master/etc/couchdb/local.ini
- macro: couchdb_httpd_port
condition: fd.sport=5984
- macro: couchdb_httpd_ssl_port
condition: fd.sport=6984
# xxx can't tell what clustering ports are used. not writing rules for this
# yet.
# Etcd ports
- macro: etcd_client_port
condition: fd.sport=2379
- macro: etcd_peer_port
condition: fd.sport=2380
# need to double-check which user etcd runs as
- condition: user.name = etcd and inbound and not (etcd_client_port or etcd_peer_port)
output: "Unexpected Etcd inbound port (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)"
priority: WARNING
- condition: user.name = etcd and outbound and not couchbase_internal_port
output: "Unexpected Etcd outbound port (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)"
priority: WARNING
# Fluentd ports
- macro: fluentd_http_port
condition: fd.sport=9880
- macro: fluentd_forward_port
condition: fd.sport=24224
- condition: user.name = td-agent and inbound and not (fluentd_forward_port or fluentd_http_port)
output: "Unexpected Fluentd inbound port (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)"
priority: WARNING
- condition: user.name = td-agent and outbound and not fluentd_forward_port
output: "Unexpected Fluentd outbound port (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)"
priority: WARNING
# Gearman ports
# http://gearman.org/protocol/
- condition: user.name = gearman and outbound and outbound and not fd.sport = 4730
output: "Unexpected Gearman outbound port (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)"
priority: WARNING
# Zookeeper
- macro: zookeeper_port
condition: fd.sport = 2181
# HBase ports
# http://blog.cloudera.com/blog/2013/07/guide-to-using-apache-hbase-ports/
- macro: hbase_master_port
condition: fd.sport = 60000
- macro: hbase_master_info_port
condition: fd.sport = 60010
- macro: hbase_regionserver_port
condition: fd.sport = 60020
- macro: hbase_regionserver_info_port
condition: fd.sport = 60030
- macro: hbase_rest_port
condition: fd.sport = 8080
- macro: hbase_rest_info_port
condition: fd.sport = 8085
- macro: hbase_regionserver_thrift_port
condition: fd.sport = 9090
- macro: hbase_thrift_info_port
condition: fd.sport = 9095
# If you're not running HBase under the 'hbase' user, adjust first expression
# in each rule below
- condition: >
user.name = hbase and inbound and not (hbase_master_port or
hbase_master_info_port or hbase_regionserver_port or
hbase_regionserver_info_port or hbase_rest_port or hbase_rest_info_port or
hbase_regionserver_thrift_port or hbase_thrift_info_port)
output: "Unexpected HBase inbound port (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)"
priority: WARNING
- condition: user.name = hbase and outbound and not (zookeeper_port or hbase_master_port or hbase_regionserver_port)
output: "Unexpected HBase outbound port (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)"
priority: WARNING
# Kafka ports
- condition: user.name = kafka and inbound and fd.sport != 9092
output: "Unexpected Kafka inbound port (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)"
priority: WARNING
# Memcached ports
- condition: user.name = memcached and inbound and fd.sport != 11211
output: "Unexpected Memcached inbound port (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)"
priority: WARNING
- condition: user.name = memcached and outbound
output: "Unexpected Memcached outbound connection (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)"
priority: WARNING
# MongoDB ports
- macro: mongodb_server_port
condition: fd.sport = 27017
- macro: mongodb_shardserver_port
condition: fd.sport = 27018
- macro: mongodb_configserver_port
condition: fd.sport = 27019
- macro: mongodb_webserver_port
condition: fd.sport = 28017
- condition: user.name = mongodb and inbound and not (mongodb_server_port or mongodb_shardserver_port or mongodb_configserver_port or mongodb_webserver_port)
output: "Unexpected MongoDB inbound port (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)"
priority: WARNING
# MySQL ports
- condition: user.name = mysql and inbound and fd.sport != 3306
output: "Unexpected MySQL inbound port (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)"
priority: WARNING
# HTTP server
- macro: http_server
condition: proc.name in (nginx, httpd, lighttpd)
- condition: http_server and inbound and fd.sport != 80 and fd.sport != 443
output: "Unexpected HTTP server inbound port (%user.name %proc.name %evt.dir %evt.type %evt.args %fd.name)"
priority: WARNING

View File

@ -6,7 +6,6 @@ include_directories(${PROJECT_SOURCE_DIR}/../sysdig/userspace/libsinsp)
include_directories("${PROJECT_BINARY_DIR}/userspace/falco")
include_directories("${CURL_INCLUDE_DIR}")
include_directories("${YAMLCPP_INCLUDE_DIR}")
include_directories("${LPEG_SRC}")
include_directories(${DRAIOS_DEPENDENCIES_DIR}/yaml-${DRAIOS_YAML_VERSION}/target/include)
add_executable(falco configuration.cpp formats.cpp fields.cpp rules.cpp logger.cpp falco.cpp)
@ -14,6 +13,8 @@ add_executable(falco configuration.cpp formats.cpp fields.cpp rules.cpp logger.c
target_link_libraries(falco sinsp)
target_link_libraries(falco
"${LPEG_SRC}/lpeg.a"
"${LYAML_LIB}"
"${LIBYAML_LIB}"
"${YAMLCPP_LIB}")
@ -21,9 +22,6 @@ set(FALCO_LUA_MAIN "rule_loader.lua")
configure_file(config_falco.h.in config_falco.h)
install(TARGETS falco DESTINATION bin)
install(FILES lua/compiler.lua
DESTINATION share/falco/lua)
install(FILES lua/rule_loader.lua
DESTINATION share/falco/lua)
install(FILES lua/output.lua
DESTINATION share/falco/lua)
install(DIRECTORY lua
DESTINATION share/falco/lua
FILES_MATCHING PATTERN *.lua)

View File

@ -15,6 +15,7 @@ extern "C" {
#include "lualib.h"
#include "lauxlib.h"
#include "lpeg.h"
#include "lyaml.h"
}
#include <sinsp.h>
@ -317,6 +318,7 @@ int falco_init(int argc, char **argv)
ls = lua_open();
luaL_openlibs(ls);
luaopen_lpeg(ls);
luaopen_yaml(ls);
add_lua_path(ls, lua_dir);
rules = new falco_rules(inspector, ls, lua_main_filename);

1
userspace/falco/lua/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
lyaml*

View File

@ -1,278 +1,5 @@
--[[
Falco grammar and parser.
Much of the scaffolding and helpers was derived from Andre Murbach Maidl's Lua parser (https://github.com/andremm/lua-parser).
Parses regular filters following the existing sysdig filter syntax (*), as well as "macro" definitions. Macro definitions are written like:
inbound: (syscall.type=listen and evt.dir='>') or (syscall.type=accept and evt.dir='<')
(*) There is currently one known difference with the syntax implemented in libsinsp: In libsinsp, field names cannot start with 'a', 'o', or 'n'. With this parser they can.
--]]
local parser = require("parser")
local compiler = {}
compiler.parser = {}
local lpeg = require "lpeg"
lpeg.locale(lpeg)
local P, S, V = lpeg.P, lpeg.S, lpeg.V
local C, Carg, Cb, Cc = lpeg.C, lpeg.Carg, lpeg.Cb, lpeg.Cc
local Cf, Cg, Cmt, Cp, Ct = lpeg.Cf, lpeg.Cg, lpeg.Cmt, lpeg.Cp, lpeg.Ct
local alpha, digit, alnum = lpeg.alpha, lpeg.digit, lpeg.alnum
local xdigit = lpeg.xdigit
local space = lpeg.space
-- error message auxiliary functions
-- creates an error message for the input string
local function syntaxerror (errorinfo, pos, msg)
local error_msg = "%s: syntax error, %s"
return string.format(error_msg, pos, msg)
end
-- gets the farthest failure position
local function getffp (s, i, t)
return t.ffp or i, t
end
-- gets the table that contains the error information
local function geterrorinfo ()
return Cmt(Carg(1), getffp) * (C(V"OneWord") + Cc("EOF")) /
function (t, u)
t.unexpected = u
return t
end
end
-- creates an errror message using the farthest failure position
local function errormsg ()
return geterrorinfo() /
function (t)
local p = t.ffp or 1
local msg = "unexpected '%s', expecting %s"
msg = string.format(msg, t.unexpected, t.expected)
return nil, syntaxerror(t, p, msg)
end
end
-- reports a syntactic error
local function report_error ()
return errormsg()
end
--- sets the farthest failure position and the expected tokens
local function setffp (s, i, t, n)
if not t.ffp or i > t.ffp then
t.ffp = i
t.list = {} ; t.list[n] = n
t.expected = "'" .. n .. "'"
elseif i == t.ffp then
if not t.list[n] then
t.list[n] = n
t.expected = "'" .. n .. "', " .. t.expected
end
end
return false
end
local function updateffp (name)
return Cmt(Carg(1) * Cc(name), setffp)
end
-- regular combinators and auxiliary functions
local function token (pat, name)
return pat * V"Skip" + updateffp(name) * P(false)
end
local function symb (str)
return token (P(str), str)
end
local function kw (str)
return token (P(str) * -V"idRest", str)
end
local function list (pat, sep)
return Ct(pat^-1 * (sep * pat^0)^0) / function(elements) return {type = "List", elements=elements} end
end
--http://lua-users.org/wiki/StringTrim
function trim(s)
if (type(s) ~= "string") then return s end
return (s:gsub("^%s*(.-)%s*$", "%1"))
end
local function terminal (tag)
-- Rather than trim the whitespace in this way, it would be nicer to exclude it from the capture...
return token(V(tag), tag) / function (tok) return { type = tag, value = trim(tok)} end
end
local function unaryboolop (op, e)
return { type = "UnaryBoolOp", operator = op, argument = e }
end
local function unaryrelop (e, op)
return { type = "UnaryRelOp", operator = op, argument = e }
end
local function binaryop (e1, op, e2)
if not op then
return e1
else
return { type = "BinaryBoolOp", operator = op, left = e1, right = e2 }
end
end
local function bool (pat, sep)
return Cf(pat * Cg(sep * pat)^0, binaryop)
end
local function rel (left, sep, right)
return left * sep * right / function(e1, op, e2) return { type = "BinaryRelOp", operator = op, left = e1, right = e2 } end
end
local function fix_str (str)
str = string.gsub(str, "\\a", "\a")
str = string.gsub(str, "\\b", "\b")
str = string.gsub(str, "\\f", "\f")
str = string.gsub(str, "\\n", "\n")
str = string.gsub(str, "\\r", "\r")
str = string.gsub(str, "\\t", "\t")
str = string.gsub(str, "\\v", "\v")
str = string.gsub(str, "\\\n", "\n")
str = string.gsub(str, "\\\r", "\n")
str = string.gsub(str, "\\'", "'")
str = string.gsub(str, '\\"', '"')
str = string.gsub(str, '\\\\', '\\')
return str
end
-- grammar
local function normalize_level(level)
valid_levels = {"emergency", "alert", "critical", "error", "warning", "notice", "informational", "debug"}
level = string.lower(level)
for i,v in ipairs(valid_levels) do
if (string.find(v, "^"..level)) then
return i - 1 -- (syslog levels start at 0, lua indices start at 1)
end
end
error("Invalid severity level: "..level)
end
local function filter(e)
return {type = "Filter", value=e}
end
local function macro (name, filter)
return {type = "MacroDef", name = name, value = filter}
end
local function outputformat (level, format)
return {type = "OutputFormat", level = normalize_level(level), value = format}
end
local function rule(filter, output)
if not output then
output = outputformat(nil)
end
return {type = "Rule", filter = filter, output = output}
end
local G = {
V"Start", -- Entry rule
Start = V"Skip" * (V"Comment" + V"MacroDef" / macro + V"Rule" / rule)^-1 * -1 + report_error();
-- Grammar
Comment = P"#" * P(1)^0;
Rule = V"Filter" / filter * ((V"Skip" * V"Pipe" * V"Skip" * V"Output")^-1 );
Filter = V"OrExpression";
OrExpression =
bool(V"AndExpression", V"OrOp");
AndExpression =
bool(V"NotExpression", V"AndOp");
NotExpression =
V"UnaryBoolOp" * V"NotExpression" / unaryboolop +
V"ExistsExpression";
ExistsExpression =
terminal "FieldName" * V"ExistsOp" / unaryrelop +
V"MacroExpression";
MacroExpression =
terminal "Macro" +
V"RelationalExpression";
RelationalExpression =
rel(terminal "FieldName", V"RelOp", V"Value") +
rel(terminal "FieldName", V"InOp", V"InList") +
V"PrimaryExp";
PrimaryExp = symb("(") * V"Filter" * symb(")");
MacroDef = (C(V"Macro") * V"Skip" * V"Colon" * (V"Filter"));
FuncArgs = symb("(") * list(V"Value", symb(",")) * symb(")");
Output = C(V"Identifier") * V"Skip" * C(P(1)^0) / outputformat;
-- Terminals
Value = terminal "Number" + terminal "String" + terminal "BareString";
InList = symb("(") * list(V"Value", symb(",")) * symb(")");
-- Lexemes
Space = space^1;
Skip = (V"Space")^0;
idStart = alpha + P("_");
idRest = alnum + P("_");
Identifier = V"idStart" * V"idRest"^0;
Macro = V"idStart" * V"idRest"^0 * -P".";
FieldName = V"Identifier" * (P"." + V"Identifier")^1 * (P"[" * V"Int" * P"]")^-1;
Name = C(V"Identifier") * -V"idRest";
Hex = (P("0x") + P("0X")) * xdigit^1;
Expo = S("eE") * S("+-")^-1 * digit^1;
Float = (((digit^1 * P(".") * digit^0) +
(P(".") * digit^1)) * V"Expo"^-1) +
(digit^1 * V"Expo");
Int = digit^1;
Number = C(V"Hex" + V"Float" + V"Int") /
function (n) return tonumber(n) end;
String = (P'"' * C(((P'\\' * P(1)) + (P(1) - P'"'))^0) * P'"' + P"'" * C(((P"\\" * P(1)) + (P(1) - P"'"))^0) * P"'") / function (s) return fix_str(s) end;
BareString = C(((P(1) - S' (),='))^1);
OrOp = kw("or") / "or";
AndOp = kw("and") / "and";
Colon = kw(":");
Pipe = kw("|");
RelOp = symb("=") / "=" +
symb("==") / "==" +
symb("!=") / "!=" +
symb("<=") / "<=" +
symb(">=") / ">=" +
symb("<") / "<" +
symb(">") / ">" +
symb("contains") / "contains" +
symb("icontains") / "icontains";
InOp = kw("in") / "in";
UnaryBoolOp = kw("not") / "not";
ExistsOp = kw("exists") / "exists";
-- for error reporting
OneWord = V"Name" + V"Number" + V"String" + P(1);
}
function map(f, arr)
local res = {}
@ -289,59 +16,6 @@ function foldr(f, acc, arr)
return acc
end
--[[
Traverses the AST and replaces `in` relational expressions with a sequence of ORs.
For example, `a.b in [1, 2]` is expanded to `a.b = 1 or a.b = 2` (in ASTs)
--]]
function expand_in(node)
local t = node.type
if t == "Filter" then
expand_in(node.value)
elseif t == "UnaryBoolOp" then
expand_in(node.argument)
elseif t == "BinaryBoolOp" then
expand_in(node.left)
expand_in(node.right)
elseif t == "BinaryRelOp" and node.operator == "in" then
if (table.maxn(node.right.elements) == 0) then
error ("In list with zero elements")
end
local mapper = function(element)
return {
type = "BinaryRelOp",
operator = "=",
left = node.left,
right = element
}
end
local equalities = map(mapper, node.right.elements)
local lasteq = equalities[table.maxn(equalities)]
equalities[table.maxn(equalities)] = nil
local folder = function(left, right)
return {
type = "BinaryBoolOp",
operator = "or",
left = left,
right = right
}
end
lasteq = foldr(folder, lasteq, equalities)
node.type=lasteq.type
node.operator=lasteq.operator
node.left=lasteq.left
node.right=lasteq.right
end
end
--[[
Given a map of macro definitions, traverse AST and replace macro references
@ -437,114 +111,30 @@ function get_macros(ast, set)
return set
end
function check_macros(ast)
local macros
if (ast.type == "Rule") then
macros = get_macros(ast.filter, {})
elseif (ast.type == "MacroDef") then
macros = get_macros(ast.value, {})
else
error ("Unexpected type: "..t)
end
for m, _ in pairs(macros) do
if macros[m] == nil then
error ("Undefined macro '"..m.."' used in '"..line.."'")
end
end
end
function print_ast(ast, level)
local t = ast.type
level = level or 0
local prefix = string.rep(" ", level*4)
level = level + 1
if t == "Rule" then
print_ast(ast.filter, level)
if (ast.output) then
print(prefix.."| ")
print_ast(ast.output)
end
elseif t == "OutputFormat" then
print(ast.value)
elseif t == "Filter" then
print_ast(ast.value, level)
elseif t == "BinaryBoolOp" or t == "BinaryRelOp" then
print(prefix..ast.operator)
print_ast(ast.left, level)
print_ast(ast.right, level)
elseif t == "UnaryRelOp" or t == "UnaryBoolOp" then
print (prefix..ast.operator)
print_ast(ast.argument, level)
elseif t == "List" then
for i, v in ipairs(ast.elements) do
print_ast(v, level)
end
elseif t == "FieldName" or t == "Number" or t == "String" or t == "BareString" or t == "Macro" then
print (prefix..t.." "..ast.value)
elseif t == "MacroDef" then
-- don't print for now
else
error ("Unexpected type in print_ast: "..t)
end
end
compiler.parser.print_ast = print_ast
--[[
Parses a single line (which should be either a macro definition or a filter) and returns the AST.
--]]
function compiler.parser.parse_line (subject)
local errorinfo = { subject = subject }
lpeg.setmaxstack(1000)
local ast, error_msg = lpeg.match(G, subject, nil, errorinfo)
return ast, error_msg
end
--[[
Compiles a single line from a falco ruleset and updates the passed-in macros table. Returns the AST of the line.
--]]
function compiler.compile_line(line, macro_defs)
local ast, error_msg = compiler.parser.parse_line(line)
function compiler.compile_macro(line)
local ast, error_msg = parser.parse_filter(line)
if (error_msg) then
print ("Compilation error: ", error_msg)
error(error_msg)
end
if (type(ast) == "number") then
-- hack: we get a number (# of matched chars) V"Skip" back if this line
-- only contained whitespace. (According to docs 'v"Skip" / 0' in Start
-- rule should not capture anything but it doesn't seem to work that
-- way...)
return {}
return ast
end
--[[
Parses a single filter, then expands macros using passed-in table of definitions. Returns resulting AST.
--]]
function compiler.compile_filter(source, macro_defs)
local ast, error_msg = parser.parse_filter(source)
if (error_msg) then
print ("Compilation error: ", error_msg)
error(error_msg)
end
-- check that any macros used have already been defined
check_macros(ast)
if (ast.type == "MacroDef") then
--expand_in(ast.value)
-- Parsed line is a macro definition, so update our dictionary of macros and
-- return
macro_defs[ast.name] = ast.value
return ast
elseif (ast.type == "Rule") then
-- Line is a filter, so expand in-clauses and macro references, then
-- stitch it into global ast
--expand_in(ast.filter)
if (ast.type == "Rule") then
-- Line is a filter, so expand macro references
repeat
expanded = expand_macros(ast, macro_defs, false)
until expanded == false

View File

@ -2,6 +2,8 @@ local mod = {}
levels = {"Emergency", "Alert", "Critical", "Error", "Warning", "Notice", "Informational", "Debug"}
local outputs = {}
function mod.stdout(evt, level, format)
format = "%evt.time: "..levels[level+1].." "..format
formatter = falco.formatter(format)
@ -39,4 +41,24 @@ function mod.syslog(evt, level, format)
falco.syslog(level, msg)
end
function mod.event(event, level, format)
for index,o in ipairs(outputs) do
o.output(event, level, format, o.config)
end
end
function add_output(output_name, config)
if not (type(mod[output_name]) == 'function') then
error("rule_loader.add_output(): invalid output_name: "..output_name)
end
-- outputs can optionally define a validation function so that we don't
-- find out at runtime (when an event finally matches a rule!) that the config is invalid
if (type(mod[output_name.."_validate"]) == 'function') then
mod[output_name.."_validate"](config)
end
table.insert(outputs, {output = mod[output_name], config=config})
end
return mod

View File

@ -15,16 +15,15 @@ function error_exit_bad
function good
{
lua5.1 test.lua "a: x.y=1; b: a and z.x exists; c: b; $1" 2> /dev/null || error_exit_good "$1"
lua5.1 test.lua "$1" 2> /dev/null || error_exit_good "$1"
}
function bad
{
lua5.1 test.lua "a: x.y=1; b: a and z.x exists; c: b; $1" 2> /dev/null && error_exit_bad "$1"
lua5.1 test.lua "$1" 2> /dev/null && error_exit_bad "$1"
}
# Filters
good " "
good " a"
good "a and b"
good "#a and b; a and b"
@ -45,7 +44,7 @@ good "not not a"
good "(not not a)"
good "not a.b=1"
good "not a.a exists"
good "notz: a and b; notz"
good "notz and a and b"
good "a.b = bla"
good "a.b = 'bla'"
good "a.b = not"
@ -62,33 +61,10 @@ good "evt.arg[0] contains /bin"
bad "evt.arg[a] contains /bin"
bad "evt.arg[] contains /bin"
bad "a.g in ()"
bad "a.b = b = 1"
bad "(a.b = 1"
# Macros
good "a: a.b exists"
good "a: b and c"
good "a: b"
good "a : b"
good "a : evt.dir=>"
good "inbound: (syscall.type=listen and evt.dir='>') or (syscall.type=accept and evt.dir='<')"
bad "a:"
bad "b and d"
# Outputs
good "a.b = a.a |"
good "a.b = a.a | %evt.type %fd.num blabla"
good "a.b = a.a | blabla (%evt.type %fd.num)"
good "a.b = a.a | f(evt)"
good "a.b = a.a | f()"
bad "a.b = a.a | f() evt"
bad "a : b | bla"
bad " | %a.b"
echo
echo "All tests passed."
exit 0

View File

@ -0,0 +1,294 @@
--[[
Falco grammar and parser.
Much of the scaffolding and helpers was derived from Andre Murbach Maidl's Lua parser (https://github.com/andremm/lua-parser).
Parses regular filters following the existing sysdig filter syntax (*), extended to support "macro" terms, which are just identifiers.
(*) There is currently one known difference with the syntax implemented in libsinsp: In libsinsp, field names cannot start with 'a', 'o', or 'n'. With this parser they can.
--]]
local parser = {}
local lpeg = require "lpeg"
lpeg.locale(lpeg)
local P, S, V = lpeg.P, lpeg.S, lpeg.V
local C, Carg, Cb, Cc = lpeg.C, lpeg.Carg, lpeg.Cb, lpeg.Cc
local Cf, Cg, Cmt, Cp, Ct = lpeg.Cf, lpeg.Cg, lpeg.Cmt, lpeg.Cp, lpeg.Ct
local alpha, digit, alnum = lpeg.alpha, lpeg.digit, lpeg.alnum
local xdigit = lpeg.xdigit
local space = lpeg.space
-- error message auxiliary functions
-- creates an error message for the input string
local function syntaxerror (errorinfo, pos, msg)
local error_msg = "%s: syntax error, %s"
return string.format(error_msg, pos, msg)
end
-- gets the farthest failure position
local function getffp (s, i, t)
return t.ffp or i, t
end
-- gets the table that contains the error information
local function geterrorinfo ()
return Cmt(Carg(1), getffp) * (C(V"OneWord") + Cc("EOF")) /
function (t, u)
t.unexpected = u
return t
end
end
-- creates an errror message using the farthest failure position
local function errormsg ()
return geterrorinfo() /
function (t)
local p = t.ffp or 1
local msg = "unexpected '%s', expecting %s"
msg = string.format(msg, t.unexpected, t.expected)
return nil, syntaxerror(t, p, msg)
end
end
-- reports a syntactic error
local function report_error ()
return errormsg()
end
--- sets the farthest failure position and the expected tokens
local function setffp (s, i, t, n)
if not t.ffp or i > t.ffp then
t.ffp = i
t.list = {} ; t.list[n] = n
t.expected = "'" .. n .. "'"
elseif i == t.ffp then
if not t.list[n] then
t.list[n] = n
t.expected = "'" .. n .. "', " .. t.expected
end
end
return false
end
local function updateffp (name)
return Cmt(Carg(1) * Cc(name), setffp)
end
-- regular combinators and auxiliary functions
local function token (pat, name)
return pat * V"Skip" + updateffp(name) * P(false)
end
local function symb (str)
return token (P(str), str)
end
local function kw (str)
return token (P(str) * -V"idRest", str)
end
local function list (pat, sep)
return Ct(pat^-1 * (sep * pat^0)^0) / function(elements) return {type = "List", elements=elements} end
end
--http://lua-users.org/wiki/StringTrim
function trim(s)
if (type(s) ~= "string") then return s end
return (s:gsub("^%s*(.-)%s*$", "%1"))
end
local function terminal (tag)
-- Rather than trim the whitespace in this way, it would be nicer to exclude it from the capture...
return token(V(tag), tag) / function (tok) return { type = tag, value = trim(tok)} end
end
local function unaryboolop (op, e)
return { type = "UnaryBoolOp", operator = op, argument = e }
end
local function unaryrelop (e, op)
return { type = "UnaryRelOp", operator = op, argument = e }
end
local function binaryop (e1, op, e2)
if not op then
return e1
else
return { type = "BinaryBoolOp", operator = op, left = e1, right = e2 }
end
end
local function bool (pat, sep)
return Cf(pat * Cg(sep * pat)^0, binaryop)
end
local function rel (left, sep, right)
return left * sep * right / function(e1, op, e2) return { type = "BinaryRelOp", operator = op, left = e1, right = e2 } end
end
local function fix_str (str)
str = string.gsub(str, "\\a", "\a")
str = string.gsub(str, "\\b", "\b")
str = string.gsub(str, "\\f", "\f")
str = string.gsub(str, "\\n", "\n")
str = string.gsub(str, "\\r", "\r")
str = string.gsub(str, "\\t", "\t")
str = string.gsub(str, "\\v", "\v")
str = string.gsub(str, "\\\n", "\n")
str = string.gsub(str, "\\\r", "\n")
str = string.gsub(str, "\\'", "'")
str = string.gsub(str, '\\"', '"')
str = string.gsub(str, '\\\\', '\\')
return str
end
-- grammar
local function filter(e)
return {type = "Filter", value=e}
end
local function rule(filter)
return {type = "Rule", filter = filter}
end
local G = {
V"Start", -- Entry rule
Start = V"Skip" * (V"Comment" + V"Rule" / rule)^-1 * -1 + report_error();
-- Grammar
Comment = P"#" * P(1)^0;
Rule = V"Filter" / filter * ((V"Skip")^-1 );
Filter = V"OrExpression";
OrExpression =
bool(V"AndExpression", V"OrOp");
AndExpression =
bool(V"NotExpression", V"AndOp");
NotExpression =
V"UnaryBoolOp" * V"NotExpression" / unaryboolop +
V"ExistsExpression";
ExistsExpression =
terminal "FieldName" * V"ExistsOp" / unaryrelop +
V"MacroExpression";
MacroExpression =
terminal "Macro" +
V"RelationalExpression";
RelationalExpression =
rel(terminal "FieldName", V"RelOp", V"Value") +
rel(terminal "FieldName", V"InOp", V"InList") +
V"PrimaryExp";
PrimaryExp = symb("(") * V"Filter" * symb(")");
FuncArgs = symb("(") * list(V"Value", symb(",")) * symb(")");
-- Terminals
Value = terminal "Number" + terminal "String" + terminal "BareString";
InList = symb("(") * list(V"Value", symb(",")) * symb(")");
-- Lexemes
Space = space^1;
Skip = (V"Space")^0;
idStart = alpha + P("_");
idRest = alnum + P("_");
Identifier = V"idStart" * V"idRest"^0;
Macro = V"idStart" * V"idRest"^0 * -P".";
FieldName = V"Identifier" * (P"." + V"Identifier")^1 * (P"[" * V"Int" * P"]")^-1;
Name = C(V"Identifier") * -V"idRest";
Hex = (P("0x") + P("0X")) * xdigit^1;
Expo = S("eE") * S("+-")^-1 * digit^1;
Float = (((digit^1 * P(".") * digit^0) +
(P(".") * digit^1)) * V"Expo"^-1) +
(digit^1 * V"Expo");
Int = digit^1;
Number = C(V"Hex" + V"Float" + V"Int") /
function (n) return tonumber(n) end;
String = (P'"' * C(((P'\\' * P(1)) + (P(1) - P'"'))^0) * P'"' + P"'" * C(((P"\\" * P(1)) + (P(1) - P"'"))^0) * P"'") / function (s) return fix_str(s) end;
BareString = C(((P(1) - S' (),='))^1);
OrOp = kw("or") / "or";
AndOp = kw("and") / "and";
Colon = kw(":");
RelOp = symb("=") / "=" +
symb("==") / "==" +
symb("!=") / "!=" +
symb("<=") / "<=" +
symb(">=") / ">=" +
symb("<") / "<" +
symb(">") / ">" +
symb("contains") / "contains" +
symb("icontains") / "icontains";
InOp = kw("in") / "in";
UnaryBoolOp = kw("not") / "not";
ExistsOp = kw("exists") / "exists";
-- for error reporting
OneWord = V"Name" + V"Number" + V"String" + P(1);
}
--[[
Parses a single filter and returns the AST.
--]]
function parser.parse_filter (subject)
local errorinfo = { subject = subject }
lpeg.setmaxstack(1000)
local ast, error_msg = lpeg.match(G, subject, nil, errorinfo)
return ast, error_msg
end
function print_ast(ast, level)
local t = ast.type
level = level or 0
local prefix = string.rep(" ", level*4)
level = level + 1
if t == "Rule" then
print_ast(ast.filter, level)
elseif t == "Filter" then
print_ast(ast.value, level)
elseif t == "BinaryBoolOp" or t == "BinaryRelOp" then
print(prefix..ast.operator)
print_ast(ast.left, level)
print_ast(ast.right, level)
elseif t == "UnaryRelOp" or t == "UnaryBoolOp" then
print (prefix..ast.operator)
print_ast(ast.argument, level)
elseif t == "List" then
for i, v in ipairs(ast.elements) do
print_ast(v, level)
end
elseif t == "FieldName" or t == "Number" or t == "String" or t == "BareString" or t == "Macro" then
print (prefix..t.." "..ast.value)
elseif t == "MacroDef" then
-- don't print for now
else
error ("Unexpected type in print_ast: "..t)
end
end
parser.print_ast = print_ast
return parser

View File

@ -6,8 +6,13 @@
--]]
local DEFAULT_OUTPUT_FORMAT = "%evt.time: %evt.num %evt.cpu %proc.name (%thread.tid) %evt.dir %evt.type %evt.args"
local DEFAULT_PRIORITY = "WARNING"
local output = require('output')
local compiler = require "compiler"
local yaml = require"lyaml"
--[[
Traverse AST, adding the passed-in 'index' to each node that contains a relational expression
@ -89,103 +94,95 @@ local function install_filter(node, parent_bool_op)
end
end
local state
--[[
Sets up compiler state and returns it.
It holds state such as macro definitions that must be kept across calls
to the line-oriented compiler.
--]]
local function init()
return {macros={}, filter_ast=nil, n_rules=0, outputs={}}
end
function set_output(output_ast)
function set_output(output_format, state)
if(output_ast.type == "OutputFormat") then
local format
if output_ast.value==nil then
format = DEFAULT_OUTPUT_FORMAT
else
format = output_ast.value
end
state.outputs[state.n_rules] = {format=format, level = output_ast.level}
else
error ("Unexpected type in set_output: ".. output_ast.type)
end
end
function load_rule(r)
if (state == nil) then
state = init()
end
local line_ast = compiler.compile_line(r, state.macros)
if (line_ast.type == nil) then -- blank line
return
elseif (line_ast.type == "MacroDef") then
return
elseif (not (line_ast.type == "Rule")) then
error ("Unexpected type in load_rule: "..line_ast.type)
end
state.n_rules = state.n_rules + 1
set_output(line_ast.output)
-- Store the index of this formatter in each relational expression that
-- this rule contains.
-- This index will eventually be stamped in events passing this rule, and
-- we'll use it later to determine which output to display when we get an
-- event.
mark_relational_nodes(line_ast.filter.value, state.n_rules)
-- Rule ASTs are merged together into one big AST, with "OR" between each
-- rule.
if (state.filter_ast == nil) then
state.filter_ast = line_ast.filter.value
else
state.filter_ast = { type = "BinaryBoolOp", operator = "or", left = state.filter_ast, right = line_ast.filter.value }
local function priority(s)
valid_levels = {"emergency", "alert", "critical", "error", "warning", "notice", "informational", "debug"}
s = string.lower(s)
for i,v in ipairs(valid_levels) do
if (string.find(v, "^"..s)) then
return i - 1 -- (syslog levels start at 0, lua indices start at 1)
end
end
error("Invalid severity level: "..level)
end
function on_done()
local state = {macros={}, filter_ast=nil, n_rules=0, outputs={}}
function load_rules(filename)
local f = assert(io.open(filename, "r"))
local s = f:read("*all")
f:close()
local rules = yaml.load(s)
for i,v in ipairs(rules) do -- iterate over yaml list
if (not (type(v) == "table")) then
error ("Unexpected element of type " ..type(v)..". Each element should be a yaml associative array.")
end
if (v['macro']) then
local ast = compiler.compile_macro(v['condition'])
state.macros[v['macro']] = ast.filter.value
else -- filter
if (v['condition'] == nil) then
error ("Missing condition in rule")
end
if (v['output'] == nil) then
error ("Missing output in rule with condition"..v['condition'])
end
local filter_ast = compiler.compile_filter(v['condition'], state.macros)
if (filter_ast.type == "Rule") then
state.n_rules = state.n_rules + 1
state.outputs[state.n_rules] = {format=v['output'] or DEFAULT_OUTPUT_FORMAT,
level=priority(v['priority'] or DEFAULT_PRIORITY)}
-- Store the index of this formatter in each relational expression that
-- this rule contains.
-- This index will eventually be stamped in events passing this rule, and
-- we'll use it later to determine which output to display when we get an
-- event.
mark_relational_nodes(filter_ast.filter.value, state.n_rules)
-- Rule ASTs are merged together into one big AST, with "OR" between each
-- rule.
if (state.filter_ast == nil) then
state.filter_ast = filter_ast.filter.value
else
state.filter_ast = { type = "BinaryBoolOp", operator = "or", left = state.filter_ast, right = filter_ast.filter.value }
end
else
error ("Unexpected type in load_rule: "..filter_ast.type)
end
end
end
install_filter(state.filter_ast)
io.flush()
end
local output_functions = require('output')
outputs = {}
function add_output(output_name, config)
if not (type(output_functions[output_name]) == 'function') then
error("rule_loader.add_output(): invalid output_name: "..output_name)
end
-- outputs can optionally define a validation function so that we don't
-- find out at runtime (when an event finally matches a rule!) that the config is invalid
if (type(output_functions[output_name.."_validate"]) == 'function') then
output_functions[output_name.."_validate"](config)
end
table.insert(outputs, {output = output_functions[output_name], config=config})
end
function on_event(evt_, rule_id)
if state.outputs[rule_id] == nil then
error ("rule_loader.on_event(): event with invalid rule_id: ", rule_id)
end
for index,o in ipairs(outputs) do
o.output(evt_, state.outputs[rule_id].level, state.outputs[rule_id].format, o.config)
end
output.event(evt_, state.outputs[rule_id].level, state.outputs[rule_id].format)
end

View File

@ -1,4 +1,4 @@
local compiler = require "compiler"
local parser = require "parser"
if #arg ~= 1 then
print("Usage: test.lua <string>")
@ -9,7 +9,7 @@ local macros = {}
local ast
local function doit(line)
ast = compiler.compile_line(line, macros)
ast = parser.parse_filter(line)
if not ast then
print("error", error_msg)
@ -22,7 +22,7 @@ for str in string.gmatch(arg[1], "([^;]+)") do
end
if (ast and ast.type) then
compiler.parser.print_ast(ast)
parser.print_ast(ast)
end
os.exit(0)

6
userspace/falco/lyaml.h Normal file
View File

@ -0,0 +1,6 @@
#pragma once
#include "lua.h"
int luaopen_yaml (lua_State *L);

View File

@ -41,48 +41,19 @@ void falco_rules::load_compiler(string lua_main_filename)
void falco_rules::load_rules(string rules_filename)
{
ifstream is;
is.open(rules_filename);
if(!is.is_open())
{
throw sinsp_exception("Can't open file " + rules_filename + ". Try setting file location in config file or use '-r' flag.");
}
lua_getglobal(m_ls, m_lua_load_rule.c_str());
lua_getglobal(m_ls, m_lua_load_rules.c_str());
if(lua_isfunction(m_ls, -1))
{
lua_pop(m_ls, 1);
} else {
throw sinsp_exception("No function " + m_lua_load_rule + " found in lua compiler module");
}
std::string line;
while (std::getline(is, line))
{
lua_getglobal(m_ls, m_lua_load_rule.c_str());
lua_pushstring(m_ls, line.c_str());
lua_pushstring(m_ls, rules_filename.c_str());
if(lua_pcall(m_ls, 1, 0, 0) != 0)
{
const char* lerr = lua_tostring(m_ls, -1);
string err = "Error loading rule '" + line + "':" + string(lerr);
throw sinsp_exception(err);
}
}
lua_getglobal(m_ls, m_lua_on_done.c_str());
if(lua_isfunction(m_ls, -1))
{
if(lua_pcall(m_ls, 0, 0, 0) != 0)
{
const char* lerr = lua_tostring(m_ls, -1);
string err = "Error installing rules: " + string(lerr);
string err = "Error loading rules:" + string(lerr);
throw sinsp_exception(err);
}
} else {
throw sinsp_exception("No function " + m_lua_on_done + " found in lua compiler module");
throw sinsp_exception("No function " + m_lua_load_rules + " found in lua compiler module");
}
}
sinsp_filter* falco_rules::get_filter()

View File

@ -17,7 +17,6 @@ class falco_rules
lua_parser* m_lua_parser;
lua_State* m_ls;
string m_lua_load_rule = "load_rule";
string m_lua_on_done = "on_done";
string m_lua_load_rules = "load_rules";
string m_lua_on_event = "on_event";
};