mirror of
https://github.com/falcosecurity/falco.git
synced 2025-06-30 08:32:12 +00:00
Merge pull request #38 from draios/rules-yaml
Change rules file format to YAML
This commit is contained in:
commit
3d02acf3af
@ -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}")
|
||||
|
||||
|
@ -1,3 +1,3 @@
|
||||
install(FILES falco_rules.conf
|
||||
install(FILES falco_rules.yaml
|
||||
DESTINATION "${DIR_ETC}")
|
||||
|
||||
|
@ -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
|
16
rules/example-1-simple.yaml
Normal file
16
rules/example-1-simple.yaml
Normal 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)"
|
@ -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
|
22
rules/example-2-macros.yaml
Normal file
22
rules/example-2-macros.yaml
Normal 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)"
|
@ -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)
|
@ -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
438
rules/falco_rules.yaml
Normal 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
|
@ -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)
|
||||
|
@ -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
1
userspace/falco/lua/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
lyaml*
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
294
userspace/falco/lua/parser.lua
Normal file
294
userspace/falco/lua/parser.lua
Normal 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
|
@ -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
|
||||
|
||||
|
@ -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
6
userspace/falco/lyaml.h
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "lua.h"
|
||||
|
||||
int luaopen_yaml (lua_State *L);
|
||||
|
@ -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()
|
||||
|
@ -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";
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user