Compare commits

..

15 Commits

Author SHA1 Message Date
Mark Stemm
498d083980 Merge branch 'dev' into agent-master 2017-09-25 10:58:36 -07:00
Luca Marturana
6fd7f0d628 Merge branch 'dev' into agent-master 2017-08-23 10:30:27 +02:00
Thom van Os
d6fe29b47d Merge branch 'dev' into agent-master 2017-07-27 14:04:16 -07:00
Riccardo Schirone
a71cbcd7ee Merge branch 'dev' into agent-master 2017-07-03 12:18:10 +02:00
Mark Stemm
99d6bccc81 Merge branch 'dev' into agent-master 2017-06-06 10:13:23 -07:00
Brett
f92f74eaa8 Merge branch 'dev' into agent-master 2017-05-05 12:01:57 -07:00
Luca Marturana
d42d0e2dd1 Merge branch 'dev' into agent-master 2017-04-14 14:57:56 +02:00
Luca Marturana
135b4d9975 Merge branch 'dev' into agent-master 2017-03-30 14:46:44 +02:00
Luca Marturana
a25166b7ac Merge branch 'dev' into agent-master 2017-03-20 15:45:29 +01:00
Luca Marturana
800a3f1ea1 Merge branch 'dev' into agent-master 2017-02-21 11:47:36 +01:00
Luca Marturana
31464de885 Merge branch 'dev' into agent-master 2017-02-07 11:06:22 +01:00
Luca Marturana
9b308d2793 Merge branch 'dev' into agent-master 2017-02-02 12:35:47 +01:00
Luca Marturana
a99f09da96 Merge branch 'dev' into agent-master 2017-01-31 11:47:33 +01:00
Luca Marturana
1e0ddba11a Merge branch 'dev' into agent-master 2017-01-25 18:08:35 +01:00
Luca Marturana
b6d1101cb6 Merge branch 'agent-master' into dev 2017-01-17 10:55:07 +01:00
27 changed files with 128 additions and 774 deletions

View File

@@ -2,58 +2,9 @@
This file documents all notable changes to Falco. The release numbering uses [semantic versioning](http://semver.org).
## v0.8.1
Released 2017-10-10
### Bug Fixes
* Fix packaging to specify correct built-in config file [[#288](https://github.com/draios/falco/pull/288)]
## v0.8.0
Released 2017-10-10
**Important**: the location for falco's configuration file has moved from `/etc/falco.yaml` to `/etc/falco/falco.yaml`. The default rules file has moved from `/etc/falco_rules.yaml` to `/etc/falco/falco_rules.yaml`. In addition, 0.8.0 has added a _local_ ruls file to `/etc/falco/falco_rules.local.yaml`. See [the documentation](https://github.com/draios/falco/wiki/Falco-Default-and-Local-Rules-Files) for more details.
### Major Changes
* Add the ability to append one list to another list by setting an `append: true` attribute. [[#264](https://github.com/draios/falco/pull/264)]
* Add the ability to append one macro/rule to another list by setting an `append: true` attribute. [[#277](https://github.com/draios/falco/pull/277)]
* Ensure that falco rules/config files are preserved across package upgrades/removes if modified. [[#278](https://github.com/draios/falco/pull/278)]
* Add the notion of a "local" rules file that should contain modifications to the default falco rules file. [[#278](https://github.com/draios/falco/pull/278)]
* When using json output, separately include the individual templated fields in the json object. [[#282](https://github.com/draios/falco/pull/282)]
* Add the ability to keep a file/program pipe handle open across rule notifications. [[#283](https://github.com/draios/falco/pull/283)]
* New argument `-V` validates rules file and immediately exits. [[#286](https://github.com/draios/falco/pull/286)]
### Minor Changes
* Minor updates to falco example programs [[#248](https://github.com/draios/falco/pull/248)] [[#275](https://github.com/draios/falco/pull/275)]
* Also validate macros at rule parse time. [[#257](https://github.com/draios/falco/pull/257)]
* Minor README typo fixes [[#276](https://github.com/draios/falco/pull/276)]
* Add a government CLA (contributor license agreement). [[#263](https://github.com/draios/falco/pull/263)]
* Add ability to only run rules with a priority >= some threshold [[#281](https://github.com/draios/falco/pull/281)]
* Add ability to make output channels unbuffered [[#285](https://github.com/draios/falco/pull/285)]
### Bug Fixes
* Fix installation of falco on OSX [[#252](https://github.com/draios/falco/pull/252)]
* Fix a bug that caused the trailing whitespace of a quoted string to be accidentally removed [[#254](https://github.com/draios/falco/pull/254)]
* When multiple sets of kernel headers are installed, find the one for the running kernel [[#260](https://github.com/draios/falco/pull/260)]
* Allow pathnames in rule/macro conditions to contain '.' characters [[#262](https://github.com/draios/falco/pull/262)]
* Fix a bug where a list named "foo" would be substituted even if it were a substring of a longer word like "my_foo" [[#258](https://github.com/draios/falco/pull/258)]
* Remove extra trailing newlines from rule output strings [[#265](https://github.com/draios/falco/pull/265)]
* Improve build pathnames to avoid relative paths when possible [[#284](https://github.com/draios/falco/pull/284)]
### Rule Changes
* Significant changes to default ruleset to address FPs. These changes resulted from hundreds of hours of use in actual customer environments. [[#247](https://github.com/draios/falco/pull/247)] [[#259](https://github.com/draios/falco/pull/259)]
* Add official gitlab EE docker image to list of known shell spawning images. Thanks @dkerwin! [[#270](https://github.com/draios/falco/pull/270)]
* Add keepalived to list of shell spawning binaries. Thanks @dkerwin! [[#269](https://github.com/draios/falco/pull/269)]
## v0.7.0
Released 2017-05-30
Released 2016-05-30
### Major Changes
@@ -76,7 +27,7 @@ None.
## v0.6.1
Released 2017-05-15
Released 2016-05-15
### Major Changes
@@ -96,7 +47,7 @@ None
## v0.6.0
Released 2017-03-29
Released 2016-03-29
### Major Changes

View File

@@ -7,7 +7,7 @@ if(NOT DEFINED FALCO_VERSION)
endif()
if(NOT DEFINED FALCO_ETC_DIR)
set(FALCO_ETC_DIR "/etc/falco")
set(FALCO_ETC_DIR "/etc")
endif()
if(NOT CMAKE_BUILD_TYPE)
@@ -399,9 +399,8 @@ add_subdirectory("${SYSDIG_DIR}/userspace/libscap" "${PROJECT_BINARY_DIR}/usersp
add_subdirectory("${SYSDIG_DIR}/userspace/libsinsp" "${PROJECT_BINARY_DIR}/userspace/libsinsp")
set(FALCO_SINSP_LIBRARY sinsp)
set(FALCO_SHARE_DIR share/falco)
set(FALCO_ABSOLUTE_SHARE_DIR "${CMAKE_INSTALL_PREFIX}/${FALCO_SHARE_DIR}")
set(FALCO_BIN_DIR bin)
set(FALCO_SHARE_DIR ${CMAKE_INSTALL_PREFIX}/share/falco)
set(FALCO_BIN_DIR ${CMAKE_INSTALL_PREFIX}/bin)
add_subdirectory(scripts)
add_subdirectory(userspace/engine)
add_subdirectory(userspace/falco)
@@ -423,7 +422,7 @@ set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Sysdig <support@sysdig.com>")
set(CPACK_DEBIAN_PACKAGE_SECTION "utils")
set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "http://www.sysdig.org")
set(CPACK_DEBIAN_PACKAGE_DEPENDS "dkms (>= 2.1.0.0)")
set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA "${CMAKE_BINARY_DIR}/scripts/debian/postinst;${CMAKE_BINARY_DIR}/scripts/debian/prerm;${PROJECT_SOURCE_DIR}/scripts/debian/postrm;${PROJECT_SOURCE_DIR}/cpack/debian/conffiles")
set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA "${CMAKE_BINARY_DIR}/scripts/debian/postinst;${CMAKE_BINARY_DIR}/scripts/debian/prerm;${PROJECT_SOURCE_DIR}/scripts/debian/postrm")
set(CPACK_RPM_PACKAGE_LICENSE "GPLv2")
set(CPACK_RPM_PACKAGE_URL "http://www.sysdig.org")

View File

@@ -2,7 +2,7 @@
#### Latest release
**v0.8.0**
**v0.7.0**
Read the [change log](https://github.com/draios/falco/blob/dev/CHANGELOG.md)
Dev Branch: [![Build Status](https://travis-ci.org/draios/falco.svg?branch=dev)](https://travis-ci.org/draios/falco)<br />

View File

@@ -1,3 +0,0 @@
/etc/falco/falco.yaml
/etc/falco/falco_rules.yaml
/etc/falco/falco_rules.local.yaml

View File

@@ -1,15 +1,5 @@
# File(s) containing Falco rules, loaded at startup.
#
# falco_rules.yaml ships with the falco package and is overridden with
# every new software version. falco_rules.local.yaml is only created
# if it doesn't exist. If you want to customize the set of rules, add
# your customizations to falco_rules.local.yaml.
#
# The files will be read in the order presented here, so make sure if
# you have overrides they appear in later files.
rules_file:
- /etc/falco/falco_rules.yaml
- /etc/falco/falco_rules.local.yaml
# File containing Falco rules, loaded at startup.
rules_file: /etc/falco_rules.yaml
# Whether to output events in json or text
json_output: false
@@ -25,16 +15,6 @@ log_syslog: true
# "alert", "critical", "error", "warning", "notice", "info", "debug".
log_level: info
# Minimum rule priority level to load and run. All rules having a
# priority more severe than this level will be loaded/run. Can be one
# of "emergency", "alert", "critical", "error", "warning", "notice",
# "info", "debug".
priority: debug
# Whether or not output to any of the output channels below is
# buffered. Defaults to true
buffered_outputs: true
# A throttling mechanism implemented as a token bucket limits the
# rate of falco notifications. This throttling is controlled by the following configuration
# options:
@@ -57,13 +37,8 @@ outputs:
syslog_output:
enabled: true
# If keep_alive is set to true, the file will be opened once and
# continuously written to, with each output message on its own
# line. If keep_alive is set to false, the file will be re-opened
# for each output message.
file_output:
enabled: false
keep_alive: false
filename: ./events.txt
stdout_output:
@@ -74,15 +49,7 @@ stdout_output:
# program: "jq '{text: .output}' | curl -d @- -X POST https://hooks.slack.com/services/XXX"
# - logging (alternate method than syslog):
# program: logger -t falco-test
# - send over a network connection:
# program: nc host.example.com 80
# If keep_alive is set to true, the program will be started once and
# continuously written to, with each output message on its own
# line. If keep_alive is set to false, the program will be re-spawned
# for each output message.
program_output:
enabled: false
keep_alive: false
program: mail -s "Falco Notification" someone@example.com

View File

@@ -1,10 +1,9 @@
if(NOT DEFINED FALCO_ETC_DIR)
set(FALCO_ETC_DIR "/etc/falco")
set(FALCO_ETC_DIR "/etc")
endif()
if(NOT DEFINED FALCO_RULES_DEST_FILENAME)
set(FALCO_RULES_DEST_FILENAME "falco_rules.yaml")
set(FALCO_LOCAL_RULES_DEST_FILENAME "falco_rules.local.yaml")
endif()
if(DEFINED FALCO_COMPONENT)
@@ -12,18 +11,9 @@ install(FILES falco_rules.yaml
COMPONENT "${FALCO_COMPONENT}"
DESTINATION "${FALCO_ETC_DIR}"
RENAME "${FALCO_RULES_DEST_FILENAME}")
install(FILES falco_rules.local.yaml
COMPONENT "${FALCO_COMPONENT}"
DESTINATION "${FALCO_ETC_DIR}"
RENAME "${FALCO_LOCAL_RULES_DEST_FILENAME}")
else()
install(FILES falco_rules.yaml
DESTINATION "${FALCO_ETC_DIR}"
RENAME "${FALCO_RULES_DEST_FILENAME}")
install(FILES falco_rules.local.yaml
DESTINATION "${FALCO_ETC_DIR}"
RENAME "${FALCO_LOCAL_RULES_DEST_FILENAME}")
DESTINATION "${FALCO_ETC_DIR}"
RENAME "${FALCO_RULES_DEST_FILENAME}")
endif()

View File

@@ -1,13 +0,0 @@
####################
# Your custom rules!
####################
# Add new rules, like this one
# - rule: The program "sudo" is run in a container
# desc: An event will trigger every time you run sudo in a container
# condition: evt.type = execve and evt.dir=< and container.id != host and proc.name = sudo
# output: "Sudo run in container (user=%user.name %container.info parent=%proc.pname cmdline=%proc.cmdline)"
# priority: ERROR
# tags: [users, container]
# Or override/append to any rule, macro, or list from the Default Rules

View File

@@ -74,9 +74,6 @@
- list: shell_binaries
items: [bash, csh, ksh, sh, tcsh, zsh, dash]
- list: shell_mgmt_binaries
items: [add-shell, remove-shell]
- macro: shell_procs
condition: proc.name in (shell_binaries)
@@ -134,13 +131,7 @@
# Utility/etc programs known to run on mesos slaves. Truncation
# intentional.
- list: mesos_slave_binaries
items: [mesos-health-ch, mesos-docker-ex, mesos-agent, mesos-slave, mesos-logrotate, mesos-fetcher, mesos-executor, 3dt]
- list: phusion_passenger_binaries
items: [PassengerAgent]
- list: chef_binaries
items: [chef-client]
items: [mesos-health-ch, mesos-docker-ex, mesos-agent, mesos-logrotate, mesos-fetcher]
- list: http_server_binaries
items: [nginx, httpd, httpd-foregroun, lighttpd]
@@ -163,7 +154,7 @@
condition: proc.name in (rpm_binaries)
- list: deb_binaries
items: [dpkg, dpkg-preconfigu, dpkg-reconfigur, apt, apt-get, aptitude,
items: [dpkg, dpkg-preconfigu, apt, apt-get, aptitude,
frontend, preinst, add-apt-reposit, apt-auto-remova, apt-key,
apt-listchanges, unattended-upgr
]
@@ -171,7 +162,7 @@
# The truncated dpkg-preconfigu is intentional, process names are
# truncated at the sysdig level.
- list: package_mgmt_binaries
items: [rpm_binaries, deb_binaries, update-alternat, gem]
items: [rpm_binaries, deb_binaries, update-alternat]
- macro: package_mgmt_procs
condition: proc.name in (package_mgmt_binaries)
@@ -187,14 +178,11 @@
- list: userexec_binaries
items: [sudo, su]
- list: known_setuid_binaries
items: [sshd, dbus-daemon-lau, ping, ping6, critical-stack-]
- list: user_mgmt_binaries
items: [login_binaries, passwd_binaries, shadowutils_binaries]
- list: dev_creation_binaries
items: [blkid, rename_device, update_engine]
items: [blkid, rename_device]
- list: aide_wrapper_binaries
items: [aide.wrapper, update-aide.con]
@@ -202,35 +190,17 @@
- list: hids_binaries
items: [aide]
- list: vpn_binaries
items: [openvpn]
- list: nomachine_binaries
items: [nxexec, nxnode.bin, nxserver.bin, nxclient.bin]
- list: x2go_binaries
items: [x2gosuspend-age, x2goagent]
- list: xray_rabbitmq_binaries
items: ['"1_scheduler"', '"2_scheduler"', '"3_scheduler"', '"4_scheduler"']
- list: nids_binaries
items: [bro, broctl]
- list: monitoring_binaries
items: [icinga2, nrpe, npcd, check_sar_perf., qualys-cloud-ag]
items: [icinga2, nrpe, npcd, check_sar_perf.]
- macro: system_procs
condition: proc.name in (coreutils_binaries, user_mgmt_binaries)
- list: mail_binaries
items: [sendmail, sendmail-msp, postfix, procmail, exim4, pickup, showq, mailq]
- list: sendmail_config_binaries
items: [
update_conf, parse_mc, makemap_hash, newaliases, update_mk, update_tlsm4,
update_db, update_mc, ssmtp.postinst, mailq
]
items: [sendmail, sendmail-msp, postfix, procmail, exim4, pickup, showq]
- list: make_binaries
items: [make, gmake, cmake]
@@ -294,14 +264,6 @@
- list: cron_binaries
items: [anacron, cron, crond]
# https://github.com/liske/needrestart
- list: needrestart_binaries
items: [needrestart, 10-dpkg, 20-rpm, 30-pacman]
# Possible scripts run by sshkit
- list: sshkit_script_binaries
items: [10_etc_sudoers., 10_passwd_group]
# System users that should never log into a system. Consider adding your own
# service users (e.g. 'apache' or 'mysqld') here.
- macro: system_users
@@ -333,8 +295,8 @@
- macro: parent_python_running_sdchecks
condition: >
(proc.pname in (python, python2.7) and
(proc.pcmdline contains /opt/draios/bin/sdchecks))
(proc.name in (python, python2.7) and
(proc.cmdline contains /opt/draios/bin/sdchecks))
- macro: parent_bro_running_python
condition: (proc.pname=python and proc.cmdline contains /usr/share/broctl)
@@ -344,78 +306,6 @@
(proc.pname=java and proc.pcmdline contains jenkins.war
or proc.pcmdline contains /tmp/slave.jar)
- macro: jenkins_script_sh
condition: (proc.pcmdline startswith "script.sh -xe /var/jenkins_home")
- macro: parent_java_running_echo
condition: (proc.pname=java and proc.cmdline startswith "sh -c echo")
- macro: parent_java_running_sbt
condition: (proc.pname=java and proc.pcmdline contains sbt-launch.jar)
# The crxlsx is a bit different than the other build-like things, but
# close enough to add here rather than create a separate macro.
- macro: parent_scripting_running_builds
condition: >
(proc.pname in (php,php5-fpm,php-fpm7.1,python,ruby,ruby2.3,node) and (
proc.cmdline startswith "sh -c git" or
proc.cmdline startswith "sh -c date" or
proc.cmdline startswith "sh -c /usr/bin/g++" or
proc.cmdline startswith "sh -c /usr/bin/gcc" or
proc.cmdline startswith "sh -c gcc" or
proc.cmdline startswith "sh -c if type gcc" or
proc.cmdline startswith "sh -c cd '/var/www/edi/';LC_ALL=en_US.UTF-8 git" or
proc.cmdline startswith "sh -c /usr/src/app/crxlsx/bin/linux/crxlsx" or
proc.pcmdline startswith "node /opt/nodejs/bin/yarn"))
- macro: parent_node_running_npm
condition: proc.pcmdline startswith "node /usr/local/bin/npm"
- macro: parent_nginx_running_serf
condition: (proc.pname=nginx and proc.cmdline startswith "sh -c serf")
- macro: parent_Xvfb_running_xkbcomp
condition: (proc.pname=Xvfb and proc.cmdline startswith 'sh -c "/usr/bin/xkbcomp"')
- macro: mysql_image_running_healthcheck
condition: container.image=mysql and proc.cmdline="sh -c /healthcheck.sh"
- macro: bundle_running_ruby
condition: >
(proc.pname=ruby and (
proc.aname[2]=bundle or
proc.aname[3]=bundle or
proc.aname[4]=bundle))
# Qualys seems to run a variety of shell subprocesses, at various
# levels. This checks at a few levels without the cost of a full
# proc.aname, which traverses the full parent heirarchy.
- macro: run_by_qualys
condition: >
(proc.pname=qualys-cloud-ag or
proc.aname[2]=qualys-cloud-ag or
proc.aname[3]=qualys-cloud-ag or
proc.aname[4]=qualys-cloud-ag)
# Chef is similar.
- macro: run_by_chef
condition: (proc.aname[2]=chef_command_wr or proc.aname[3]=chef_command_wr)
- macro: run_by_adclient
condition: (proc.aname[2]=adclient or proc.aname[3]=adclient or proc.aname[4]=adclient)
- macro: run_by_centrify
condition: (proc.aname[2]=centrify or proc.aname[3]=centrify or proc.aname[4]=centrify)
- macro: run_by_puppet
condition: (proc.aname[2]=puppet or proc.aname[3]=puppet)
- macro: run_by_h2o
condition: (proc.pname=perl and proc.aname[2]=h2o)
- macro: run_by_passenger_agent
condition: (proc.pname=ruby and proc.aname[2]=PassengerAgent)
# As a part of kernel upgrades, dpkg will spawn a perl script with the
# name linux-image-N.N. This macro matches that.
- macro: parent_linux_image_upgrade_script
@@ -437,62 +327,25 @@
priority: ERROR
tags: [filesystem]
- list: safe_etc_dirs
items: [/etc/cassandra, /etc/ssl/certs/java, /etc/logstash, /etc/nginx/conf.d, /etc/container_environment, /etc/hrmconfig]
- macro: fluentd_writing_conf_files
condition: (proc.name=start-fluentd and fd.name in (/etc/fluent/fluent.conf, /etc/td-agent/td-agent.conf))
- macro: qualys_writing_conf_files
condition: (proc.name=qualys-cloud-ag and fd.name=/etc/qualys/cloud-agent/qagent-log.conf)
- macro: git_writing_nssdb
condition: (proc.cmdline="git-remote-http origin" and fd.directory=/etc/pki/nssdb)
# Add conditions to this macro (probably in a separate file,
# overwriting this macro) to allow for specific combinations of
# programs writing below specific directories below
# /etc. fluentd_writing_conf_files is a good example to follow, as it
# specifies both the program doing the writing as well as the specific
# files it is allowed to modify.
#
# In this file, it just takes one of the programs in the base macro
# and repeats it.
- macro: user_known_write_etc_conditions
condition: proc.name=confd
- macro: write_etc_common
condition: >
etc_dir and evt.dir = < and open_write
and not proc.name in (passwd_binaries, shadowutils_binaries, sysdigcloud_binaries,
package_mgmt_binaries, ssl_mgmt_binaries, dhcp_binaries,
dev_creation_binaries, shell_mgmt_binaries,
sendmail_config_binaries,
sshkit_script_binaries,
dev_creation_binaries,
ldconfig.real, ldconfig, confd, gpg, insserv,
apparmor_parser, update-mime, tzdata.config, tzdata.postinst,
systemd, systemd-machine, systemd-sysuser,
debconf-show, rollerd, bind9.postinst, sv,
gen_resolvconf., update-ca-certi, certbot, runsv,
qualys-cloud-ag, locales.postins, nomachine_binaries,
adclient, certutil, crlutil)
and not proc.pname in (sysdigcloud_binaries, sendmail_config_binaries, hddtemp.postins, sshkit_script_binaries)
and not fd.name pmatch (safe_etc_dirs)
and not fd.name in (/etc/container_environment.sh, /etc/container_environment.json, /etc/motd, /etc/motd.svc)
systemd-machine, debconf-show, rollerd, bind9.postinst, sv,
gen_resolvconf.)
and not proc.pname in (sysdigcloud_binaries)
and not fd.directory in (/etc/cassandra, /etc/ssl/certs/java)
and not ansible_running_python
and not python_running_denyhosts
and not fluentd_writing_conf_files
and not user_known_write_etc_conditions
and not run_by_centrify
and not run_by_adclient
and not qualys_writing_conf_files
and not git_writing_nssdb
- rule: Write below etc
desc: an attempt to write to any file below /etc, not in a pipe installer session
condition: write_etc_common and not proc.sname=fbash
output: "File below /etc opened for writing (user=%user.name command=%proc.cmdline parent=%proc.pname file=%fd.name name=%proc.name gparent=%proc.aname[2] ggparent=%proc.aname[3] gggparent=%proc.aname[4])"
output: "File below /etc opened for writing (user=%user.name command=%proc.cmdline file=%fd.name)"
priority: ERROR
tags: [filesystem]
@@ -517,28 +370,12 @@
condition: sensitive_files and open_read and server_procs and not proc_is_new and proc.name!="sshd"
output: >
Sensitive file opened for reading by trusted program after startup (user=%user.name
command=%proc.cmdline parent=%proc.pname file=%fd.name parent=%proc.pname gparent=%proc.aname[2])
command=%proc.cmdline file=%fd.name)
priority: WARNING
tags: [filesystem]
- list: read_sensitive_file_binaries
items: [
iptables, ps, lsb_release, check-new-relea, dumpe2fs, accounts-daemon, sshd,
vsftpd, systemd, mysql_install_d, psql
]
# Add conditions to this macro (probably in a separate file,
# overwriting this macro) to allow for specific combinations of
# programs accessing sensitive files.
# fluentd_writing_conf_files is a good example to follow, as it
# specifies both the program doing the writing as well as the specific
# files it is allowed to modify.
#
# In this file, it just takes one of the macros in the base rule
# and repeats it.
- macro: user_read_sensitive_file_conditions
condition: cmp_cp_by_passwd
items: [iptables, ps, lsb_release, check-new-relea, dumpe2fs, accounts-daemon, sshd, vsftpd, systemd]
- rule: Read sensitive file untrusted
desc: >
@@ -547,17 +384,13 @@
condition: >
sensitive_files and open_read
and not proc.name in (user_mgmt_binaries, userexec_binaries, package_mgmt_binaries,
cron_binaries, read_sensitive_file_binaries, shell_binaries, hids_binaries,
vpn_binaries, sendmail_config_binaries, nomachine_binaries, sshkit_script_binaries)
cron_binaries, read_sensitive_file_binaries, shell_binaries, hids_binaries)
and not cmp_cp_by_passwd
and not ansible_running_python
and not proc.cmdline contains /usr/bin/mandb
and not run_by_qualys
and not run_by_chef
and not user_read_sensitive_file_conditions
output: >
Sensitive file opened for reading by non-trusted program (user=%user.name name=%proc.name
command=%proc.cmdline file=%fd.name parent=%proc.pname gparent=%proc.aname[2] ggparent=%proc.aname[3] gggparent=%proc.aname[4])
command=%proc.cmdline file=%fd.name)
priority: WARNING
tags: [filesystem]
@@ -637,15 +470,13 @@
- list: known_shell_spawn_binaries
items: [
sshd, sudo, su, tmux, screen, emacs, systemd, login, flock, fbash,
nginx, monit, supervisord, dragent, aws, awslogs, initdb, docker-compose,
nginx, monit, supervisord, dragent, aws, initdb, docker-compose,
configure, awk, falco, fail2ban-server, fleetctl,
logrotate, ansible, less, adduser, pycompile, py3compile,
pyclean, py3clean, pip, pip2, ansible-playboo, man-db,
init, pluto, mkinitramfs, unattended-upgr, watch, sysdig,
landscape-sysin, nessusd, PM2, syslog-summary, erl_child_setup,
npm, cloud-init, toybox, ceph, hhvm, certbot, mysql_install_d,
serf, a2enmod, runsv, supervisord, varnishd, authconfig, tini,
timeout, updatedb.findut, mysql_ssl_rsa_s, adclient, systemd-udevd
npm, cloud-init, toybox, ceph
]
- rule: Run shell untrusted
@@ -656,31 +487,16 @@
and proc.pname exists
and not proc.pname in (cron_binaries, shell_binaries, make_binaries, known_shell_spawn_binaries, docker_binaries,
k8s_binaries, package_mgmt_binaries, aide_wrapper_binaries, nids_binaries,
monitoring_binaries, gitlab_binaries, mesos_slave_binaries,
keepalived_binaries,
needrestart_binaries, phusion_passenger_binaries, chef_binaries, nomachine_binaries,
x2go_binaries)
monitoring_binaries, gitlab_binaries, mesos_slave_binaries, keepalived_binaries)
and not parent_ansible_running_python
and not parent_bro_running_python
and not parent_python_running_denyhosts
and not parent_python_running_sdchecks
and not parent_linux_image_upgrade_script
and not parent_java_running_jenkins
and not jenkins_script_sh
and not parent_java_running_echo
and not parent_scripting_running_builds
and not parent_Xvfb_running_xkbcomp
and not parent_nginx_running_serf
and not parent_node_running_npm
and not parent_java_running_sbt
and not run_by_chef
and not run_by_puppet
and not run_by_adclient
and not run_by_centrify
output: >
Shell spawned by untrusted binary (user=%user.name shell=%proc.name parent=%proc.pname
cmdline=%proc.cmdline pcmdline=%proc.pcmdline gparent=%proc.aname[2] ggparent=%proc.aname[3]
gggparent=%proc.aname[4] ggggparent=%proc.aname[5])
cmdline=%proc.cmdline pcmdline=%proc.pcmdline)
priority: DEBUG
tags: [host, shell]
@@ -692,8 +508,7 @@
container.image startswith sysdig/sysdig or
container.image startswith gcr.io/google_containers/hyperkube or
container.image startswith quay.io/coreos/flannel or
container.image startswith gcr.io/google_containers/kube-proxy or
container.image startswith calico/node)
container.image startswith gcr.io/google_containers/kube-proxy)
# These containers are ones that are known to spawn lots of
# shells. Generally, they are for systems where the container is used
@@ -713,20 +528,6 @@
- macro: sensitive_mount
condition: (container.mount.dest[/proc*] != "N/A")
# The steps libcontainer performs to set up the root program for a container are:
# - clone + exec self to a program runc:[0:PARENT]
# - clone a program runc:[1:CHILD] which sets up all the namespaces
# - clone a second program runc:[2:INIT] + exec to the root program.
# The parent of runc:[2:INIT] is runc:0:PARENT]
# As soon as 1:CHILD is created, 0:PARENT exits, so there's a race
# where at the time 2:INIT execs the root program, 0:PARENT might have
# already exited, or might still be around. So we handle both.
# We also let runc:[1:CHILD] count as the parent process, which can occur
# when we lose events and lose track of state.
- macro: container_entrypoint
condition: (not proc.pname exists or proc.pname in (runc:[0:PARENT], runc:[1:CHILD], docker-runc))
- rule: Launch Sensitive Mount Container
desc: >
Detect the initial process started by a container that has a mount from a sensitive host directory
@@ -772,90 +573,27 @@
'"sh -c curl http://localhost:6060/debug/vars>/dev/null "',
'"sh -c pgrep java && exit 0 || exit 1 "',
'"sh -c uname -p 2> /dev/null"',
'"sh -c uname -s 2>&1"',
'"sh -c uname -r 2>&1"',
'"sh -c uname -v 2>&1"',
'"sh -c uname -a 2>&1"',
'"sh -c ruby -v 2>&1"',
'"sh -c echo healthy "',
'"sh -c echo alive "',
'"sh -c getconf CLK_TCK"',
'"sh -c getconf PAGESIZE"',
'"sh -c LC_ALL=C LANG=C /sbin/ldconfig -p 2>/dev/null"',
'"sh -c /sbin/ldconfig -p 2>/dev/null"',
'"sh -c stty -a 2>/dev/null"',
'"sh -c node index.js"',
'"sh -c node index"',
'"sh -c node ./src/start.js"',
'"sh -c node app.js"',
'"sh -c node -e \"require(''nan'')\")"',
'"sh -c node $NODE_DEBUG_OPTION index.js "',
'"sh -c crontab -l 2"',
'"sh -c lsb_release -a"',
'"sh -c whoami"',
'"sh -c node_modules/.bin/bower-installer"'
'"sh -c echo alive "'
]
# This list allows for easy additions to the set of commands allowed
# to run shells in containers without having to without having to copy
# and override the entire run shell in container macro. Once
# https://github.com/draios/falco/issues/255 is fixed this will be a
# bit easier, as someone could append of any of the existing lists.
- list: user_known_container_shell_spawn_binaries
items: []
# This macro allows for easy additions to the set of commands allowed
# to run shells in containers without having to override the entire
# rule. Its default value is an expression that always is false, which
# becomes true when the "not ..." in the rule is applied.
- macro: user_shell_container_exclusions
condition: (evt.num=0)
# Temporarily adding as an example
- macro: node_running_edi_dynamodb
condition: >
(proc.pname=node and (proc.pcmdline contains /var/www/edi/process.js or
proc.pcmdline contains "sh -c /var/www/edi/bin/sftp.sh"))
- rule: Run shell in container
desc: a shell was spawned by a non-shell program in a container. Container entrypoints are excluded.
condition: >
spawned_process and container
and shell_procs
and not container_entrypoint
and not proc.pname in (shell_binaries, make_binaries, docker_binaries, k8s_binaries, package_mgmt_binaries,
and proc.pname exists
and not proc.pname in (shell_binaries, make_binaries, docker_binaries, k8s_binaries,
lxd_binaries, mesos_slave_binaries, aide_wrapper_binaries, nids_binaries,
user_known_container_shell_spawn_binaries,
needrestart_binaries,
phusion_passenger_binaries,
chef_binaries,
nomachine_binaries,
x2go_binaries,
xray_rabbitmq_binaries,
monitoring_binaries, gitlab_binaries, initdb, pg_ctl, awk, falco, cron,
erl_child_setup, ceph, PM2, pycompile, py3compile, hhvm, npm, mysql_install_d, serf,
runsv, supervisord, varnishd, crond, logrotate, timeout, tini,
xrdb, xfce4-session, weave, mysql_ssl_rsa_s, logdna-agent, bundle, configure)
erl_child_setup, ceph, PM2)
and not trusted_containers
and not shell_spawning_containers
and not parent_java_running_echo
and not parent_scripting_running_builds
and not parent_Xvfb_running_xkbcomp
and not mysql_image_running_healthcheck
and not parent_nginx_running_serf
and not proc.cmdline in (known_container_shell_spawn_cmdlines)
and not parent_node_running_npm
and not user_shell_container_exclusions
and not node_running_edi_dynamodb
and not run_by_h2o
and not run_by_passenger_agent
and not parent_java_running_jenkins
and not jenkins_script_sh
and not bundle_running_ruby
output: >
Shell spawned in a container other than entrypoint (user=%user.name %container.info image=%container.image
shell=%proc.name pcmdline=%proc.pcmdline cmdline=%proc.cmdline parent=%proc.pname gparent=%proc.aname[2] ggparent=%proc.aname[3])
priority: DEBUG
Shell spawned in a container other than entrypoint (user=%user.name %container.info
shell=%proc.name parent=%proc.pname cmdline=%proc.cmdline)
priority: NOTICE
tags: [container, shell]
# sockfamily ip is to exclude certain processes (like 'groups') that communicate on unix-domain sockets
@@ -880,20 +618,7 @@
- macro: somebody_becoming_themself
condition: ((user.name=nobody and evt.arg.uid=nobody) or
(user.name=www-data and evt.arg.uid=www-data) or
(user.name=_apt and evt.arg.uid=_apt) or
(user.name=postfix and evt.arg.uid=postfix) or
(user.name=pki-agent and evt.arg.uid=pki-agent) or
(user.name=pki-acme and evt.arg.uid=pki-acme) or
(user.name=nfsnobody and evt.arg.uid=nfsnobody))
# In containers, the user name might be for a uid that exists in the
# container but not on the host. (See
# https://github.com/draios/sysdig/issues/954). So in that case, allow
# a setuid.
- macro: unknown_user_in_container
condition: (user.name="<NA>" and container)
(user.name=www-data and evt.arg.uid=www-data))
# sshd, mail programs attempt to setuid to root even when running as non-root. Excluding here to avoid meaningless FPs
- rule: Non sudo setuid
@@ -901,11 +626,10 @@
an attempt to change users by calling setuid. sudo/su are excluded. users "root" and "nobody"
suing to itself are also excluded, as setuid calls typically involve dropping privileges.
condition: >
evt.type=setuid and evt.dir=>
and not unknown_user_in_container
and not user.name=root and not somebody_becoming_themself
and not proc.name in (known_setuid_binaries, userexec_binaries, mail_binaries, docker_binaries, nomachine_binaries)
and not java_running_sdjagent
evt.type=setuid and evt.dir=> and
not user.name=root and not somebody_becoming_themself
and not proc.name in (userexec_binaries, mail_binaries, docker_binaries,
sshd, dbus-daemon-lau, ping, ping6, critical-stack-)
output: >
Unexpected setuid call by non-sudo, non-root program (user=%user.name parent=%proc.pname
command=%proc.cmdline uid=%evt.arg.uid)
@@ -917,18 +641,13 @@
activity by any programs that can manage users, passwords, or permissions. sudo and su are excluded.
Activity in containers is also excluded--some containers create custom users on top
of a base linux distribution at startup.
Some innocuous commandlines that don't actually change anything are excluded.
condition: >
spawned_process and proc.name in (user_mgmt_binaries) and
not proc.name in (su, sudo, lastlog) and not container and
not proc.pname in (cron_binaries, systemd, run-parts) and
not proc.cmdline startswith "passwd -S" and
not proc.cmdline startswith "useradd -D" and
not proc.cmdline startswith "systemd --version" and
not run_by_qualys
not proc.name in (su, sudo) and not container and
not proc.pname in (cron_binaries, systemd, run-parts)
output: >
User management binary command run outside of container
(user=%user.name command=%proc.cmdline parent=%proc.pname gparent=%proc.aname[2] ggparent=%proc.aname[3] gggparent=%proc.aname[4])
(user=%user.name command=%proc.cmdline parent=%proc.pname)
priority: NOTICE
tags: [host, users]

View File

@@ -30,7 +30,6 @@ class FalcoTest(Test):
self.trace_file = os.path.join(self.basedir, self.trace_file)
self.json_output = self.params.get('json_output', '*', default=False)
self.priority = self.params.get('priority', '*', default='debug')
self.rules_file = self.params.get('rules_file', '*', default=os.path.join(self.basedir, '../rules/falco_rules.yaml'))
if not isinstance(self.rules_file, list):
@@ -296,7 +295,7 @@ class FalcoTest(Test):
res = process.run("docker rm falco-test")
elif self.package.endswith(".deb"):
cmdline = "dpkg --purge falco"
cmdline = "dpkg -r falco"
self.log.debug("Uninstalling debian package via \"{}\"".format(cmdline))
res = process.run(cmdline, timeout=120, sudo=True)
@@ -348,8 +347,8 @@ class FalcoTest(Test):
trace_arg = "-e {}".format(self.trace_file)
# Run falco
cmd = '{} {} {} -c {} {} -o json_output={} -o priority={} -v'.format(
self.falco_binary_path, self.rules_args, self.disabled_args, self.conf_file, trace_arg, self.json_output, self.priority)
cmd = '{} {} {} -c {} {} -o json_output={} -v'.format(
self.falco_binary_path, self.rules_args, self.disabled_args, self.conf_file, trace_arg, self.json_output)
for tag in self.disable_tags:
cmd += ' -T {}'.format(tag)

View File

@@ -129,21 +129,6 @@ trace_files: !mux
- rules/double_rule.yaml
trace_file: trace_files/cat_write.scap
multiple_rules_suppress_info:
detect: True
detect_level:
- WARNING
- ERROR
priority: WARNING
detect_counts:
- open_from_cat: 8
- exec_from_cat: 1
- access_from_cat: 0
rules_file:
- rules/single_rule.yaml
- rules/double_rule.yaml
trace_file: trace_files/cat_write.scap
multiple_rules_overriding:
detect: False
rules_file:

View File

@@ -149,7 +149,7 @@ traces: !mux
shell-in-container:
trace_file: traces-positive/shell-in-container.scap
detect: True
detect_level: DEBUG
detect_level: NOTICE
detect_counts:
- "Run shell in container": 1

View File

@@ -18,5 +18,5 @@ along with falco. If not, see <http://www.gnu.org/licenses/>.
#pragma once
#define FALCO_ENGINE_LUA_DIR "${FALCO_ABSOLUTE_SHARE_DIR}/lua/"
#define FALCO_ENGINE_LUA_DIR "${FALCO_SHARE_DIR}/lua/"
#define FALCO_ENGINE_SOURCE_LUA_DIR "${PROJECT_SOURCE_DIR}/../falco/userspace/engine/lua/"

View File

@@ -21,16 +21,6 @@ along with falco. If not, see <http://www.gnu.org/licenses/>.
#include "config_falco_engine.h"
#include "falco_common.h"
std::vector<std::string> falco_common::priority_names = {
"Emergency",
"Alert",
"Critical",
"Error",
"Warning",
"Notice",
"Informational",
"Debug"};
falco_common::falco_common()
{
m_ls = lua_open();

View File

@@ -74,22 +74,6 @@ public:
void set_inspector(sinsp *inspector);
// Priority levels, as a vector of strings
static std::vector<std::string> priority_names;
// Same as numbers/indices into the above vector
enum priority_type
{
PRIORITY_EMERGENCY = 0,
PRIORITY_ALERT = 1,
PRIORITY_CRITICAL = 2,
PRIORITY_ERROR = 3,
PRIORITY_WARNING = 4,
PRIORITY_NOTICE = 5,
PRIORITY_INFORMATIONAL = 6,
PRIORITY_DEBUG = 7
};
protected:
lua_State *m_ls;

View File

@@ -41,7 +41,6 @@ using namespace std;
falco_engine::falco_engine(bool seed_rng)
: m_rules(NULL), m_next_ruleset_id(0),
m_min_priority(falco_common::PRIORITY_DEBUG),
m_sampling_ratio(1), m_sampling_multiplier(0),
m_replace_container_info(false)
{
@@ -90,7 +89,7 @@ void falco_engine::load_rules(const string &rules_content, bool verbose, bool al
bool json_output = false;
falco_formats::init(m_inspector, m_ls, json_output);
m_rules->load_rules(rules_content, verbose, all_events, m_extra, m_replace_container_info, m_min_priority);
m_rules->load_rules(rules_content, verbose, all_events, m_extra, m_replace_container_info);
}
void falco_engine::load_rules_file(const string &rules_filename, bool verbose, bool all_events)
@@ -135,11 +134,6 @@ void falco_engine::enable_rule_by_tag(const set<string> &tags, bool enabled)
enable_rule_by_tag(tags, enabled, m_default_ruleset);
}
void falco_engine::set_min_priority(falco_common::priority_type priority)
{
m_min_priority = priority;
}
uint16_t falco_engine::find_ruleset_id(const std::string &ruleset)
{
auto it = m_known_rulesets.lower_bound(ruleset);
@@ -184,7 +178,7 @@ unique_ptr<falco_engine::rule_result> falco_engine::process_event(sinsp_evt *ev,
res->evt = ev;
const char *p = lua_tostring(m_ls, -3);
res->rule = p;
res->priority_num = (falco_common::priority_type) lua_tonumber(m_ls, -2);
res->priority = lua_tostring(m_ls, -2);
res->format = lua_tostring(m_ls, -1);
lua_pop(m_ls, 3);
}

View File

@@ -67,13 +67,10 @@ public:
// Wrapper that assumes the default ruleset
void enable_rule_by_tag(const std::set<std::string> &tags, bool enabled);
// Only load rules having this priority or more severe.
void set_min_priority(falco_common::priority_type priority);
struct rule_result {
sinsp_evt *evt;
std::string rule;
falco_common::priority_type priority_num;
std::string priority;
std::string format;
};
@@ -161,7 +158,6 @@ private:
uint16_t m_next_ruleset_id;
std::map<string, uint16_t> m_known_rulesets;
std::unique_ptr<sinsp_evttype_filter> m_evttype_filter;
falco_common::priority_type m_min_priority;
//
// Here's how the sampling ratio and multiplier influence

View File

@@ -92,7 +92,6 @@ int falco_formats::free_formatters(lua_State *ls)
int falco_formats::format_event (lua_State *ls)
{
string line;
string json_line;
if (!lua_isstring(ls, -1) ||
!lua_isstring(ls, -2) ||
@@ -110,20 +109,6 @@ int falco_formats::format_event (lua_State *ls)
try {
s_formatters->tostring(evt, sformat, &line);
if(s_json_output)
{
s_inspector->set_buffer_format(sinsp_evt::PF_JSON);
s_formatters->tostring(evt, sformat, &json_line);
// The formatted string might have a leading newline. If it does, remove it.
if (json_line[0] == '\n')
{
json_line.erase(0, 1);
}
s_inspector->set_buffer_format(sinsp_evt::PF_NORMAL);
}
}
catch (sinsp_exception& e)
{
@@ -132,15 +117,13 @@ int falco_formats::format_event (lua_State *ls)
lua_error(ls);
}
// For JSON output, the formatter returned a json-as-text
// object containing all the fields in the original format
// message as well as the event time in ns. Use this to build
// a more detailed object containing the event time, rule,
// severity, full output, and fields.
// For JSON output, the formatter returned just the output
// string containing the format text and values. Use this to
// build a more detailed object containing the event time,
// rule, severity, full output, and fields.
if (s_json_output) {
Json::Value event;
Json::FastWriter writer;
string full_line;
// Convert the time-as-nanoseconds to a more json-friendly ISO8601.
time_t evttime = evt->get_ts()/1000000000;
@@ -155,26 +138,16 @@ int falco_formats::format_event (lua_State *ls)
event["time"] = iso8601evttime;
event["rule"] = rule;
event["priority"] = level;
// This is the filled-in output line.
event["output"] = line;
full_line = writer.write(event);
line = writer.write(event);
// Json::FastWriter may add a trailing newline. If it
// does, remove it.
if (full_line[full_line.length()-1] == '\n')
if (line[line.length()-1] == '\n')
{
full_line.resize(full_line.length()-1);
line.resize(line.length()-1);
}
// Cheat-graft the output from the formatter into this
// string. Avoids an unnecessary json parse just to
// merge the formatted fields at the object level.
full_line.pop_back();
full_line.append(", \"output_fields\": ");
full_line.append(json_line);
full_line.append("}");
line = full_line;
}
lua_pushstring(ls, line.c_str());

View File

@@ -58,17 +58,6 @@ function map(f, arr)
return res
end
priorities = {"Emergency", "Alert", "Critical", "Error", "Warning", "Notice", "Informational", "Debug"}
local function priority_num_for(s)
s = string.lower(s)
for i,v in ipairs(priorities) do
if (string.find(string.lower(v), "^"..s)) then
return i - 1 -- (numbers start at 0, lua indices start at 1)
end
end
error("Invalid priority level: "..s)
end
--[[
Take a filter AST and set it up in the libsinsp runtime, using the filter API.
@@ -182,7 +171,7 @@ function table.tostring( tbl )
end
function load_rules(rules_content, rules_mgr, verbose, all_events, extra, replace_container_info, min_priority)
function load_rules(rules_content, rules_mgr, verbose, all_events, extra, replace_container_info)
compiler.set_verbose(verbose)
compiler.set_all_events(all_events)
@@ -304,23 +293,18 @@ function load_rules(rules_content, rules_mgr, verbose, all_events, extra, replac
end
end
-- Convert the priority-as-string to a priority-as-number now
v['priority_num'] = priority_num_for(v['priority'])
if v['priority_num'] <= min_priority then
-- Note that we can overwrite rules, but the rules are still
-- loaded in the order in which they first appeared,
-- potentially across multiple files.
if state.rules_by_name[v['rule']] == nil then
state.ordered_rule_names[#state.ordered_rule_names+1] = v['rule']
end
-- The output field might be a folded-style, which adds a
-- newline to the end. Remove any trailing newlines.
v['output'] = compiler.trim(v['output'])
state.rules_by_name[v['rule']] = v
-- Note that we can overwrite rules, but the rules are still
-- loaded in the order in which they first appeared,
-- potentially across multiple files.
if state.rules_by_name[v['rule']] == nil then
state.ordered_rule_names[#state.ordered_rule_names+1] = v['rule']
end
-- The output field might be a folded-style, which adds a
-- newline to the end. Remove any trailing newlines.
v['output'] = compiler.trim(v['output'])
state.rules_by_name[v['rule']] = v
end
else
error ("Unknown rule object: "..table.tostring(v))
@@ -520,7 +504,7 @@ function on_event(evt_, rule_id)
-- Prefix output with '*' so formatting is permissive
output = "*"..rule.output
return rule.rule, rule.priority_num, output
return rule.rule, rule.priority, output
end
function print_stats()

View File

@@ -145,8 +145,7 @@ void falco_rules::enable_rule(string &rule, bool enabled)
void falco_rules::load_rules(const string &rules_content,
bool verbose, bool all_events,
string &extra, bool replace_container_info,
falco_common::priority_type min_priority)
string &extra, bool replace_container_info)
{
lua_getglobal(m_ls, m_lua_load_rules.c_str());
if(lua_isfunction(m_ls, -1))
@@ -222,8 +221,7 @@ void falco_rules::load_rules(const string &rules_content,
lua_pushboolean(m_ls, (all_events ? 1 : 0));
lua_pushstring(m_ls, extra.c_str());
lua_pushboolean(m_ls, (replace_container_info ? 1 : 0));
lua_pushnumber(m_ls, min_priority);
if(lua_pcall(m_ls, 7, 0, 0) != 0)
if(lua_pcall(m_ls, 6, 0, 0) != 0)
{
const char* lerr = lua_tostring(m_ls, -1);
string err = "Error loading rules:" + string(lerr);

View File

@@ -24,8 +24,6 @@ along with falco. If not, see <http://www.gnu.org/licenses/>.
#include "lua_parser.h"
#include "falco_common.h"
class falco_engine;
class falco_rules
@@ -34,8 +32,7 @@ class falco_rules
falco_rules(sinsp* inspector, falco_engine *engine, lua_State *ls);
~falco_rules();
void load_rules(const string &rules_content, bool verbose, bool all_events,
std::string &extra, bool replace_container_info,
falco_common::priority_type min_priority);
std::string &extra, bool replace_container_info);
void describe_rule(string *rule);
static void init(lua_State *ls);

View File

@@ -5,7 +5,7 @@
#define FALCO_LUA_DIR "${CMAKE_INSTALL_PREFIX}/${FALCO_SHARE_DIR}/lua/"
#define FALCO_SOURCE_DIR "${PROJECT_SOURCE_DIR}"
#define FALCO_SOURCE_CONF_FILE "${PROJECT_SOURCE_DIR}/falco.yaml"
#define FALCO_INSTALL_CONF_FILE "/etc/falco/falco.yaml"
#define FALCO_INSTALL_CONF_FILE "/etc/falco.yaml"
#define FALCO_SOURCE_LUA_DIR "${PROJECT_SOURCE_DIR}/userspace/falco/lua/"
#define PROBE_NAME "${PROBE_NAME}"

View File

@@ -22,8 +22,7 @@ along with falco. If not, see <http://www.gnu.org/licenses/>.
using namespace std;
falco_configuration::falco_configuration()
: m_buffered_outputs(true),
m_config(NULL)
: m_config(NULL)
{
}
@@ -52,37 +51,20 @@ void falco_configuration::init(string conf_filename, list<string> &cmdline_optio
init_cmdline_options(cmdline_options);
list<string> rules_files;
m_config->get_sequence<list<string>>(rules_files, string("rules_file"));
for(auto &file : rules_files)
{
// Here, we only include files that exist
struct stat buffer;
if(stat(file.c_str(), &buffer) == 0)
{
m_rules_filenames.push_back(file);
}
}
m_rules_filenames.push_back(m_config->get_scalar<string>("rules_file", "/etc/falco_rules.yaml"));
m_json_output = m_config->get_scalar<bool>("json_output", false);
falco_outputs::output_config file_output;
file_output.name = "file";
if (m_config->get_scalar<bool>("file_output", "enabled", false))
{
string filename, keep_alive;
string filename;
filename = m_config->get_scalar<string>("file_output", "filename", "");
if (filename == string(""))
{
throw invalid_argument("Error reading config file (" + m_config_file + "): file output enabled but no filename in configuration block");
}
file_output.options["filename"] = filename;
keep_alive = m_config->get_scalar<string>("file_output", "keep_alive", "");
file_output.options["keep_alive"] = keep_alive;
m_outputs.push_back(file_output);
}
@@ -104,17 +86,13 @@ void falco_configuration::init(string conf_filename, list<string> &cmdline_optio
program_output.name = "program";
if (m_config->get_scalar<bool>("program_output", "enabled", false))
{
string program, keep_alive;
string program;
program = m_config->get_scalar<string>("program_output", "program", "");
if (program == string(""))
{
throw sinsp_exception("Error reading config file (" + m_config_file + "): program output enabled but no program in configuration block");
}
program_output.options["program"] = program;
keep_alive = m_config->get_scalar<string>("program_output", "keep_alive", "");
program_output.options["keep_alive"] = keep_alive;
m_outputs.push_back(program_output);
}
@@ -130,21 +108,6 @@ void falco_configuration::init(string conf_filename, list<string> &cmdline_optio
m_notifications_rate = m_config->get_scalar<uint32_t>("outputs", "rate", 1);
m_notifications_max_burst = m_config->get_scalar<uint32_t>("outputs", "max_burst", 1000);
string priority = m_config->get_scalar<string>("priority", "debug");
vector<string>::iterator it;
auto comp = [priority] (string &s) {
return (strcasecmp(s.c_str(), priority.c_str()) == 0);
};
if((it = std::find_if(falco_common::priority_names.begin(), falco_common::priority_names.end(), comp)) == falco_common::priority_names.end())
{
throw invalid_argument("Unknown priority \"" + priority + "\"--must be one of emergency, alert, critical, error, warning, notice, informational, debug");
}
m_min_priority = (falco_common::priority_type) (it - falco_common::priority_names.begin());
m_buffered_outputs = m_config->get_scalar<bool>("buffered_outputs", true);
falco_logger::log_stderr = m_config->get_scalar<bool>("log_stderr", false);
falco_logger::log_syslog = m_config->get_scalar<bool>("log_syslog", true);
}

View File

@@ -18,9 +18,6 @@ along with falco. If not, see <http://www.gnu.org/licenses/>.
#pragma once
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <yaml-cpp/yaml.h>
#include <string>
#include <vector>
@@ -130,27 +127,6 @@ public:
}
}
// called with the last variadic arg (where the sequence is expected to be found)
template <typename T>
void get_sequence(T& ret, const std::string& name)
{
YAML::Node child_node = m_root[name];
if(child_node.IsDefined())
{
if(child_node.IsSequence())
{
for(const YAML::Node& item : child_node)
{
ret.insert(ret.end(), item.as<typename T::value_type>());
}
}
else if(child_node.IsScalar())
{
ret.insert(ret.end(), child_node.as<typename T::value_type>());
}
}
}
private:
YAML::Node m_root;
};
@@ -170,10 +146,6 @@ class falco_configuration
std::vector<falco_outputs::output_config> m_outputs;
uint32_t m_notifications_rate;
uint32_t m_notifications_max_burst;
falco_common::priority_type m_min_priority;
bool m_buffered_outputs;
private:
void init_cmdline_options(std::list<std::string> &cmdline_options);

View File

@@ -107,11 +107,6 @@ static void usage()
" Can not be specified with -t.\n"
" -t <tag> Only run those rules with a tag=<tag>. Can be specified multiple times.\n"
" Can not be specified with -T/-D.\n"
" -U,--unbuffered Turn off output buffering to configured outputs. This causes every\n"
" single line emitted by falco to be flushed, which generates higher CPU\n"
" usage but is useful when piping those outputs into another process\n"
" or into a script.\n"
" -V,--validate <rules_file> Read the contents of the specified rules file and exit\n"
" -v Verbose output.\n"
" --version Print version number.\n"
"\n"
@@ -217,7 +212,7 @@ uint64_t do_inspect(falco_engine *engine,
unique_ptr<falco_engine::rule_result> res = engine->process_event(ev);
if(res)
{
outputs->handle_event(res->evt, res->rule, res->priority_num, res->format);
outputs->handle_event(res->evt, res->rule, res->priority, res->format);
}
num_evts++;
@@ -245,7 +240,6 @@ int falco_init(int argc, char **argv)
string pidfilename = "/var/run/falco.pid";
bool describe_all_rules = false;
string describe_rule = "";
string validate_rules_file = "";
string stats_filename = "";
bool verbose = false;
bool all_events = false;
@@ -262,8 +256,6 @@ int falco_init(int argc, char **argv)
int file_limit = 0;
unsigned long event_limit = 0L;
bool compress = false;
bool buffered_outputs = true;
bool buffered_cmdline = false;
// Used for stats
uint64_t num_evts;
@@ -280,9 +272,7 @@ int falco_init(int argc, char **argv)
{"option", required_argument, 0, 'o'},
{"print", required_argument, 0, 'p' },
{"pidfile", required_argument, 0, 'P' },
{"unbuffered", no_argument, 0, 'U' },
{"version", no_argument, 0, 0 },
{"validate", required_argument, 0, 0 },
{"writefile", required_argument, 0, 'w' },
{0, 0, 0, 0}
@@ -300,7 +290,7 @@ int falco_init(int argc, char **argv)
// Parse the args
//
while((op = getopt_long(argc, argv,
"hc:AdD:e:k:K:Ll:m:M:o:P:p:r:s:T:t:UvV:w:",
"hc:AdD:e:k:K:Ll:m:M:o:P:p:r:s:T:t:vw:",
long_options, &long_index)) != -1)
{
switch(op)
@@ -388,16 +378,9 @@ int falco_init(int argc, char **argv)
case 't':
enabled_rule_tags.insert(optarg);
break;
case 'U':
buffered_outputs = false;
buffered_cmdline = true;
break;
case 'v':
verbose = true;
break;
case 'V':
validate_rules_file = optarg;
break;
case 'w':
outfile = optarg;
break;
@@ -460,14 +443,6 @@ int falco_init(int argc, char **argv)
}
}
if(validate_rules_file != "")
{
falco_logger::log(LOG_INFO, "Validating rules file: " + validate_rules_file + "...\n");
engine->load_rules_file(validate_rules_file, verbose, all_events);
falco_logger::log(LOG_INFO, "Ok\n");
goto exit;
}
falco_configuration config;
if (conf_filename.size())
{
@@ -486,18 +461,6 @@ int falco_init(int argc, char **argv)
config.m_rules_filenames = rules_filenames;
}
engine->set_min_priority(config.m_min_priority);
if(buffered_cmdline)
{
config.m_buffered_outputs = buffered_outputs;
}
if(config.m_rules_filenames.size() == 0)
{
throw std::invalid_argument("You must specify at least one rules file via -r or a rules_file entry in falco.yaml");
}
for (auto filename : config.m_rules_filenames)
{
engine->load_rules_file(filename, verbose, all_events);
@@ -538,9 +501,7 @@ int falco_init(int argc, char **argv)
engine->enable_rule_by_tag(enabled_rule_tags, true);
}
outputs->init(config.m_json_output,
config.m_notifications_rate, config.m_notifications_max_burst,
config.m_buffered_outputs);
outputs->init(config.m_json_output, config.m_notifications_rate, config.m_notifications_max_burst);
if(!all_events)
{

View File

@@ -27,8 +27,7 @@ along with falco. If not, see <http://www.gnu.org/licenses/>.
using namespace std;
falco_outputs::falco_outputs()
: m_initialized(false),
m_buffered(true)
: m_initialized(false)
{
}
@@ -52,7 +51,7 @@ falco_outputs::~falco_outputs()
}
}
void falco_outputs::init(bool json_output, uint32_t rate, uint32_t max_burst, bool buffered)
void falco_outputs::init(bool json_output, uint32_t rate, uint32_t max_burst)
{
// The engine must have been given an inspector by now.
if(! m_inspector)
@@ -71,14 +70,12 @@ void falco_outputs::init(bool json_output, uint32_t rate, uint32_t max_burst, bo
m_notifications_tb.init(rate, max_burst);
m_buffered = buffered;
m_initialized = true;
}
void falco_outputs::add_output(output_config oc)
{
uint8_t nargs = 2;
uint8_t nargs = 1;
lua_getglobal(m_ls, m_lua_add_output.c_str());
if(!lua_isfunction(m_ls, -1))
@@ -86,12 +83,11 @@ void falco_outputs::add_output(output_config oc)
throw falco_exception("No function " + m_lua_add_output + " found. ");
}
lua_pushstring(m_ls, oc.name.c_str());
lua_pushnumber(m_ls, (m_buffered ? 1 : 0));
// If we have options, build up a lua table containing them
if (oc.options.size())
{
nargs = 3;
nargs = 2;
lua_createtable(m_ls, 0, oc.options.size());
for (auto it = oc.options.cbegin(); it != oc.options.cend(); ++it)
@@ -109,7 +105,7 @@ void falco_outputs::add_output(output_config oc)
}
void falco_outputs::handle_event(sinsp_evt *ev, string &rule, falco_common::priority_type priority, string &format)
void falco_outputs::handle_event(sinsp_evt *ev, string &rule, string &priority, string &format)
{
if(!m_notifications_tb.claim())
{
@@ -123,11 +119,10 @@ void falco_outputs::handle_event(sinsp_evt *ev, string &rule, falco_common::prio
{
lua_pushlightuserdata(m_ls, ev);
lua_pushstring(m_ls, rule.c_str());
lua_pushstring(m_ls, falco_common::priority_names[priority].c_str());
lua_pushnumber(m_ls, priority);
lua_pushstring(m_ls, priority.c_str());
lua_pushstring(m_ls, format.c_str());
if(lua_pcall(m_ls, 5, 0, 0) != 0)
if(lua_pcall(m_ls, 4, 0, 0) != 0)
{
const char* lerr = lua_tostring(m_ls, -1);
string err = "Error invoking function output: " + string(lerr);

View File

@@ -41,7 +41,7 @@ public:
std::map<std::string, std::string> options;
};
void init(bool json_output, uint32_t rate, uint32_t max_burst, bool buffered);
void init(bool json_output, uint32_t rate, uint32_t max_burst);
void add_output(output_config oc);
@@ -49,7 +49,7 @@ public:
// ev is an event that has matched some rule. Pass the event
// to all configured outputs.
//
void handle_event(sinsp_evt *ev, std::string &rule, falco_common::priority_type priority, std::string &format);
void handle_event(sinsp_evt *ev, std::string &rule, std::string &priority, std::string &format);
private:
bool m_initialized;
@@ -57,8 +57,6 @@ private:
// Rate limits notifications
token_bucket m_notifications_tb;
bool m_buffered;
std::string m_lua_add_output = "add_output";
std::string m_lua_output_event = "output_event";
std::string m_lua_output_cleanup = "output_cleanup";

View File

@@ -18,25 +18,22 @@
local mod = {}
levels = {"Emergency", "Alert", "Critical", "Error", "Warning", "Notice", "Informational", "Debug"}
mod.levels = levels
local outputs = {}
function mod.stdout(priority, priority_num, buffered, msg)
if buffered == 0 then
io.stdout:setvbuf 'no'
end
function mod.stdout(level, msg)
print (msg)
end
function mod.stdout_cleanup()
io.stdout:flush()
end
function mod.file_validate(options)
if (not type(options.filename) == 'string') then
error("File output needs to be configured with a valid filename")
end
local file, err = io.open(options.filename, "a+")
file, err = io.open(options.filename, "a+")
if file == nil then
error("Error with file output: "..err)
end
@@ -44,100 +41,60 @@ function mod.file_validate(options)
end
function mod.file(priority, priority_num, buffered, msg, options)
if options.keep_alive == "true" then
if file == nil then
file = io.open(options.filename, "a+")
if buffered == 0 then
file:setvbuf 'no'
end
end
else
file = io.open(options.filename, "a+")
end
function mod.file(level, msg, options)
file = io.open(options.filename, "a+")
file:write(msg, "\n")
if options.keep_alive == nil or
options.keep_alive ~= "true" then
file:close()
file = nil
end
file:close()
end
function mod.file_cleanup()
if file ~= nil then
file:flush()
file:close()
file = nil
end
function mod.syslog(level, msg, options)
falco.syslog(level, msg)
end
function mod.syslog(priority, priority_num, buffered, msg, options)
falco.syslog(priority_num, msg)
end
function mod.syslog_cleanup()
end
function mod.program(priority, priority_num, buffered, msg, options)
function mod.program(level, msg, options)
-- XXX Ideally we'd check that the program ran
-- successfully. However, the luajit we're using returns true even
-- when the shell can't run the program.
-- Note: options are all strings
if options.keep_alive == "true" then
if file == nil then
file = io.popen(options.program, "w")
if buffered == 0 then
file:setvbuf 'no'
end
end
else
file = io.popen(options.program, "w")
end
file = io.popen(options.program, "w")
file:write(msg, "\n")
if options.keep_alive == nil or
options.keep_alive ~= "true" then
file:close()
file = nil
end
file:close()
end
function mod.program_cleanup()
if file ~= nil then
file:flush()
file:close()
file = nil
local function level_of(s)
s = string.lower(s)
for i,v in ipairs(levels) do
if (string.find(string.lower(v), "^"..s)) then
return i - 1 -- (syslog levels start at 0, lua indices start at 1)
end
end
error("Invalid severity level: "..s)
end
function output_event(event, rule, priority, priority_num, format)
function output_event(event, rule, priority, format)
local level = level_of(priority)
-- If format starts with a *, remove it, as we're adding our own
-- prefix here.
if format:sub(1,1) == "*" then
format = format:sub(2)
end
format = "*%evt.time: "..priority.." "..format
format = "*%evt.time: "..levels[level+1].." "..format
msg = formats.format_event(event, rule, priority, format)
msg = formats.format_event(event, rule, levels[level+1], format)
for index,o in ipairs(outputs) do
o.output(priority, priority_num, o.buffered, msg, o.config)
o.output(level, msg, o.config)
end
end
function output_cleanup()
formats.free_formatters()
for index,o in ipairs(outputs) do
o.cleanup()
end
end
function add_output(output_name, buffered, config)
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
@@ -148,9 +105,7 @@ function add_output(output_name, buffered, config)
mod[output_name.."_validate"](config)
end
table.insert(outputs, {output = mod[output_name],
cleanup = mod[output_name.."_cleanup"],
buffered=buffered, config=config})
table.insert(outputs, {output = mod[output_name], config=config})
end
return mod