mirror of
https://github.com/falcosecurity/falco.git
synced 2025-07-31 06:01:52 +00:00
Add a playbook which starts to capturing data using Sysdig and uploads capture to a s3 bucket (#414)
* Fix spec name * Add a playbook for capturing stuff using sysdig in a container * Add event-name to job name for avoid collisions among captures * Implement job for starting container in Pod in Kubernetes Client We are going to pick data for all Pod, not limited to one container * Use sysdig/capturer image for capture and upload to s3 the capture * There is a bug with environment string splitting in kubeless https://github.com/kubeless/kubeless/issues/824 So here is a workaround which uses multiple --env flags, one for each environment. * Use shorter job name. Kubernetes limit is 64 characters. * Add a deployable playbook with Kubeless for capturing stuff with Sysdig * Document the integration with Sysdig capture * Add Dockerfile for creating sysdig-capturer
This commit is contained in:
parent
f746c4cd57
commit
e4ffa55d58
@ -178,3 +178,24 @@ This playbook creates an incident in Demisto
|
||||
* VERIFY_SSL: Verify SSL certificates for HTTPS requests. By default is enabled.
|
||||
|
||||
In this example, when Falco raises any kind of alert, the alert will be created in Demisto
|
||||
|
||||
### Start a capture using Sysdig
|
||||
|
||||
This playbook starts to capture information about pod using sysdig and uploads
|
||||
to a s3 bucket.
|
||||
|
||||
```
|
||||
$ ./deploy_playbook -p capture -e CAPTURE_DURATION=300 -e AWS_S3_BUCKET=s3://xxxxxxx -e AWS_ACCESS_KEY_ID=xxxxXXXxxXXxXX -e AWS_SECRET_ACCESS_KEY=xxXxXXxxxxXXX -t "falco.notice.terminal_shell_in_container"
|
||||
```
|
||||
|
||||
#### Parameters:
|
||||
* CAPTURE_DURATION: Captures data for this duration in seconds. By default is
|
||||
120 seconds (2 minutes)
|
||||
* AWS_S3_BUCKET: This is the bucket where data is going to be uploaded. Jobs
|
||||
starts with sysdig- prefix and contain pod name and time where event starts.
|
||||
* AWS_ACCESS_KEY_ID: This is the Amazon access key id.
|
||||
* AWS_SECRET_ACCESS_KEY: This is the Amazon secret access key.
|
||||
|
||||
In this example, when we detect a shell in a container, we start to collect data
|
||||
for 300 seconds. This playbook requires permissions for creating a new pod from
|
||||
a Kubeless function.
|
||||
|
20
integrations/kubernetes-response-engine/playbooks/capture.py
Normal file
20
integrations/kubernetes-response-engine/playbooks/capture.py
Normal file
@ -0,0 +1,20 @@
|
||||
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.StartSysdigCaptureForContainer(
|
||||
infrastructure.KubernetesClient(),
|
||||
int(os.environ.get('CAPTURE_DURATION', 120)),
|
||||
os.environ['AWS_S3_BUCKET'],
|
||||
os.environ['AWS_ACCESS_KEY_ID'],
|
||||
os.environ['AWS_SECRET_ACCESS_KEY']
|
||||
)
|
||||
|
||||
|
||||
def handler(event, context):
|
||||
playbook.run(event['data'])
|
@ -21,7 +21,11 @@ EOF
|
||||
exit 1
|
||||
}
|
||||
|
||||
function join { local IFS="$1"; shift; echo "$*"; }
|
||||
function create_environment_flags {
|
||||
for env in ${environment[*]}; do
|
||||
echo "--env ${env} "
|
||||
done
|
||||
}
|
||||
|
||||
playbook=""
|
||||
environment=()
|
||||
@ -54,7 +58,7 @@ zip "${playbook}".zip -r playbooks/*.py "${playbook}".py
|
||||
|
||||
kubeless function deploy --from-file "${playbook}".zip \
|
||||
--dependencies requirements.txt \
|
||||
--env "$(join , ${environment[*]})" \
|
||||
$(create_environment_flags ${environment[*]}) \
|
||||
--runtime python3.6 \
|
||||
--handler "${playbook}".handler \
|
||||
falco-"${playbook}"
|
||||
|
@ -137,3 +137,24 @@ class CreateIncidentInDemisto:
|
||||
'Informational': 5,
|
||||
'Debug': 5,
|
||||
}
|
||||
|
||||
|
||||
class StartSysdigCaptureForContainer:
|
||||
def __init__(self, k8s_client, duration_in_seconds, s3_bucket,
|
||||
aws_access_key_id, aws_secret_access_key):
|
||||
self._k8s_client = k8s_client
|
||||
self._duration_in_seconds = duration_in_seconds
|
||||
self._s3_bucket = s3_bucket
|
||||
self._aws_access_key_id = aws_access_key_id
|
||||
self._aws_secret_access_key = aws_secret_access_key
|
||||
|
||||
def run(self, alert):
|
||||
pod = alert['output_fields']['k8s.pod.name']
|
||||
event_time = alert['output_fields']['evt.time']
|
||||
|
||||
self._k8s_client.start_sysdig_capture_for(pod,
|
||||
event_time,
|
||||
self._duration_in_seconds,
|
||||
self._s3_bucket,
|
||||
self._aws_access_key_id,
|
||||
self._aws_secret_access_key)
|
||||
|
@ -14,6 +14,7 @@ class KubernetesClient:
|
||||
config.load_incluster_config()
|
||||
|
||||
self._v1 = client.CoreV1Api()
|
||||
self._batch_v1 = client.BatchV1Api()
|
||||
|
||||
def delete_pod(self, name):
|
||||
namespace = self._find_pod_namespace(name)
|
||||
@ -65,6 +66,152 @@ class KubernetesClient:
|
||||
|
||||
return self._v1.patch_namespaced_pod(name, namespace, body)
|
||||
|
||||
def start_sysdig_capture_for(self, pod_name, event_time,
|
||||
duration_in_seconds, s3_bucket,
|
||||
aws_access_key_id, aws_secret_access_key):
|
||||
job_name = 'sysdig-{}-{}'.format(pod_name, event_time)
|
||||
|
||||
node_name = self.find_node_running_pod(pod_name)
|
||||
namespace = self._find_pod_namespace(pod_name)
|
||||
body = self._build_sysdig_capture_job_body(job_name,
|
||||
node_name,
|
||||
duration_in_seconds,
|
||||
s3_bucket,
|
||||
aws_access_key_id,
|
||||
aws_secret_access_key)
|
||||
|
||||
return self._batch_v1.create_namespaced_job(namespace, body)
|
||||
|
||||
def _build_sysdig_capture_job_body(self, job_name, node_name,
|
||||
duration_in_seconds, s3_bucket,
|
||||
aws_access_key_id, aws_secret_access_key):
|
||||
return client.V1Job(
|
||||
metadata=client.V1ObjectMeta(
|
||||
name=job_name
|
||||
),
|
||||
spec=client.V1JobSpec(
|
||||
template=client.V1PodTemplateSpec(
|
||||
metadata=client.V1ObjectMeta(
|
||||
name=job_name
|
||||
),
|
||||
spec=client.V1PodSpec(
|
||||
containers=[client.V1Container(
|
||||
name='capturer',
|
||||
image='sysdig/capturer',
|
||||
image_pull_policy='Always',
|
||||
security_context=client.V1SecurityContext(
|
||||
privileged=True
|
||||
),
|
||||
env=[
|
||||
client.V1EnvVar(
|
||||
name='AWS_S3_BUCKET',
|
||||
value=s3_bucket
|
||||
),
|
||||
client.V1EnvVar(
|
||||
name='CAPTURE_DURATION',
|
||||
value=str(duration_in_seconds)
|
||||
),
|
||||
client.V1EnvVar(
|
||||
name='CAPTURE_FILE_NAME',
|
||||
value=job_name
|
||||
),
|
||||
client.V1EnvVar(
|
||||
name='AWS_ACCESS_KEY_ID',
|
||||
value=aws_access_key_id,
|
||||
),
|
||||
client.V1EnvVar(
|
||||
name='AWS_SECRET_ACCESS_KEY',
|
||||
value=aws_secret_access_key,
|
||||
)
|
||||
],
|
||||
volume_mounts=[
|
||||
client.V1VolumeMount(
|
||||
mount_path='/host/var/run/docker.sock',
|
||||
name='docker-socket'
|
||||
),
|
||||
client.V1VolumeMount(
|
||||
mount_path='/host/dev',
|
||||
name='dev-fs'
|
||||
),
|
||||
client.V1VolumeMount(
|
||||
mount_path='/host/proc',
|
||||
name='proc-fs',
|
||||
read_only=True
|
||||
),
|
||||
client.V1VolumeMount(
|
||||
mount_path='/host/boot',
|
||||
name='boot-fs',
|
||||
read_only=True
|
||||
),
|
||||
client.V1VolumeMount(
|
||||
mount_path='/host/lib/modules',
|
||||
name='lib-modules',
|
||||
read_only=True
|
||||
),
|
||||
client.V1VolumeMount(
|
||||
mount_path='/host/usr',
|
||||
name='usr-fs',
|
||||
read_only=True
|
||||
),
|
||||
client.V1VolumeMount(
|
||||
mount_path='/dev/shm',
|
||||
name='dshm'
|
||||
)
|
||||
]
|
||||
)],
|
||||
volumes=[
|
||||
client.V1Volume(
|
||||
name='dshm',
|
||||
empty_dir=client.V1EmptyDirVolumeSource(
|
||||
medium='Memory'
|
||||
)
|
||||
),
|
||||
client.V1Volume(
|
||||
name='docker-socket',
|
||||
host_path=client.V1HostPathVolumeSource(
|
||||
path='/var/run/docker.sock'
|
||||
)
|
||||
),
|
||||
client.V1Volume(
|
||||
name='dev-fs',
|
||||
host_path=client.V1HostPathVolumeSource(
|
||||
|
||||
path='/dev'
|
||||
)
|
||||
),
|
||||
client.V1Volume(
|
||||
name='proc-fs',
|
||||
host_path=client.V1HostPathVolumeSource(
|
||||
path='/proc'
|
||||
)
|
||||
),
|
||||
|
||||
client.V1Volume(
|
||||
name='boot-fs',
|
||||
host_path=client.V1HostPathVolumeSource(
|
||||
path='/boot'
|
||||
)
|
||||
),
|
||||
client.V1Volume(
|
||||
name='lib-modules',
|
||||
host_path=client.V1HostPathVolumeSource(
|
||||
path='/lib/modules'
|
||||
)
|
||||
),
|
||||
client.V1Volume(
|
||||
name='usr-fs',
|
||||
host_path=client.V1HostPathVolumeSource(
|
||||
path='/usr'
|
||||
)
|
||||
)
|
||||
],
|
||||
node_name=node_name,
|
||||
restart_policy='Never'
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class SlackClient:
|
||||
def __init__(self, slack_webhook_url):
|
||||
|
@ -1,8 +1,9 @@
|
||||
from mamba import description, context, it, before
|
||||
from expects import expect, be_false, be_true, start_with, equal, have_key
|
||||
from expects import expect, be_false, be_true, start_with, equal, have_key, be_none
|
||||
|
||||
import subprocess
|
||||
import os.path
|
||||
import time
|
||||
|
||||
from playbooks import infrastructure
|
||||
|
||||
@ -46,7 +47,7 @@ with description(infrastructure.KubernetesClient) as self:
|
||||
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'):
|
||||
with it('adds label to a pod'):
|
||||
self._create_nginx_pod()
|
||||
|
||||
pod = self.kubernetes_client.add_label_to_pod('nginx',
|
||||
@ -55,6 +56,18 @@ with description(infrastructure.KubernetesClient) as self:
|
||||
|
||||
expect(pod.metadata.labels).to(have_key('testing', 'true'))
|
||||
|
||||
with it('starts sysdig capture for'):
|
||||
self._create_nginx_pod()
|
||||
|
||||
job = self.kubernetes_client.start_sysdig_capture_for('nginx',
|
||||
int(time.time()),
|
||||
10,
|
||||
'any s3 bucket',
|
||||
'any aws key id',
|
||||
'any aws secret key')
|
||||
|
||||
expect(job).not_to(be_none)
|
||||
|
||||
def _create_nginx_pod(self):
|
||||
current_directory = os.path.dirname(os.path.realpath(__file__))
|
||||
pod_manifesto = os.path.join(current_directory,
|
||||
|
@ -0,0 +1,40 @@
|
||||
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.StartSysdigCaptureForContainer) as self:
|
||||
with before.each:
|
||||
self.k8s_client = Spy(infrastructure.KubernetesClient)
|
||||
self.duration_in_seconds = 'any duration in seconds'
|
||||
self.s3_bucket = 'any s3 bucket url'
|
||||
self.aws_access_key_id = 'any aws access key id'
|
||||
self.aws_secret_access_key = 'any aws secret access key'
|
||||
self.playbook = playbooks.StartSysdigCaptureForContainer(self.k8s_client,
|
||||
self.duration_in_seconds,
|
||||
self.s3_bucket,
|
||||
self.aws_access_key_id,
|
||||
self.aws_secret_access_key)
|
||||
|
||||
with it('add starts capturing job in same node than Pod alerted'):
|
||||
pod_name = 'any pod name'
|
||||
event_time = 'any event time'
|
||||
alert = {'output_fields': {
|
||||
'k8s.pod.name': pod_name,
|
||||
'evt.time': event_time,
|
||||
}}
|
||||
|
||||
self.playbook.run(alert)
|
||||
|
||||
expect(self.k8s_client.start_sysdig_capture_for)\
|
||||
.to(have_been_called_with(pod_name,
|
||||
event_time,
|
||||
self.duration_in_seconds,
|
||||
self.s3_bucket,
|
||||
self.aws_access_key_id,
|
||||
self.aws_secret_access_key))
|
@ -0,0 +1,24 @@
|
||||
FROM sysdig/sysdig:latest
|
||||
|
||||
MAINTAINER Néstor Salceda <nestor.salceda@sysdig.com>
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get --fix-broken install -y \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
s3cmd \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# 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
|
||||
|
||||
ENV CAPTURE_DURATION 120
|
||||
|
||||
COPY ./docker-entrypoint.sh /
|
||||
|
||||
ENTRYPOINT ["/docker-entrypoint.sh"]
|
15
integrations/kubernetes-response-engine/sysdig-capturer/docker-entrypoint.sh
Executable file
15
integrations/kubernetes-response-engine/sysdig-capturer/docker-entrypoint.sh
Executable file
@ -0,0 +1,15 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -exuo
|
||||
|
||||
echo "* Setting up /usr/src links from host"
|
||||
|
||||
for i in $(ls $SYSDIG_HOST_ROOT/usr/src)
|
||||
do
|
||||
ln -s $SYSDIG_HOST_ROOT/usr/src/$i /usr/src/$i
|
||||
done
|
||||
|
||||
/usr/bin/sysdig-probe-loader
|
||||
|
||||
sysdig -S -M $CAPTURE_DURATION -pk -z -w $CAPTURE_FILE_NAME.scap.gz
|
||||
s3cmd --access_key=$AWS_ACCESS_KEY_ID --secret_key=$AWS_SECRET_ACCESS_KEY put $CAPTURE_FILE_NAME.scap.gz $AWS_S3_BUCKET
|
Loading…
Reference in New Issue
Block a user