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:
Néstor Salceda 2018-10-12 01:55:40 +02:00 committed by Mark Stemm
parent f746c4cd57
commit e4ffa55d58
9 changed files with 309 additions and 4 deletions

View File

@ -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.

View 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'])

View File

@ -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}"

View File

@ -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)

View File

@ -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):

View File

@ -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,

View File

@ -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))

View File

@ -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"]

View 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