diff --git a/integrations/kubernetes-response-engine/playbooks/README.md b/integrations/kubernetes-response-engine/playbooks/README.md index 951a8b04..a9622fc5 100644 --- a/integrations/kubernetes-response-engine/playbooks/README.md +++ b/integrations/kubernetes-response-engine/playbooks/README.md @@ -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. diff --git a/integrations/kubernetes-response-engine/playbooks/capture.py b/integrations/kubernetes-response-engine/playbooks/capture.py new file mode 100644 index 00000000..f36c321d --- /dev/null +++ b/integrations/kubernetes-response-engine/playbooks/capture.py @@ -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']) diff --git a/integrations/kubernetes-response-engine/playbooks/deploy_playbook b/integrations/kubernetes-response-engine/playbooks/deploy_playbook index 766cee37..eec205b0 100755 --- a/integrations/kubernetes-response-engine/playbooks/deploy_playbook +++ b/integrations/kubernetes-response-engine/playbooks/deploy_playbook @@ -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}" diff --git a/integrations/kubernetes-response-engine/playbooks/playbooks/__init__.py b/integrations/kubernetes-response-engine/playbooks/playbooks/__init__.py index 210da6ff..0028b6ee 100644 --- a/integrations/kubernetes-response-engine/playbooks/playbooks/__init__.py +++ b/integrations/kubernetes-response-engine/playbooks/playbooks/__init__.py @@ -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) diff --git a/integrations/kubernetes-response-engine/playbooks/playbooks/infrastructure.py b/integrations/kubernetes-response-engine/playbooks/playbooks/infrastructure.py index 75ed3c98..49364dd3 100644 --- a/integrations/kubernetes-response-engine/playbooks/playbooks/infrastructure.py +++ b/integrations/kubernetes-response-engine/playbooks/playbooks/infrastructure.py @@ -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): diff --git a/integrations/kubernetes-response-engine/playbooks/specs/infrastructure/kubernetes_client_spec.py b/integrations/kubernetes-response-engine/playbooks/specs/infrastructure/kubernetes_client_spec.py index 6e3a2aae..c6cf7ac3 100644 --- a/integrations/kubernetes-response-engine/playbooks/specs/infrastructure/kubernetes_client_spec.py +++ b/integrations/kubernetes-response-engine/playbooks/specs/infrastructure/kubernetes_client_spec.py @@ -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, diff --git a/integrations/kubernetes-response-engine/playbooks/specs/playbooks/start_sysdig_capture_for_container_playbook_spec.py b/integrations/kubernetes-response-engine/playbooks/specs/playbooks/start_sysdig_capture_for_container_playbook_spec.py new file mode 100644 index 00000000..20162ac5 --- /dev/null +++ b/integrations/kubernetes-response-engine/playbooks/specs/playbooks/start_sysdig_capture_for_container_playbook_spec.py @@ -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)) diff --git a/integrations/kubernetes-response-engine/sysdig-capturer/Dockerfile b/integrations/kubernetes-response-engine/sysdig-capturer/Dockerfile new file mode 100644 index 00000000..35efbd9b --- /dev/null +++ b/integrations/kubernetes-response-engine/sysdig-capturer/Dockerfile @@ -0,0 +1,24 @@ +FROM sysdig/sysdig:latest + +MAINTAINER Néstor Salceda + +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"] diff --git a/integrations/kubernetes-response-engine/sysdig-capturer/docker-entrypoint.sh b/integrations/kubernetes-response-engine/sysdig-capturer/docker-entrypoint.sh new file mode 100755 index 00000000..1e2e8475 --- /dev/null +++ b/integrations/kubernetes-response-engine/sysdig-capturer/docker-entrypoint.sh @@ -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