mirror of
https://github.com/falcosecurity/falco.git
synced 2026-03-20 11:42:06 +00:00
Compare commits
66 Commits
agent/0.79
...
0.12.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e38f130cc | ||
|
|
eaaff5a773 | ||
|
|
81e2e672f0 | ||
|
|
071e7dff17 | ||
|
|
e8ba42cae4 | ||
|
|
470710366b | ||
|
|
24ca38a819 | ||
|
|
ab0413a9ee | ||
|
|
6acb13e6bb | ||
|
|
fdbe62fdae | ||
|
|
d63542d8ff | ||
|
|
7289315837 | ||
|
|
25efce033b | ||
|
|
8bc4a5e38f | ||
|
|
c05319927a | ||
|
|
1e32d637b2 | ||
|
|
ccf35552dd | ||
|
|
ec0c109d2a | ||
|
|
46b0fd833c | ||
|
|
bed5993500 | ||
|
|
bed360497e | ||
|
|
3afe04629a | ||
|
|
bebdff3d67 | ||
|
|
9543514270 | ||
|
|
46405510e2 | ||
|
|
42285687d4 | ||
|
|
8b82a08148 | ||
|
|
19d251ef4b | ||
|
|
66ba09ea3b | ||
|
|
4867c47d4b | ||
|
|
526f32b54b | ||
|
|
26ca866162 | ||
|
|
893554e0f0 | ||
|
|
c5523d89a7 | ||
|
|
81dcee23a9 | ||
|
|
81a38fb909 | ||
|
|
e9e9bd85c3 | ||
|
|
70f768d9ea | ||
|
|
c3b0f0d96d | ||
|
|
2a7851c77b | ||
|
|
512a36dfe1 | ||
|
|
73e1ae616a | ||
|
|
b496116fe3 | ||
|
|
2a0911dcfd | ||
|
|
af57f2b5c8 | ||
|
|
30ae3447c3 | ||
|
|
9d3392e9b9 | ||
|
|
6be4830342 | ||
|
|
e6bf402117 | ||
|
|
e922a849a9 | ||
|
|
b6b490e26e | ||
|
|
ac190ca457 | ||
|
|
96b4ff0ee5 | ||
|
|
5c58da2604 | ||
|
|
c5b3097a65 | ||
|
|
8389e44d7b | ||
|
|
a5daf8b058 | ||
|
|
a0053dba18 | ||
|
|
94df00e512 | ||
|
|
3ee76637f4 | ||
|
|
e8aee19f6c | ||
|
|
74556e5f6e | ||
|
|
809d20c294 | ||
|
|
b0ae29c23a | ||
|
|
d1b6b2be87 | ||
|
|
e00181d553 |
@@ -10,7 +10,7 @@ before_install:
|
||||
- sudo apt-get update
|
||||
install:
|
||||
- sudo apt-get --force-yes install g++-4.8
|
||||
- sudo apt-get install rpm linux-headers-$(uname -r)
|
||||
- sudo apt-get install rpm linux-headers-$(uname -r) libelf-dev
|
||||
- git clone https://github.com/draios/sysdig.git ../sysdig
|
||||
- sudo apt-get install -y python-pip libvirt-dev jq dkms
|
||||
- cd ..
|
||||
|
||||
114
CHANGELOG.md
114
CHANGELOG.md
@@ -2,6 +2,120 @@
|
||||
|
||||
This file documents all notable changes to Falco. The release numbering uses [semantic versioning](http://semver.org).
|
||||
|
||||
## v0.12.0
|
||||
|
||||
Released 2018-09-11
|
||||
|
||||
## Major Changes
|
||||
|
||||
* Improved IPv6 Support to fully support use of IPv6 addresses in events, connections and filters [[#sysdig/1204](https://github.com/draios/sysdig/pull/1204)]
|
||||
|
||||
* Ability to associate connections with dns names: new filterchecks `fd.*ip.name` allow looking up the DNS name for a connection's IP address. This can be used to identify or restrict connections by dns names e.g. `evt.type=connect and fd.sip.name=github.com`. [[#412](https://github.com/draios/falco/pull/412)] [[#sysdig/1213](https://github.com/draios/sysdig/pull/1213)]
|
||||
|
||||
* New filterchecks `user.loginuid` and `user.loginname` can be used to match the login uid, which stays consistent across sudo/su. This can be used to find the actual user running a given process [[#sysdig/1189](https://github.com/draios/sysdig/pull/1189)]
|
||||
|
||||
## Minor Changes
|
||||
|
||||
* Upgrade zlib to 1.2.11, openssl to 1.0.2n, and libcurl to 7.60.0 to address software vulnerabilities [[#402](https://github.com/draios/falco/pull/402)]
|
||||
* New `endswith` operator can be used for suffix matching on strings [[#sysdig/1209](https://github.com/draios/sysdig/pull/1209)]
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
* Better control of specifying location of lua source code [[#406](https://github.com/draios/falco/pull/406)]
|
||||
|
||||
## Rule Changes
|
||||
|
||||
* None for this release.
|
||||
|
||||
## v0.11.1
|
||||
|
||||
Released 2018-07-31
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
* Fix a problem that caused the kernel module to not load on certain kernel versions [[#397](https://github.com/draios/falco/pull/397)] [[#394](https://github.com/draios/falco/issues/394)]
|
||||
|
||||
## v0.11.0
|
||||
|
||||
Released 2018-07-24
|
||||
|
||||
## Major Changes
|
||||
|
||||
* **EBPF Support** (Beta): Falco can now read events via an ebpf program loaded into the kernel instead of the `falco-probe` kernel module. Full docs [here](https://github.com/draios/sysdig/wiki/eBPF-(beta)). [[#365](https://github.com/draios/falco/pull/365)]
|
||||
|
||||
## Minor Changes
|
||||
|
||||
* Rules may now have an `skip-if-unknown-filter` property. If set to true, a rule will be skipped if its condition/output property refers to a filtercheck (e.g. `fd.some-new-attibute`) that is not present in the current falco version. [[#364](https://github.com/draios/falco/pull/364)] [[#345](https://github.com/draios/falco/issues/345)]
|
||||
* Small changes to Falco `COPYING` file so github automatically recognizes license [[#380](https://github.com/draios/falco/pull/380)]
|
||||
* New example integration showing how to connect Falco with Anchore to dynamically create falco rules based on negative scan results [[#390](https://github.com/draios/falco/pull/390)]
|
||||
* New example integration showing how to connect Falco, [nats](https://nats.io/), and K8s to run flexible "playbooks" based on Falco events [[#389](https://github.com/draios/falco/pull/389)]
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
* Ensure all rules are enabled by default [[#379](https://github.com/draios/falco/pull/379)]
|
||||
* Fix libcurl compilation problems [[#374](https://github.com/draios/falco/pull/374)]
|
||||
* Add gcc-6 to docker container, which improves compatibility when building kernel module [[#382](https://github.com/draios/falco/pull/382)] [[#371](https://github.com/draios/falco/issues/371)]
|
||||
* Ensure the /lib/modules symlink to /host/lib/modules is set correctly [[#392](https://github.com/draios/falco/issues/392)]
|
||||
|
||||
## Rule Changes
|
||||
|
||||
* Add additional binary writing programs [[#366](https://github.com/draios/falco/pull/366)]
|
||||
* Add additional package management programs [[#388](https://github.com/draios/falco/pull/388)] [[#366](https://github.com/draios/falco/pull/366)]
|
||||
* Expand write_below_etc handling for additional programs [[#388](https://github.com/draios/falco/pull/388)] [[#366](https://github.com/draios/falco/pull/366)]
|
||||
* Expand set of programs allowed to write to `/etc/pki` [[#388](https://github.com/draios/falco/pull/388)]
|
||||
* Expand set of root written directories/files [[#388](https://github.com/draios/falco/pull/388)] [[#366](https://github.com/draios/falco/pull/366)]
|
||||
* Let pam-config read sensitive files [[#388](https://github.com/draios/falco/pull/388)]
|
||||
* Add additional trusted containers: openshift, datadog, docker ucp agent, gliderlabs logspout [[#388](https://github.com/draios/falco/pull/388)]
|
||||
* Let coreos update-ssh-keys write to /home/core/.ssh [[#388](https://github.com/draios/falco/pull/388)]
|
||||
* Expand coverage for MS OMS [[#388](https://github.com/draios/falco/issues/388)] [[#387](https://github.com/draios/falco/issues/387)]
|
||||
* Expand the set of shell spawning programs [[#366](https://github.com/draios/falco/pull/366)]
|
||||
* Add additional mysql programs/directories [[#366](https://github.com/draios/falco/pull/366)]
|
||||
* Let program `id` open network connections [[#366](https://github.com/draios/falco/pull/366)]
|
||||
* Opt-in rule for protecting tomcat shell spawns [[#366](https://github.com/draios/falco/pull/366)]
|
||||
* New rule `Write below monitored directory` [[#366](https://github.com/draios/falco/pull/366)]
|
||||
|
||||
## v0.10.0
|
||||
|
||||
Released 2018-04-24
|
||||
|
||||
## Major Changes
|
||||
|
||||
* **Rules Directory Support**: Falco will read rules files from `/etc/falco/rules.d` in addition to `/etc/falco/falco_rules.yaml` and `/etc/falco/falco_rules.local.yaml`. Also, when the argument to `-r`/falco.yaml `rules_file` is a directory, falco will read rules files from that directory. [[#348](https://github.com/draios/falco/pull/348)] [[#187](https://github.com/draios/falco/issues/187)]
|
||||
* Properly support all syscalls (e.g. those without parameter extraction by the kernel module) in falco conditions, so they can be included in `evt.type=<name>` conditions. [[#352](https://github.com/draios/falco/pull/352)]
|
||||
* When packaged as a container, start building kernel module with gcc 5.0 instead of gcc 4.9. [[#331](https://github.com/draios/falco/pull/331)]
|
||||
* New example puppet module for falco. [[#341](https://github.com/draios/falco/pull/341)] [[#115](https://github.com/draios/falco/issues/115)]
|
||||
* When signaled with `USR1`, falco will close/reopen log files. Include a [logrotate](https://github.com/logrotate/logrotate) example that shows how to use this feature for log rotation. [[#347](https://github.com/draios/falco/pull/347)] [[#266](https://github.com/draios/falco/issues/266)]
|
||||
* To improve resource usage, further restrict the set of system calls available to falco [[#351](https://github.com/draios/falco/pull/351)] [[draios/sysdig#1105](https://github.com/draios/sysdig/pull/1105)]
|
||||
|
||||
## Minor Changes
|
||||
|
||||
* Add gdb to the development Docker image (sysdig/falco:dev) to aid in debugging. [[#323](https://github.com/draios/falco/pull/323)]
|
||||
* You can now specify -V multiple times on the command line to validate multiple rules files at once. [[#329](https://github.com/draios/falco/pull/329)]
|
||||
* When run with `-v`, falco will print *dangling* macros/lists that are not used by any rules. [[#329](https://github.com/draios/falco/pull/329)]
|
||||
* Add an example demonstrating cryptomining attack that exploits an open docker daemon using host mounts. [[#336](https://github.com/draios/falco/pull/336)]
|
||||
* New falco.yaml option `json_include_output_property` controls whether the formatted string "output" is included in the json object when json output is enabled. [[#342](https://github.com/draios/falco/pull/342)]
|
||||
* Centralize testing event types for consideration by falco into a single function [[draios/sysdig#1105](https://github.com/draios/sysdig/pull/1105)) [[#356](https://github.com/draios/falco/pull/356)]
|
||||
* If a rule has an attribute `warn_evttypes`, falco will not complain about `evt.type` restrictions on that rule [[#355](https://github.com/draios/falco/pull/355)]
|
||||
* When run with `-i`, print all ignored events/syscalls and exit. [[#359](https://github.com/draios/falco/pull/359)]
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
* Minor bug fixes to k8s daemonset configuration. [[#325](https://github.com/draios/falco/pull/325)] [[#296](https://github.com/draios/falco/pull/296)] [[#295](https://github.com/draios/falco/pull/295)]
|
||||
* Ensure `--validate` can be used interchangeably with `-V`. [[#334](https://github.com/draios/falco/pull/334)] [[#322](https://github.com/draios/falco/issues/322)]
|
||||
* Rule conditions like `fd.net` can now be used with the `in` operator e.g. `evt.type=connect and fd.net in ("127.0.0.1/24")`. [[draios/sysdig#1091](https://github.com/draios/sysdig/pull/1091)] [[#343](https://github.com/draios/falco/pull/343)]
|
||||
* Ensure that `keep_alive` can be used both with file and program output at the same time. [[#335](https://github.com/draios/falco/pull/335)]
|
||||
* Make it possible to append to a skipped macro/rule without falco complaining [[#346](https://github.com/draios/falco/pull/346)] [[#305](https://github.com/draios/falco/issues/305)]
|
||||
* Ensure rule order is preserved even when rules do not contain any `evt.type` restriction. [[#354](https://github.com/draios/falco/issues/354)] [[#355](https://github.com/draios/falco/pull/355)]
|
||||
|
||||
## Rule Changes
|
||||
|
||||
* Make it easier to extend the `Change thread namespace` rule via a `user_known_change_thread_namespace_binaries` list. [[#324](https://github.com/draios/falco/pull/324)]
|
||||
* Various FP fixes from users. [[#321](https://github.com/draios/falco/pull/321)] [[#326](https://github.com/draios/falco/pull/326)] [[#344](https://github.com/draios/falco/pull/344)] [[#350](https://github.com/draios/falco/pull/350)]
|
||||
* New rule `Disallowed SSH Connection` detects attempts ssh connection attempts to hosts outside of an expected set. In order to be effective, you need to override the macro `allowed_ssh_hosts` in a user rules file. [[#321](https://github.com/draios/falco/pull/321)]
|
||||
* New rule `Unexpected K8s NodePort Connection` detects attempts to contact the K8s NodePort range from a program running inside a container. In order to be effective, you need to override the macro `nodeport_containers` in a user rules file. [[#321](https://github.com/draios/falco/pull/321)]
|
||||
* Improve `Modify binary dirs` rule to work with new syscalls [[#353](https://github.com/draios/falco/pull/353)]
|
||||
* New rule `Unexpected UDP Traffic` checks for udp traffic not on a list of expected ports. Somewhat FP-prone, so it must be explicitly enabled by overriding the macro `do_unexpected_udp_check` in a user rules file. [[#320](https://github.com/draios/falco/pull/320)] [[#357](https://github.com/draios/falco/pull/357)]
|
||||
|
||||
## v0.9.0
|
||||
|
||||
Released 2018-01-18
|
||||
|
||||
@@ -78,8 +78,10 @@ else()
|
||||
set(ZLIB_INCLUDE "${ZLIB_SRC}")
|
||||
set(ZLIB_LIB "${ZLIB_SRC}/libz.a")
|
||||
ExternalProject_Add(zlib
|
||||
URL "http://s3.amazonaws.com/download.draios.com/dependencies/zlib-1.2.8.tar.gz"
|
||||
URL_MD5 "44d667c142d7cda120332623eab69f40"
|
||||
# START CHANGE for CVE-2016-9840, CVE-2016-9841, CVE-2016-9842, CVE-2016-9843
|
||||
URL "http://s3.amazonaws.com/download.draios.com/dependencies/zlib-1.2.11.tar.gz"
|
||||
URL_MD5 "1c9f62f0778697a09d36121ead88e08e"
|
||||
# END CHANGE for CVE-2016-9840, CVE-2016-9841, CVE-2016-9842, CVE-2016-9843
|
||||
CONFIGURE_COMMAND "./configure"
|
||||
BUILD_COMMAND ${CMD_MAKE}
|
||||
BUILD_IN_SOURCE 1
|
||||
@@ -215,8 +217,10 @@ else()
|
||||
message(STATUS "Using bundled openssl in '${OPENSSL_BUNDLE_DIR}'")
|
||||
|
||||
ExternalProject_Add(openssl
|
||||
URL "http://s3.amazonaws.com/download.draios.com/dependencies/openssl-1.0.2j.tar.gz"
|
||||
URL_MD5 "96322138f0b69e61b7212bc53d5e912b"
|
||||
# START CHANGE for CVE-2017-3735, CVE-2017-3731, CVE-2017-3737, CVE-2017-3738, CVE-2017-3736
|
||||
URL "http://s3.amazonaws.com/download.draios.com/dependencies/openssl-1.0.2n.tar.gz"
|
||||
URL_MD5 "13bdc1b1d1ff39b6fd42a255e74676a4"
|
||||
# END CHANGE for CVE-2017-3735, CVE-2017-3731, CVE-2017-3737, CVE-2017-3738, CVE-2017-3736
|
||||
CONFIGURE_COMMAND ./config shared --prefix=${OPENSSL_INSTALL_DIR}
|
||||
BUILD_COMMAND ${CMD_MAKE}
|
||||
BUILD_IN_SOURCE 1
|
||||
@@ -246,8 +250,10 @@ else()
|
||||
|
||||
ExternalProject_Add(curl
|
||||
DEPENDS openssl
|
||||
URL "http://s3.amazonaws.com/download.draios.com/dependencies/curl-7.56.0.tar.bz2"
|
||||
URL_MD5 "e0caf257103e0c77cee5be7e9ac66ca4"
|
||||
# START CHANGE for CVE-2017-8816, CVE-2017-8817, CVE-2017-8818, CVE-2018-1000007
|
||||
URL "http://s3.amazonaws.com/download.draios.com/dependencies/curl-7.60.0.tar.bz2"
|
||||
URL_MD5 "bd2aabf78ded6a9aec8a54532fd6b5d7"
|
||||
# END CHANGE for CVE-2017-8816, CVE-2017-8817, CVE-2017-8818, CVE-2018-1000007
|
||||
CONFIGURE_COMMAND ./configure ${CURL_SSL_OPTION} --disable-shared --enable-optimize --disable-curldebug --disable-rt --enable-http --disable-ftp --disable-file --disable-ldap --disable-ldaps --disable-rtsp --disable-telnet --disable-tftp --disable-pop3 --disable-imap --disable-smb --disable-smtp --disable-gopher --disable-sspi --disable-ntlm-wb --disable-tls-srp --without-winssl --without-darwinssl --without-polarssl --without-cyassl --without-nss --without-axtls --without-ca-path --without-ca-bundle --without-libmetalink --without-librtmp --without-winidn --without-libidn --without-nghttp2 --without-libssh2 --disable-threaded-resolver
|
||||
BUILD_COMMAND ${CMD_MAKE}
|
||||
BUILD_IN_SOURCE 1
|
||||
@@ -389,6 +395,32 @@ else()
|
||||
INSTALL_COMMAND sh -c "cp -R ${PROJECT_BINARY_DIR}/lyaml-prefix/src/lyaml/lib/* ${PROJECT_SOURCE_DIR}/userspace/engine/lua")
|
||||
endif()
|
||||
|
||||
option(USE_BUNDLED_TBB "Enable building of the bundled tbb" ${USE_BUNDLED_DEPS})
|
||||
if(NOT USE_BUNDLED_TBB)
|
||||
find_path(TBB_INCLUDE tbb.h PATH_SUFFIXES tbb)
|
||||
find_library(TBB_LIB NAMES tbb)
|
||||
if(TBB_INCLUDE AND TBB_LIB)
|
||||
message(STATUS "Found tbb: include: ${TBB_INCLUDE}, lib: ${TBB_LIB}")
|
||||
else()
|
||||
message(FATAL_ERROR "Couldn't find system tbb")
|
||||
endif()
|
||||
else()
|
||||
set(TBB_SRC "${PROJECT_BINARY_DIR}/tbb-prefix/src/tbb")
|
||||
|
||||
message(STATUS "Using bundled tbb in '${TBB_SRC}'")
|
||||
|
||||
set(TBB_INCLUDE "${TBB_SRC}/include/")
|
||||
set(TBB_LIB "${TBB_SRC}/build/lib_release/libtbb.a")
|
||||
ExternalProject_Add(tbb
|
||||
URL "http://s3.amazonaws.com/download.draios.com/dependencies/tbb-2018_U5.tar.gz"
|
||||
URL_MD5 "ff3ae09f8c23892fbc3008c39f78288f"
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ${CMD_MAKE} tbb_build_dir=${TBB_SRC}/build tbb_build_prefix=lib extra_inc=big_iron.inc
|
||||
BUILD_IN_SOURCE 1
|
||||
BUILD_BYPRODUCTS ${TBB_LIB}
|
||||
INSTALL_COMMAND "")
|
||||
endif()
|
||||
|
||||
install(FILES falco.yaml
|
||||
DESTINATION "${FALCO_ETC_DIR}")
|
||||
|
||||
|
||||
12
COPYING
12
COPYING
@@ -277,18 +277,6 @@ YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
* In addition, as a special exception, the copyright holders give
|
||||
* permission to link the code of portions of this program with the
|
||||
* OpenSSL library under certain conditions as described in each
|
||||
* individual source file, and distribute linked combinations
|
||||
* including the two.
|
||||
* You must obey the GNU General Public License in all respects
|
||||
* for all of the code used other than OpenSSL. If you modify
|
||||
* file(s) with this exception, you may extend this exception to your
|
||||
* version of the file(s), but you are not obligated to do so. If you
|
||||
* do not wish to do so, delete this exception statement from your
|
||||
* version.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#### Latest release
|
||||
|
||||
**v0.9.0**
|
||||
**v0.12.0**
|
||||
Read the [change log](https://github.com/draios/falco/blob/dev/CHANGELOG.md)
|
||||
|
||||
Dev Branch: [](https://travis-ci.org/draios/falco)<br />
|
||||
@@ -41,6 +41,10 @@ License Terms
|
||||
---
|
||||
Falco is licensed to you under the [GPL 2.0](./COPYING) open source license.
|
||||
|
||||
In addition, as a special exception, the copyright holders give permission to link the code of portions of this program with the OpenSSL library under certain conditions as described in each individual source file, and distribute linked combinations including the two.
|
||||
|
||||
You must obey the GNU General Public License in all respects for all of the code used other than OpenSSL. If you modify file(s) with this exception, you may extend this exception to your version of the file(s), but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version.
|
||||
|
||||
Contributor License Agreements
|
||||
---
|
||||
### Background
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/etc/falco/falco.yaml
|
||||
/etc/falco/falco_rules.yaml
|
||||
/etc/falco/application_rules.yaml
|
||||
/etc/falco/rules.available/application_rules.yaml
|
||||
/etc/falco/falco_rules.local.yaml
|
||||
|
||||
@@ -17,18 +17,30 @@ ADD http://download.draios.com/apt-draios-priority /etc/apt/preferences.d/
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
bash-completion \
|
||||
curl \
|
||||
jq \
|
||||
gnupg2 \
|
||||
bc \
|
||||
clang-7 \
|
||||
ca-certificates \
|
||||
curl \
|
||||
gnupg2 \
|
||||
gcc \
|
||||
gcc-5 \
|
||||
gdb && rm -rf /var/lib/apt/lists/*
|
||||
gcc-6 \
|
||||
gdb \
|
||||
jq \
|
||||
libc6-dev \
|
||||
libelf-dev \
|
||||
llvm-7 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Since our base Debian image ships with GCC 7 which breaks older kernels, revert the
|
||||
# default to gcc-5.
|
||||
RUN rm -rf /usr/bin/gcc && ln -s /usr/bin/gcc-5 /usr/bin/gcc
|
||||
|
||||
RUN rm -rf /usr/bin/clang \
|
||||
&& rm -rf /usr/bin/llc \
|
||||
&& ln -s /usr/bin/clang-7 /usr/bin/clang \
|
||||
&& ln -s /usr/bin/llc-7 /usr/bin/llc
|
||||
|
||||
RUN curl -s https://s3.amazonaws.com/download.draios.com/DRAIOS-GPG-KEY.public | apt-key add - \
|
||||
&& curl -s -o /etc/apt/sources.list.d/draios.list http://download.draios.com/$FALCO_REPOSITORY/deb/draios.list \
|
||||
&& apt-get update \
|
||||
@@ -36,7 +48,20 @@ RUN curl -s https://s3.amazonaws.com/download.draios.com/DRAIOS-GPG-KEY.public |
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN ln -s $SYSDIG_HOST_ROOT/lib/modules /lib/modules
|
||||
# Some base images have an empty /lib/modules by default
|
||||
# If it's not empty, docker build will fail instead of
|
||||
# silently overwriting the existing directory
|
||||
RUN rm -df /lib/modules \
|
||||
&& ln -s $SYSDIG_HOST_ROOT/lib/modules /lib/modules
|
||||
|
||||
# debian:unstable head contains binutils 2.31, which generates
|
||||
# binaries that are incompatible with kernels < 4.16. So manually
|
||||
# forcibly install binutils 2.30-22 instead.
|
||||
RUN curl -s -o binutils_2.30-22_amd64.deb http://snapshot.debian.org/archive/debian/20180622T211149Z/pool/main/b/binutils/binutils_2.30-22_amd64.deb \
|
||||
&& curl -s -o libbinutils_2.30-22_amd64.deb http://snapshot.debian.org/archive/debian/20180622T211149Z/pool/main/b/binutils/libbinutils_2.30-22_amd64.deb \
|
||||
&& curl -s -o binutils-x86-64-linux-gnu_2.30-22_amd64.deb http://snapshot.debian.org/archive/debian/20180622T211149Z/pool/main/b/binutils/binutils-x86-64-linux-gnu_2.30-22_amd64.deb \
|
||||
&& curl -s -o binutils-common_2.30-22_amd64.deb http://snapshot.debian.org/archive/debian/20180622T211149Z/pool/main/b/binutils/binutils-common_2.30-22_amd64.deb \
|
||||
&& dpkg -i *binutils*.deb
|
||||
|
||||
COPY ./docker-entrypoint.sh /
|
||||
|
||||
|
||||
@@ -296,23 +296,21 @@ void system_user_interactive() {
|
||||
}
|
||||
|
||||
void network_activity() {
|
||||
printf("Opening a listening socket on port 8192...\n");
|
||||
printf("Connecting a udp socket to 10.2.3.4:8192...\n");
|
||||
int rc;
|
||||
int sock = socket(PF_INET, SOCK_DGRAM, 0);
|
||||
struct sockaddr_in localhost;
|
||||
|
||||
localhost.sin_family = AF_INET;
|
||||
localhost.sin_port = htons(8192);
|
||||
inet_aton("127.0.0.1", &(localhost.sin_addr));
|
||||
inet_aton("10.2.3.4", &(localhost.sin_addr));
|
||||
|
||||
if((rc = bind(sock, (struct sockaddr *) &localhost, sizeof(localhost))) != 0)
|
||||
if((rc = connect(sock, (struct sockaddr *) &localhost, sizeof(localhost))) != 0)
|
||||
{
|
||||
fprintf(stderr, "Could not bind listening socket to localhost: %s\n", strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
listen(sock, 1);
|
||||
|
||||
close(sock);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,23 +17,48 @@ ADD http://download.draios.com/apt-draios-priority /etc/apt/preferences.d/
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
bash-completion \
|
||||
curl \
|
||||
jq \
|
||||
gnupg2 \
|
||||
bc \
|
||||
clang-7 \
|
||||
ca-certificates \
|
||||
curl \
|
||||
dkms \
|
||||
gnupg2 \
|
||||
gcc \
|
||||
gcc-5 \
|
||||
dkms && rm -rf /var/lib/apt/lists/*
|
||||
gcc-6 \
|
||||
jq \
|
||||
libc6-dev \
|
||||
libelf-dev \
|
||||
llvm-7 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Since our base Debian image ships with GCC 7 which breaks older kernels, revert the
|
||||
# default to gcc-5.
|
||||
RUN rm -rf /usr/bin/gcc && ln -s /usr/bin/gcc-5 /usr/bin/gcc
|
||||
|
||||
RUN ln -s $SYSDIG_HOST_ROOT/lib/modules /lib/modules
|
||||
RUN rm -rf /usr/bin/clang \
|
||||
&& rm -rf /usr/bin/llc \
|
||||
&& ln -s /usr/bin/clang-7 /usr/bin/clang \
|
||||
&& ln -s /usr/bin/llc-7 /usr/bin/llc
|
||||
|
||||
# Some base images have an empty /lib/modules by default
|
||||
# If it's not empty, docker build will fail instead of
|
||||
# silently overwriting the existing directory
|
||||
RUN rm -df /lib/modules \
|
||||
&& ln -s $SYSDIG_HOST_ROOT/lib/modules /lib/modules
|
||||
|
||||
ADD falco-${FALCO_VERSION}-x86_64.deb /
|
||||
RUN dpkg -i /falco-${FALCO_VERSION}-x86_64.deb
|
||||
|
||||
# debian:unstable head contains binutils 2.31, which generates
|
||||
# binaries that are incompatible with kernels < 4.16. So manually
|
||||
# forcibly install binutils 2.30-22 instead.
|
||||
RUN curl -s -o binutils_2.30-22_amd64.deb http://snapshot.debian.org/archive/debian/20180622T211149Z/pool/main/b/binutils/binutils_2.30-22_amd64.deb \
|
||||
&& curl -s -o libbinutils_2.30-22_amd64.deb http://snapshot.debian.org/archive/debian/20180622T211149Z/pool/main/b/binutils/libbinutils_2.30-22_amd64.deb \
|
||||
&& curl -s -o binutils-x86-64-linux-gnu_2.30-22_amd64.deb http://snapshot.debian.org/archive/debian/20180622T211149Z/pool/main/b/binutils/binutils-x86-64-linux-gnu_2.30-22_amd64.deb \
|
||||
&& curl -s -o binutils-common_2.30-22_amd64.deb http://snapshot.debian.org/archive/debian/20180622T211149Z/pool/main/b/binutils/binutils-common_2.30-22_amd64.deb \
|
||||
&& dpkg -i *binutils*.deb
|
||||
|
||||
COPY ./docker-entrypoint.sh /
|
||||
|
||||
ENTRYPOINT ["/docker-entrypoint.sh"]
|
||||
|
||||
@@ -17,17 +17,29 @@ ADD http://download.draios.com/apt-draios-priority /etc/apt/preferences.d/
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
bash-completion \
|
||||
curl \
|
||||
jq \
|
||||
bc \
|
||||
clang-7 \
|
||||
ca-certificates \
|
||||
curl \
|
||||
gnupg2 \
|
||||
gcc \
|
||||
gcc-5 && rm -rf /var/lib/apt/lists/*
|
||||
gcc-5 \
|
||||
gcc-6 \
|
||||
jq \
|
||||
libc6-dev \
|
||||
libelf-dev \
|
||||
llvm-7 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Since our base Debian image ships with GCC 7 which breaks older kernels, revert the
|
||||
# default to gcc-5.
|
||||
RUN rm -rf /usr/bin/gcc && ln -s /usr/bin/gcc-5 /usr/bin/gcc
|
||||
|
||||
RUN rm -rf /usr/bin/clang \
|
||||
&& rm -rf /usr/bin/llc \
|
||||
&& ln -s /usr/bin/clang-7 /usr/bin/clang \
|
||||
&& ln -s /usr/bin/llc-7 /usr/bin/llc
|
||||
|
||||
RUN curl -s https://s3.amazonaws.com/download.draios.com/DRAIOS-GPG-KEY.public | apt-key add - \
|
||||
&& curl -s -o /etc/apt/sources.list.d/draios.list http://download.draios.com/$FALCO_REPOSITORY/deb/draios.list \
|
||||
&& apt-get update \
|
||||
@@ -35,7 +47,20 @@ RUN curl -s https://s3.amazonaws.com/download.draios.com/DRAIOS-GPG-KEY.public |
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN ln -s $SYSDIG_HOST_ROOT/lib/modules /lib/modules
|
||||
# Some base images have an empty /lib/modules by default
|
||||
# If it's not empty, docker build will fail instead of
|
||||
# silently overwriting the existing directory
|
||||
RUN rm -df /lib/modules \
|
||||
&& ln -s $SYSDIG_HOST_ROOT/lib/modules /lib/modules
|
||||
|
||||
# debian:unstable head contains binutils 2.31, which generates
|
||||
# binaries that are incompatible with kernels < 4.16. So manually
|
||||
# forcibly install binutils 2.30-22 instead.
|
||||
RUN curl -s -o binutils_2.30-22_amd64.deb http://snapshot.debian.org/archive/debian/20180622T211149Z/pool/main/b/binutils/binutils_2.30-22_amd64.deb \
|
||||
&& curl -s -o libbinutils_2.30-22_amd64.deb http://snapshot.debian.org/archive/debian/20180622T211149Z/pool/main/b/binutils/libbinutils_2.30-22_amd64.deb \
|
||||
&& curl -s -o binutils-x86-64-linux-gnu_2.30-22_amd64.deb http://snapshot.debian.org/archive/debian/20180622T211149Z/pool/main/b/binutils/binutils-x86-64-linux-gnu_2.30-22_amd64.deb \
|
||||
&& curl -s -o binutils-common_2.30-22_amd64.deb http://snapshot.debian.org/archive/debian/20180622T211149Z/pool/main/b/binutils/binutils-common_2.30-22_amd64.deb \
|
||||
&& dpkg -i *binutils*.deb
|
||||
|
||||
COPY ./docker-entrypoint.sh /
|
||||
|
||||
|
||||
14
falco.yaml
14
falco.yaml
@@ -1,4 +1,7 @@
|
||||
# File(s) containing Falco rules, loaded at startup.
|
||||
# File(s) or Directories containing Falco rules, loaded at startup.
|
||||
# The name "rules_file" is only for backwards compatibility.
|
||||
# If the entry is a file, it will be read directly. If the entry is a directory,
|
||||
# every file in that directory will be read, in alphabetical order.
|
||||
#
|
||||
# falco_rules.yaml ships with the falco package and is overridden with
|
||||
# every new software version. falco_rules.local.yaml is only created
|
||||
@@ -10,6 +13,7 @@
|
||||
rules_file:
|
||||
- /etc/falco/falco_rules.yaml
|
||||
- /etc/falco/falco_rules.local.yaml
|
||||
- /etc/falco/rules.d
|
||||
|
||||
# Whether to output events in json or text
|
||||
json_output: false
|
||||
@@ -66,6 +70,10 @@ syslog_output:
|
||||
# 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.
|
||||
#
|
||||
# Also, the file will be closed and reopened if falco is signaled with
|
||||
# SIGUSR1.
|
||||
|
||||
file_output:
|
||||
enabled: false
|
||||
keep_alive: false
|
||||
@@ -86,7 +94,9 @@ stdout_output:
|
||||
# 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.
|
||||
|
||||
#
|
||||
# Also, the program will be closed and reopened if falco is signaled with
|
||||
# SIGUSR1.
|
||||
program_output:
|
||||
enabled: false
|
||||
keep_alive: false
|
||||
|
||||
13
integrations/anchore-falco/Dockerfile
Normal file
13
integrations/anchore-falco/Dockerfile
Normal file
@@ -0,0 +1,13 @@
|
||||
FROM python:3-stretch
|
||||
|
||||
RUN pip install pipenv
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
ADD Pipfile /app/Pipfile
|
||||
ADD Pipfile.lock /app/Pipfile.lock
|
||||
RUN pipenv install --system --deploy
|
||||
|
||||
ADD . /app
|
||||
|
||||
CMD ["python", "main.py"]
|
||||
16
integrations/anchore-falco/Pipfile
Normal file
16
integrations/anchore-falco/Pipfile
Normal file
@@ -0,0 +1,16 @@
|
||||
[[source]]
|
||||
url = "https://pypi.python.org/simple"
|
||||
verify_ssl = true
|
||||
name = "pypi"
|
||||
|
||||
[dev-packages]
|
||||
doublex-expects = "==0.7.0rc2"
|
||||
doublex = "*"
|
||||
mamba = "*"
|
||||
expects = "*"
|
||||
|
||||
[packages]
|
||||
requests = "*"
|
||||
|
||||
[requires]
|
||||
python_version = "3.6"
|
||||
156
integrations/anchore-falco/Pipfile.lock
generated
Normal file
156
integrations/anchore-falco/Pipfile.lock
generated
Normal file
@@ -0,0 +1,156 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "f2737a14e8f562cf355e13ae09f1eed0f80415effd2aa01b86125e94523da345"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
"python_version": "3.6"
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
"name": "pypi",
|
||||
"url": "https://pypi.python.org/simple",
|
||||
"verify_ssl": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
"sha256:13e698f54293db9f89122b0581843a782ad0934a4fe0172d2a980ba77fc61bb7",
|
||||
"sha256:9fa520c1bacfb634fa7af20a76bcbd3d5fb390481724c597da32c719a7dca4b0"
|
||||
],
|
||||
"version": "==2018.4.16"
|
||||
},
|
||||
"chardet": {
|
||||
"hashes": [
|
||||
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
|
||||
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
|
||||
],
|
||||
"version": "==3.0.4"
|
||||
},
|
||||
"idna": {
|
||||
"hashes": [
|
||||
"sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e",
|
||||
"sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16"
|
||||
],
|
||||
"version": "==2.7"
|
||||
},
|
||||
"requests": {
|
||||
"hashes": [
|
||||
"sha256:63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1",
|
||||
"sha256:ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.19.1"
|
||||
},
|
||||
"urllib3": {
|
||||
"hashes": [
|
||||
"sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf",
|
||||
"sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5"
|
||||
],
|
||||
"version": "==1.23"
|
||||
}
|
||||
},
|
||||
"develop": {
|
||||
"args": {
|
||||
"hashes": [
|
||||
"sha256:a785b8d837625e9b61c39108532d95b85274acd679693b71ebb5156848fcf814"
|
||||
],
|
||||
"version": "==0.1.0"
|
||||
},
|
||||
"clint": {
|
||||
"hashes": [
|
||||
"sha256:05224c32b1075563d0b16d0015faaf9da43aa214e4a2140e51f08789e7a4c5aa"
|
||||
],
|
||||
"version": "==0.5.1"
|
||||
},
|
||||
"coverage": {
|
||||
"hashes": [
|
||||
"sha256:03481e81d558d30d230bc12999e3edffe392d244349a90f4ef9b88425fac74ba",
|
||||
"sha256:0b136648de27201056c1869a6c0d4e23f464750fd9a9ba9750b8336a244429ed",
|
||||
"sha256:104ab3934abaf5be871a583541e8829d6c19ce7bde2923b2751e0d3ca44db60a",
|
||||
"sha256:15b111b6a0f46ee1a485414a52a7ad1d703bdf984e9ed3c288a4414d3871dcbd",
|
||||
"sha256:198626739a79b09fa0a2f06e083ffd12eb55449b5f8bfdbeed1df4910b2ca640",
|
||||
"sha256:1c383d2ef13ade2acc636556fd544dba6e14fa30755f26812f54300e401f98f2",
|
||||
"sha256:28b2191e7283f4f3568962e373b47ef7f0392993bb6660d079c62bd50fe9d162",
|
||||
"sha256:2eb564bbf7816a9d68dd3369a510be3327f1c618d2357fa6b1216994c2e3d508",
|
||||
"sha256:337ded681dd2ef9ca04ef5d93cfc87e52e09db2594c296b4a0a3662cb1b41249",
|
||||
"sha256:3a2184c6d797a125dca8367878d3b9a178b6fdd05fdc2d35d758c3006a1cd694",
|
||||
"sha256:3c79a6f7b95751cdebcd9037e4d06f8d5a9b60e4ed0cd231342aa8ad7124882a",
|
||||
"sha256:3d72c20bd105022d29b14a7d628462ebdc61de2f303322c0212a054352f3b287",
|
||||
"sha256:3eb42bf89a6be7deb64116dd1cc4b08171734d721e7a7e57ad64cc4ef29ed2f1",
|
||||
"sha256:4635a184d0bbe537aa185a34193898eee409332a8ccb27eea36f262566585000",
|
||||
"sha256:56e448f051a201c5ebbaa86a5efd0ca90d327204d8b059ab25ad0f35fbfd79f1",
|
||||
"sha256:5a13ea7911ff5e1796b6d5e4fbbf6952381a611209b736d48e675c2756f3f74e",
|
||||
"sha256:69bf008a06b76619d3c3f3b1983f5145c75a305a0fea513aca094cae5c40a8f5",
|
||||
"sha256:6bc583dc18d5979dc0f6cec26a8603129de0304d5ae1f17e57a12834e7235062",
|
||||
"sha256:701cd6093d63e6b8ad7009d8a92425428bc4d6e7ab8d75efbb665c806c1d79ba",
|
||||
"sha256:7608a3dd5d73cb06c531b8925e0ef8d3de31fed2544a7de6c63960a1e73ea4bc",
|
||||
"sha256:76ecd006d1d8f739430ec50cc872889af1f9c1b6b8f48e29941814b09b0fd3cc",
|
||||
"sha256:7aa36d2b844a3e4a4b356708d79fd2c260281a7390d678a10b91ca595ddc9e99",
|
||||
"sha256:7d3f553904b0c5c016d1dad058a7554c7ac4c91a789fca496e7d8347ad040653",
|
||||
"sha256:7e1fe19bd6dce69d9fd159d8e4a80a8f52101380d5d3a4d374b6d3eae0e5de9c",
|
||||
"sha256:8c3cb8c35ec4d9506979b4cf90ee9918bc2e49f84189d9bf5c36c0c1119c6558",
|
||||
"sha256:9d6dd10d49e01571bf6e147d3b505141ffc093a06756c60b053a859cb2128b1f",
|
||||
"sha256:9e112fcbe0148a6fa4f0a02e8d58e94470fc6cb82a5481618fea901699bf34c4",
|
||||
"sha256:ac4fef68da01116a5c117eba4dd46f2e06847a497de5ed1d64bb99a5fda1ef91",
|
||||
"sha256:b8815995e050764c8610dbc82641807d196927c3dbed207f0a079833ffcf588d",
|
||||
"sha256:be6cfcd8053d13f5f5eeb284aa8a814220c3da1b0078fa859011c7fffd86dab9",
|
||||
"sha256:c1bb572fab8208c400adaf06a8133ac0712179a334c09224fb11393e920abcdd",
|
||||
"sha256:de4418dadaa1c01d497e539210cb6baa015965526ff5afc078c57ca69160108d",
|
||||
"sha256:e05cb4d9aad6233d67e0541caa7e511fa4047ed7750ec2510d466e806e0255d6",
|
||||
"sha256:e4d96c07229f58cb686120f168276e434660e4358cc9cf3b0464210b04913e77",
|
||||
"sha256:f3f501f345f24383c0000395b26b726e46758b71393267aeae0bd36f8b3ade80",
|
||||
"sha256:f8a923a85cb099422ad5a2e345fe877bbc89a8a8b23235824a93488150e45f6e"
|
||||
],
|
||||
"version": "==4.5.1"
|
||||
},
|
||||
"doublex": {
|
||||
"hashes": [
|
||||
"sha256:062af49d9e4148bc47b7512d3fdc8e145dea4671d074ffd54b2464a19d3757ab"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.8.4"
|
||||
},
|
||||
"doublex-expects": {
|
||||
"hashes": [
|
||||
"sha256:5421bd92319c77ccc5a81d595d06e9c9f7f670de342b33e8007a81e70f9fade8"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.7.0rc2"
|
||||
},
|
||||
"expects": {
|
||||
"hashes": [
|
||||
"sha256:37538d7b0fa9c0d53e37d07b0e8c07d89754d3deec1f0f8ed1be27f4f10363dd"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.8.0"
|
||||
},
|
||||
"mamba": {
|
||||
"hashes": [
|
||||
"sha256:63e70a8666039cf143a255000e23f29be4ea4b5b8169f2b053f94eb73a2ea9e2"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.9.3"
|
||||
},
|
||||
"pyhamcrest": {
|
||||
"hashes": [
|
||||
"sha256:6b672c02fdf7470df9674ab82263841ce8333fb143f32f021f6cb26f0e512420",
|
||||
"sha256:7a4bdade0ed98c699d728191a058a60a44d2f9c213c51e2dd1e6fb42f2c6128a",
|
||||
"sha256:8ffaa0a53da57e89de14ced7185ac746227a8894dbd5a3c718bf05ddbd1d56cd",
|
||||
"sha256:bac0bea7358666ce52e3c6c85139632ed89f115e9af52d44b3c36e0bf8cf16a9",
|
||||
"sha256:f30e9a310bcc1808de817a92e95169ffd16b60cbc5a016a49c8d0e8ababfae79"
|
||||
],
|
||||
"version": "==1.9.0"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
"sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",
|
||||
"sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"
|
||||
],
|
||||
"version": "==1.11.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
89
integrations/anchore-falco/README.md
Normal file
89
integrations/anchore-falco/README.md
Normal file
@@ -0,0 +1,89 @@
|
||||
# Create Falco rule from Anchore policy result
|
||||
|
||||
This integration creates a rule for Sysdig Falco based on Anchore policy result.
|
||||
So that when we will try to run an image which has a ```stop``` final action result
|
||||
in Anchore, Falco will alert us.
|
||||
|
||||
## Getting started
|
||||
|
||||
### Prerequisites
|
||||
|
||||
For running this integration you will need:
|
||||
|
||||
* Python 3.6
|
||||
* pipenv
|
||||
* An [anchore-engine](https://github.com/anchore/anchore-engine) running
|
||||
|
||||
### Configuration
|
||||
|
||||
This integration uses the [same environment variables that anchore-cli](https://github.com/anchore/anchore-cli#configuring-the-anchore-cli):
|
||||
|
||||
* ANCHORE_CLI_USER: The user used to conect to anchore-engine. By default is ```admin```
|
||||
* ANCHORE_CLI_PASS: The password used to connect to anchore-engine.
|
||||
* ANCHORE_CLI_URL: The url where anchore-engine listens. Make sure does not end with a slash. By default is ```http://localhost:8228/v1```
|
||||
* ANCHORE_CLI_SSL_VERIFY: Flag for enabling if HTTP client verifies SSL. By default is ```true```
|
||||
|
||||
### Running
|
||||
|
||||
This is a Python program which generates a Falco rule based on anchore-engine
|
||||
information:
|
||||
|
||||
```
|
||||
pipenv run python main.py
|
||||
```
|
||||
|
||||
And this will output something like:
|
||||
|
||||
|
||||
```yaml
|
||||
- macro: anchore_stop_policy_evaluation_containers
|
||||
condition: container.image.id in ("8626492fecd368469e92258dfcafe055f636cb9cbc321a5865a98a0a6c99b8dd", "e86d9bb526efa0b0401189d8df6e3856d0320a3d20045c87b4e49c8a8bdb22c1")
|
||||
|
||||
- rule: Run Anchore Containers with Stop Policy Evaluation
|
||||
desc: Detect containers which does not receive a positive Policy Evaluation from Anchore Engine.
|
||||
|
||||
condition: evt.type=execve and proc.vpid=1 and container and anchore_stop_policy_evaluation_containers
|
||||
output: A stop policy evaluation container from anchore has started (%container.info image=%container.image)
|
||||
priority: INFO
|
||||
tags: [container]
|
||||
```
|
||||
|
||||
You can save that output to ```/etc/falco/rules.d/anchore-integration-rules.yaml```
|
||||
and Falco will start checking this rule.
|
||||
|
||||
As long as information in anchore-engine can change, it's a good idea to run this
|
||||
integration **periodically** and keep the rule synchronized with anchore-engine
|
||||
policy evaluation result.
|
||||
|
||||
## Tests
|
||||
|
||||
As long as there are contract tests with anchore-engine, it needs a working
|
||||
anchore-engine and its environment variables.
|
||||
|
||||
```
|
||||
pipenv install -d
|
||||
pipenv run mamba --format=documentation
|
||||
```
|
||||
|
||||
## Docker support
|
||||
|
||||
### Build the image
|
||||
|
||||
```
|
||||
docker build -t sysdig/anchore-falco .
|
||||
```
|
||||
|
||||
### Running the image
|
||||
|
||||
An image exists on DockerHub, its name is ```sysdig/anchore-falco```.
|
||||
|
||||
So you can run directly with Docker:
|
||||
|
||||
```
|
||||
docker run --rm -e ANCHORE_CLI_USER=<user-for-custom-anchore-engine> \
|
||||
-e ANCHORE_CLI_PASS=<passsword-for-user-for-custom-anchore-engine> \
|
||||
-e ANCHORE_CLI_URL=http://<custom-anchore-engine-host>:8228/v1 \
|
||||
sysdig/anchore-falco
|
||||
```
|
||||
|
||||
And this will output the Falco rule based on *custom-anchore-engine-host*.
|
||||
25
integrations/anchore-falco/actions.py
Normal file
25
integrations/anchore-falco/actions.py
Normal file
@@ -0,0 +1,25 @@
|
||||
import string
|
||||
|
||||
FALCO_RULE_TEMPLATE = string.Template('''
|
||||
- macro: anchore_stop_policy_evaluation_containers
|
||||
condition: container.image.id in ($images)
|
||||
|
||||
- rule: Run Anchore Containers with Stop Policy Evaluation
|
||||
desc: Detect containers which does not receive a positive Policy Evaluation from Anchore Engine.
|
||||
|
||||
condition: evt.type=execve and proc.vpid=1 and container and anchore_stop_policy_evaluation_containers
|
||||
output: A stop policy evaluation container from anchore has started (%container.info image=%container.image)
|
||||
priority: INFO
|
||||
tags: [container]
|
||||
''')
|
||||
|
||||
|
||||
class CreateFalcoRuleFromAnchoreStopPolicyResults:
|
||||
def __init__(self, anchore_client):
|
||||
self._anchore_client = anchore_client
|
||||
|
||||
def run(self):
|
||||
images = self._anchore_client.get_images_with_policy_result('stop')
|
||||
|
||||
images = ['"{}"'.format(image) for image in images]
|
||||
return FALCO_RULE_TEMPLATE.substitute(images=', '.join(images))
|
||||
39
integrations/anchore-falco/infrastructure.py
Normal file
39
integrations/anchore-falco/infrastructure.py
Normal file
@@ -0,0 +1,39 @@
|
||||
import requests
|
||||
|
||||
|
||||
class AnchoreClient:
|
||||
def __init__(self, user, password, url, ssl_verify):
|
||||
self._user = user
|
||||
self._password = password
|
||||
self._url = url
|
||||
self._ssl_verify = ssl_verify
|
||||
|
||||
def get_images_with_policy_result(self, policy_result):
|
||||
results = []
|
||||
for image in self._get_all_images():
|
||||
final_action = self._evaluate_image(image)
|
||||
|
||||
if final_action == 'stop':
|
||||
results.append(image['image_id'])
|
||||
|
||||
return results
|
||||
|
||||
def _get_all_images(self):
|
||||
response = self._do_get_request(self._url + '/images')
|
||||
return [
|
||||
{
|
||||
'image_id': image['image_detail'][0]['imageId'],
|
||||
'image_digest': image['image_detail'][0]['imageDigest'],
|
||||
'full_tag': image['image_detail'][0]['fulltag']
|
||||
} for image in response.json()]
|
||||
|
||||
def _do_get_request(self, url):
|
||||
return requests.get(url,
|
||||
auth=(self._user, self._password),
|
||||
verify=self._ssl_verify,
|
||||
headers={'Content-Type': 'application/json'})
|
||||
|
||||
def _evaluate_image(self, image):
|
||||
response = self._do_get_request(self._url + '/images/{}/check?tag={}'.format(image['image_digest'], image['full_tag']))
|
||||
if response.status_code == 200:
|
||||
return response.json()[0][image['image_digest']][image['full_tag']][0]['detail']['result']['final_action']
|
||||
21
integrations/anchore-falco/main.py
Normal file
21
integrations/anchore-falco/main.py
Normal file
@@ -0,0 +1,21 @@
|
||||
import os
|
||||
|
||||
import actions, infrastructure
|
||||
|
||||
|
||||
def main():
|
||||
anchore_client = infrastructure.AnchoreClient(
|
||||
os.environ.get('ANCHORE_CLI_USER', 'admin'),
|
||||
os.environ['ANCHORE_CLI_PASS'],
|
||||
os.environ.get('ANCHORE_CLI_URL', 'http://localhost:8228/v1'),
|
||||
os.environ.get('ANCHORE_CLI_SSL_VERIFY', True)
|
||||
)
|
||||
action = actions.CreateFalcoRuleFromAnchoreStopPolicyResults(anchore_client)
|
||||
|
||||
result = action.run()
|
||||
|
||||
print(result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,21 @@
|
||||
from mamba import description, it, before
|
||||
from expects import expect, contain
|
||||
|
||||
from doublex import Stub, when
|
||||
|
||||
import actions
|
||||
import infrastructure
|
||||
|
||||
|
||||
with description(actions.CreateFalcoRuleFromAnchoreStopPolicyResults) as self:
|
||||
with before.each:
|
||||
self.anchore_client = Stub(infrastructure.AnchoreClient)
|
||||
self.action = actions.CreateFalcoRuleFromAnchoreStopPolicyResults(self.anchore_client)
|
||||
|
||||
with it('queries Anchore Server for images with Stop as policy results'):
|
||||
image_id = 'any image id'
|
||||
when(self.anchore_client).get_images_with_policy_result('stop').returns([image_id])
|
||||
|
||||
result = self.action.run()
|
||||
|
||||
expect(result).to(contain(image_id))
|
||||
@@ -0,0 +1,19 @@
|
||||
from mamba import description, it
|
||||
from expects import expect, have_length, be_above
|
||||
|
||||
import os
|
||||
|
||||
import infrastructure
|
||||
|
||||
|
||||
with description(infrastructure.AnchoreClient) as self:
|
||||
with it('retrieves images with stop policy results'):
|
||||
user = os.environ['ANCHORE_CLI_USER']
|
||||
password = os.environ['ANCHORE_CLI_PASS']
|
||||
url = os.environ['ANCHORE_CLI_URL']
|
||||
|
||||
client = infrastructure.AnchoreClient(user, password, url, True)
|
||||
|
||||
result = client.get_images_with_policy_result('stop')
|
||||
|
||||
expect(result).to(have_length(be_above(1)))
|
||||
18
integrations/kubernetes-response-engine/README.md
Normal file
18
integrations/kubernetes-response-engine/README.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# Kubernetes Response Engine for Sysdig Falco
|
||||
|
||||
A response engine for Falco that allows to process security events executing playbooks to respond to security threats.
|
||||
|
||||
## Architecture
|
||||
|
||||
* *[Falco](https://sysdig.com/opensource/falco/)* monitors containers and processes to alert on unexpected behavior. This is defined through the runtime policy built from multiple rules that define what the system should and shouldn't do.
|
||||
* *falco-nats* forwards the alert to a message broker service into a topic compound by `falco.<severity>.<rule_name_slugified>`.
|
||||
* *[NATS](https://nats.io/)*, our message broker, delivers the alert to any subscribers to the different topics.
|
||||
* *[Kubeless](https://kubeless.io/)*, a FaaS framework that runs in Kubernetes, receives the security events and executes the configured playbooks.
|
||||
|
||||
## Glossary
|
||||
|
||||
* *Security event*: Alert sent by Falco when a configured rule matches the behaviour on that host.
|
||||
* *Playbook*: Each piece code executed when an alert is received to respond to that threat in an automated way, some examples include:
|
||||
- sending an alert to Slack
|
||||
- stop the pod killing the container
|
||||
- taint the specific node where the pod is running
|
||||
@@ -0,0 +1,9 @@
|
||||
deploy:
|
||||
kubectl apply -f nats/
|
||||
kubectl apply -f kubeless/
|
||||
kubectl apply -f network-policy.yaml
|
||||
|
||||
clean:
|
||||
kubectl delete -f kubeless/
|
||||
kubectl delete -f nats/
|
||||
kubectl delete -f network-policy.yaml
|
||||
20
integrations/kubernetes-response-engine/deployment/README.md
Normal file
20
integrations/kubernetes-response-engine/deployment/README.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# Kubernetes Manifests for Kubernetes Response Engine
|
||||
|
||||
In this directory are the manifests for creating required infrastructure in the
|
||||
Kubernetes cluster
|
||||
|
||||
## Deploy
|
||||
|
||||
For deploying NATS, Falco + Falco-NATS output and Kubeless just run default Makefile target:
|
||||
|
||||
```
|
||||
make
|
||||
```
|
||||
|
||||
## Clean
|
||||
|
||||
You can clean your cluster with:
|
||||
|
||||
```
|
||||
make clean
|
||||
```
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: kubeless
|
||||
@@ -0,0 +1,369 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: controller-acct
|
||||
namespace: kubeless
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: kubeless-controller-deployer
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- services
|
||||
- configmaps
|
||||
verbs:
|
||||
- create
|
||||
- get
|
||||
- delete
|
||||
- list
|
||||
- update
|
||||
- patch
|
||||
- apiGroups:
|
||||
- apps
|
||||
- extensions
|
||||
resources:
|
||||
- deployments
|
||||
verbs:
|
||||
- create
|
||||
- get
|
||||
- delete
|
||||
- list
|
||||
- update
|
||||
- patch
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- pods
|
||||
verbs:
|
||||
- list
|
||||
- delete
|
||||
- apiGroups:
|
||||
- ""
|
||||
resourceNames:
|
||||
- kubeless-registry-credentials
|
||||
resources:
|
||||
- secrets
|
||||
verbs:
|
||||
- get
|
||||
- apiGroups:
|
||||
- kubeless.io
|
||||
resources:
|
||||
- functions
|
||||
- httptriggers
|
||||
- cronjobtriggers
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- update
|
||||
- delete
|
||||
- apiGroups:
|
||||
- batch
|
||||
resources:
|
||||
- cronjobs
|
||||
- jobs
|
||||
verbs:
|
||||
- create
|
||||
- get
|
||||
- delete
|
||||
- deletecollection
|
||||
- list
|
||||
- update
|
||||
- patch
|
||||
- apiGroups:
|
||||
- autoscaling
|
||||
resources:
|
||||
- horizontalpodautoscalers
|
||||
verbs:
|
||||
- create
|
||||
- get
|
||||
- delete
|
||||
- list
|
||||
- update
|
||||
- patch
|
||||
- apiGroups:
|
||||
- apiextensions.k8s.io
|
||||
resources:
|
||||
- customresourcedefinitions
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- apiGroups:
|
||||
- monitoring.coreos.com
|
||||
resources:
|
||||
- alertmanagers
|
||||
- prometheuses
|
||||
- servicemonitors
|
||||
verbs:
|
||||
- '*'
|
||||
- apiGroups:
|
||||
- extensions
|
||||
resources:
|
||||
- ingresses
|
||||
verbs:
|
||||
- create
|
||||
- get
|
||||
- list
|
||||
- update
|
||||
- delete
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: kubeless-controller-deployer
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: kubeless-controller-deployer
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: controller-acct
|
||||
namespace: kubeless
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
description: Kubernetes Native Serverless Framework
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: functions.kubeless.io
|
||||
spec:
|
||||
group: kubeless.io
|
||||
names:
|
||||
kind: Function
|
||||
plural: functions
|
||||
singular: function
|
||||
scope: Namespaced
|
||||
version: v1beta1
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
description: CRD object for HTTP trigger type
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: httptriggers.kubeless.io
|
||||
spec:
|
||||
group: kubeless.io
|
||||
names:
|
||||
kind: HTTPTrigger
|
||||
plural: httptriggers
|
||||
singular: httptrigger
|
||||
scope: Namespaced
|
||||
version: v1beta1
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
description: CRD object for HTTP trigger type
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: cronjobtriggers.kubeless.io
|
||||
spec:
|
||||
group: kubeless.io
|
||||
names:
|
||||
kind: CronJobTrigger
|
||||
plural: cronjobtriggers
|
||||
singular: cronjobtrigger
|
||||
scope: Namespaced
|
||||
version: v1beta1
|
||||
---
|
||||
apiVersion: v1
|
||||
data:
|
||||
builder-image: kubeless/function-image-builder:v1.0.0-alpha.6
|
||||
builder-image-secret: ""
|
||||
deployment: '{}'
|
||||
enable-build-step: "false"
|
||||
function-registry-tls-verify: "true"
|
||||
ingress-enabled: "false"
|
||||
provision-image: kubeless/unzip@sha256:f162c062973cca05459834de6ed14c039d45df8cdb76097f50b028a1621b3697
|
||||
provision-image-secret: ""
|
||||
runtime-images: |-
|
||||
[
|
||||
{
|
||||
"ID": "python",
|
||||
"compiled": false,
|
||||
"versions": [
|
||||
{
|
||||
"name": "python27",
|
||||
"version": "2.7",
|
||||
"runtimeImage": "kubeless/python@sha256:07cfb0f3d8b6db045dc317d35d15634d7be5e436944c276bf37b1c630b03add8",
|
||||
"initImage": "python:2.7"
|
||||
},
|
||||
{
|
||||
"name": "python34",
|
||||
"version": "3.4",
|
||||
"runtimeImage": "kubeless/python@sha256:f19640c547a3f91dbbfb18c15b5e624029b4065c1baf2892144e07c36f0a7c8f",
|
||||
"initImage": "python:3.4"
|
||||
},
|
||||
{
|
||||
"name": "python36",
|
||||
"version": "3.6",
|
||||
"runtimeImage": "kubeless/python@sha256:0c9f8f727d42625a4e25230cfe612df7488b65f283e7972f84108d87e7443d72",
|
||||
"initImage": "python:3.6"
|
||||
}
|
||||
],
|
||||
"depName": "requirements.txt",
|
||||
"fileNameSuffix": ".py"
|
||||
},
|
||||
{
|
||||
"ID": "nodejs",
|
||||
"compiled": false,
|
||||
"versions": [
|
||||
{
|
||||
"name": "node6",
|
||||
"version": "6",
|
||||
"runtimeImage": "kubeless/nodejs@sha256:013facddb0f66c150844192584d823d7dfb2b5b8d79fd2ae98439c86685da657",
|
||||
"initImage": "node:6.10"
|
||||
},
|
||||
{
|
||||
"name": "node8",
|
||||
"version": "8",
|
||||
"runtimeImage": "kubeless/nodejs@sha256:b155d7e20e333044b60009c12a25a97c84eed610f2a3d9d314b47449dbdae0e5",
|
||||
"initImage": "node:8"
|
||||
}
|
||||
],
|
||||
"depName": "package.json",
|
||||
"fileNameSuffix": ".js"
|
||||
},
|
||||
{
|
||||
"ID": "nodejs_distroless",
|
||||
"compiled": false,
|
||||
"versions": [
|
||||
{
|
||||
"name": "node8",
|
||||
"version": "8",
|
||||
"runtimeImage": "henrike42/kubeless/runtimes/nodejs/distroless:0.0.2",
|
||||
"initImage": "node:8"
|
||||
}
|
||||
],
|
||||
"depName": "package.json",
|
||||
"fileNameSuffix": ".js"
|
||||
},
|
||||
{
|
||||
"ID": "ruby",
|
||||
"compiled": false,
|
||||
"versions": [
|
||||
{
|
||||
"name": "ruby24",
|
||||
"version": "2.4",
|
||||
"runtimeImage": "kubeless/ruby@sha256:01665f1a32fe4fab4195af048627857aa7b100e392ae7f3e25a44bd296d6f105",
|
||||
"initImage": "bitnami/ruby:2.4"
|
||||
}
|
||||
],
|
||||
"depName": "Gemfile",
|
||||
"fileNameSuffix": ".rb"
|
||||
},
|
||||
{
|
||||
"ID": "php",
|
||||
"compiled": false,
|
||||
"versions": [
|
||||
{
|
||||
"name": "php72",
|
||||
"version": "7.2",
|
||||
"runtimeImage": "kubeless/php@sha256:9b86066b2640bedcd88acb27f43dfaa2b338f0d74d9d91131ea781402f7ec8ec",
|
||||
"initImage": "composer:1.6"
|
||||
}
|
||||
],
|
||||
"depName": "composer.json",
|
||||
"fileNameSuffix": ".php"
|
||||
},
|
||||
{
|
||||
"ID": "go",
|
||||
"compiled": true,
|
||||
"versions": [
|
||||
{
|
||||
"name": "go1.10",
|
||||
"version": "1.10",
|
||||
"runtimeImage": "kubeless/go@sha256:e2fd49f09b6ff8c9bac6f1592b3119ea74237c47e2955a003983e08524cb3ae5",
|
||||
"initImage": "kubeless/go-init@sha256:983b3f06452321a2299588966817e724d1a9c24be76cf1b12c14843efcdff502"
|
||||
}
|
||||
],
|
||||
"depName": "Gopkg.toml",
|
||||
"fileNameSuffix": ".go"
|
||||
},
|
||||
{
|
||||
"ID": "dotnetcore",
|
||||
"compiled": true,
|
||||
"versions": [
|
||||
{
|
||||
"name": "dotnetcore2.0",
|
||||
"version": "2.0",
|
||||
"runtimeImage": "allantargino/kubeless-dotnetcore@sha256:1699b07d9fc0276ddfecc2f823f272d96fd58bbab82d7e67f2fd4982a95aeadc",
|
||||
"initImage": "allantargino/aspnetcore-build@sha256:0d60f845ff6c9c019362a68b87b3920f3eb2d32f847f2d75e4d190cc0ce1d81c"
|
||||
}
|
||||
],
|
||||
"depName": "project.csproj",
|
||||
"fileNameSuffix": ".cs"
|
||||
},
|
||||
{
|
||||
"ID": "java",
|
||||
"compiled": true,
|
||||
"versions": [
|
||||
{
|
||||
"name": "java1.8",
|
||||
"version": "1.8",
|
||||
"runtimeImage": "kubeless/java@sha256:debf9502545f4c0e955eb60fabb45748c5d98ed9365c4a508c07f38fc7fefaac",
|
||||
"initImage": "kubeless/java-init@sha256:7e5e4376d3ab76c336d4830c9ed1b7f9407415feca49b8c2bf013e279256878f"
|
||||
}
|
||||
],
|
||||
"depName": "pom.xml",
|
||||
"fileNameSuffix": ".java"
|
||||
},
|
||||
{
|
||||
"ID": "ballerina",
|
||||
"compiled": true,
|
||||
"versions": [
|
||||
{
|
||||
"name": "ballerina0.975.0",
|
||||
"version": "0.975.0",
|
||||
"runtimeImage": "kubeless/ballerina@sha256:83e51423972f4b0d6b419bee0b4afb3bb87d2bf1b604ebc4366c430e7cc28a35",
|
||||
"initImage": "kubeless/ballerina-init@sha256:05857ce439a7e290f9d86f8cb38ea3b574670c0c0e91af93af06686fa21ecf4f"
|
||||
}
|
||||
],
|
||||
"depName": "",
|
||||
"fileNameSuffix": ".bal"
|
||||
}
|
||||
]
|
||||
service-type: ClusterIP
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: kubeless-config
|
||||
namespace: kubeless
|
||||
---
|
||||
apiVersion: apps/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
kubeless: controller
|
||||
name: kubeless-controller-manager
|
||||
namespace: kubeless
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
kubeless: controller
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
kubeless: controller
|
||||
spec:
|
||||
containers:
|
||||
- env:
|
||||
- name: KUBELESS_INGRESS_ENABLED
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
key: ingress-enabled
|
||||
name: kubeless-config
|
||||
- name: KUBELESS_SERVICE_TYPE
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
key: service-type
|
||||
name: kubeless-config
|
||||
- name: KUBELESS_NAMESPACE
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
- name: KUBELESS_CONFIG
|
||||
value: kubeless-config
|
||||
image: bitnami/kubeless-controller-manager:v1.0.0-alpha.6
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: kubeless-controller-manager
|
||||
serviceAccountName: controller-acct
|
||||
@@ -0,0 +1,74 @@
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: nats-controller-deployer
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- services
|
||||
- configmaps
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- apiGroups:
|
||||
- kubeless.io
|
||||
resources:
|
||||
- functions
|
||||
- natstriggers
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- update
|
||||
- delete
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: nats-controller-deployer
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: nats-controller-deployer
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: controller-acct
|
||||
namespace: kubeless
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1beta1
|
||||
description: CRD object for NATS trigger type
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: natstriggers.kubeless.io
|
||||
spec:
|
||||
group: kubeless.io
|
||||
names:
|
||||
kind: NATSTrigger
|
||||
plural: natstriggers
|
||||
singular: natstrigger
|
||||
scope: Namespaced
|
||||
version: v1beta1
|
||||
---
|
||||
apiVersion: apps/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
kubeless: nats-trigger-controller
|
||||
name: nats-trigger-controller
|
||||
namespace: kubeless
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
kubeless: nats-trigger-controller
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
kubeless: nats-trigger-controller
|
||||
spec:
|
||||
containers:
|
||||
- image: bitnami/nats-trigger-controller:v1.0.0-alpha.6
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: nats-trigger-controller
|
||||
serviceAccountName: controller-acct
|
||||
@@ -0,0 +1,82 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: nats-io
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: nats-operator
|
||||
namespace: nats-io
|
||||
---
|
||||
apiVersion: apps/v1beta2
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: nats-operator
|
||||
namespace: nats-io
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
name: nats-operator
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
name: nats-operator
|
||||
spec:
|
||||
serviceAccountName: nats-operator
|
||||
containers:
|
||||
- name: nats-operator
|
||||
image: connecteverything/nats-operator:0.2.2-v1alpha2
|
||||
imagePullPolicy: Always
|
||||
env:
|
||||
- name: MY_POD_NAMESPACE
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
- name: MY_POD_NAME
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.name
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: nats-io:nats-operator-binding
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: nats-io:nats-operator
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: nats-operator
|
||||
namespace: nats-io
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: nats-io:nats-operator
|
||||
rules:
|
||||
# Allow creating CRDs
|
||||
- apiGroups:
|
||||
- apiextensions.k8s.io
|
||||
resources:
|
||||
- customresourcedefinitions
|
||||
verbs: ["*"]
|
||||
# Allow all actions on NatsClusters
|
||||
- apiGroups:
|
||||
- nats.io
|
||||
resources:
|
||||
- natsclusters
|
||||
verbs: ["*"]
|
||||
# Allow actions on basic Kubernetes objects
|
||||
- apiGroups: [""]
|
||||
resources:
|
||||
- configmaps
|
||||
- secrets
|
||||
- pods
|
||||
- services
|
||||
- endpoints
|
||||
- events
|
||||
verbs: ["*"]
|
||||
@@ -0,0 +1,8 @@
|
||||
apiVersion: "nats.io/v1alpha2"
|
||||
kind: "NatsCluster"
|
||||
metadata:
|
||||
name: "nats"
|
||||
namespace: "nats-io"
|
||||
spec:
|
||||
size: 3
|
||||
version: "1.1.0"
|
||||
@@ -0,0 +1,11 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: isolate
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
isolated: 'true'
|
||||
policyTypes:
|
||||
- Ingress
|
||||
- Egress
|
||||
1
integrations/kubernetes-response-engine/falco-nats/.gitignore
vendored
Normal file
1
integrations/kubernetes-response-engine/falco-nats/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
falco-nats
|
||||
@@ -0,0 +1,5 @@
|
||||
FROM alpine:latest
|
||||
|
||||
COPY ./falco-nats /bin/
|
||||
|
||||
CMD ["/bin/falco-nats"]
|
||||
12
integrations/kubernetes-response-engine/falco-nats/Makefile
Normal file
12
integrations/kubernetes-response-engine/falco-nats/Makefile
Normal file
@@ -0,0 +1,12 @@
|
||||
build:
|
||||
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="-s" -o falco-nats main.go
|
||||
|
||||
deps:
|
||||
go get -u github.com/nats-io/go-nats
|
||||
|
||||
clean:
|
||||
rm falco-nats
|
||||
|
||||
docker: build
|
||||
docker build -t sysdig/falco-nats .
|
||||
docker push sysdig/falco-nats
|
||||
27
integrations/kubernetes-response-engine/falco-nats/README.md
Normal file
27
integrations/kubernetes-response-engine/falco-nats/README.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# NATS output for Sysdig Falco
|
||||
|
||||
As Falco does not support a NATS output natively, we have created this small
|
||||
golang utility wich reads Falco alerts from a named pipe and sends them to a
|
||||
NATS server.
|
||||
|
||||
This utility is designed to being run in a sidecar container in the same
|
||||
Pod as Falco.
|
||||
|
||||
## Configuration
|
||||
|
||||
You have a [complete Kubernetes manifest available](https://github.com/draios/falco/tree/kubernetes-response-engine/deployment/falco/falco-daemonset.yaml) for future reading.
|
||||
|
||||
Take a look at sidecar container and to the initContainers directive which
|
||||
craetes the shared pipe between containers.
|
||||
|
||||
### Container image
|
||||
|
||||
You have this adapter available as a container image. Its name is *sysdig/falco-nats*.
|
||||
|
||||
### Parameters Reference
|
||||
|
||||
* -s: Specifies the NATS server URL where message will be published. By default
|
||||
is: *nats://nats.nats-io.svc.cluster.local:4222*
|
||||
|
||||
* -f: Specifies the named pipe path where Falco publishes its alerts. By default
|
||||
is: */var/run/falco/nats*
|
||||
100
integrations/kubernetes-response-engine/falco-nats/main.go
Normal file
100
integrations/kubernetes-response-engine/falco-nats/main.go
Normal file
@@ -0,0 +1,100 @@
|
||||
// Copyright 2012-2018 The NATS Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"github.com/nats-io/go-nats"
|
||||
"log"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var slugRegularExpression = regexp.MustCompile("[^a-z0-9]+")
|
||||
|
||||
func main() {
|
||||
var urls = flag.String("s", "nats://nats.nats-io.svc.cluster.local:4222", "The nats server URLs (separated by comma)")
|
||||
var pipePath = flag.String("f", "/var/run/falco/nats", "The named pipe path")
|
||||
|
||||
log.SetFlags(0)
|
||||
flag.Usage = usage
|
||||
flag.Parse()
|
||||
|
||||
nc, err := nats.Connect(*urls)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer nc.Close()
|
||||
|
||||
pipe, err := os.OpenFile(*pipePath, os.O_RDONLY, 0600)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
log.Printf("Opened pipe %s", *pipePath)
|
||||
|
||||
reader := bufio.NewReader(pipe)
|
||||
scanner := bufio.NewScanner(reader)
|
||||
|
||||
log.Printf("Scanning %s", *pipePath)
|
||||
|
||||
for scanner.Scan() {
|
||||
msg := []byte(scanner.Text())
|
||||
|
||||
subj, err := subjectAndRuleSlug(msg)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
nc.Publish(subj, msg)
|
||||
nc.Flush()
|
||||
|
||||
if err := nc.LastError(); err != nil {
|
||||
log.Fatal(err)
|
||||
} else {
|
||||
log.Printf("Published [%s] : '%s'\n", subj, msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func usage() {
|
||||
log.Fatalf("Usage: nats-pub [-s server (%s)] <subject> <msg> \n", nats.DefaultURL)
|
||||
}
|
||||
|
||||
type parsedAlert struct {
|
||||
Priority string `json:"priority"`
|
||||
Rule string `json:"rule"`
|
||||
}
|
||||
|
||||
func subjectAndRuleSlug(alert []byte) (string, error) {
|
||||
var result parsedAlert
|
||||
err := json.Unmarshal(alert, &result)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
subject := "falco." + result.Priority + "." + slugify(result.Rule)
|
||||
subject = strings.ToLower(subject)
|
||||
|
||||
return subject, nil
|
||||
}
|
||||
|
||||
func slugify(input string) string {
|
||||
return strings.Trim(slugRegularExpression.ReplaceAllString(strings.ToLower(input), "_"), "_")
|
||||
}
|
||||
104
integrations/kubernetes-response-engine/playbooks/.gitignore
vendored
Normal file
104
integrations/kubernetes-response-engine/playbooks/.gitignore
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# celery beat schedule file
|
||||
celerybeat-schedule
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
19
integrations/kubernetes-response-engine/playbooks/Pipfile
Normal file
19
integrations/kubernetes-response-engine/playbooks/Pipfile
Normal file
@@ -0,0 +1,19 @@
|
||||
[[source]]
|
||||
url = "https://pypi.python.org/simple"
|
||||
verify_ssl = true
|
||||
name = "pypi"
|
||||
|
||||
[dev-packages]
|
||||
mamba = "*"
|
||||
expects = "*"
|
||||
doublex = "*"
|
||||
doublex-expects = "==0.7.0rc2"
|
||||
|
||||
[packages]
|
||||
kubernetes = "*"
|
||||
requests = "*"
|
||||
"e1839a8" = {path = ".", editable = true}
|
||||
maya = "*"
|
||||
|
||||
[requires]
|
||||
python_version = "3.6"
|
||||
367
integrations/kubernetes-response-engine/playbooks/Pipfile.lock
generated
Normal file
367
integrations/kubernetes-response-engine/playbooks/Pipfile.lock
generated
Normal file
@@ -0,0 +1,367 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "00ca5a9cb1f462d534a06bca990e987e75a05b7baf6ba5ddac529f03312135e6"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
"python_version": "3.6"
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
"name": "pypi",
|
||||
"url": "https://pypi.python.org/simple",
|
||||
"verify_ssl": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"cachetools": {
|
||||
"hashes": [
|
||||
"sha256:90f1d559512fc073483fe573ef5ceb39bf6ad3d39edc98dc55178a2b2b176fa3",
|
||||
"sha256:d1c398969c478d336f767ba02040fa22617333293fb0b8968e79b16028dfee35"
|
||||
],
|
||||
"version": "==2.1.0"
|
||||
},
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
"sha256:13e698f54293db9f89122b0581843a782ad0934a4fe0172d2a980ba77fc61bb7",
|
||||
"sha256:9fa520c1bacfb634fa7af20a76bcbd3d5fb390481724c597da32c719a7dca4b0"
|
||||
],
|
||||
"version": "==2018.4.16"
|
||||
},
|
||||
"chardet": {
|
||||
"hashes": [
|
||||
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
|
||||
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
|
||||
],
|
||||
"version": "==3.0.4"
|
||||
},
|
||||
"dateparser": {
|
||||
"hashes": [
|
||||
"sha256:940828183c937bcec530753211b70f673c0a9aab831e43273489b310538dff86",
|
||||
"sha256:b452ef8b36cd78ae86a50721794bc674aa3994e19b570f7ba92810f4e0a2ae03"
|
||||
],
|
||||
"version": "==0.7.0"
|
||||
},
|
||||
"e1839a8": {
|
||||
"editable": true,
|
||||
"path": "."
|
||||
},
|
||||
"google-auth": {
|
||||
"hashes": [
|
||||
"sha256:1745c9066f698eac3da99cef082914495fb71bc09597ba7626efbbb64c4acc57",
|
||||
"sha256:82a34e1a59ad35f01484d283d2a36b7a24c8c404a03a71b3afddd0a4d31e169f"
|
||||
],
|
||||
"version": "==1.5.0"
|
||||
},
|
||||
"humanize": {
|
||||
"hashes": [
|
||||
"sha256:a43f57115831ac7c70de098e6ac46ac13be00d69abbf60bdcac251344785bb19"
|
||||
],
|
||||
"version": "==0.5.1"
|
||||
},
|
||||
"idna": {
|
||||
"hashes": [
|
||||
"sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e",
|
||||
"sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16"
|
||||
],
|
||||
"version": "==2.7"
|
||||
},
|
||||
"ipaddress": {
|
||||
"hashes": [
|
||||
"sha256:64b28eec5e78e7510698f6d4da08800a5c575caa4a286c93d651c5d3ff7b6794",
|
||||
"sha256:b146c751ea45cad6188dd6cf2d9b757f6f4f8d6ffb96a023e6f2e26eea02a72c"
|
||||
],
|
||||
"version": "==1.0.22"
|
||||
},
|
||||
"kubernetes": {
|
||||
"hashes": [
|
||||
"sha256:b370ab4abd925309db69a14a4723487948e9a83de60ca92782ec14992b741c89",
|
||||
"sha256:c80dcf531deca2037105df09c933355c80830ffbf9e496b5e6a3967ac6809ef7"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==6.0.0"
|
||||
},
|
||||
"maya": {
|
||||
"hashes": [
|
||||
"sha256:6f63bc69aa77309fc220bc02618da8701a21da87c2e7a747ee5ccd56a907c3a5",
|
||||
"sha256:f526bc8596d993f4bd9755668f66aaf61d635bb4149e084d4a2bc0ebe42aa0b6"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.5.0"
|
||||
},
|
||||
"oauthlib": {
|
||||
"hashes": [
|
||||
"sha256:ac35665a61c1685c56336bda97d5eefa246f1202618a1d6f34fccb1bdd404162",
|
||||
"sha256:d883b36b21a6ad813953803edfa563b1b579d79ca758fe950d1bc9e8b326025b"
|
||||
],
|
||||
"version": "==2.1.0"
|
||||
},
|
||||
"pendulum": {
|
||||
"hashes": [
|
||||
"sha256:4173ce3e81ad0d9d61dbce86f4286c43a26a398270df6a0a89f501f0c28ad27d",
|
||||
"sha256:56a347d0457859c84b8cdba161fc37c7df5db9b3becec7881cd770e9d2058b3c",
|
||||
"sha256:738878168eb26e5446da5d1f7b3312ae993a542061be8882099c00ef4866b1a2",
|
||||
"sha256:95536b33ae152e3c831eb236c1bf9ac9dcfb3b5b98fdbe8e9e601eab6c373897",
|
||||
"sha256:c04fcf955e622e97e405e5f6d1b1f4a7adc69d79d82f3609643de69283170d6d",
|
||||
"sha256:dd6500d27bb7ccc029d497da4f9bd09549bd3c0ea276dad894ea2fdf309e83f3",
|
||||
"sha256:ddaf97a061eb5e2ae37857a8cb548e074125017855690d20e443ad8d9f31e164",
|
||||
"sha256:e7df37447824f9af0b58c7915a4caf349926036afd86ad38e7529a6b2f8fc34b",
|
||||
"sha256:e9732b8bb214fad2c72ddcbfec07542effa8a8b704e174347ede1ff8dc679cce",
|
||||
"sha256:f4eee1e1735487d9d25cc435c519fd4380cb1f82cde3ebad1efbc2fc30deca5b"
|
||||
],
|
||||
"version": "==1.5.1"
|
||||
},
|
||||
"pyasn1": {
|
||||
"hashes": [
|
||||
"sha256:2f57960dc7a2820ea5a1782b872d974b639aa3b448ac6628d1ecc5d0fe3986f2",
|
||||
"sha256:3651774ca1c9726307560792877db747ba5e8a844ea1a41feb7670b319800ab3",
|
||||
"sha256:602fda674355b4701acd7741b2be5ac188056594bf1eecf690816d944e52905e",
|
||||
"sha256:8fb265066eac1d3bb5015c6988981b009ccefd294008ff7973ed5f64335b0f2d",
|
||||
"sha256:9334cb427609d2b1e195bb1e251f99636f817d7e3e1dffa150cb3365188fb992",
|
||||
"sha256:9a15cc13ff6bf5ed29ac936ca941400be050dff19630d6cd1df3fb978ef4c5ad",
|
||||
"sha256:a66dcda18dbf6e4663bde70eb30af3fc4fe1acb2d14c4867a861681887a5f9a2",
|
||||
"sha256:ba77f1e8d7d58abc42bfeddd217b545fdab4c1eeb50fd37c2219810ad56303bf",
|
||||
"sha256:cdc8eb2eaafb56de66786afa6809cd9db2df1b3b595dcb25aa5b9dc61189d40a",
|
||||
"sha256:d01fbba900c80b42af5c3fe1a999acf61e27bf0e452e0f1ef4619065e57622da",
|
||||
"sha256:f281bf11fe204f05859225ec2e9da7a7c140b65deccd8a4eb0bc75d0bd6949e0",
|
||||
"sha256:fb81622d8f3509f0026b0683fe90fea27be7284d3826a5f2edf97f69151ab0fc"
|
||||
],
|
||||
"version": "==0.4.3"
|
||||
},
|
||||
"pyasn1-modules": {
|
||||
"hashes": [
|
||||
"sha256:041e9fbafac548d095f5b6c3b328b80792f006196e15a232b731a83c93d59493",
|
||||
"sha256:0cdca76a68dcb701fff58c397de0ef9922b472b1cb3ea9695ca19d03f1869787",
|
||||
"sha256:0cea139045c38f84abaa803bcb4b5e8775ea12a42af10019d942f227acc426c3",
|
||||
"sha256:0f2e50d20bc670be170966638fa0ae603f0bc9ed6ebe8e97a6d1d4cef30cc889",
|
||||
"sha256:47fb6757ab78fe966e7c58b2030b546854f78416d653163f0ce9290cf2278e8b",
|
||||
"sha256:598a6004ec26a8ab40a39ea955068cf2a3949ad9c0030da970f2e1ca4c9f1cc9",
|
||||
"sha256:72fd8b0c11191da088147c6e4678ec53e573923ecf60b57eeac9e97433e09fc2",
|
||||
"sha256:854700bbdd01394e2ada9c1bfbd0ed9f5d0c551350dbbd023e88b11d2771ae06",
|
||||
"sha256:af00ea8f2022b6287dc375b2c70f31ab5af83989fc6fe9eacd4976ce26cd7ccc",
|
||||
"sha256:b1f395cae2d669e0830cb023aa86f9f283b7a9aa32317d7f80d8e78aa2745812",
|
||||
"sha256:c6747146e95d2b14cc2a8399b2b0bde3f93778f8f9ec704690d2b589c376c137",
|
||||
"sha256:f53fe5bcebdf318f51399b250fe8325ef3a26d927f012cc0c8e0f9e9af7f9deb"
|
||||
],
|
||||
"version": "==0.2.1"
|
||||
},
|
||||
"python-dateutil": {
|
||||
"hashes": [
|
||||
"sha256:1adb80e7a782c12e52ef9a8182bebeb73f1d7e24e374397af06fb4956c8dc5c0",
|
||||
"sha256:e27001de32f627c22380a688bcc43ce83504a7bc5da472209b4c70f02829f0b8"
|
||||
],
|
||||
"version": "==2.7.3"
|
||||
},
|
||||
"pytz": {
|
||||
"hashes": [
|
||||
"sha256:65ae0c8101309c45772196b21b74c46b2e5d11b6275c45d251b150d5da334555",
|
||||
"sha256:c06425302f2cf668f1bba7a0a03f3c1d34d4ebeef2c72003da308b3947c7f749"
|
||||
],
|
||||
"version": "==2018.4"
|
||||
},
|
||||
"pytzdata": {
|
||||
"hashes": [
|
||||
"sha256:1d936da41ee06216d89fdc7ead1ee9a5da2811a8787515a976b646e110c3f622",
|
||||
"sha256:e4ef42e82b0b493c5849eed98b5ab49d6767caf982127e9a33167f1153b36cc5"
|
||||
],
|
||||
"version": "==2018.5"
|
||||
},
|
||||
"pyyaml": {
|
||||
"hashes": [
|
||||
"sha256:0c507b7f74b3d2dd4d1322ec8a94794927305ab4cebbe89cc47fe5e81541e6e8",
|
||||
"sha256:16b20e970597e051997d90dc2cddc713a2876c47e3d92d59ee198700c5427736",
|
||||
"sha256:3262c96a1ca437e7e4763e2843746588a965426550f3797a79fca9c6199c431f",
|
||||
"sha256:326420cbb492172dec84b0f65c80942de6cedb5233c413dd824483989c000608",
|
||||
"sha256:4474f8ea030b5127225b8894d626bb66c01cda098d47a2b0d3429b6700af9fd8",
|
||||
"sha256:592766c6303207a20efc445587778322d7f73b161bd994f227adaa341ba212ab",
|
||||
"sha256:5ac82e411044fb129bae5cfbeb3ba626acb2af31a8d17d175004b70862a741a7",
|
||||
"sha256:5f84523c076ad14ff5e6c037fe1c89a7f73a3e04cf0377cb4d017014976433f3",
|
||||
"sha256:827dc04b8fa7d07c44de11fabbc888e627fa8293b695e0f99cb544fdfa1bf0d1",
|
||||
"sha256:b4c423ab23291d3945ac61346feeb9a0dc4184999ede5e7c43e1ffb975130ae6",
|
||||
"sha256:bc6bced57f826ca7cb5125a10b23fd0f2fff3b7c4701d64c439a300ce665fff8",
|
||||
"sha256:c01b880ec30b5a6e6aa67b09a2fe3fb30473008c85cd6a67359a1b15ed6d83a4",
|
||||
"sha256:ca233c64c6e40eaa6c66ef97058cdc80e8d0157a443655baa1b2966e812807ca",
|
||||
"sha256:e863072cdf4c72eebf179342c94e6989c67185842d9997960b3e69290b2fa269"
|
||||
],
|
||||
"version": "==3.12"
|
||||
},
|
||||
"regex": {
|
||||
"hashes": [
|
||||
"sha256:0201b4cb42f03842a75044a3d08b62a79114f753b33ee421182c631d9f5c81f5",
|
||||
"sha256:204524604456e3e0e25c3f24da4efc43db78edfe7623f1049e03d3aa51ddda48",
|
||||
"sha256:24c0e838bde42fe9d4d5650e75bff2d4bb5867968fb9409331dbe39154f6e8e2",
|
||||
"sha256:4360143da844cd985effb7fb9af04beaa2d371ab13e4a1996424aa2f6fbfb877",
|
||||
"sha256:4b8c6fd44dbd46cdbf755c20a7b9dedb32b8d15b707a0e470dfa66ba5df00a35",
|
||||
"sha256:4fb5622987f3863cfa76c40ab3338a7dc8ed2bac236bb53e638b21ea397a3252",
|
||||
"sha256:5eebefef6e3d97e4c1f9f77eac6555c32ed3afbd769955a9f7339256a4d50d6c",
|
||||
"sha256:7222204c6acb9e52688678ec7306b2dfd84df68bc8eb251be74fec4e9dd85bf9",
|
||||
"sha256:809cbbcbe291cf7bc9cf6aeac6a9a400a71318292d0a2a07effaf4b4782203a0",
|
||||
"sha256:9c9075c727afec23eab196be51737eedb00cd67bb4a2e0170fa8dc65163838f3",
|
||||
"sha256:a105b1d7287d412e8fe99959c1b80f7cbd76184b6466d63579b6d256a406a76e",
|
||||
"sha256:c3d9cfd214a3e5a25f2da9817c389e32069e210b067ebb901e10f3270da9b259",
|
||||
"sha256:c3ebfb5ec2dd750f7861734b25ea7d5ae89d6f33b427cccf3cafa36a1511d862",
|
||||
"sha256:c670acd71d975b0c91579d40ae7f703d0daa1c871f12e46394a2c7be0ec8e217",
|
||||
"sha256:e371482ee3e6e5ca19ea83cdfc84bf69cac230e3cb1073c8c3bebf3f143cd7a5"
|
||||
],
|
||||
"version": "==2018.6.9"
|
||||
},
|
||||
"requests": {
|
||||
"hashes": [
|
||||
"sha256:63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1",
|
||||
"sha256:ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.19.1"
|
||||
},
|
||||
"requests-oauthlib": {
|
||||
"hashes": [
|
||||
"sha256:8886bfec5ad7afb391ed5443b1f697c6f4ae98d0e5620839d8b4499c032ada3f",
|
||||
"sha256:e21232e2465808c0e892e0e4dbb8c2faafec16ac6dc067dd546e9b466f3deac8",
|
||||
"sha256:fe3282f48fb134ee0035712159f5429215459407f6d5484013343031ff1a400d"
|
||||
],
|
||||
"version": "==1.0.0"
|
||||
},
|
||||
"rsa": {
|
||||
"hashes": [
|
||||
"sha256:25df4e10c263fb88b5ace923dd84bf9aa7f5019687b5e55382ffcdb8bede9db5",
|
||||
"sha256:43f682fea81c452c98d09fc316aae12de6d30c4b5c84226642cf8f8fd1c93abd"
|
||||
],
|
||||
"version": "==3.4.2"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
"sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",
|
||||
"sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"
|
||||
],
|
||||
"version": "==1.11.0"
|
||||
},
|
||||
"snaptime": {
|
||||
"hashes": [
|
||||
"sha256:e3f1eb89043d58d30721ab98cb65023f1a4c2740e3b197704298b163c92d508b"
|
||||
],
|
||||
"version": "==0.2.4"
|
||||
},
|
||||
"tzlocal": {
|
||||
"hashes": [
|
||||
"sha256:4ebeb848845ac898da6519b9b31879cf13b6626f7184c496037b818e238f2c4e"
|
||||
],
|
||||
"version": "==1.5.1"
|
||||
},
|
||||
"urllib3": {
|
||||
"hashes": [
|
||||
"sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf",
|
||||
"sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5"
|
||||
],
|
||||
"version": "==1.23"
|
||||
},
|
||||
"websocket-client": {
|
||||
"hashes": [
|
||||
"sha256:18f1170e6a1b5463986739d9fd45c4308b0d025c1b2f9b88788d8f69e8a5eb4a",
|
||||
"sha256:db70953ae4a064698b27ae56dcad84d0ee68b7b43cb40940f537738f38f510c1"
|
||||
],
|
||||
"version": "==0.48.0"
|
||||
}
|
||||
},
|
||||
"develop": {
|
||||
"args": {
|
||||
"hashes": [
|
||||
"sha256:a785b8d837625e9b61c39108532d95b85274acd679693b71ebb5156848fcf814"
|
||||
],
|
||||
"version": "==0.1.0"
|
||||
},
|
||||
"clint": {
|
||||
"hashes": [
|
||||
"sha256:05224c32b1075563d0b16d0015faaf9da43aa214e4a2140e51f08789e7a4c5aa"
|
||||
],
|
||||
"version": "==0.5.1"
|
||||
},
|
||||
"coverage": {
|
||||
"hashes": [
|
||||
"sha256:03481e81d558d30d230bc12999e3edffe392d244349a90f4ef9b88425fac74ba",
|
||||
"sha256:0b136648de27201056c1869a6c0d4e23f464750fd9a9ba9750b8336a244429ed",
|
||||
"sha256:104ab3934abaf5be871a583541e8829d6c19ce7bde2923b2751e0d3ca44db60a",
|
||||
"sha256:15b111b6a0f46ee1a485414a52a7ad1d703bdf984e9ed3c288a4414d3871dcbd",
|
||||
"sha256:198626739a79b09fa0a2f06e083ffd12eb55449b5f8bfdbeed1df4910b2ca640",
|
||||
"sha256:1c383d2ef13ade2acc636556fd544dba6e14fa30755f26812f54300e401f98f2",
|
||||
"sha256:28b2191e7283f4f3568962e373b47ef7f0392993bb6660d079c62bd50fe9d162",
|
||||
"sha256:2eb564bbf7816a9d68dd3369a510be3327f1c618d2357fa6b1216994c2e3d508",
|
||||
"sha256:337ded681dd2ef9ca04ef5d93cfc87e52e09db2594c296b4a0a3662cb1b41249",
|
||||
"sha256:3a2184c6d797a125dca8367878d3b9a178b6fdd05fdc2d35d758c3006a1cd694",
|
||||
"sha256:3c79a6f7b95751cdebcd9037e4d06f8d5a9b60e4ed0cd231342aa8ad7124882a",
|
||||
"sha256:3d72c20bd105022d29b14a7d628462ebdc61de2f303322c0212a054352f3b287",
|
||||
"sha256:3eb42bf89a6be7deb64116dd1cc4b08171734d721e7a7e57ad64cc4ef29ed2f1",
|
||||
"sha256:4635a184d0bbe537aa185a34193898eee409332a8ccb27eea36f262566585000",
|
||||
"sha256:56e448f051a201c5ebbaa86a5efd0ca90d327204d8b059ab25ad0f35fbfd79f1",
|
||||
"sha256:5a13ea7911ff5e1796b6d5e4fbbf6952381a611209b736d48e675c2756f3f74e",
|
||||
"sha256:69bf008a06b76619d3c3f3b1983f5145c75a305a0fea513aca094cae5c40a8f5",
|
||||
"sha256:6bc583dc18d5979dc0f6cec26a8603129de0304d5ae1f17e57a12834e7235062",
|
||||
"sha256:701cd6093d63e6b8ad7009d8a92425428bc4d6e7ab8d75efbb665c806c1d79ba",
|
||||
"sha256:7608a3dd5d73cb06c531b8925e0ef8d3de31fed2544a7de6c63960a1e73ea4bc",
|
||||
"sha256:76ecd006d1d8f739430ec50cc872889af1f9c1b6b8f48e29941814b09b0fd3cc",
|
||||
"sha256:7aa36d2b844a3e4a4b356708d79fd2c260281a7390d678a10b91ca595ddc9e99",
|
||||
"sha256:7d3f553904b0c5c016d1dad058a7554c7ac4c91a789fca496e7d8347ad040653",
|
||||
"sha256:7e1fe19bd6dce69d9fd159d8e4a80a8f52101380d5d3a4d374b6d3eae0e5de9c",
|
||||
"sha256:8c3cb8c35ec4d9506979b4cf90ee9918bc2e49f84189d9bf5c36c0c1119c6558",
|
||||
"sha256:9d6dd10d49e01571bf6e147d3b505141ffc093a06756c60b053a859cb2128b1f",
|
||||
"sha256:9e112fcbe0148a6fa4f0a02e8d58e94470fc6cb82a5481618fea901699bf34c4",
|
||||
"sha256:ac4fef68da01116a5c117eba4dd46f2e06847a497de5ed1d64bb99a5fda1ef91",
|
||||
"sha256:b8815995e050764c8610dbc82641807d196927c3dbed207f0a079833ffcf588d",
|
||||
"sha256:be6cfcd8053d13f5f5eeb284aa8a814220c3da1b0078fa859011c7fffd86dab9",
|
||||
"sha256:c1bb572fab8208c400adaf06a8133ac0712179a334c09224fb11393e920abcdd",
|
||||
"sha256:de4418dadaa1c01d497e539210cb6baa015965526ff5afc078c57ca69160108d",
|
||||
"sha256:e05cb4d9aad6233d67e0541caa7e511fa4047ed7750ec2510d466e806e0255d6",
|
||||
"sha256:e4d96c07229f58cb686120f168276e434660e4358cc9cf3b0464210b04913e77",
|
||||
"sha256:f3f501f345f24383c0000395b26b726e46758b71393267aeae0bd36f8b3ade80",
|
||||
"sha256:f8a923a85cb099422ad5a2e345fe877bbc89a8a8b23235824a93488150e45f6e"
|
||||
],
|
||||
"version": "==4.5.1"
|
||||
},
|
||||
"doublex": {
|
||||
"hashes": [
|
||||
"sha256:062af49d9e4148bc47b7512d3fdc8e145dea4671d074ffd54b2464a19d3757ab"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.8.4"
|
||||
},
|
||||
"doublex-expects": {
|
||||
"hashes": [
|
||||
"sha256:5421bd92319c77ccc5a81d595d06e9c9f7f670de342b33e8007a81e70f9fade8"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.7.0rc2"
|
||||
},
|
||||
"expects": {
|
||||
"hashes": [
|
||||
"sha256:37538d7b0fa9c0d53e37d07b0e8c07d89754d3deec1f0f8ed1be27f4f10363dd"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.8.0"
|
||||
},
|
||||
"mamba": {
|
||||
"hashes": [
|
||||
"sha256:63e70a8666039cf143a255000e23f29be4ea4b5b8169f2b053f94eb73a2ea9e2"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.9.3"
|
||||
},
|
||||
"pyhamcrest": {
|
||||
"hashes": [
|
||||
"sha256:6b672c02fdf7470df9674ab82263841ce8333fb143f32f021f6cb26f0e512420",
|
||||
"sha256:7a4bdade0ed98c699d728191a058a60a44d2f9c213c51e2dd1e6fb42f2c6128a",
|
||||
"sha256:8ffaa0a53da57e89de14ced7185ac746227a8894dbd5a3c718bf05ddbd1d56cd",
|
||||
"sha256:bac0bea7358666ce52e3c6c85139632ed89f115e9af52d44b3c36e0bf8cf16a9",
|
||||
"sha256:f30e9a310bcc1808de817a92e95169ffd16b60cbc5a016a49c8d0e8ababfae79"
|
||||
],
|
||||
"version": "==1.9.0"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
"sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",
|
||||
"sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"
|
||||
],
|
||||
"version": "==1.11.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
164
integrations/kubernetes-response-engine/playbooks/README.md
Normal file
164
integrations/kubernetes-response-engine/playbooks/README.md
Normal file
@@ -0,0 +1,164 @@
|
||||
# Playbooks
|
||||
|
||||
Following [owasp ideas](https://owaspsummit.org/Working-Sessions/Security-Playbooks/index.html),
|
||||
playbooks are workflows and prescriptive instructions on how to handle specific
|
||||
Security activities or incidents.
|
||||
|
||||
Being more specific, playbooks are actions that are going to be executed when
|
||||
Falco finds a weird behavior in our Kubernetes cluster. We have implemented
|
||||
them with Python and we have found that several Serverless concepts fits well
|
||||
with playbooks, so we use [Kubeless](https://kubeless.io/) for its deployment.
|
||||
|
||||
## Requirements
|
||||
|
||||
* A working Kubernetes cluster
|
||||
* [kubeless cli executable](https://kubeless.io/docs/quick-start/)
|
||||
* Python 3.6
|
||||
* pipenv
|
||||
|
||||
## Deploying a playbook
|
||||
|
||||
Deploying a playbook involves a couple of components, the function that is going
|
||||
to be with Kubeless and a trigger for that function.
|
||||
|
||||
We have automated those steps in a generic script *deploy_playbook* who packages
|
||||
the reaction and its dependencies, uploads to Kubernetes and creates the kubeless
|
||||
trigger.
|
||||
|
||||
```
|
||||
./deploy_playbook -p slack -e SLACK_WEBHOOK_URL="https://..." -t "falco.error.*" -t "falco.info.*"
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
* -p: The playbook to deploy, it must match with the top-level script. In this
|
||||
example *slack.py* that contains the wiring between playbooks and Kubeless
|
||||
functions
|
||||
|
||||
* -e: Sets configuration settings for Playbook. In this case the URL where we
|
||||
have to post messages. You can specify multiple *-e* flags.
|
||||
|
||||
* -t: Topic to susbcribe. You can specify multiple *-t* flags and a trigger
|
||||
will be created for each topic, so when we receive a message in that topic,
|
||||
our function will be ran. In this case, playbook will be run when a
|
||||
falco.error or falco.info alert is raised.
|
||||
|
||||
### Kubeless 101
|
||||
|
||||
Under the hood, there are several useful commands for checking function state with kubeless.
|
||||
|
||||
|
||||
We can retrieve all functions deployed in our cluster:
|
||||
```
|
||||
kubeless function list
|
||||
```
|
||||
|
||||
And we can see several interesting stats about a function usage:
|
||||
```
|
||||
kubeless function top
|
||||
```
|
||||
|
||||
And we can see bindings between functions and NATS topics:
|
||||
```
|
||||
kubeless trigger nats list
|
||||
```
|
||||
|
||||
### Undeploying a function
|
||||
|
||||
You have to delete every component using kubeless cli tool.
|
||||
|
||||
Generally, it takes 2 steps: Remove the triggers and remove the function.
|
||||
|
||||
Remove the triggers:
|
||||
```
|
||||
kubeless trigger nats delete trigger-name
|
||||
```
|
||||
|
||||
If you have deployed with the script, trigger-name look like:
|
||||
*falco-<playbook>-trigger-<index>* where index is the index of the topic created.
|
||||
Anyway, you can list all triggers and select the name.
|
||||
|
||||
|
||||
Remove the function:
|
||||
```
|
||||
kubeless function delete function-name
|
||||
```
|
||||
|
||||
If you have deployed with the script, the function name will start with *falco-<playbook>*,
|
||||
but you can list all functions and select its name.
|
||||
|
||||
## Testing
|
||||
|
||||
One of the goals of the project was that playbooks were tested.
|
||||
|
||||
You can execute the tests with:
|
||||
|
||||
```
|
||||
pipenv --three install -d
|
||||
export KUBERNETES_LOAD_KUBE_CONFIG=1
|
||||
pipenv run mamba --format=documentation
|
||||
```
|
||||
|
||||
The first line install development tools, which includes test runner and assertions.
|
||||
The second one tells Kubernetes Client to use the same configuration than kubectl and
|
||||
the third one runs the test.
|
||||
|
||||
The tests under *specs/infrastructure* runs against a real Kubernetes cluster,
|
||||
but the *spec/reactions* can be run without any kind of infrastructure.
|
||||
|
||||
## Available Playbooks
|
||||
|
||||
### Delete a Pod
|
||||
|
||||
This playbook kills a pod using Kubernetes API
|
||||
|
||||
```
|
||||
./deploy_playbook -p delete -t "falco.notice.terminal_shell_in_container"
|
||||
```
|
||||
|
||||
In this example, everytime we receive a *Terminal shell in container* alert from
|
||||
Falco, that pod will be deleted.
|
||||
|
||||
### Send message to Slack
|
||||
|
||||
This playbook posts a message to Slack
|
||||
|
||||
```
|
||||
./deploy_playbook -p slack -t "falco.error.*" -e SLACK_WEBHOOK_URL="https://..."
|
||||
```
|
||||
|
||||
#### Parameters
|
||||
|
||||
* SLACK_WEBHOOK_URL: This is the webhook used for posting messages in Slack
|
||||
|
||||
In this example, when Falco raises an error we will be notified in Slack
|
||||
|
||||
### Taint a Node
|
||||
|
||||
This playbook taints the node which where pod is running.
|
||||
|
||||
```
|
||||
$ ./deploy_playbook -p taint -t “falco.notice.contact_k8s_api_server_from_container”
|
||||
```
|
||||
|
||||
#### Parameters:
|
||||
* TAINT_KEY: This is the taint key. Default value: ‘falco/alert’
|
||||
* TAINT_VALUE: This is the taint value. Default value: ‘true’
|
||||
* TAINT_EFFECT: This is the taint effect. Default value: ‘NoSchedule’
|
||||
|
||||
In this example, we avoid scheduling in the node which originates the Contact
|
||||
K8S API server from container. But we can use a more aggresive approach and
|
||||
use -e TAINT_EFFECT=NoExecute
|
||||
|
||||
### Network isolate a Pod
|
||||
|
||||
This reaction denies all ingress/egress traffic from a Pod. It's intended to
|
||||
be used with Calico or other similar projects for managing networking in
|
||||
Kubernetes.
|
||||
|
||||
```
|
||||
./deploy_playbook -p isolate -t “falco.notice.write_below_binary_dir” -t “falco.error.write_below_etc”
|
||||
```
|
||||
|
||||
So as soon as we notice someone wrote under /bin (and additional binaries) or
|
||||
/etc, we disconnect that pod. It's like a trap for our attackers.
|
||||
16
integrations/kubernetes-response-engine/playbooks/delete.py
Normal file
16
integrations/kubernetes-response-engine/playbooks/delete.py
Normal file
@@ -0,0 +1,16 @@
|
||||
import sys
|
||||
import os.path
|
||||
sys.path.append(os.path.join(os.path.abspath(os.path.dirname(__file__))))
|
||||
|
||||
import os
|
||||
import playbooks
|
||||
from playbooks import infrastructure
|
||||
|
||||
|
||||
playbook = playbooks.DeletePod(
|
||||
infrastructure.KubernetesClient()
|
||||
)
|
||||
|
||||
|
||||
def handler(event, context):
|
||||
playbook.run(event['data'])
|
||||
68
integrations/kubernetes-response-engine/playbooks/deploy_playbook
Executable file
68
integrations/kubernetes-response-engine/playbooks/deploy_playbook
Executable file
@@ -0,0 +1,68 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Deploys a playbook
|
||||
|
||||
set -e
|
||||
|
||||
function usage() {
|
||||
cat<<EOF
|
||||
Usage: $0 [options]
|
||||
|
||||
-p playbook Playbook to be deployed. Is the script for Kubeless: slack, taint, isolate.
|
||||
-e environment Environment variables for the Kubeless function. You can pass multiple environment variables passing several -e parameters.
|
||||
-t topic NATS topic to subscribe function. You can bind to multiple topics passing several -t parameters.
|
||||
|
||||
You must pass the playbook and at least one topic to subscribe.
|
||||
|
||||
Example:
|
||||
|
||||
deploy_playbook -r slack -t "falco.error.*" -e SLACK_WEBHOOK_URL=http://foobar.com/...
|
||||
EOF
|
||||
exit 1
|
||||
}
|
||||
|
||||
function join { local IFS="$1"; shift; echo "$*"; }
|
||||
|
||||
playbook=""
|
||||
environment=()
|
||||
topics=()
|
||||
|
||||
while getopts "r:e:t:" arg; do
|
||||
case $arg in
|
||||
r)
|
||||
playbook="${OPTARG}"
|
||||
;;
|
||||
e)
|
||||
environment+=("${OPTARG}")
|
||||
;;
|
||||
t)
|
||||
topics+=("${OPTARG}")
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ "${playbook}" == "" || ${#topics[@]} -eq 0 ]]; then
|
||||
usage
|
||||
fi
|
||||
|
||||
pipenv lock --requirements | sed '/^-/ d' > requirements.txt
|
||||
|
||||
zip "${playbook}".zip -r playbooks/*.py "${playbook}".py
|
||||
|
||||
kubeless function deploy --from-file "${playbook}".zip \
|
||||
--dependencies requirements.txt \
|
||||
--env "$(join , ${environment[*]})" \
|
||||
--runtime python3.6 \
|
||||
--handler "${playbook}".handler \
|
||||
falco-"${playbook}"
|
||||
|
||||
rm requirements.txt ${playbook}.zip
|
||||
|
||||
for index in ${!topics[*]}; do
|
||||
kubeless trigger nats create falco-"${playbook}"-trigger-"${index}" \
|
||||
--function-selector created-by=kubeless,function=falco-${playbook} \
|
||||
--trigger-topic "${topics[$index]}"
|
||||
done
|
||||
16
integrations/kubernetes-response-engine/playbooks/isolate.py
Normal file
16
integrations/kubernetes-response-engine/playbooks/isolate.py
Normal file
@@ -0,0 +1,16 @@
|
||||
import sys
|
||||
import os.path
|
||||
sys.path.append(os.path.join(os.path.abspath(os.path.dirname(__file__))))
|
||||
|
||||
import os
|
||||
import playbooks
|
||||
from playbooks import infrastructure
|
||||
|
||||
|
||||
playbook = playbooks.NetworkIsolatePod(
|
||||
infrastructure.KubernetesClient()
|
||||
)
|
||||
|
||||
|
||||
def handler(event, context):
|
||||
playbook.run(event['data'])
|
||||
@@ -0,0 +1,101 @@
|
||||
import maya
|
||||
|
||||
|
||||
class DeletePod:
|
||||
def __init__(self, k8s_client):
|
||||
self._k8s_client = k8s_client
|
||||
|
||||
def run(self, alert):
|
||||
pod_name = alert['output_fields']['k8s.pod.name']
|
||||
|
||||
self._k8s_client.delete_pod(pod_name)
|
||||
|
||||
|
||||
class AddMessageToSlack:
|
||||
def __init__(self, slack_client):
|
||||
self._slack_client = slack_client
|
||||
|
||||
def run(self, alert):
|
||||
message = self._build_slack_message(alert)
|
||||
self._slack_client.post_message(message)
|
||||
|
||||
return message
|
||||
|
||||
def _build_slack_message(self, alert):
|
||||
return {
|
||||
'text': self._output(alert),
|
||||
'attachments': [{
|
||||
'color': self._color_from(alert['priority']),
|
||||
'fields': [
|
||||
{
|
||||
'title': 'Rule',
|
||||
'value': alert['rule'],
|
||||
'short': False
|
||||
},
|
||||
{
|
||||
'title': 'Priority',
|
||||
'value': alert['priority'],
|
||||
'short': True
|
||||
},
|
||||
{
|
||||
'title': 'Time',
|
||||
'value': str(maya.parse(alert['time'])),
|
||||
'short': True
|
||||
},
|
||||
{
|
||||
'title': 'Kubernetes Pod Name',
|
||||
'value': alert['output_fields']['k8s.pod.name'],
|
||||
'short': True
|
||||
},
|
||||
{
|
||||
'title': 'Container Id',
|
||||
'value': alert['output_fields']['container.id'],
|
||||
'short': True
|
||||
}
|
||||
]
|
||||
}]
|
||||
}
|
||||
|
||||
def _output(self, alert):
|
||||
output = alert['output'].split(': ')[1]
|
||||
priority_plus_whitespace_length = len(alert['priority']) + 1
|
||||
|
||||
return output[priority_plus_whitespace_length:]
|
||||
|
||||
_COLORS = {
|
||||
'Emergency': '#b12737',
|
||||
'Alert': '#f24141',
|
||||
'Critical': '#fc7335',
|
||||
'Error': '#f28143',
|
||||
'Warning': '#f9c414',
|
||||
'Notice': '#397ec3',
|
||||
'Informational': '#8fc0e7',
|
||||
'Debug': '#8fc0e7',
|
||||
}
|
||||
|
||||
def _color_from(self, priority):
|
||||
return self._COLORS.get(priority, '#eeeeee')
|
||||
|
||||
|
||||
class TaintNode:
|
||||
def __init__(self, k8s_client, key, value, effect):
|
||||
self._k8s_client = k8s_client
|
||||
self._key = key
|
||||
self._value = value
|
||||
self._effect = effect
|
||||
|
||||
def run(self, alert):
|
||||
pod = alert['output_fields']['k8s.pod.name']
|
||||
node = self._k8s_client.find_node_running_pod(pod)
|
||||
|
||||
self._k8s_client.taint_node(node, self._key, self._value, self._effect)
|
||||
|
||||
|
||||
class NetworkIsolatePod:
|
||||
def __init__(self, k8s_client):
|
||||
self._k8s_client = k8s_client
|
||||
|
||||
def run(self, alert):
|
||||
pod = alert['output_fields']['k8s.pod.name']
|
||||
|
||||
self._k8s_client.add_label_to_pod(pod, 'isolated', 'true')
|
||||
@@ -0,0 +1,74 @@
|
||||
import os
|
||||
import json
|
||||
|
||||
from kubernetes import client, config
|
||||
import requests
|
||||
|
||||
|
||||
class KubernetesClient:
|
||||
def __init__(self):
|
||||
if 'KUBERNETES_LOAD_KUBE_CONFIG' in os.environ:
|
||||
config.load_kube_config()
|
||||
else:
|
||||
config.load_incluster_config()
|
||||
|
||||
self._v1 = client.CoreV1Api()
|
||||
|
||||
def delete_pod(self, name):
|
||||
namespace = self._find_pod_namespace(name)
|
||||
body = client.V1DeleteOptions()
|
||||
self._v1.delete_namespaced_pod(name=name,
|
||||
namespace=namespace,
|
||||
body=body)
|
||||
|
||||
def exists_pod(self, name):
|
||||
response = self._v1.list_pod_for_all_namespaces(watch=False)
|
||||
for item in response.items:
|
||||
if item.metadata.name == name:
|
||||
if item.metadata.deletion_timestamp is None:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def _find_pod_namespace(self, name):
|
||||
response = self._v1.list_pod_for_all_namespaces(watch=False)
|
||||
for item in response.items:
|
||||
if item.metadata.name == name:
|
||||
return item.metadata.namespace
|
||||
|
||||
def find_node_running_pod(self, name):
|
||||
response = self._v1.list_pod_for_all_namespaces(watch=False)
|
||||
for item in response.items:
|
||||
if item.metadata.name == name:
|
||||
return item.spec.node_name
|
||||
|
||||
def taint_node(self, name, key, value, effect):
|
||||
body = client.V1Node(
|
||||
spec=client.V1NodeSpec(
|
||||
taints=[
|
||||
client.V1Taint(key=key, value=value, effect=effect)
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
return self._v1.patch_node(name, body)
|
||||
|
||||
def add_label_to_pod(self, name, label, value):
|
||||
namespace = self._find_pod_namespace(name)
|
||||
|
||||
body = client.V1Pod(
|
||||
metadata=client.V1ObjectMeta(
|
||||
labels={label: value}
|
||||
)
|
||||
)
|
||||
|
||||
return self._v1.patch_namespaced_pod(name, namespace, body)
|
||||
|
||||
|
||||
class SlackClient:
|
||||
def __init__(self, slack_webhook_url):
|
||||
self._slack_webhook_url = slack_webhook_url
|
||||
|
||||
def post_message(self, message):
|
||||
requests.post(self._slack_webhook_url,
|
||||
data=json.dumps(message))
|
||||
11
integrations/kubernetes-response-engine/playbooks/setup.py
Normal file
11
integrations/kubernetes-response-engine/playbooks/setup.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from setuptools import setup
|
||||
|
||||
setup(name='playbooks',
|
||||
version='0.1',
|
||||
description='A set of playbooks for Falco alerts',
|
||||
url='http://github.com/draios/falco-playbooks',
|
||||
author='Néstor Salceda',
|
||||
author_email='nestor.salceda@sysdig.com',
|
||||
license='',
|
||||
packages=['playbooks'],
|
||||
zip_safe=False)
|
||||
16
integrations/kubernetes-response-engine/playbooks/slack.py
Normal file
16
integrations/kubernetes-response-engine/playbooks/slack.py
Normal file
@@ -0,0 +1,16 @@
|
||||
import sys
|
||||
import os.path
|
||||
sys.path.append(os.path.join(os.path.abspath(os.path.dirname(__file__))))
|
||||
|
||||
import os
|
||||
import playbooks
|
||||
from playbooks import infrastructure
|
||||
|
||||
|
||||
playbook = playbooks.AddMessageToSlack(
|
||||
infrastructure.SlackClient(os.environ['SLACK_WEBHOOK_URL'])
|
||||
)
|
||||
|
||||
|
||||
def handler(event, context):
|
||||
playbook.run(event['data'])
|
||||
@@ -0,0 +1,65 @@
|
||||
from mamba import description, context, it, before
|
||||
from expects import expect, be_false, be_true, start_with, equal, have_key
|
||||
|
||||
import subprocess
|
||||
import os.path
|
||||
|
||||
from playbooks import infrastructure
|
||||
|
||||
|
||||
with description(infrastructure.KubernetesClient) as self:
|
||||
with before.each:
|
||||
self.kubernetes_client = infrastructure.KubernetesClient()
|
||||
|
||||
with context('when checking if a pod exists'):
|
||||
with before.each:
|
||||
self._create_nginx_pod()
|
||||
|
||||
with context('and pod exists'):
|
||||
with it('returns true'):
|
||||
expect(self.kubernetes_client.exists_pod('nginx')).to(be_true)
|
||||
|
||||
with context('and pod does not exist'):
|
||||
with it('returns false'):
|
||||
self.kubernetes_client.delete_pod('nginx')
|
||||
|
||||
expect(self.kubernetes_client.exists_pod('nginx')).to(be_false)
|
||||
|
||||
with it('finds node running pod'):
|
||||
self._create_nginx_pod()
|
||||
|
||||
node = self.kubernetes_client.find_node_running_pod('nginx')
|
||||
|
||||
expect(node).to(start_with('gke-sysdig-work-default-pool'))
|
||||
|
||||
with it('taints node'):
|
||||
self._create_nginx_pod()
|
||||
|
||||
node_name = self.kubernetes_client.find_node_running_pod('nginx')
|
||||
|
||||
node = self.kubernetes_client.taint_node(node_name,
|
||||
'playbooks',
|
||||
'true',
|
||||
'NoSchedule')
|
||||
|
||||
expect(node.spec.taints[0].effect).to(equal('NoSchedule'))
|
||||
expect(node.spec.taints[0].key).to(equal('playbooks'))
|
||||
expect(node.spec.taints[0].value).to(equal('true'))
|
||||
|
||||
with it('adds labels to a pod'):
|
||||
self._create_nginx_pod()
|
||||
|
||||
pod = self.kubernetes_client.add_label_to_pod('nginx',
|
||||
'testing',
|
||||
'true')
|
||||
|
||||
expect(pod.metadata.labels).to(have_key('testing', 'true'))
|
||||
|
||||
def _create_nginx_pod(self):
|
||||
current_directory = os.path.dirname(os.path.realpath(__file__))
|
||||
pod_manifesto = os.path.join(current_directory,
|
||||
'..',
|
||||
'support',
|
||||
'deployment.yaml')
|
||||
|
||||
subprocess.run(['kubectl', 'create', '-f', pod_manifesto])
|
||||
@@ -0,0 +1,16 @@
|
||||
from mamba import description, it
|
||||
|
||||
import os
|
||||
|
||||
from playbooks import infrastructure
|
||||
|
||||
|
||||
with description(infrastructure.SlackClient) as self:
|
||||
with it('posts a message to #kubeless-demo channel'):
|
||||
slack_client = infrastructure.SlackClient(os.environ['SLACK_WEBHOOK_URL'])
|
||||
|
||||
message = {
|
||||
'text': 'Hello from Python! :metal:'
|
||||
}
|
||||
|
||||
slack_client.post_message(message)
|
||||
@@ -0,0 +1,62 @@
|
||||
from mamba import description, it, before, context
|
||||
from expects import expect, have_key, have_keys, contain
|
||||
|
||||
from doublex import Spy
|
||||
from doublex_expects import have_been_called_with
|
||||
|
||||
from playbooks import infrastructure
|
||||
import playbooks
|
||||
|
||||
|
||||
|
||||
with description(playbooks.AddMessageToSlack) as self:
|
||||
with before.each:
|
||||
self.slack_client = Spy(infrastructure.SlackClient)
|
||||
self.playbook = playbooks.AddMessageToSlack(self.slack_client)
|
||||
|
||||
with context('when publishing a message to slack'):
|
||||
with before.each:
|
||||
self.alert = {
|
||||
"output": "10:22:15.576767292: Notice Unexpected setuid call by non-sudo, non-root program (user=bin cur_uid=2 parent=event_generator command=event_generator uid=root) k8s.pod=falco-event-generator-6fd89678f9-cdkvz container=1c76f49f40b4",
|
||||
"output_fields": {
|
||||
"container.id": "1c76f49f40b4",
|
||||
"evt.arg.uid": "root",
|
||||
"evt.time": 1527157335576767292,
|
||||
"k8s.pod.name": "falco-event-generator-6fd89678f9-cdkvz",
|
||||
"proc.cmdline": "event_generator ",
|
||||
"proc.pname": "event_generator",
|
||||
"user.name": "bin",
|
||||
"user.uid": 2
|
||||
},
|
||||
"priority": "Notice",
|
||||
"rule": "Non sudo setuid",
|
||||
"time": "2018-05-24T10:22:15.576767292Z"
|
||||
}
|
||||
|
||||
self.message = self.playbook.run(self.alert)
|
||||
|
||||
with it('publishes message to slack'):
|
||||
expect(self.slack_client.post_message).to(have_been_called_with(self.message))
|
||||
|
||||
with it('includes falco output'):
|
||||
falco_output = 'Unexpected setuid call by non-sudo, non-root program (user=bin cur_uid=2 parent=event_generator command=event_generator uid=root) k8s.pod=falco-event-generator-6fd89678f9-cdkvz container=1c76f49f40b4'
|
||||
|
||||
expect(self.message).to(have_key('text', falco_output))
|
||||
|
||||
with it('includes color based on priority'):
|
||||
expect(self.message['attachments'][0]).to(have_key('color'))
|
||||
|
||||
with it('includes priority'):
|
||||
expect(self.message['attachments'][0]['fields']).to(contain(have_keys(title='Priority', value='Notice')))
|
||||
|
||||
with it('includes rule name'):
|
||||
expect(self.message['attachments'][0]['fields']).to(contain(have_keys(title='Rule', value='Non sudo setuid')))
|
||||
|
||||
with it('includes time when alert happened'):
|
||||
expect(self.message['attachments'][0]['fields']).to(contain(have_keys(title='Time', value='Thu, 24 May 2018 10:22:15 GMT')))
|
||||
|
||||
with it('includes kubernetes pod name'):
|
||||
expect(self.message['attachments'][0]['fields']).to(contain(have_keys(title='Kubernetes Pod Name', value='falco-event-generator-6fd89678f9-cdkvz')))
|
||||
|
||||
with it('includes container id'):
|
||||
expect(self.message['attachments'][0]['fields']).to(contain(have_keys(title='Container Id', value='1c76f49f40b4')))
|
||||
@@ -0,0 +1,22 @@
|
||||
from mamba import description, it, before
|
||||
from expects import expect
|
||||
|
||||
from doublex import Spy
|
||||
from doublex_expects import have_been_called_with
|
||||
|
||||
from playbooks import infrastructure
|
||||
import playbooks
|
||||
|
||||
|
||||
with description(playbooks.DeletePod) as self:
|
||||
with before.each:
|
||||
self.k8s_client = Spy(infrastructure.KubernetesClient)
|
||||
self.playbook = playbooks.DeletePod(self.k8s_client)
|
||||
|
||||
with it('deletes a pod'):
|
||||
pod_name = 'a pod name'
|
||||
alert = {'output_fields': {'k8s.pod.name': pod_name}}
|
||||
|
||||
self.playbook.run(alert)
|
||||
|
||||
expect(self.k8s_client.delete_pod).to(have_been_called_with(pod_name))
|
||||
@@ -0,0 +1,22 @@
|
||||
from mamba import description, it, before
|
||||
from expects import expect
|
||||
|
||||
from doublex import Spy
|
||||
from doublex_expects import have_been_called
|
||||
|
||||
from playbooks import infrastructure
|
||||
import playbooks
|
||||
|
||||
|
||||
with description(playbooks.NetworkIsolatePod) as self:
|
||||
with before.each:
|
||||
self.k8s_client = Spy(infrastructure.KubernetesClient)
|
||||
self.playbook = playbooks.NetworkIsolatePod(self.k8s_client)
|
||||
|
||||
with it('adds isolation label to pod'):
|
||||
pod_name = 'any pod name'
|
||||
alert = {'output_fields': {'k8s.pod.name': pod_name}}
|
||||
|
||||
self.playbook.run(alert)
|
||||
|
||||
expect(self.k8s_client.add_label_to_pod).to(have_been_called)
|
||||
@@ -0,0 +1,34 @@
|
||||
from mamba import description, it, before
|
||||
from expects import expect
|
||||
|
||||
from doublex import Spy, when
|
||||
from doublex_expects import have_been_called_with
|
||||
|
||||
from playbooks import infrastructure
|
||||
import playbooks
|
||||
|
||||
|
||||
with description(playbooks.TaintNode) as self:
|
||||
with before.each:
|
||||
self.k8s_client = Spy(infrastructure.KubernetesClient)
|
||||
self.key = 'falco/alert'
|
||||
self.value = 'true'
|
||||
self.effect = 'NoSchedule'
|
||||
self.playbook = playbooks.TaintNode(self.k8s_client,
|
||||
self.key,
|
||||
self.value,
|
||||
self.effect)
|
||||
|
||||
with it('taints the node'):
|
||||
pod_name = 'any pod name'
|
||||
alert = {'output_fields': {'k8s.pod.name': pod_name}}
|
||||
|
||||
node = 'any node'
|
||||
when(self.k8s_client).find_node_running_pod(pod_name).returns(node)
|
||||
|
||||
self.playbook.run(alert)
|
||||
|
||||
expect(self.k8s_client.taint_node).to(have_been_called_with(node,
|
||||
self.key,
|
||||
self.value,
|
||||
self.effect))
|
||||
@@ -0,0 +1,10 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: nginx
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:latest
|
||||
ports:
|
||||
- containerPort: 80
|
||||
19
integrations/kubernetes-response-engine/playbooks/taint.py
Normal file
19
integrations/kubernetes-response-engine/playbooks/taint.py
Normal file
@@ -0,0 +1,19 @@
|
||||
import sys
|
||||
import os.path
|
||||
sys.path.append(os.path.join(os.path.abspath(os.path.dirname(__file__))))
|
||||
|
||||
import os
|
||||
import playbooks
|
||||
from playbooks import infrastructure
|
||||
|
||||
|
||||
playbook = playbooks.TaintNode(
|
||||
infrastructure.KubernetesClient(),
|
||||
os.environ.get('TAINT_KEY', 'falco/alert'),
|
||||
os.environ.get('TAINT_VALUE', 'true'),
|
||||
os.environ.get('TAINT_EFFECT', 'NoSchedule')
|
||||
)
|
||||
|
||||
|
||||
def handler(event, context):
|
||||
playbook.run(event['data'])
|
||||
7
integrations/logrotate/falco
Normal file
7
integrations/logrotate/falco
Normal file
@@ -0,0 +1,7 @@
|
||||
/var/log/falco-events.log {
|
||||
rotate 5
|
||||
size 1M
|
||||
postrotate
|
||||
/usr/bin/killall -USR1 falco
|
||||
endscript
|
||||
}
|
||||
@@ -31,7 +31,9 @@ install(FILES falco_rules.local.yaml
|
||||
RENAME "${FALCO_LOCAL_RULES_DEST_FILENAME}")
|
||||
|
||||
install(FILES application_rules.yaml
|
||||
DESTINATION "${FALCO_ETC_DIR}"
|
||||
DESTINATION "/etc/falco/rules.available"
|
||||
RENAME "${FALCO_APP_RULES_DEST_FILENAME}")
|
||||
|
||||
install(DIRECTORY DESTINATION "/etc/falco/rules.d")
|
||||
endif()
|
||||
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
- macro: never_true
|
||||
condition: (evt.num=0)
|
||||
|
||||
- macro: always_true
|
||||
condition: (evt.num=>0)
|
||||
|
||||
# In some cases, such as dropped system call events, information about
|
||||
# the process name may be missing. For some rules that really depend
|
||||
# on the identity of the process performing an action such as opening
|
||||
@@ -23,7 +26,7 @@
|
||||
condition: (proc.name!="<NA>")
|
||||
|
||||
- macro: rename
|
||||
condition: evt.type = rename
|
||||
condition: evt.type in (rename, renameat)
|
||||
- macro: mkdir
|
||||
condition: evt.type = mkdir
|
||||
- macro: remove
|
||||
@@ -149,13 +152,13 @@
|
||||
- list: rpm_binaries
|
||||
items: [dnf, rpm, rpmkey, yum, '"75-system-updat"', rhsmcertd-worke, subscription-ma,
|
||||
repoquery, rpmkeys, rpmq, yum-cron, yum-config-mana, yum-debug-dump,
|
||||
abrt-action-sav, rpmdb_stat]
|
||||
abrt-action-sav, rpmdb_stat, microdnf]
|
||||
|
||||
- macro: rpm_procs
|
||||
condition: proc.name in (rpm_binaries) or proc.name in (salt-minion)
|
||||
|
||||
- list: deb_binaries
|
||||
items: [dpkg, dpkg-preconfigu, dpkg-reconfigur, apt, apt-get, aptitude,
|
||||
items: [dpkg, dpkg-preconfigu, dpkg-reconfigur, dpkg-divert, apt, apt-get, aptitude,
|
||||
frontend, preinst, add-apt-reposit, apt-auto-remova, apt-key,
|
||||
apt-listchanges, unattended-upgr, apt-add-reposit
|
||||
]
|
||||
@@ -163,11 +166,14 @@
|
||||
# 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, pip, sane-utils.post]
|
||||
items: [rpm_binaries, deb_binaries, update-alternat, gem, pip, pip3, sane-utils.post, alternatives, chef-client]
|
||||
|
||||
- macro: package_mgmt_procs
|
||||
condition: proc.name in (package_mgmt_binaries)
|
||||
|
||||
- macro: coreos_write_ssh_dir
|
||||
condition: (proc.name=update-ssh-keys and fd.name startswith /home/core/.ssh)
|
||||
|
||||
- macro: run_by_package_mgmt_binaries
|
||||
condition: proc.aname in (package_mgmt_binaries, needrestart)
|
||||
|
||||
@@ -237,12 +243,27 @@
|
||||
|
||||
# Network
|
||||
- macro: inbound
|
||||
condition: ((evt.type=listen and evt.dir=>) or (evt.type=accept and evt.dir=<))
|
||||
condition: >
|
||||
(((evt.type in (accept,listen) and evt.dir=<)) or
|
||||
(fd.typechar = 4 or fd.typechar = 6) and
|
||||
(fd.ip != "0.0.0.0" and fd.net != "127.0.0.0/8") and
|
||||
(evt.rawres >= 0 or evt.res = EINPROGRESS))
|
||||
|
||||
# Currently sendto is an ignored syscall, otherwise this could also
|
||||
# check for (evt.type=sendto and evt.dir=>)
|
||||
- macro: outbound
|
||||
condition: evt.type=connect and evt.dir=< and (fd.typechar=4 or fd.typechar=6)
|
||||
condition: >
|
||||
(((evt.type = connect and evt.dir=<)) or
|
||||
(fd.typechar = 4 or fd.typechar = 6) and
|
||||
(fd.ip != "0.0.0.0" and fd.net != "127.0.0.0/8") and
|
||||
(evt.rawres >= 0 or evt.res = EINPROGRESS))
|
||||
|
||||
# Very similar to inbound/outbound, but combines the tests together
|
||||
# for efficiency.
|
||||
- macro: inbound_outbound
|
||||
condition: >
|
||||
(((evt.type in (accept,listen,connect) and evt.dir=<)) or
|
||||
(fd.typechar = 4 or fd.typechar = 6) and
|
||||
(fd.ip != "0.0.0.0" and fd.net != "127.0.0.0/8") and
|
||||
(evt.rawres >= 0 or evt.res = EINPROGRESS))
|
||||
|
||||
- macro: ssh_port
|
||||
condition: fd.sport=22
|
||||
@@ -262,7 +283,7 @@
|
||||
|
||||
- rule: Disallowed SSH Connection
|
||||
desc: Detect any new ssh connection to a host other than those in an allowed group of hosts
|
||||
condition: (outbound or inbound) and ssh_port and not allowed_ssh_hosts
|
||||
condition: (inbound_outbound) and ssh_port and not allowed_ssh_hosts
|
||||
output: Disallowed SSH Connection (command=%proc.cmdline connection=%fd.name user=%user.name)
|
||||
priority: NOTICE
|
||||
tags: [network]
|
||||
@@ -350,6 +371,16 @@
|
||||
proc.pcmdline startswith "node /root/.config/yarn" or
|
||||
proc.pcmdline startswith "node /opt/yarn/bin/yarn.js"))
|
||||
|
||||
|
||||
- macro: httpd_writing_ssl_conf
|
||||
condition: >
|
||||
(proc.pname=run-httpd and
|
||||
(proc.cmdline startswith "sed -ri" or proc.cmdline startswith "sed -i") and
|
||||
(fd.name startswith /etc/httpd/conf.d/ or fd.name startswith /etc/httpd/conf))
|
||||
|
||||
- macro: userhelper_writing_etc_security
|
||||
condition: (proc.name=userhelper and fd.name startswith /etc/security)
|
||||
|
||||
- macro: parent_Xvfb_running_xkbcomp
|
||||
condition: (proc.pname=Xvfb and proc.cmdline startswith 'sh -c "/usr/bin/xkbcomp"')
|
||||
|
||||
@@ -370,18 +401,13 @@
|
||||
- list: known_shell_spawn_binaries
|
||||
items: []
|
||||
|
||||
- macro: shell_spawning_containers
|
||||
condition: (container.image startswith jenkins or
|
||||
container.image startswith gitlab/gitlab-ce or
|
||||
container.image startswith gitlab/gitlab-ee)
|
||||
|
||||
## End Deprecated
|
||||
|
||||
- macro: ansible_running_python
|
||||
condition: (proc.name in (python, pypy) and proc.cmdline contains ansible)
|
||||
|
||||
- macro: chef_running_yum_dump
|
||||
condition: (proc.name=python and proc.cmdline contains yum-dump.py)
|
||||
- macro: python_running_chef
|
||||
condition: (proc.name=python and (proc.cmdline contains yum-dump.py or proc.cmdline="python /usr/bin/chef-monitor.py"))
|
||||
|
||||
- macro: python_running_denyhosts
|
||||
condition: >
|
||||
@@ -465,9 +491,13 @@
|
||||
- macro: htpasswd_writing_passwd
|
||||
condition: (proc.name=htpasswd and fd.name=/etc/nginx/.htpasswd)
|
||||
|
||||
- macro: lvprogs_writing_lvm_archive
|
||||
condition: (proc.name in (dmeventd,lvcreate) and (fd.name startswith /etc/lvm/archive or
|
||||
fd.name startswith /etc/lvm/backup))
|
||||
- macro: lvprogs_writing_conf
|
||||
condition: >
|
||||
(proc.name in (dmeventd,lvcreate,pvscan) and
|
||||
(fd.name startswith /etc/lvm/archive or
|
||||
fd.name startswith /etc/lvm/backup or
|
||||
fd.name startswith /etc/lvm/cache))
|
||||
|
||||
- macro: ovsdb_writing_openvswitch
|
||||
condition: (proc.name=ovsdb-server and fd.directory=/etc/openvswitch)
|
||||
|
||||
@@ -492,10 +522,14 @@
|
||||
- macro: countly_writing_nginx_conf
|
||||
condition: (proc.cmdline startswith "nodejs /opt/countly/bin" and fd.name startswith /etc/nginx)
|
||||
|
||||
- list: ms_oms_binaries
|
||||
items: [omi.postinst, omsconfig.posti, scx.postinst, omsadmin.sh, omiagent]
|
||||
|
||||
- macro: ms_oms_writing_conf
|
||||
condition: >
|
||||
((proc.name in (omiagent,omsagent,in_heartbeat_r*,omsadmin.sh,PerformInventor)
|
||||
or proc.pname in (omi.postinst,omsconfig.posti,scx.postinst,omsadmin.sh,omiagent))
|
||||
or proc.pname in (ms_oms_binaries)
|
||||
or proc.aname[2] in (ms_oms_binaries))
|
||||
and (fd.name startswith /etc/opt/omi or fd.name startswith /etc/opt/microsoft/omsagent))
|
||||
|
||||
- macro: ms_scx_writing_conf
|
||||
@@ -516,6 +550,15 @@
|
||||
- macro: slapadd_writing_conf
|
||||
condition: (proc.name=slapadd and fd.name startswith /etc/ldap)
|
||||
|
||||
- macro: openldap_writing_conf
|
||||
condition: (proc.pname=run-openldap.sh and fd.name startswith /etc/openldap)
|
||||
|
||||
- macro: ucpagent_writing_conf
|
||||
condition: (proc.name=apiserver and container.image startswith docker/ucp-agent and fd.name=/etc/authorization_config.cfg)
|
||||
|
||||
- macro: iscsi_writing_conf
|
||||
condition: (proc.name=iscsiadm and fd.name startswith /etc/iscsi)
|
||||
|
||||
- macro: symantec_writing_conf
|
||||
condition: >
|
||||
((proc.name=symcfgd and fd.name startswith /etc/symantec) or
|
||||
@@ -529,6 +572,17 @@
|
||||
(proc.name=urlgrabber-ext- and proc.aname[3]=sosreport and
|
||||
(fd.name startswith /etc/pkt/nssdb or fd.name startswith /etc/pki/nssdb))
|
||||
|
||||
- macro: pkgmgmt_progs_writing_pki
|
||||
condition: >
|
||||
(proc.name=urlgrabber-ext- and proc.pname in (yum, yum-cron, repoquery) and
|
||||
(fd.name startswith /etc/pkt/nssdb or fd.name startswith /etc/pki/nssdb))
|
||||
|
||||
- macro: update_ca_trust_writing_pki
|
||||
condition: (proc.pname=update-ca-trust and proc.name=trust and fd.name startswith /etc/pki)
|
||||
|
||||
- macro: brandbot_writing_os_release
|
||||
condition: proc.name=brandbot and fd.name=/etc/os-release
|
||||
|
||||
- macro: selinux_writing_conf
|
||||
condition: (proc.name in (semodule,genhomedircon,sefcontext_comp) and fd.name startswith /etc/selinux)
|
||||
|
||||
@@ -542,7 +596,7 @@
|
||||
condition: (proc.name in (veritas_binaries) or veritas_driver_script)
|
||||
|
||||
- macro: veritas_writing_config
|
||||
condition: (veritas_progs and fd.name startswith /etc/vx)
|
||||
condition: (veritas_progs and (fd.name startswith /etc/vx or fd.name startswith /etc/opt/VRTS or fd.name startswith /etc/vom))
|
||||
|
||||
- macro: nginx_writing_conf
|
||||
condition: (proc.name=nginx and fd.name startswith /etc/nginx)
|
||||
@@ -568,6 +622,11 @@
|
||||
- macro: exe_running_docker_save
|
||||
condition: (proc.cmdline startswith "exe /var/lib/docker" and proc.pname in (dockerd, docker))
|
||||
|
||||
# Ideally we'd have a length check here as well but sysdig
|
||||
# filterchecks don't have operators like len()
|
||||
- macro: sed_temporary_file
|
||||
condition: (proc.name=sed and fd.name startswith "/etc/sed")
|
||||
|
||||
- macro: python_running_get_pip
|
||||
condition: (proc.cmdline startswith "python get-pip.py")
|
||||
|
||||
@@ -577,6 +636,21 @@
|
||||
- macro: gugent_writing_guestagent_log
|
||||
condition: (proc.name=gugent and fd.name=GuestAgent.log)
|
||||
|
||||
- macro: dse_writing_tmp
|
||||
condition: (proc.name=dse-entrypoint and fd.name=/root/tmp__)
|
||||
|
||||
- macro: zap_writing_state
|
||||
condition: (proc.name=java and proc.cmdline contains "jar /zap" and fd.name startswith /root/.ZAP)
|
||||
|
||||
- macro: airflow_writing_state
|
||||
condition: (proc.name=airflow and fd.name startswith /root/airflow)
|
||||
|
||||
- macro: rpm_writing_root_rpmdb
|
||||
condition: (proc.name=rpm and fd.directory=/root/.rpmdb)
|
||||
|
||||
- macro: maven_writing_groovy
|
||||
condition: (proc.name=java and proc.cmdline contains "classpath /usr/local/apache-maven" and fd.name startswith /root/.groovy)
|
||||
|
||||
- rule: Write below binary dir
|
||||
desc: an attempt to write to any file below a set of binary directories
|
||||
condition: >
|
||||
@@ -591,6 +665,45 @@
|
||||
priority: ERROR
|
||||
tags: [filesystem]
|
||||
|
||||
# If you'd like to generally monitor a wider set of directories on top
|
||||
# of the ones covered by the rule Write below binary dir, you can use
|
||||
# the following rule and lists.
|
||||
|
||||
- list: monitored_directories
|
||||
items: [/boot, /lib, /lib64, /usr/lib, /usr/local/lib, /usr/local/sbin, /usr/local/bin, /root/.ssh, /etc/cardserver]
|
||||
|
||||
# Until https://github.com/draios/sysdig/pull/1153, which fixes
|
||||
# https://github.com/draios/sysdig/issues/1152, is widely available,
|
||||
# we can't use glob operators to match pathnames. Until then, we do a
|
||||
# looser check to match ssh directories.
|
||||
# When fixed, we will use "fd.name glob '/home/*/.ssh/*'"
|
||||
- macro: user_ssh_directory
|
||||
condition: (fd.name startswith '/home' and fd.name contains '.ssh')
|
||||
|
||||
- macro: mkinitramfs_writing_boot
|
||||
condition: (proc.pname in (mkinitramfs, update-initramf) and fd.directory=/boot)
|
||||
|
||||
- macro: monitored_dir
|
||||
condition: >
|
||||
(fd.directory in (monitored_directories)
|
||||
or user_ssh_directory)
|
||||
and not mkinitramfs_writing_boot
|
||||
|
||||
- rule: Write below monitored dir
|
||||
desc: an attempt to write to any file below a set of binary directories
|
||||
condition: >
|
||||
evt.dir = < and open_write and monitored_dir
|
||||
and not package_mgmt_procs
|
||||
and not coreos_write_ssh_dir
|
||||
and not exe_running_docker_save
|
||||
and not python_running_get_pip
|
||||
and not python_running_ms_oms
|
||||
output: >
|
||||
File below a monitored directory opened for writing (user=%user.name
|
||||
command=%proc.cmdline file=%fd.name parent=%proc.pname pcmdline=%proc.pcmdline gparent=%proc.aname[2])
|
||||
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]
|
||||
|
||||
@@ -652,7 +765,13 @@
|
||||
condition: (proc.name=httpd and fd.name startswith /etc/httpd/)
|
||||
|
||||
- macro: mysql_writing_conf
|
||||
condition: ((proc.name=start-mysql.sh or proc.pname=start-mysql.sh) and fd.name startswith /etc/mysql)
|
||||
condition: >
|
||||
((proc.name in (start-mysql.sh, run-mysqld) or proc.pname=start-mysql.sh) and
|
||||
(fd.name startswith /etc/mysql or fd.directory=/etc/my.cnf.d))
|
||||
|
||||
- macro: redis_writing_conf
|
||||
condition: >
|
||||
(proc.name in (run-redis, redis-launcher.) and fd.name=/etc/redis.conf or fd.name startswith /etc/redis)
|
||||
|
||||
- macro: openvpn_writing_conf
|
||||
condition: (proc.name in (openvpn,openvpn-entrypo) and fd.name startswith /etc/openvpn)
|
||||
@@ -708,7 +827,8 @@
|
||||
and not proc.pname in (sysdigcloud_binaries, mail_config_binaries, hddtemp.postins, sshkit_script_binaries, locales.postins, deb_binaries, dhcp_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)
|
||||
and not exe_running_docker_save
|
||||
and not sed_temporary_file
|
||||
and not exe_running_docker_save
|
||||
and not ansible_running_python
|
||||
and not python_running_denyhosts
|
||||
and not fluentd_writing_conf_files
|
||||
@@ -729,7 +849,7 @@
|
||||
and not supervise_writing_status
|
||||
and not pki_realm_writing_realms
|
||||
and not htpasswd_writing_passwd
|
||||
and not lvprogs_writing_lvm_archive
|
||||
and not lvprogs_writing_conf
|
||||
and not ovsdb_writing_openvswitch
|
||||
and not datadog_writing_conf
|
||||
and not curl_writing_pki_db
|
||||
@@ -765,6 +885,15 @@
|
||||
and not centrify_writing_krb
|
||||
and not cockpit_writing_conf
|
||||
and not ipsec_writing_conf
|
||||
and not httpd_writing_ssl_conf
|
||||
and not userhelper_writing_etc_security
|
||||
and not pkgmgmt_progs_writing_pki
|
||||
and not update_ca_trust_writing_pki
|
||||
and not brandbot_writing_os_release
|
||||
and not redis_writing_conf
|
||||
and not openldap_writing_conf
|
||||
and not ucpagent_writing_conf
|
||||
and not iscsi_writing_conf
|
||||
|
||||
- rule: Write below etc
|
||||
desc: an attempt to write to any file below /etc
|
||||
@@ -776,7 +905,7 @@
|
||||
- list: known_root_files
|
||||
items: [/root/.monit.state, /root/.auth_tokens, /root/.bash_history, /root/.ash_history, /root/.aws/credentials,
|
||||
/root/.viminfo.tmp, /root/.lesshst, /root/.bzr.log, /root/.gitconfig.lock, /root/.babel.json, /root/.localstack,
|
||||
/root/.node_repl_history, /root/.mongorc.js, /root/.dbshell, /root/.augeas/history, /root/.rnd]
|
||||
/root/.node_repl_history, /root/.mongorc.js, /root/.dbshell, /root/.augeas/history, /root/.rnd, /root/.wget-hsts]
|
||||
|
||||
- list: known_root_directories
|
||||
items: [/root/.oracle_jre_usage, /root/.ssh, /root/.subversion, /root/.nami]
|
||||
@@ -811,7 +940,12 @@
|
||||
or fd.name startswith /root/.dbus
|
||||
or fd.name startswith /root/.composer
|
||||
or fd.name startswith /root/.gconf
|
||||
or fd.name startswith /root/.nv)
|
||||
or fd.name startswith /root/.nv
|
||||
or fd.name startswith /root/.local/share/jupyter
|
||||
or fd.name startswith /root/oradiag_root
|
||||
or fd.name startswith /root/workspace
|
||||
or fd.name startswith /root/jvm
|
||||
or fd.name startswith /root/.node-gyp)
|
||||
|
||||
- rule: Write below root
|
||||
desc: an attempt to write to any file directly below / or /root
|
||||
@@ -821,6 +955,11 @@
|
||||
and not fd.directory in (known_root_directories)
|
||||
and not exe_running_docker_save
|
||||
and not gugent_writing_guestagent_log
|
||||
and not dse_writing_tmp
|
||||
and not zap_writing_state
|
||||
and not airflow_writing_state
|
||||
and not rpm_writing_root_rpmdb
|
||||
and not maven_writing_groovy
|
||||
and not known_root_conditions
|
||||
output: "File below / or /root opened for writing (user=%user.name command=%proc.cmdline parent=%proc.pname file=%fd.name program=%proc.name)"
|
||||
priority: ERROR
|
||||
@@ -845,8 +984,8 @@
|
||||
items: [
|
||||
iptables, ps, lsb_release, check-new-relea, dumpe2fs, accounts-daemon, sshd,
|
||||
vsftpd, systemd, mysql_install_d, psql, screen, debconf-show, sa-update,
|
||||
pam-auth-update, /usr/sbin/spamd, polkit-agent-he, lsattr, file, sosreport,
|
||||
scxcimservera, adclient, rtvscand, cockpit-session
|
||||
pam-auth-update, pam-config, /usr/sbin/spamd, polkit-agent-he, lsattr, file, sosreport,
|
||||
scxcimservera, adclient, rtvscand, cockpit-session, userhelper, ossec-syscheckd
|
||||
]
|
||||
|
||||
# Add conditions to this macro (probably in a separate file,
|
||||
@@ -892,8 +1031,8 @@
|
||||
# Only let rpm-related programs write to the rpm database
|
||||
- rule: Write below rpm database
|
||||
desc: an attempt to write to the rpm database by any non-rpm related program
|
||||
condition: fd.name startswith /var/lib/rpm and open_write and not rpm_procs and not ansible_running_python and not chef_running_yum_dump
|
||||
output: "Rpm database opened for writing by a non-rpm program (command=%proc.cmdline file=%fd.name)"
|
||||
condition: fd.name startswith /var/lib/rpm and open_write and not rpm_procs and not ansible_running_python and not python_running_chef
|
||||
output: "Rpm database opened for writing by a non-rpm program (command=%proc.cmdline file=%fd.name parent=%proc.pname pcmdline=%proc.pcmdline)"
|
||||
priority: ERROR
|
||||
tags: [filesystem, software_mgmt]
|
||||
|
||||
@@ -904,11 +1043,19 @@
|
||||
condition: (proc.aname[2]=redis-server and (proc.cmdline contains "redis-server.post-up.d" or proc.cmdline contains "redis-server.pre-up.d"))
|
||||
|
||||
- macro: rabbitmq_running_scripts
|
||||
condition: (proc.pname=beam.smp and (proc.cmdline startswith "sh -c exec ps" or proc.cmdline startswith "sh -c exec inet_gethost"))
|
||||
condition: >
|
||||
(proc.pname=beam.smp and
|
||||
(proc.cmdline startswith "sh -c exec ps" or
|
||||
proc.cmdline startswith "sh -c exec inet_gethost" or
|
||||
proc.cmdline= "sh -s unix:cmd" or
|
||||
proc.cmdline= "sh -c exec /bin/sh -s unix:cmd 2>&1"))
|
||||
|
||||
- macro: rabbitmqctl_running_scripts
|
||||
condition: (proc.aname[2]=rabbitmqctl and proc.cmdline startswith "sh -c ")
|
||||
|
||||
- macro: run_by_appdynamics
|
||||
condition: (proc.pname=java and proc.pcmdline startswith "java -jar -Dappdynamics")
|
||||
|
||||
- rule: DB program spawned process
|
||||
desc: >
|
||||
a database-server related program spawned a new process other than itself.
|
||||
@@ -926,10 +1073,10 @@
|
||||
|
||||
- rule: Modify binary dirs
|
||||
desc: an attempt to modify any file below a set of binary directories.
|
||||
condition: bin_dir_rename and modify and not package_mgmt_procs and not exe_running_docker_save
|
||||
condition: (bin_dir_rename) and modify and not package_mgmt_procs and not exe_running_docker_save
|
||||
output: >
|
||||
File below known binary directory renamed/removed (user=%user.name command=%proc.cmdline
|
||||
operation=%evt.type file=%fd.name %evt.args)
|
||||
pcmdline=%proc.pcmdline operation=%evt.type file=%fd.name %evt.args)
|
||||
priority: ERROR
|
||||
tags: [filesystem]
|
||||
|
||||
@@ -1041,6 +1188,17 @@
|
||||
- macro: possibly_node_in_container
|
||||
condition: (never_true and (proc.pname=node and proc.aname[3]=docker-containe))
|
||||
|
||||
# Similarly, you may want to consider any shell spawned by apache
|
||||
# tomcat as suspect. The famous apache struts attack (CVE-2017-5638)
|
||||
# could be exploited to do things like spawn shells.
|
||||
#
|
||||
# However, many applications *do* use tomcat to run arbitrary shells,
|
||||
# as a part of build pipelines, etc.
|
||||
#
|
||||
# Like for node, we make this case opt-in.
|
||||
- macro: possibly_parent_java_running_tomcat
|
||||
condition: (never_true and proc.pname=java and proc.pcmdline contains org.apache.catalina.startup.Bootstrap)
|
||||
|
||||
- macro: protected_shell_spawner
|
||||
condition: >
|
||||
(proc.aname in (protected_shell_spawning_binaries)
|
||||
@@ -1053,6 +1211,7 @@
|
||||
or parent_java_running_glassfish
|
||||
or parent_java_running_hadoop
|
||||
or parent_java_running_datastax
|
||||
or possibly_parent_java_running_tomcat
|
||||
or possibly_node_in_container)
|
||||
|
||||
- list: mesos_shell_binaries
|
||||
@@ -1091,11 +1250,12 @@
|
||||
and not redis_running_prepost_scripts
|
||||
and not rabbitmq_running_scripts
|
||||
and not rabbitmqctl_running_scripts
|
||||
and not run_by_appdynamics
|
||||
and not user_shell_container_exclusions
|
||||
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])
|
||||
aname[4]=%proc.aname[4] aname[5]=%proc.aname[5] aname[6]=%proc.aname[6] aname[7]=%proc.aname[7])
|
||||
priority: DEBUG
|
||||
tags: [shell]
|
||||
|
||||
@@ -1115,11 +1275,16 @@
|
||||
container.image startswith registry.access.redhat.com/openshift3/metrics-cassandra or
|
||||
container.image startswith openshift3/ose-sti-builder or
|
||||
container.image startswith registry.access.redhat.com/openshift3/ose-sti-builder or
|
||||
container.image startswith registry.access.redhat.com/openshift3/ose-docker-builder or
|
||||
container.image startswith registry.access.redhat.com/openshift3/image-inspector or
|
||||
container.image startswith cloudnativelabs/kube-router or
|
||||
container.image startswith "consul:" or
|
||||
container.image startswith mesosphere/mesos-slave or
|
||||
container.image startswith istio/proxy_ or
|
||||
container.image startswith datadog/docker-dd-agent)
|
||||
container.image startswith datadog/docker-dd-agent or
|
||||
container.image startswith datadog/agent or
|
||||
container.image startswith docker/ucp-agent or
|
||||
container.image startswith gliderlabs/logspout)
|
||||
|
||||
# Add conditions to this macro (probably in a separate file,
|
||||
# overwriting this macro) to specify additional containers that are
|
||||
@@ -1298,8 +1463,8 @@
|
||||
desc: any network activity performed by system binaries that are not expected to send or receive any network traffic
|
||||
condition: >
|
||||
(fd.sockfamily = ip and system_procs)
|
||||
and (inbound or outbound)
|
||||
and not proc.name in (systemd, hostid)
|
||||
and (inbound_outbound)
|
||||
and not proc.name in (systemd, hostid, id)
|
||||
and not login_doing_dns_lookup
|
||||
output: >
|
||||
Known system binary sent/received network traffic
|
||||
@@ -1307,6 +1472,47 @@
|
||||
priority: NOTICE
|
||||
tags: [network]
|
||||
|
||||
- list: openvpn_udp_ports
|
||||
items: [1194, 1197, 1198, 8080, 9201]
|
||||
|
||||
- list: l2tp_udp_ports
|
||||
items: [500, 1701, 4500, 10000]
|
||||
|
||||
- list: statsd_ports
|
||||
items: [8125]
|
||||
|
||||
- list: ntp_ports
|
||||
items: [123]
|
||||
|
||||
# Some applications will connect a udp socket to an address only to
|
||||
# test connectivity. Assuming the udp connect works, they will follow
|
||||
# up with a tcp connect that actually sends/receives data.
|
||||
#
|
||||
# With that in mind, we listed a few commonly seen ports here to avoid
|
||||
# some false positives. In addition, we make the main rule opt-in, so
|
||||
# it's disabled by default.
|
||||
|
||||
- list: test_connect_ports
|
||||
items: [0, 9, 80, 3306]
|
||||
|
||||
- macro: do_unexpected_udp_check
|
||||
condition: (never_true)
|
||||
|
||||
- list: expected_udp_ports
|
||||
items: [53, openvpn_udp_ports, l2tp_udp_ports, statsd_ports, ntp_ports, test_connect_ports]
|
||||
|
||||
- macro: expected_udp_traffic
|
||||
condition: fd.port in (expected_udp_ports)
|
||||
|
||||
- rule: Unexpected UDP Traffic
|
||||
desc: UDP traffic not on port 53 (DNS) or other commonly used ports
|
||||
condition: (inbound_outbound) and do_unexpected_udp_check and fd.l4proto=udp and not expected_udp_traffic
|
||||
output: >
|
||||
Unexpected UDP Traffic Seen
|
||||
(user=%user.name command=%proc.cmdline connection=%fd.name proto=%fd.l4proto evt=%evt.type %evt.args)
|
||||
priority: NOTICE
|
||||
tags: [network]
|
||||
|
||||
# With the current restriction on system calls handled by falco
|
||||
# (e.g. excluding read/write/sendto/recvfrom/etc, this rule won't
|
||||
# trigger).
|
||||
@@ -1457,7 +1663,7 @@
|
||||
|
||||
- rule: Unexpected K8s NodePort Connection
|
||||
desc: Detect attempts to use K8s NodePorts from a container
|
||||
condition: (outbound or inbound) and fd.sport >= 30000 and fd.sport <= 32767 and container and not nodeport_containers
|
||||
condition: (inbound_outbound) and fd.sport >= 30000 and fd.sport <= 32767 and container and not nodeport_containers
|
||||
output: Unexpected K8s NodePort Connection (command=%proc.cmdline connection=%fd.name)
|
||||
priority: NOTICE
|
||||
tags: [network, k8s, container]
|
||||
|
||||
@@ -31,6 +31,7 @@ class FalcoTest(Test):
|
||||
|
||||
self.json_output = self.params.get('json_output', '*', default=False)
|
||||
self.json_include_output_property = self.params.get('json_include_output_property', '*', default=True)
|
||||
self.all_events = self.params.get('all_events', '*', 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'))
|
||||
|
||||
@@ -213,7 +214,7 @@ class FalcoTest(Test):
|
||||
triggered_rules = match.group(1)
|
||||
|
||||
for rule, count in self.detect_counts.iteritems():
|
||||
expected = '{}: (\d+)'.format(rule)
|
||||
expected = '\s{}: (\d+)'.format(rule)
|
||||
match = re.search(expected, triggered_rules)
|
||||
|
||||
if match is None:
|
||||
@@ -365,6 +366,9 @@ class FalcoTest(Test):
|
||||
if self.run_duration:
|
||||
cmd += ' -M {}'.format(self.run_duration)
|
||||
|
||||
if self.all_events:
|
||||
cmd += ' -A'
|
||||
|
||||
self.falco_proc = process.SubProcess(cmd)
|
||||
|
||||
res = self.falco_proc.run(timeout=180, sig=9)
|
||||
|
||||
@@ -128,6 +128,18 @@ trace_files: !mux
|
||||
- rules/single_rule.yaml
|
||||
- rules/double_rule.yaml
|
||||
trace_file: trace_files/cat_write.scap
|
||||
all_events: True
|
||||
|
||||
rules_directory:
|
||||
detect: True
|
||||
detect_level:
|
||||
- WARNING
|
||||
- INFO
|
||||
- ERROR
|
||||
rules_file:
|
||||
- rules/rules_dir
|
||||
trace_file: trace_files/cat_write.scap
|
||||
all_events: True
|
||||
|
||||
multiple_rules_suppress_info:
|
||||
detect: True
|
||||
@@ -143,6 +155,7 @@ trace_files: !mux
|
||||
- rules/single_rule.yaml
|
||||
- rules/double_rule.yaml
|
||||
trace_file: trace_files/cat_write.scap
|
||||
all_events: True
|
||||
|
||||
multiple_rules_overriding:
|
||||
detect: False
|
||||
@@ -642,6 +655,14 @@ trace_files: !mux
|
||||
- rules/rule_append_failure.yaml
|
||||
trace_file: trace_files/cat_write.scap
|
||||
|
||||
rule_append_skipped:
|
||||
detect: False
|
||||
priority: ERROR
|
||||
rules_file:
|
||||
- rules/single_rule.yaml
|
||||
- rules/append_single_rule.yaml
|
||||
trace_file: trace_files/cat_write.scap
|
||||
|
||||
rule_append:
|
||||
detect: True
|
||||
detect_level: WARNING
|
||||
@@ -670,4 +691,52 @@ trace_files: !mux
|
||||
detect_level: INFO
|
||||
rules_file:
|
||||
- rules/detect_connect_using_in.yaml
|
||||
trace_file: trace_files/connect_localhost.scap
|
||||
trace_file: trace_files/connect_localhost.scap
|
||||
|
||||
syscalls:
|
||||
detect: True
|
||||
detect_level: INFO
|
||||
rules_file:
|
||||
- rules/syscalls.yaml
|
||||
detect_counts:
|
||||
- detect_madvise: 2
|
||||
- detect_open: 2
|
||||
trace_file: trace_files/syscall.scap
|
||||
all_events: True
|
||||
|
||||
catchall_order:
|
||||
detect: True
|
||||
detect_level: INFO
|
||||
rules_file:
|
||||
- rules/catchall_order.yaml
|
||||
detect_counts:
|
||||
- open_dev_null: 1
|
||||
dev_null: 0
|
||||
trace_file: trace_files/cat_write.scap
|
||||
|
||||
skip_unknown_noevt:
|
||||
detect: False
|
||||
stdout_contains: Skipping rule "Contains Unknown Event And Skipping" that contains unknown filter proc.nobody
|
||||
rules_file:
|
||||
- rules/skip_unknown_evt.yaml
|
||||
trace_file: trace_files/cat_write.scap
|
||||
|
||||
skip_unknown_prefix:
|
||||
detect: False
|
||||
rules_file:
|
||||
- rules/skip_unknown_prefix.yaml
|
||||
trace_file: trace_files/cat_write.scap
|
||||
|
||||
skip_unknown_error:
|
||||
exit_status: 1
|
||||
stderr_contains: Rule "Contains Unknown Event And Not Skipping" contains unknown filter proc.nobody. Exiting.
|
||||
rules_file:
|
||||
- rules/skip_unknown_error.yaml
|
||||
trace_file: trace_files/cat_write.scap
|
||||
|
||||
skip_unknown_unspec_error:
|
||||
exit_status: 1
|
||||
stderr_contains: Rule "Contains Unknown Event And Unspecified" contains unknown filter proc.nobody. Exiting.
|
||||
rules_file:
|
||||
- rules/skip_unknown_unspec.yaml
|
||||
trace_file: trace_files/cat_write.scap
|
||||
|
||||
3
test/rules/append_single_rule.yaml
Normal file
3
test/rules/append_single_rule.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
- rule: open_from_cat
|
||||
append: true
|
||||
condition: and fd.name=/tmp
|
||||
12
test/rules/catchall_order.yaml
Normal file
12
test/rules/catchall_order.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
- rule: open_dev_null
|
||||
desc: Any open of the file /dev/null
|
||||
condition: evt.type=open and fd.name=/dev/null
|
||||
output: An open of /dev/null was seen (command=%proc.cmdline evt=%evt.type %evt.args)
|
||||
priority: INFO
|
||||
|
||||
- rule: dev_null
|
||||
desc: Anything related to /dev/null
|
||||
condition: fd.name=/dev/null
|
||||
output: Something related to /dev/null was seen (command=%proc.cmdline evt=%evt.type %evt.args)
|
||||
priority: INFO
|
||||
warn_evttypes: false
|
||||
14
test/rules/rules_dir/000-single_rule.yaml
Normal file
14
test/rules/rules_dir/000-single_rule.yaml
Normal file
@@ -0,0 +1,14 @@
|
||||
- list: cat_binaries
|
||||
items: [cat]
|
||||
|
||||
- list: cat_capable_binaries
|
||||
items: [cat_binaries]
|
||||
|
||||
- macro: is_cat
|
||||
condition: proc.name in (cat_capable_binaries)
|
||||
|
||||
- rule: open_from_cat
|
||||
desc: A process named cat does an open
|
||||
condition: evt.type=open and is_cat
|
||||
output: "An open was seen (command=%proc.cmdline)"
|
||||
priority: WARNING
|
||||
13
test/rules/rules_dir/001-double_rule.yaml
Normal file
13
test/rules/rules_dir/001-double_rule.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
# This ruleset depends on the is_cat macro defined in single_rule.yaml
|
||||
|
||||
- rule: exec_from_cat
|
||||
desc: A process named cat does execve
|
||||
condition: evt.type=execve and is_cat
|
||||
output: "An exec was seen (command=%proc.cmdline)"
|
||||
priority: ERROR
|
||||
|
||||
- rule: access_from_cat
|
||||
desc: A process named cat does an access
|
||||
condition: evt.type=access and is_cat
|
||||
output: "An access was seen (command=%proc.cmdline)"
|
||||
priority: INFO
|
||||
6
test/rules/skip_unknown_error.yaml
Normal file
6
test/rules/skip_unknown_error.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
- rule: Contains Unknown Event And Not Skipping
|
||||
desc: Contains an unknown event
|
||||
condition: proc.nobody=cat
|
||||
output: Never
|
||||
skip-if-unknown-filter: false
|
||||
priority: INFO
|
||||
6
test/rules/skip_unknown_evt.yaml
Normal file
6
test/rules/skip_unknown_evt.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
- rule: Contains Unknown Event And Skipping
|
||||
desc: Contains an unknown event
|
||||
condition: evt.type=open and proc.nobody=cat
|
||||
output: Never
|
||||
skip-if-unknown-filter: true
|
||||
priority: INFO
|
||||
8
test/rules/skip_unknown_prefix.yaml
Normal file
8
test/rules/skip_unknown_prefix.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
- rule: Contains Prefix of Filter
|
||||
desc: Testing matching filter prefixes
|
||||
condition: >
|
||||
evt.type=open and evt.arg.path="foo" and evt.arg[0]="foo"
|
||||
and proc.aname="ls" and proc.aname[1]="ls"
|
||||
and proc.apid=10 and proc.apid[1]=10
|
||||
output: Never
|
||||
priority: INFO
|
||||
5
test/rules/skip_unknown_unspec.yaml
Normal file
5
test/rules/skip_unknown_unspec.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
- rule: Contains Unknown Event And Unspecified
|
||||
desc: Contains an unknown event
|
||||
condition: proc.nobody=cat
|
||||
output: Never
|
||||
priority: INFO
|
||||
11
test/rules/syscalls.yaml
Normal file
11
test/rules/syscalls.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
- rule: detect_madvise
|
||||
desc: Detect any call to madvise
|
||||
condition: evt.type=madvise and evt.dir=<
|
||||
output: A madvise syscall was seen (command=%proc.cmdline evt=%evt.type)
|
||||
priority: INFO
|
||||
|
||||
- rule: detect_open
|
||||
desc: Detect any call to open
|
||||
condition: evt.type=open and evt.dir=< and fd.name=/dev/null
|
||||
output: An open syscall was seen (command=%proc.cmdline evt=%evt.type file=%fd.name)
|
||||
priority: INFO
|
||||
BIN
test/trace_files/syscall.scap
Normal file
BIN
test/trace_files/syscall.scap
Normal file
Binary file not shown.
@@ -3,6 +3,7 @@ include_directories("${PROJECT_SOURCE_DIR}/../sysdig/userspace/libscap")
|
||||
include_directories("${PROJECT_SOURCE_DIR}/../sysdig/userspace/libsinsp")
|
||||
include_directories("${PROJECT_BINARY_DIR}/userspace/engine")
|
||||
include_directories("${LUAJIT_INCLUDE}")
|
||||
include_directories("${CURL_INCLUDE_DIR}")
|
||||
|
||||
add_library(falco_engine STATIC rules.cpp falco_common.cpp falco_engine.cpp token_bucket.cpp formats.cpp)
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ string lua_print_stats = "print_stats";
|
||||
|
||||
using namespace std;
|
||||
|
||||
falco_engine::falco_engine(bool seed_rng)
|
||||
falco_engine::falco_engine(bool seed_rng, const std::string& source_dir)
|
||||
: m_rules(NULL), m_next_ruleset_id(0),
|
||||
m_min_priority(falco_common::PRIORITY_DEBUG),
|
||||
m_sampling_ratio(1), m_sampling_multiplier(0),
|
||||
@@ -48,7 +48,7 @@ falco_engine::falco_engine(bool seed_rng)
|
||||
luaopen_lpeg(m_ls);
|
||||
luaopen_yaml(m_ls);
|
||||
|
||||
falco_common::init(m_lua_main_filename.c_str(), FALCO_ENGINE_SOURCE_LUA_DIR);
|
||||
falco_common::init(m_lua_main_filename.c_str(), source_dir.c_str());
|
||||
falco_rules::init(m_ls);
|
||||
|
||||
m_evttype_filter.reset(new sinsp_evttype_filter());
|
||||
@@ -162,6 +162,13 @@ void falco_engine::evttypes_for_ruleset(std::vector<bool> &evttypes, const std::
|
||||
return m_evttype_filter->evttypes_for_ruleset(evttypes, ruleset_id);
|
||||
}
|
||||
|
||||
void falco_engine::syscalls_for_ruleset(std::vector<bool> &syscalls, const std::string &ruleset)
|
||||
{
|
||||
uint16_t ruleset_id = find_ruleset_id(ruleset);
|
||||
|
||||
return m_evttype_filter->syscalls_for_ruleset(syscalls, ruleset_id);
|
||||
}
|
||||
|
||||
unique_ptr<falco_engine::rule_result> falco_engine::process_event(sinsp_evt *ev, uint16_t ruleset_id)
|
||||
{
|
||||
if(should_drop_evt())
|
||||
@@ -237,10 +244,11 @@ void falco_engine::print_stats()
|
||||
|
||||
void falco_engine::add_evttype_filter(string &rule,
|
||||
set<uint32_t> &evttypes,
|
||||
set<uint32_t> &syscalls,
|
||||
set<string> &tags,
|
||||
sinsp_filter* filter)
|
||||
{
|
||||
m_evttype_filter->add(rule, evttypes, tags, filter);
|
||||
m_evttype_filter->add(rule, evttypes, syscalls, tags, filter);
|
||||
}
|
||||
|
||||
void falco_engine::clear_filters()
|
||||
|
||||
@@ -27,6 +27,7 @@ along with falco. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#include "rules.h"
|
||||
|
||||
#include "config_falco_engine.h"
|
||||
#include "falco_common.h"
|
||||
|
||||
//
|
||||
@@ -38,7 +39,7 @@ along with falco. If not, see <http://www.gnu.org/licenses/>.
|
||||
class falco_engine : public falco_common
|
||||
{
|
||||
public:
|
||||
falco_engine(bool seed_rng=true);
|
||||
falco_engine(bool seed_rng=true, const std::string& rules_dir=FALCO_ENGINE_SOURCE_LUA_DIR);
|
||||
virtual ~falco_engine();
|
||||
|
||||
//
|
||||
@@ -91,6 +92,12 @@ public:
|
||||
//
|
||||
void evttypes_for_ruleset(std::vector<bool> &evttypes, const std::string &ruleset);
|
||||
|
||||
//
|
||||
// Given a ruleset, fill in a bitset containing the syscalls
|
||||
// for which this ruleset can run.
|
||||
//
|
||||
void syscalls_for_ruleset(std::vector<bool> &syscalls, const std::string &ruleset);
|
||||
|
||||
//
|
||||
// Given an event, check it against the set of rules in the
|
||||
// engine and if a matching rule is found, return details on
|
||||
@@ -122,10 +129,11 @@ public:
|
||||
|
||||
//
|
||||
// Add a filter, which is related to the specified set of
|
||||
// event types, to the engine.
|
||||
// event types/syscalls, to the engine.
|
||||
//
|
||||
void add_evttype_filter(std::string &rule,
|
||||
std::set<uint32_t> &evttypes,
|
||||
std::set<uint32_t> &syscalls,
|
||||
std::set<std::string> &tags,
|
||||
sinsp_filter* filter);
|
||||
|
||||
|
||||
@@ -191,19 +191,20 @@ function check_for_ignored_syscalls_events(ast, filter_type, source)
|
||||
parser.traverse_ast(ast, {BinaryRelOp=1}, cb)
|
||||
end
|
||||
|
||||
-- Examine the ast and find the event types for which the rule should
|
||||
-- run. All evt.type references are added as event types up until the
|
||||
-- first "!=" binary operator or unary not operator. If no event type
|
||||
-- checks are found afterward in the rule, the rule is considered
|
||||
-- optimized and is associated with the event type(s).
|
||||
-- Examine the ast and find the event types/syscalls for which the
|
||||
-- rule should run. All evt.type references are added as event types
|
||||
-- up until the first "!=" binary operator or unary not operator. If
|
||||
-- no event type checks are found afterward in the rule, the rule is
|
||||
-- considered optimized and is associated with the event type(s).
|
||||
--
|
||||
-- Otherwise, the rule is associated with a 'catchall' category and is
|
||||
-- run for all event types. (Also, a warning is printed).
|
||||
-- run for all event types/syscalls. (Also, a warning is printed).
|
||||
--
|
||||
|
||||
function get_evttypes(name, ast, source)
|
||||
function get_evttypes_syscalls(name, ast, source, warn_evttypes)
|
||||
|
||||
local evttypes = {}
|
||||
local syscallnums = {}
|
||||
local evtnames = {}
|
||||
local found_event = false
|
||||
local found_not = false
|
||||
@@ -226,17 +227,45 @@ function get_evttypes(name, ast, source)
|
||||
if node.operator == "in" or node.operator == "pmatch" then
|
||||
for i, v in ipairs(node.right.elements) do
|
||||
if v.type == "BareString" then
|
||||
|
||||
-- The event must be a known event
|
||||
if events[v.value] == nil and syscalls[v.value] == nil then
|
||||
error("Unknown event/syscall \""..v.value.."\" in filter: "..source)
|
||||
end
|
||||
|
||||
evtnames[v.value] = 1
|
||||
for id in string.gmatch(events[v.value], "%S+") do
|
||||
evttypes[id] = 1
|
||||
if events[v.value] ~= nil then
|
||||
for id in string.gmatch(events[v.value], "%S+") do
|
||||
evttypes[id] = 1
|
||||
end
|
||||
end
|
||||
|
||||
if syscalls[v.value] ~= nil then
|
||||
for id in string.gmatch(syscalls[v.value], "%S+") do
|
||||
syscallnums[id] = 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
if node.right.type == "BareString" then
|
||||
|
||||
-- The event must be a known event
|
||||
if events[node.right.value] == nil and syscalls[node.right.value] == nil then
|
||||
error("Unknown event/syscall \""..node.right.value.."\" in filter: "..source)
|
||||
end
|
||||
|
||||
evtnames[node.right.value] = 1
|
||||
for id in string.gmatch(events[node.right.value], "%S+") do
|
||||
evttypes[id] = 1
|
||||
if events[node.right.value] ~= nil then
|
||||
for id in string.gmatch(events[node.right.value], "%S+") do
|
||||
evttypes[id] = 1
|
||||
end
|
||||
end
|
||||
|
||||
if syscalls[node.right.value] ~= nil then
|
||||
for id in string.gmatch(syscalls[node.right.value], "%S+") do
|
||||
syscallnums[id] = 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -247,23 +276,29 @@ function get_evttypes(name, ast, source)
|
||||
parser.traverse_ast(ast.filter.value, {BinaryRelOp=1, UnaryBoolOp=1} , cb)
|
||||
|
||||
if not found_event then
|
||||
io.stderr:write("Rule "..name..": warning (no-evttype):\n")
|
||||
io.stderr:write(source.."\n")
|
||||
io.stderr:write(" did not contain any evt.type restriction, meaning it will run for all event types.\n")
|
||||
io.stderr:write(" This has a significant performance penalty. Consider adding an evt.type restriction if possible.\n")
|
||||
if warn_evttypes == true then
|
||||
io.stderr:write("Rule "..name..": warning (no-evttype):\n")
|
||||
io.stderr:write(source.."\n")
|
||||
io.stderr:write(" did not contain any evt.type restriction, meaning it will run for all event types.\n")
|
||||
io.stderr:write(" This has a significant performance penalty. Consider adding an evt.type restriction if possible.\n")
|
||||
end
|
||||
evttypes = {}
|
||||
syscallnums = {}
|
||||
evtnames = {}
|
||||
end
|
||||
|
||||
if found_event_after_not then
|
||||
io.stderr:write("Rule "..name..": warning (trailing-evttype):\n")
|
||||
io.stderr:write(source.."\n")
|
||||
io.stderr:write(" does not have all evt.type restrictions at the beginning of the condition,\n")
|
||||
io.stderr:write(" or uses a negative match (i.e. \"not\"/\"!=\") for some evt.type restriction.\n")
|
||||
io.stderr:write(" This has a performance penalty, as the rule can not be limited to specific event types.\n")
|
||||
io.stderr:write(" Consider moving all evt.type restrictions to the beginning of the rule and/or\n")
|
||||
io.stderr:write(" replacing negative matches with positive matches if possible.\n")
|
||||
if warn_evttypes == true then
|
||||
io.stderr:write("Rule "..name..": warning (trailing-evttype):\n")
|
||||
io.stderr:write(source.."\n")
|
||||
io.stderr:write(" does not have all evt.type restrictions at the beginning of the condition,\n")
|
||||
io.stderr:write(" or uses a negative match (i.e. \"not\"/\"!=\") for some evt.type restriction.\n")
|
||||
io.stderr:write(" This has a performance penalty, as the rule can not be limited to specific event types.\n")
|
||||
io.stderr:write(" Consider moving all evt.type restrictions to the beginning of the rule and/or\n")
|
||||
io.stderr:write(" replacing negative matches with positive matches if possible.\n")
|
||||
end
|
||||
evttypes = {}
|
||||
syscallnums = {}
|
||||
evtnames = {}
|
||||
end
|
||||
|
||||
@@ -281,10 +316,25 @@ function get_evttypes(name, ast, source)
|
||||
table.sort(evtnames_only)
|
||||
|
||||
if compiler.verbose then
|
||||
io.stderr:write("Event types for rule "..name..": "..table.concat(evtnames_only, ",").."\n")
|
||||
io.stderr:write("Event types/Syscalls for rule "..name..": "..table.concat(evtnames_only, ",").."\n")
|
||||
end
|
||||
|
||||
return evttypes
|
||||
return evttypes, syscallnums
|
||||
end
|
||||
|
||||
function get_filters(ast)
|
||||
|
||||
local filters = {}
|
||||
|
||||
function cb(node)
|
||||
if node.type == "FieldName" then
|
||||
filters[node.value] = 1
|
||||
end
|
||||
end
|
||||
|
||||
parser.traverse_ast(ast.filter.value, {FieldName=1} , cb)
|
||||
|
||||
return filters
|
||||
end
|
||||
|
||||
function compiler.expand_lists_in(source, list_defs)
|
||||
@@ -344,7 +394,7 @@ end
|
||||
--[[
|
||||
Parses a single filter, then expands macros using passed-in table of definitions. Returns resulting AST.
|
||||
--]]
|
||||
function compiler.compile_filter(name, source, macro_defs, list_defs)
|
||||
function compiler.compile_filter(name, source, macro_defs, list_defs, warn_evttypes)
|
||||
|
||||
source = compiler.expand_lists_in(source, list_defs)
|
||||
|
||||
@@ -371,9 +421,11 @@ function compiler.compile_filter(name, source, macro_defs, list_defs)
|
||||
error("Unexpected top-level AST type: "..ast.type)
|
||||
end
|
||||
|
||||
evttypes = get_evttypes(name, ast, source)
|
||||
evttypes, syscallnums = get_evttypes_syscalls(name, ast, source, warn_evttypes)
|
||||
|
||||
return ast, evttypes
|
||||
filters = get_filters(ast)
|
||||
|
||||
return ast, evttypes, syscallnums, filters
|
||||
end
|
||||
|
||||
|
||||
|
||||
@@ -132,7 +132,8 @@ end
|
||||
-- object. The by_name index is used for things like describing rules,
|
||||
-- and the by_idx index is used to map the relational node index back
|
||||
-- to a rule.
|
||||
local state = {macros={}, lists={}, filter_ast=nil, rules_by_name={}, macros_by_name={}, lists_by_name={},
|
||||
local state = {macros={}, lists={}, filter_ast=nil, rules_by_name={},
|
||||
skipped_rules_by_name={}, macros_by_name={}, lists_by_name={},
|
||||
n_rules=0, rules_by_idx={}, ordered_rule_names={}, ordered_macro_names={}, ordered_list_names={}}
|
||||
|
||||
local function reset_rules(rules_mgr)
|
||||
@@ -274,6 +275,12 @@ function load_rules(rules_content, rules_mgr, verbose, all_events, extra, replac
|
||||
error ("Missing name in rule")
|
||||
end
|
||||
|
||||
-- By default, if a rule's condition refers to an unknown
|
||||
-- filter like evt.type, etc the loader throws an error.
|
||||
if v['skip-if-unknown-filter'] == nil then
|
||||
v['skip-if-unknown-filter'] = false
|
||||
end
|
||||
|
||||
-- Possibly append to the condition field of an existing rule
|
||||
append = false
|
||||
|
||||
@@ -291,11 +298,13 @@ function load_rules(rules_content, rules_mgr, verbose, all_events, extra, replac
|
||||
end
|
||||
|
||||
if state.rules_by_name[v['rule']] == nil then
|
||||
error ("Rule " ..v['rule'].. " has 'append' key but no rule by that name already exists")
|
||||
if state.skipped_rules_by_name[v['rule']] == nil then
|
||||
error ("Rule " ..v['rule'].. " has 'append' key but no rule by that name already exists")
|
||||
end
|
||||
else
|
||||
state.rules_by_name[v['rule']]['condition'] = state.rules_by_name[v['rule']]['condition'] .. " " .. v['condition']
|
||||
end
|
||||
|
||||
state.rules_by_name[v['rule']]['condition'] = state.rules_by_name[v['rule']]['condition'] .. " " .. v['condition']
|
||||
|
||||
else
|
||||
|
||||
for i, field in ipairs({'condition', 'output', 'desc', 'priority'}) do
|
||||
@@ -320,6 +329,8 @@ function load_rules(rules_content, rules_mgr, verbose, all_events, extra, replac
|
||||
v['output'] = compiler.trim(v['output'])
|
||||
|
||||
state.rules_by_name[v['rule']] = v
|
||||
else
|
||||
state.skipped_rules_by_name[v['rule']] = v
|
||||
end
|
||||
end
|
||||
else
|
||||
@@ -368,8 +379,39 @@ function load_rules(rules_content, rules_mgr, verbose, all_events, extra, replac
|
||||
|
||||
local v = state.rules_by_name[name]
|
||||
|
||||
local filter_ast, evttypes = compiler.compile_filter(v['rule'], v['condition'],
|
||||
state.macros, state.lists)
|
||||
warn_evttypes = true
|
||||
if v['warn_evttypes'] ~= nil then
|
||||
warn_evttypes = v['warn_evttypes']
|
||||
end
|
||||
|
||||
local filter_ast, evttypes, syscallnums, filters = compiler.compile_filter(v['rule'], v['condition'],
|
||||
state.macros, state.lists,
|
||||
warn_evttypes)
|
||||
|
||||
-- If a filter in the rule doesn't exist, either skip the rule
|
||||
-- or raise an error, depending on the value of
|
||||
-- skip-if-unknown-filter.
|
||||
for filter, _ in pairs(filters) do
|
||||
found = false
|
||||
|
||||
for pat, _ in pairs(defined_filters) do
|
||||
if string.match(filter, pat) ~= nil then
|
||||
found = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not found then
|
||||
if v['skip-if-unknown-filter'] then
|
||||
if verbose then
|
||||
print("Skipping rule \""..v['rule'].."\" that contains unknown filter "..filter)
|
||||
end
|
||||
goto next_rule
|
||||
else
|
||||
error("Rule \""..v['rule'].."\" contains unknown filter "..filter)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if (filter_ast.type == "Rule") then
|
||||
state.n_rules = state.n_rules + 1
|
||||
@@ -390,7 +432,7 @@ function load_rules(rules_content, rules_mgr, verbose, all_events, extra, replac
|
||||
end
|
||||
|
||||
-- Pass the filter and event types back up
|
||||
falco_rules.add_filter(rules_mgr, v['rule'], evttypes, v['tags'])
|
||||
falco_rules.add_filter(rules_mgr, v['rule'], evttypes, syscallnums, v['tags'])
|
||||
|
||||
-- Rule ASTs are merged together into one big AST, with "OR" between each
|
||||
-- rule.
|
||||
@@ -407,6 +449,8 @@ function load_rules(rules_content, rules_mgr, verbose, all_events, extra, replac
|
||||
|
||||
if (v['enabled'] == false) then
|
||||
falco_rules.enable_rule(rules_mgr, v['rule'], 0)
|
||||
else
|
||||
falco_rules.enable_rule(rules_mgr, v['rule'], 1)
|
||||
end
|
||||
|
||||
-- If the format string contains %container.info, replace it
|
||||
@@ -441,6 +485,8 @@ function load_rules(rules_content, rules_mgr, verbose, all_events, extra, replac
|
||||
else
|
||||
error ("Unexpected type in load_rule: "..filter_ast.type)
|
||||
end
|
||||
|
||||
::next_rule::
|
||||
end
|
||||
|
||||
if verbose then
|
||||
|
||||
@@ -66,8 +66,9 @@ void falco_rules::clear_filters()
|
||||
|
||||
int falco_rules::add_filter(lua_State *ls)
|
||||
{
|
||||
if (! lua_islightuserdata(ls, -4) ||
|
||||
! lua_isstring(ls, -3) ||
|
||||
if (! lua_islightuserdata(ls, -5) ||
|
||||
! lua_isstring(ls, -4) ||
|
||||
! lua_istable(ls, -3) ||
|
||||
! lua_istable(ls, -2) ||
|
||||
! lua_istable(ls, -1))
|
||||
{
|
||||
@@ -75,16 +76,28 @@ int falco_rules::add_filter(lua_State *ls)
|
||||
lua_error(ls);
|
||||
}
|
||||
|
||||
falco_rules *rules = (falco_rules *) lua_topointer(ls, -4);
|
||||
const char *rulec = lua_tostring(ls, -3);
|
||||
falco_rules *rules = (falco_rules *) lua_topointer(ls, -5);
|
||||
const char *rulec = lua_tostring(ls, -4);
|
||||
|
||||
set<uint32_t> evttypes;
|
||||
|
||||
lua_pushnil(ls); /* first key */
|
||||
while (lua_next(ls, -4) != 0) {
|
||||
// key is at index -2, value is at index
|
||||
// -1. We want the keys.
|
||||
evttypes.insert(luaL_checknumber(ls, -2));
|
||||
|
||||
// Remove value, keep key for next iteration
|
||||
lua_pop(ls, 1);
|
||||
}
|
||||
|
||||
set<uint32_t> syscalls;
|
||||
|
||||
lua_pushnil(ls); /* first key */
|
||||
while (lua_next(ls, -3) != 0) {
|
||||
// key is at index -2, value is at index
|
||||
// -1. We want the keys.
|
||||
evttypes.insert(luaL_checknumber(ls, -2));
|
||||
syscalls.insert(luaL_checknumber(ls, -2));
|
||||
|
||||
// Remove value, keep key for next iteration
|
||||
lua_pop(ls, 1);
|
||||
@@ -95,7 +108,7 @@ int falco_rules::add_filter(lua_State *ls)
|
||||
lua_pushnil(ls); /* first key */
|
||||
while (lua_next(ls, -2) != 0) {
|
||||
// key is at index -2, value is at index
|
||||
// -1. We want the keys.
|
||||
// -1. We want the values.
|
||||
tags.insert(lua_tostring(ls, -1));
|
||||
|
||||
// Remove value, keep key for next iteration
|
||||
@@ -103,19 +116,19 @@ int falco_rules::add_filter(lua_State *ls)
|
||||
}
|
||||
|
||||
std::string rule = rulec;
|
||||
rules->add_filter(rule, evttypes, tags);
|
||||
rules->add_filter(rule, evttypes, syscalls, tags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void falco_rules::add_filter(string &rule, set<uint32_t> &evttypes, set<string> &tags)
|
||||
void falco_rules::add_filter(string &rule, set<uint32_t> &evttypes, set<uint32_t> &syscalls, set<string> &tags)
|
||||
{
|
||||
// While the current rule was being parsed, a sinsp_filter
|
||||
// object was being populated by lua_parser. Grab that filter
|
||||
// and pass it to the engine.
|
||||
sinsp_filter *filter = m_lua_parser->get_filter(true);
|
||||
|
||||
m_engine->add_evttype_filter(rule, evttypes, tags, filter);
|
||||
m_engine->add_evttype_filter(rule, evttypes, syscalls, tags, filter);
|
||||
}
|
||||
|
||||
int falco_rules::enable_rule(lua_State *ls)
|
||||
@@ -183,6 +196,35 @@ void falco_rules::load_rules(const string &rules_content,
|
||||
|
||||
lua_setglobal(m_ls, m_lua_events.c_str());
|
||||
|
||||
map<string,string> syscalls_by_name;
|
||||
for(uint32_t j = 0; j < PPM_SC_MAX; j++)
|
||||
{
|
||||
auto it = syscalls_by_name.find(stable[j].name);
|
||||
|
||||
if (it == syscalls_by_name.end())
|
||||
{
|
||||
syscalls_by_name[stable[j].name] = to_string(j);
|
||||
}
|
||||
else
|
||||
{
|
||||
string cur = it->second;
|
||||
cur += " ";
|
||||
cur += to_string(j);
|
||||
syscalls_by_name[stable[j].name] = cur;
|
||||
}
|
||||
}
|
||||
|
||||
lua_newtable(m_ls);
|
||||
|
||||
for( auto kv : syscalls_by_name)
|
||||
{
|
||||
lua_pushstring(m_ls, kv.first.c_str());
|
||||
lua_pushstring(m_ls, kv.second.c_str());
|
||||
lua_settable(m_ls, -3);
|
||||
}
|
||||
|
||||
lua_setglobal(m_ls, m_lua_syscalls.c_str());
|
||||
|
||||
// Create a table containing the syscalls/events that
|
||||
// are ignored by the kernel module. load_rules will
|
||||
// return an error if any rule references one of these
|
||||
@@ -216,6 +258,63 @@ void falco_rules::load_rules(const string &rules_content,
|
||||
|
||||
lua_setglobal(m_ls, m_lua_ignored_syscalls.c_str());
|
||||
|
||||
// Create a table containing all filtercheck names.
|
||||
lua_newtable(m_ls);
|
||||
|
||||
vector<const filter_check_info*> fc_plugins;
|
||||
sinsp::get_filtercheck_fields_info(&fc_plugins);
|
||||
|
||||
for(uint32_t j = 0; j < fc_plugins.size(); j++)
|
||||
{
|
||||
const filter_check_info* fci = fc_plugins[j];
|
||||
|
||||
if(fci->m_flags & filter_check_info::FL_HIDDEN)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for(int32_t k = 0; k < fci->m_nfields; k++)
|
||||
{
|
||||
const filtercheck_field_info* fld = &fci->m_fields[k];
|
||||
|
||||
if(fld->m_flags & EPF_TABLE_ONLY ||
|
||||
fld->m_flags & EPF_PRINT_ONLY)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Some filters can work with or without an argument
|
||||
std::set<string> flexible_filters = {
|
||||
"^proc.aname",
|
||||
"^proc.apid"
|
||||
};
|
||||
|
||||
std::list<string> fields;
|
||||
std::string field_base = string("^") + fld->m_name;
|
||||
|
||||
if(fld->m_flags & EPF_REQUIRES_ARGUMENT ||
|
||||
flexible_filters.find(field_base) != flexible_filters.end())
|
||||
{
|
||||
fields.push_back(field_base + "[%[%.]");
|
||||
}
|
||||
|
||||
if(!(fld->m_flags & EPF_REQUIRES_ARGUMENT) ||
|
||||
flexible_filters.find(field_base) != flexible_filters.end())
|
||||
{
|
||||
fields.push_back(field_base + "$");
|
||||
}
|
||||
|
||||
for(auto &field : fields)
|
||||
{
|
||||
lua_pushstring(m_ls, field.c_str());
|
||||
lua_pushnumber(m_ls, 1);
|
||||
lua_settable(m_ls, -3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lua_setglobal(m_ls, m_lua_defined_filters.c_str());
|
||||
|
||||
lua_pushstring(m_ls, rules_content.c_str());
|
||||
lua_pushlightuserdata(m_ls, this);
|
||||
lua_pushboolean(m_ls, (verbose ? 1 : 0));
|
||||
|
||||
@@ -45,7 +45,7 @@ class falco_rules
|
||||
|
||||
private:
|
||||
void clear_filters();
|
||||
void add_filter(string &rule, std::set<uint32_t> &evttypes, std::set<string> &tags);
|
||||
void add_filter(string &rule, std::set<uint32_t> &evttypes, std::set<uint32_t> &syscalls, std::set<string> &tags);
|
||||
void enable_rule(string &rule, bool enabled);
|
||||
|
||||
lua_parser* m_lua_parser;
|
||||
@@ -56,6 +56,8 @@ class falco_rules
|
||||
string m_lua_load_rules = "load_rules";
|
||||
string m_lua_ignored_syscalls = "ignored_syscalls";
|
||||
string m_lua_ignored_events = "ignored_events";
|
||||
string m_lua_defined_filters = "defined_filters";
|
||||
string m_lua_events = "events";
|
||||
string m_lua_syscalls = "syscalls";
|
||||
string m_lua_describe_rule = "describe_rule";
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@ include_directories("${PROJECT_SOURCE_DIR}/../sysdig/userspace/libscap")
|
||||
include_directories("${PROJECT_SOURCE_DIR}/../sysdig/userspace/libsinsp")
|
||||
include_directories("${PROJECT_SOURCE_DIR}/userspace/engine")
|
||||
include_directories("${PROJECT_BINARY_DIR}/userspace/falco")
|
||||
include_directories("${PROJECT_BINARY_DIR}/userspace/engine")
|
||||
include_directories("${CURL_INCLUDE_DIR}")
|
||||
include_directories("${YAMLCPP_INCLUDE_DIR}")
|
||||
include_directories("${DRAIOS_DEPENDENCIES_DIR}/yaml-${DRAIOS_YAML_VERSION}/target/include")
|
||||
|
||||
@@ -16,6 +16,13 @@ You should have received a copy of the GNU General Public License
|
||||
along with falco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <dirent.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "configuration.h"
|
||||
#include "logger.h"
|
||||
|
||||
@@ -62,7 +69,7 @@ void falco_configuration::init(string conf_filename, list<string> &cmdline_optio
|
||||
struct stat buffer;
|
||||
if(stat(file.c_str(), &buffer) == 0)
|
||||
{
|
||||
m_rules_filenames.push_back(file);
|
||||
read_rules_file_directory(file, m_rules_filenames);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,6 +157,70 @@ void falco_configuration::init(string conf_filename, list<string> &cmdline_optio
|
||||
falco_logger::log_syslog = m_config->get_scalar<bool>("log_syslog", true);
|
||||
}
|
||||
|
||||
void falco_configuration::read_rules_file_directory(const string &path, list<string> &rules_filenames)
|
||||
{
|
||||
struct stat st;
|
||||
|
||||
int rc = stat(path.c_str(), &st);
|
||||
|
||||
if(rc != 0)
|
||||
{
|
||||
std::cerr << "Could not get info on rules file " << path << ": " << strerror(errno) << std::endl;
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
if(st.st_mode & S_IFDIR)
|
||||
{
|
||||
// It's a directory. Read the contents, sort
|
||||
// alphabetically, and add every path to
|
||||
// rules_filenames
|
||||
vector<string> dir_filenames;
|
||||
|
||||
DIR *dir = opendir(path.c_str());
|
||||
|
||||
if(!dir)
|
||||
{
|
||||
std::cerr << "Could not get read contents of directory " << path << ": " << strerror(errno) << std::endl;
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
for (struct dirent *ent = readdir(dir); ent; ent = readdir(dir))
|
||||
{
|
||||
string efile = path + "/" + ent->d_name;
|
||||
|
||||
rc = stat(efile.c_str(), &st);
|
||||
|
||||
if(rc != 0)
|
||||
{
|
||||
std::cerr << "Could not get info on rules file " << efile << ": " << strerror(errno) << std::endl;
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
if(st.st_mode & S_IFREG)
|
||||
{
|
||||
dir_filenames.push_back(efile);
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
|
||||
std::sort(dir_filenames.begin(),
|
||||
dir_filenames.end());
|
||||
|
||||
for (string &ent : dir_filenames)
|
||||
{
|
||||
rules_filenames.push_back(ent);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Assume it's a file and just add to
|
||||
// rules_filenames. If it can't be opened/etc that
|
||||
// will be reported later..
|
||||
rules_filenames.push_back(path);
|
||||
}
|
||||
}
|
||||
|
||||
static bool split(const string &str, char delim, pair<string,string> &parts)
|
||||
{
|
||||
size_t pos;
|
||||
|
||||
@@ -165,6 +165,8 @@ class falco_configuration
|
||||
void init(std::string conf_filename, std::list<std::string> &cmdline_options);
|
||||
void init(std::list<std::string> &cmdline_options);
|
||||
|
||||
static void read_rules_file_directory(const string &path, list<string> &rules_filenames);
|
||||
|
||||
std::list<std::string> m_rules_filenames;
|
||||
bool m_json_output;
|
||||
bool m_json_include_output_property;
|
||||
|
||||
@@ -22,6 +22,8 @@ along with falco. If not, see <http://www.gnu.org/licenses/>.
|
||||
#include <fstream>
|
||||
#include <set>
|
||||
#include <list>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <signal.h>
|
||||
#include <fcntl.h>
|
||||
@@ -32,6 +34,7 @@ along with falco. If not, see <http://www.gnu.org/licenses/>.
|
||||
#include <sinsp.h>
|
||||
|
||||
#include "logger.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include "configuration.h"
|
||||
#include "falco_engine.h"
|
||||
@@ -39,6 +42,8 @@ along with falco. If not, see <http://www.gnu.org/licenses/>.
|
||||
#include "statsfilewriter.h"
|
||||
|
||||
bool g_terminate = false;
|
||||
bool g_reopen_outputs = false;
|
||||
|
||||
//
|
||||
// Helper functions
|
||||
//
|
||||
@@ -47,6 +52,11 @@ static void signal_callback(int signal)
|
||||
g_terminate = true;
|
||||
}
|
||||
|
||||
static void reopen_outputs(int signal)
|
||||
{
|
||||
g_reopen_outputs = true;
|
||||
}
|
||||
|
||||
//
|
||||
// Program help
|
||||
//
|
||||
@@ -99,8 +109,9 @@ static void usage()
|
||||
" of %%container.info in rule output fields\n"
|
||||
" See the examples section below for more info.\n"
|
||||
" -P, --pidfile <pid_file> When run as a daemon, write pid to specified file\n"
|
||||
" -r <rules_file> Rules file (defaults to value set in configuration file, or /etc/falco_rules.yaml).\n"
|
||||
" Can be specified multiple times to read from multiple files.\n"
|
||||
" -r <rules_file> Rules file/directory (defaults to value set in configuration file,\n"
|
||||
" or /etc/falco_rules.yaml). Can be specified multiple times to read\n"
|
||||
" from multiple files/directories.\n"
|
||||
" -s <stats_file> If specified, write statistics related to falco's reading/processing of events\n"
|
||||
" to this file. (Only useful in live mode).\n"
|
||||
" -T <tag> Disable any rules with a tag=<tag>. Can be specified multiple times.\n"
|
||||
@@ -143,10 +154,11 @@ uint64_t do_inspect(falco_engine *engine,
|
||||
falco_outputs *outputs,
|
||||
sinsp* inspector,
|
||||
uint64_t duration_to_tot_ns,
|
||||
string &stats_filename)
|
||||
string &stats_filename,
|
||||
bool all_events)
|
||||
{
|
||||
uint64_t num_evts = 0;
|
||||
int32_t res;
|
||||
int32_t rc;
|
||||
sinsp_evt* ev;
|
||||
StatsFileWriter writer;
|
||||
uint64_t duration_start = 0;
|
||||
@@ -167,29 +179,35 @@ uint64_t do_inspect(falco_engine *engine,
|
||||
while(1)
|
||||
{
|
||||
|
||||
res = inspector->next(&ev);
|
||||
rc = inspector->next(&ev);
|
||||
|
||||
writer.handle();
|
||||
|
||||
if(g_reopen_outputs)
|
||||
{
|
||||
outputs->reopen_outputs();
|
||||
g_reopen_outputs = false;
|
||||
}
|
||||
|
||||
if (g_terminate)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if(res == SCAP_TIMEOUT)
|
||||
else if(rc == SCAP_TIMEOUT)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else if(res == SCAP_EOF)
|
||||
else if(rc == SCAP_EOF)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if(res != SCAP_SUCCESS)
|
||||
else if(rc != SCAP_SUCCESS)
|
||||
{
|
||||
//
|
||||
// Event read error.
|
||||
// Notify the chisels that we're exiting, and then die with an error.
|
||||
//
|
||||
cerr << "res = " << res << endl;
|
||||
cerr << "rc = " << rc << endl;
|
||||
throw sinsp_exception(inspector->getlasterr().c_str());
|
||||
}
|
||||
|
||||
@@ -204,8 +222,7 @@ uint64_t do_inspect(falco_engine *engine,
|
||||
}
|
||||
}
|
||||
|
||||
if(!inspector->is_debug_enabled() &&
|
||||
ev->get_category() & EC_INTERNAL)
|
||||
if(!ev->falco_consider() && !all_events)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -227,6 +244,47 @@ uint64_t do_inspect(falco_engine *engine,
|
||||
return num_evts;
|
||||
}
|
||||
|
||||
static void print_all_ignored_events(sinsp *inspector)
|
||||
{
|
||||
sinsp_evttables* einfo = inspector->get_event_info_tables();
|
||||
const struct ppm_event_info* etable = einfo->m_event_info;
|
||||
const struct ppm_syscall_desc* stable = einfo->m_syscall_info_table;
|
||||
|
||||
std::set<string> ignored_event_names;
|
||||
for(uint32_t j = 0; j < PPM_EVENT_MAX; j++)
|
||||
{
|
||||
if(!sinsp::falco_consider_evtnum(j))
|
||||
{
|
||||
std::string name = etable[j].name;
|
||||
// Ignore event names NA*
|
||||
if(name.find("NA") != 0)
|
||||
{
|
||||
ignored_event_names.insert(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(uint32_t j = 0; j < PPM_SC_MAX; j++)
|
||||
{
|
||||
if(!sinsp::falco_consider_syscallid(j))
|
||||
{
|
||||
std::string name = stable[j].name;
|
||||
// Ignore event names NA*
|
||||
if(name.find("NA") != 0)
|
||||
{
|
||||
ignored_event_names.insert(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
printf("Ignored Event(s):");
|
||||
for(auto it : ignored_event_names)
|
||||
{
|
||||
printf(" %s", it.c_str());
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
//
|
||||
// ARGUMENT PARSING AND PROGRAM SETUP
|
||||
//
|
||||
@@ -256,6 +314,7 @@ int falco_init(int argc, char **argv)
|
||||
string output_format = "";
|
||||
bool replace_container_info = false;
|
||||
int duration_to_tot = 0;
|
||||
bool print_ignored_events = false;
|
||||
|
||||
// Used for writing trace files
|
||||
int duration_seconds = 0;
|
||||
@@ -285,6 +344,7 @@ int falco_init(int argc, char **argv)
|
||||
{"version", no_argument, 0, 0 },
|
||||
{"validate", required_argument, 0, 'V' },
|
||||
{"writefile", required_argument, 0, 'w' },
|
||||
{"ignored-events", no_argument, 0, 'i'},
|
||||
|
||||
{0, 0, 0, 0}
|
||||
};
|
||||
@@ -301,7 +361,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:ik:K:Ll:m:M:o:P:p:r:s:T:t:UvV:w:",
|
||||
long_options, &long_index)) != -1)
|
||||
{
|
||||
switch(op)
|
||||
@@ -327,6 +387,9 @@ int falco_init(int argc, char **argv)
|
||||
k8s_api = new string();
|
||||
mesos_api = new string();
|
||||
break;
|
||||
case 'i':
|
||||
print_ignored_events = true;
|
||||
break;
|
||||
case 'k':
|
||||
k8s_api = new string(optarg);
|
||||
break;
|
||||
@@ -378,7 +441,7 @@ int falco_init(int argc, char **argv)
|
||||
}
|
||||
break;
|
||||
case 'r':
|
||||
rules_filenames.push_back(optarg);
|
||||
falco_configuration::read_rules_file_directory(string(optarg), rules_filenames);
|
||||
break;
|
||||
case 's':
|
||||
stats_filename = optarg;
|
||||
@@ -417,12 +480,20 @@ int falco_init(int argc, char **argv)
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
inspector = new sinsp();
|
||||
|
||||
if(print_ignored_events)
|
||||
{
|
||||
print_all_ignored_events(inspector);
|
||||
delete(inspector);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
engine = new falco_engine();
|
||||
engine->set_inspector(inspector);
|
||||
engine->set_extra(output_format, replace_container_info);
|
||||
|
||||
|
||||
outputs = new falco_outputs();
|
||||
outputs->set_inspector(inspector);
|
||||
|
||||
@@ -503,13 +574,19 @@ int falco_init(int argc, char **argv)
|
||||
|
||||
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");
|
||||
throw std::invalid_argument("You must specify at least one rules file/directory via -r or a rules_file entry in falco.yaml");
|
||||
}
|
||||
|
||||
falco_logger::log(LOG_DEBUG, "Configured rules filenames:\n");
|
||||
for (auto filename : config.m_rules_filenames)
|
||||
{
|
||||
falco_logger::log(LOG_DEBUG, string(" ") + filename + "\n");
|
||||
}
|
||||
|
||||
for (auto filename : config.m_rules_filenames)
|
||||
{
|
||||
falco_logger::log(LOG_INFO, "Loading rules from file " + filename + ":\n");
|
||||
engine->load_rules_file(filename, verbose, all_events);
|
||||
falco_logger::log(LOG_INFO, "Parsed rules from file " + filename + "\n");
|
||||
}
|
||||
|
||||
// You can't both disable and enable rules
|
||||
@@ -554,6 +631,7 @@ int falco_init(int argc, char **argv)
|
||||
if(!all_events)
|
||||
{
|
||||
inspector->set_drop_event_flags(EF_DROP_FALCO);
|
||||
inspector->start_dropping_mode(1);
|
||||
}
|
||||
|
||||
if (describe_all_rules)
|
||||
@@ -589,6 +667,13 @@ int falco_init(int argc, char **argv)
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if(signal(SIGUSR1, reopen_outputs) == SIG_ERR)
|
||||
{
|
||||
fprintf(stderr, "An error occurred while setting SIGUSR1 signal handler.\n");
|
||||
result = EXIT_FAILURE;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (scap_filename.size())
|
||||
{
|
||||
inspector->open(scap_filename);
|
||||
@@ -733,7 +818,8 @@ int falco_init(int argc, char **argv)
|
||||
outputs,
|
||||
inspector,
|
||||
uint64_t(duration_to_tot*ONE_SECOND_IN_NS),
|
||||
stats_filename);
|
||||
stats_filename,
|
||||
all_events);
|
||||
|
||||
duration = ((double)clock()) / CLOCKS_PER_SEC - duration;
|
||||
|
||||
|
||||
@@ -142,3 +142,19 @@ void falco_outputs::handle_event(sinsp_evt *ev, string &rule, falco_common::prio
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void falco_outputs::reopen_outputs()
|
||||
{
|
||||
lua_getglobal(m_ls, m_lua_output_reopen.c_str());
|
||||
|
||||
if(!lua_isfunction(m_ls, -1))
|
||||
{
|
||||
throw falco_exception("No function " + m_lua_output_reopen + " found. ");
|
||||
}
|
||||
|
||||
if(lua_pcall(m_ls, 0, 0, 0) != 0)
|
||||
{
|
||||
const char* lerr = lua_tostring(m_ls, -1);
|
||||
throw falco_exception(string(lerr));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,8 @@ public:
|
||||
//
|
||||
void handle_event(sinsp_evt *ev, std::string &rule, falco_common::priority_type priority, std::string &format);
|
||||
|
||||
void reopen_outputs();
|
||||
|
||||
private:
|
||||
bool m_initialized;
|
||||
|
||||
@@ -64,5 +66,6 @@ private:
|
||||
std::string m_lua_add_output = "add_output";
|
||||
std::string m_lua_output_event = "output_event";
|
||||
std::string m_lua_output_cleanup = "output_cleanup";
|
||||
std::string m_lua_output_reopen = "output_reopen";
|
||||
std::string m_lua_main_filename = "output.lua";
|
||||
};
|
||||
|
||||
@@ -20,8 +20,8 @@ local mod = {}
|
||||
|
||||
local outputs = {}
|
||||
|
||||
function mod.stdout(priority, priority_num, buffered, msg)
|
||||
if buffered == 0 then
|
||||
function mod.stdout(priority, priority_num, msg, options)
|
||||
if options.buffered == 0 then
|
||||
io.stdout:setvbuf 'no'
|
||||
end
|
||||
print (msg)
|
||||
@@ -31,6 +31,10 @@ function mod.stdout_cleanup()
|
||||
io.stdout:flush()
|
||||
end
|
||||
|
||||
-- Note: not actually closing/reopening stdout
|
||||
function mod.stdout_reopen(options)
|
||||
end
|
||||
|
||||
function mod.file_validate(options)
|
||||
if (not type(options.filename) == 'string') then
|
||||
error("File output needs to be configured with a valid filename")
|
||||
@@ -44,73 +48,98 @@ 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
|
||||
function mod.file_open(options)
|
||||
if ffile == nil then
|
||||
ffile = io.open(options.filename, "a+")
|
||||
if options.buffered == 0 then
|
||||
ffile:setvbuf 'no'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function mod.file(priority, priority_num, msg, options)
|
||||
if options.keep_alive == "true" then
|
||||
mod.file_open(options)
|
||||
else
|
||||
file = io.open(options.filename, "a+")
|
||||
ffile = io.open(options.filename, "a+")
|
||||
end
|
||||
|
||||
file:write(msg, "\n")
|
||||
ffile:write(msg, "\n")
|
||||
|
||||
if options.keep_alive == nil or
|
||||
options.keep_alive ~= "true" then
|
||||
file:close()
|
||||
file = nil
|
||||
options.keep_alive ~= "true" then
|
||||
ffile:close()
|
||||
ffile = nil
|
||||
end
|
||||
end
|
||||
|
||||
function mod.file_cleanup()
|
||||
if file ~= nil then
|
||||
file:flush()
|
||||
file:close()
|
||||
file = nil
|
||||
if ffile ~= nil then
|
||||
ffile:flush()
|
||||
ffile:close()
|
||||
ffile = nil
|
||||
end
|
||||
end
|
||||
|
||||
function mod.syslog(priority, priority_num, buffered, msg, options)
|
||||
function mod.file_reopen(options)
|
||||
if options.keep_alive == "true" then
|
||||
mod.file_cleanup()
|
||||
mod.file_open(options)
|
||||
end
|
||||
end
|
||||
|
||||
function mod.syslog(priority, priority_num, msg, options)
|
||||
falco.syslog(priority_num, msg)
|
||||
end
|
||||
|
||||
function mod.syslog_cleanup()
|
||||
end
|
||||
|
||||
function mod.program(priority, priority_num, buffered, msg, options)
|
||||
function mod.syslog_reopen()
|
||||
end
|
||||
|
||||
function mod.program_open(options)
|
||||
if pfile == nil then
|
||||
pfile = io.popen(options.program, "w")
|
||||
if options.buffered == 0 then
|
||||
pfile:setvbuf 'no'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function mod.program(priority, priority_num, 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
|
||||
mod.program_open(options)
|
||||
else
|
||||
file = io.popen(options.program, "w")
|
||||
pfile = io.popen(options.program, "w")
|
||||
end
|
||||
|
||||
file:write(msg, "\n")
|
||||
pfile:write(msg, "\n")
|
||||
|
||||
if options.keep_alive == nil or
|
||||
options.keep_alive ~= "true" then
|
||||
file:close()
|
||||
file = nil
|
||||
options.keep_alive ~= "true" then
|
||||
pfile:close()
|
||||
pfile = nil
|
||||
end
|
||||
end
|
||||
|
||||
function mod.program_cleanup()
|
||||
if file ~= nil then
|
||||
file:flush()
|
||||
file:close()
|
||||
file = nil
|
||||
if pfile ~= nil then
|
||||
pfile:flush()
|
||||
pfile:close()
|
||||
pfile = nil
|
||||
end
|
||||
end
|
||||
|
||||
function mod.program_reopen(options)
|
||||
if options.keep_alive == "true" then
|
||||
mod.program_cleanup()
|
||||
mod.program_open(options)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -126,7 +155,7 @@ function output_event(event, rule, priority, priority_num, format)
|
||||
msg = formats.format_event(event, rule, priority, format)
|
||||
|
||||
for index,o in ipairs(outputs) do
|
||||
o.output(priority, priority_num, o.buffered, msg, o.config)
|
||||
o.output(priority, priority_num, msg, o.options)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -137,20 +166,33 @@ function output_cleanup()
|
||||
end
|
||||
end
|
||||
|
||||
function add_output(output_name, buffered, config)
|
||||
function output_reopen()
|
||||
for index,o in ipairs(outputs) do
|
||||
o.reopen(o.options)
|
||||
end
|
||||
end
|
||||
|
||||
function add_output(output_name, buffered, options)
|
||||
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
|
||||
-- find out at runtime (when an event finally matches a rule!) that the options are invalid
|
||||
if (type(mod[output_name.."_validate"]) == 'function') then
|
||||
mod[output_name.."_validate"](config)
|
||||
mod[output_name.."_validate"](options)
|
||||
end
|
||||
|
||||
if options == nil then
|
||||
options = {}
|
||||
end
|
||||
|
||||
options.buffered = buffered
|
||||
|
||||
table.insert(outputs, {output = mod[output_name],
|
||||
cleanup = mod[output_name.."_cleanup"],
|
||||
buffered=buffered, config=config})
|
||||
reopen = mod[output_name.."_reopen"],
|
||||
options=options})
|
||||
end
|
||||
|
||||
return mod
|
||||
|
||||
Reference in New Issue
Block a user