From c3f7d15e2617e3ee22681b91d8bb420d85b5c538 Mon Sep 17 00:00:00 2001 From: Mark Stemm Date: Tue, 14 Jan 2020 16:26:18 -0800 Subject: [PATCH] Add k8s audit support to falco event generator Currently, the falco event generator only generates system call activity. This adds support for k8s_audit events by adding a script + supporting k8s object files that generate activity that matches the k8s audit event ruleset. The main script is k8s_event_generator.sh, which loops over the files in the yaml subdirectory, running kubectl apply -f for each. In the interests of keeping things self-contained, all objects are created in a `falco-event-generator` namespace. This means that some activity related with cluster roles/cluster role bindings is not performed. Each k8s object has annotations that note: 1. The specific falco rules that should trigger. 2. A user-friendly message to print when apply-ing the file. You can provide a specific rule name to the script. If provided, only those objects related to that rule will trigger. The default is "all", meaning that all objects are created. The script loops forever, deleting the falco-event-generator namespace after each iteration. Additionally, the docker image has been updated to also copy the script + supporting files, as well as fetching the latest available `kubectl` binary. The entrypoint is now a script that allows choosing between: - syscall activity: run with .... "syscall" - k8s_audit activity: run with .... "k8s_audit" - spawn a shell: run with .... "bash" The default is "syscall" to preserve existing behavior. In most cases, you'll need to provide kube config files/directories that allow access to your cluster. A command like the following will work: ``` docker run -v $HOME/.kube:/root/.kube -it falcosecurity/falco-event-generator k8s_audit ``` Signed-off-by: Mark Stemm --- docker/event-generator/Dockerfile | 7 ++- docker/event-generator/docker-entrypoint.sh | 21 +++++++ docker/event-generator/k8s_event_generator.sh | 57 +++++++++++++++++++ .../yaml/configmap-private-creds.yaml | 16 ++++++ .../yaml/disallowed-pod-deployment.yaml | 25 ++++++++ .../yaml/hostnetwork-deployment.yaml | 26 +++++++++ .../yaml/nodeport-service.yaml | 16 ++++++ .../yaml/privileged-deployment.yaml | 27 +++++++++ .../event-generator/yaml/role-pod-exec.yaml | 17 ++++++ .../yaml/role-wildcard-resources.yaml | 17 ++++++ .../yaml/role-write-privileges.yaml | 17 ++++++ .../yaml/sensitive-mount-deployment.yaml | 32 +++++++++++ .../yaml/vanilla-configmap.yaml | 15 +++++ .../yaml/vanilla-deployment.yaml | 25 ++++++++ ...nilla-role-rolebinding-serviceaccount.yaml | 46 +++++++++++++++ .../event-generator/yaml/vanilla-service.yaml | 16 ++++++ 16 files changed, 378 insertions(+), 2 deletions(-) create mode 100755 docker/event-generator/docker-entrypoint.sh create mode 100644 docker/event-generator/k8s_event_generator.sh create mode 100644 docker/event-generator/yaml/configmap-private-creds.yaml create mode 100644 docker/event-generator/yaml/disallowed-pod-deployment.yaml create mode 100644 docker/event-generator/yaml/hostnetwork-deployment.yaml create mode 100644 docker/event-generator/yaml/nodeport-service.yaml create mode 100644 docker/event-generator/yaml/privileged-deployment.yaml create mode 100644 docker/event-generator/yaml/role-pod-exec.yaml create mode 100644 docker/event-generator/yaml/role-wildcard-resources.yaml create mode 100644 docker/event-generator/yaml/role-write-privileges.yaml create mode 100644 docker/event-generator/yaml/sensitive-mount-deployment.yaml create mode 100644 docker/event-generator/yaml/vanilla-configmap.yaml create mode 100644 docker/event-generator/yaml/vanilla-deployment.yaml create mode 100644 docker/event-generator/yaml/vanilla-role-rolebinding-serviceaccount.yaml create mode 100644 docker/event-generator/yaml/vanilla-service.yaml diff --git a/docker/event-generator/Dockerfile b/docker/event-generator/Dockerfile index d543d846..50b1c6e2 100644 --- a/docker/event-generator/Dockerfile +++ b/docker/event-generator/Dockerfile @@ -1,7 +1,10 @@ FROM alpine:latest LABEL maintainer="opensource@sysdig.com" -RUN apk add --no-cache bash g++ +RUN apk add --no-cache bash g++ curl COPY ./event_generator.cpp /usr/local/bin +COPY ./docker-entrypoint.sh ./k8s_event_generator.sh / +COPY ./yaml /yaml RUN mkdir -p /var/lib/rpm RUN g++ --std=c++0x /usr/local/bin/event_generator.cpp -o /usr/local/bin/event_generator -CMD ["/usr/local/bin/event_generator"] +RUN curl -o /usr/local/bin/kubectl -LO https://storage.googleapis.com/kubernetes-release/release/`curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt`/bin/linux/amd64/kubectl && chmod +x /usr/local/bin/kubectl +ENTRYPOINT ["/docker-entrypoint.sh"] diff --git a/docker/event-generator/docker-entrypoint.sh b/docker/event-generator/docker-entrypoint.sh new file mode 100755 index 00000000..ade40222 --- /dev/null +++ b/docker/event-generator/docker-entrypoint.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +CMD=${1:-syscall} + +shift + +set -euo pipefail + +if [[ "$CMD" == "syscall" ]]; then + /usr/local/bin/event_generator +elif [[ "$CMD" == "k8s_audit" ]]; then + . k8s_event_generator.sh +elif [[ "$CMD" == "bash" ]]; then + bash +else + echo "Unknown command. Can be one of" + echo " \"syscall\": generate falco syscall-related activity" + echo " \"k8s_audit\": generate falco k8s audit-related activity" + echo " \"bash\": spawn a shell" + exit 1 +fi diff --git a/docker/event-generator/k8s_event_generator.sh b/docker/event-generator/k8s_event_generator.sh new file mode 100644 index 00000000..79b406a0 --- /dev/null +++ b/docker/event-generator/k8s_event_generator.sh @@ -0,0 +1,57 @@ +#!/bin/bash + +set -euo pipefail + +# You can pass a specific falco rule name and only yaml files matching +# that rule will be considered. The default is "all", meaning all yaml +# files will be applied. + +RULE=${1:-all} + +# Replace any '/' in RULES with a '.' and any space with a dash. (K8s +# label values can not contain slashes/spaces) +RULE=$(echo "$RULE" | tr '/ ' '.-') + +echo "***Testing kubectl configuration..." +kubectl version --short + +while true; do + + RET=$(kubectl get namespaces --output=name | grep falco-event-generator || true) + + if [[ "$RET" == *falco-event-generator* ]]; then + echo "***Deleting existing falco-event-generator namespace..." + kubectl delete namespace falco-event-generator + fi + + echo "***Creating falco-event-generator namespace..." + kubectl create namespace falco-event-generator + + for file in yaml/*.yaml; do + + MATCH=0 + if [[ "${RULE}" == "all" ]]; then + MATCH=1 + else + RET=$(grep -E "falco.rules:.*${RULE}" $file || true) + if [[ "$RET" != "" ]]; then + MATCH=1 + fi + fi + + if [[ $MATCH == 1 ]]; then + MESSAGES=$(grep -E 'message' $file | cut -d: -f2 | tr '\n' ',') + RULES=$(grep -E 'falco.rules' $file | cut -d: -f2 | tr '\n' ',') + + # The message uses dashes in place of spaces, convert them back to spaces + MESSAGES=$(echo "$MESSAGES" | tr '-' ' ' | sed -e 's/ *//' | sed -e 's/,$//') + RULES=$(echo "$RULES" | tr '-' ' '| tr '.' '/' | sed -e 's/ *//' | sed -e 's/,$//') + + echo "***$MESSAGES (Rule(s) $RULES)..." + kubectl apply -f $file + sleep 2 + fi + done + + sleep 10 +done diff --git a/docker/event-generator/yaml/configmap-private-creds.yaml b/docker/event-generator/yaml/configmap-private-creds.yaml new file mode 100644 index 00000000..4735798e --- /dev/null +++ b/docker/event-generator/yaml/configmap-private-creds.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: private-creds-configmap + namespace: falco-event-generator + labels: + app.kubernetes.io/name: private-creds-configmap + app.kubernetes.io/part-of: falco-event-generator + falco.rules: Create.Modify-Configmap-With-Private-Credentials + message: Creating-configmap-with-private-credentials +data: + ui.properties: | + color.good=purple + color.bad=yellow + allow.textmode=true + password=some_secret_password diff --git a/docker/event-generator/yaml/disallowed-pod-deployment.yaml b/docker/event-generator/yaml/disallowed-pod-deployment.yaml new file mode 100644 index 00000000..a12eb773 --- /dev/null +++ b/docker/event-generator/yaml/disallowed-pod-deployment.yaml @@ -0,0 +1,25 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: disallowed-pod-deployment + namespace: falco-event-generator + labels: + app.kubernetes.io/name: disallowed-pod-deployment + app.kubernetes.io/part-of: falco-event-generator + falco.rules: Create-Disallowed-Pod + message: Creating-pod-with-image-outside-of-allowed-images +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: disallowed-pod-busybox + template: + metadata: + labels: + app.kubernetes.io/name: disallowed-pod-busybox + app.kubernetes.io/part-of: falco-event-generator + spec: + containers: + - name: busybox + image: busybox + command: ["/bin/sh", "-c", "while true; do echo sleeping; sleep 3600; done"] diff --git a/docker/event-generator/yaml/hostnetwork-deployment.yaml b/docker/event-generator/yaml/hostnetwork-deployment.yaml new file mode 100644 index 00000000..1dc3f93d --- /dev/null +++ b/docker/event-generator/yaml/hostnetwork-deployment.yaml @@ -0,0 +1,26 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: hostnetwork-deployment + namespace: falco-event-generator + labels: + app.kubernetes.io/name: hostnetwork-deployment + app.kubernetes.io/part-of: falco-event-generator + falco.rules: Create-HostNetwork-Pod + message: Creating-deployment-with-hostNetwork-true-pod +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: hostnetwork-busybox + template: + metadata: + labels: + app.kubernetes.io/name: hostnetwork-busybox + app.kubernetes.io/part-of: falco-event-generator + spec: + hostNetwork: true + containers: + - name: busybox + image: busybox + command: ["/bin/sh", "-c", "while true; do echo sleeping; sleep 3600; done"] diff --git a/docker/event-generator/yaml/nodeport-service.yaml b/docker/event-generator/yaml/nodeport-service.yaml new file mode 100644 index 00000000..d6f83951 --- /dev/null +++ b/docker/event-generator/yaml/nodeport-service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: nodeport-service + namespace: falco-event-generator + labels: + app.kubernetes.io/name: nodeport-service + app.kubernetes.io/part-of: falco-event-generator + falco.rules: Create-NodePort-Service + message: Creating-service-of-type-NodePort +spec: + type: NodePort + ports: + - port: 80 + selector: + app: busybox diff --git a/docker/event-generator/yaml/privileged-deployment.yaml b/docker/event-generator/yaml/privileged-deployment.yaml new file mode 100644 index 00000000..de96279f --- /dev/null +++ b/docker/event-generator/yaml/privileged-deployment.yaml @@ -0,0 +1,27 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: privileged-deployment + namespace: falco-event-generator + labels: + app.kubernetes.io/name: privileged-deployment + app.kubernetes.io/part-of: falco-event-generator + falco.rules: Create-Privileged-Pod + message: Creating-deployment-with-privileged-true-pod +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: privileged-busybox + template: + metadata: + labels: + app.kubernetes.io/name: privileged-busybox + app.kubernetes.io/part-of: falco-event-generator + spec: + containers: + - securityContext: + privileged: true + name: busybox + image: busybox + command: ["/bin/sh", "-c", "while true; do echo sleeping; sleep 3600; done"] \ No newline at end of file diff --git a/docker/event-generator/yaml/role-pod-exec.yaml b/docker/event-generator/yaml/role-pod-exec.yaml new file mode 100644 index 00000000..626077f3 --- /dev/null +++ b/docker/event-generator/yaml/role-pod-exec.yaml @@ -0,0 +1,17 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: pod-exec-role + namespace: falco-event-generator + labels: + app.kubernetes.io/name: pod-exec-role + app.kubernetes.io/part-of: falco-event-generator + falco.rules: ClusterRole-With-Pod-Exec-Created + message: Creating-role-that-can-exec-to-pods +rules: +- apiGroups: + - "" + resources: + - "pods/exec" + verbs: + - get diff --git a/docker/event-generator/yaml/role-wildcard-resources.yaml b/docker/event-generator/yaml/role-wildcard-resources.yaml new file mode 100644 index 00000000..1344e990 --- /dev/null +++ b/docker/event-generator/yaml/role-wildcard-resources.yaml @@ -0,0 +1,17 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: wildcard-resources-role + namespace: falco-event-generator + labels: + app.kubernetes.io/name: wildcard-resources-role + app.kubernetes.io/part-of: falco-event-generator + falco.rules: ClusterRole-With-Write-Privileges-Created + message: Creating-role-with-wildcard-resources +rules: +- apiGroups: + - "" + resources: + - "*" + verbs: + - get diff --git a/docker/event-generator/yaml/role-write-privileges.yaml b/docker/event-generator/yaml/role-write-privileges.yaml new file mode 100644 index 00000000..e3065deb --- /dev/null +++ b/docker/event-generator/yaml/role-write-privileges.yaml @@ -0,0 +1,17 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: write-privileges-role + namespace: falco-event-generator + labels: + app.kubernetes.io/name: write-privileges-role + app.kubernetes.io/part-of: falco-event-generator + falco.rules: ClusterRole-With-Write-Privileges-Created + message: Creating-role-with-write-privileges +rules: +- apiGroups: + - "" + resources: + - "pods" + verbs: + - create diff --git a/docker/event-generator/yaml/sensitive-mount-deployment.yaml b/docker/event-generator/yaml/sensitive-mount-deployment.yaml new file mode 100644 index 00000000..ca7cbbfa --- /dev/null +++ b/docker/event-generator/yaml/sensitive-mount-deployment.yaml @@ -0,0 +1,32 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: sensitive-mount-deployment + namespace: falco-event-generator + labels: + app.kubernetes.io/name: sensitive-mount-deployment + app.kubernetes.io/part-of: falco-event-generator + falco.rules: Create-Sensitive-Mount-Pod + message: Creating-deployment-with-pod-mounting-sensitive-path-from-host +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: sensitive-mount-busybox + template: + metadata: + labels: + app.kubernetes.io/name: sensitive-mount-busybox + app.kubernetes.io/part-of: falco-event-generator + spec: + containers: + - name: busybox + image: busybox + command: ["/bin/sh", "-c", "while true; do echo sleeping; sleep 3600; done"] + volumeMounts: + - mountPath: /host/etc + name: etc + volumes: + - name: etc + hostPath: + path: /etc diff --git a/docker/event-generator/yaml/vanilla-configmap.yaml b/docker/event-generator/yaml/vanilla-configmap.yaml new file mode 100644 index 00000000..0dd9f45a --- /dev/null +++ b/docker/event-generator/yaml/vanilla-configmap.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: vanilla-configmap + namespace: falco-event-generator + labels: + app.kubernetes.io/name: vanilla-configmap + app.kubernetes.io/part-of: falco-event-generator + falco.rules: K8s-ConfigMap-Created + message: Creating-configmap +data: + ui.properties: | + color.good=purple + color.bad=yellow + allow.textmode=true \ No newline at end of file diff --git a/docker/event-generator/yaml/vanilla-deployment.yaml b/docker/event-generator/yaml/vanilla-deployment.yaml new file mode 100644 index 00000000..34f41c7e --- /dev/null +++ b/docker/event-generator/yaml/vanilla-deployment.yaml @@ -0,0 +1,25 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: vanilla-deployment + namespace: falco-event-generator + labels: + app.kubernetes.io/name: vanilla-deployment + app.kubernetes.io/part-of: falco-event-generator + falco.rules: K8s-Deployment-Created + message: Creating-deployment +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: vanilla-busybox + template: + metadata: + labels: + app.kubernetes.io/name: vanilla-busybox + app.kubernetes.io/part-of: falco-event-generator + spec: + containers: + - name: busybox + image: busybox + command: ["/bin/sh", "-c", "while true; do echo sleeping; sleep 3600; done"] diff --git a/docker/event-generator/yaml/vanilla-role-rolebinding-serviceaccount.yaml b/docker/event-generator/yaml/vanilla-role-rolebinding-serviceaccount.yaml new file mode 100644 index 00000000..580b4e8e --- /dev/null +++ b/docker/event-generator/yaml/vanilla-role-rolebinding-serviceaccount.yaml @@ -0,0 +1,46 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: vanilla-role + namespace: falco-event-generator + labels: + app.kubernetes.io/name: vanilla-role + app.kubernetes.io/part-of: falco-event-generator + falco.rules: K8s-Role.Clusterrole-Created + message: Creating-role +rules: +- apiGroups: + - "" + resources: + - "pods" + verbs: + - list +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: vanilla-role-binding + namespace: falco-event-generator + labels: + app.kubernetes.io/name: vanilla-role-binding + app.kubernetes.io/part-of: falco-event-generator + falco.rules: K8s-Role.Clusterrolebinding-Created + message: Creating-rolebinding +roleRef: + kind: Role + name: vanilla-role + apiGroup: rbac.authorization.k8s.io +subjects: + - kind: ServiceAccount + name: vanilla-service-account +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: vanilla-serviceaccount + namespace: falco-event-generator + labels: + app.kubernetes.io/name: vanilla-serviceaccount + app.kubernetes.io/part-of: falco-event-generator + falco.rules: K8s-Serviceaccount-Created + message: Creating-serviceaccount diff --git a/docker/event-generator/yaml/vanilla-service.yaml b/docker/event-generator/yaml/vanilla-service.yaml new file mode 100644 index 00000000..db91cdbe --- /dev/null +++ b/docker/event-generator/yaml/vanilla-service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: vanilla-service + namespace: falco-event-generator + labels: + app.kubernetes.io/name: vanilla-service + app.kubernetes.io/part-of: falco-event-generator + falco.rules: K8s-Service-Created + message: Creating-service +spec: + type: ClusterIP + ports: + - port: 80 + selector: + app: busybox \ No newline at end of file