Zum Inhalt springen

Services, Wiki-Artikel, Blog-Beiträge und Glossar-Einträge durchsuchen

↑↓NavigierenEnterÖffnenESCSchließen
Kubernetes Runtime Security: Falco, Seccomp und OPA Gatekeeper im Einsatz - Container-Sicherheit und Orchestrierung
Cloud Security

Kubernetes Runtime Security: Falco, Seccomp und OPA Gatekeeper im Einsatz

Runtime Security in Kubernetes erkennt Bedrohungen während der Ausführung: Falco für Kernel-Level-Monitoring, Seccomp-Profile für Syscall-Filterung, AppArmor für MAC, OPA Gatekeeper und Kyverno für Policy-as-Code, Pod Security Admission (PSA) als nativer K8s-Schutz. Incident-Response-Playbooks für kompromittierte Pods und Container-Forensik.

Vincent Heinen Vincent Heinen Abteilungsleiter Offensive Services
11 Min. Lesezeit
OSCP+ OSCP OSWP OSWA

TL;DR

Runtime Security ist entscheidend, um Kubernetes-Angriffe zu erkennen, die statische Konfigurationskontrollen umgehen. Sie überwacht dynamisch Systemaufrufe, Dateizugriffe und Netzwerkaktivitäten innerhalb laufender Container. Falco, ein CNCF-Projekt, nutzt eBPF oder Kernel-Module, um Kernel-Systemaufrufe in Echtzeit zu analysieren und bei Regelverstößen wie dem Start einer Shell in einem Web-Container Alarm zu schlagen. Ergänzend dazu filtert Seccomp (Secure Computing Mode) auf Linux-Kernel-Ebene unerwünschte Systemaufrufe, indem es Pods mit spezifischen Profilen versieht, die nur erlaubte Syscalls zulassen. Ein Beispielprofil für eine Anwendung kann über 50 Systemaufrufe wie "read", "write" und "execve" explizit erlauben, während alle anderen blockiert werden.

Diese Zusammenfassung wurde KI-gestützt erstellt (EU AI Act Art. 52).

Inhaltsverzeichnis (6 Abschnitte)

Kubernetes-Sicherheit endet nicht bei der Konfiguration von RBAC und Network Policies. Runtime Security überwacht was während der Ausführung passiert: welche Syscalls ein Container aufruft, ob Dateisysteme unerwartet modifiziert werden, ob Prozesse neue Netzwerkverbindungen aufbauen. Diese zweite Verteidigungslinie erkennt Angriffe die statische Konfigurationskontrollen umgehen.

Runtime Security: Warum Konfiguration alleine nicht reicht

Das Lückenmodell:

  Prävention (statisch):
  ✓ RBAC: wer darf was deployen?
  ✓ Network Policies: wer darf mit wem kommunizieren?
  ✓ Pod Security: privileged=false, read-only rootfs
  ✓ Image Scanning: bekannte CVEs in Images prüfen

  Problem: Image-Scan findet keine ZERO-DAY-Schwachstellen!
  Problem: Kompromittierter Container sieht von außen "normal" aus!

  Runtime Security (dynamisch):
  ✓ Was passiert INNERHALB des Containers während er läuft?
  ✓ Kernel-Systemcalls: welche Syscalls werden aufgerufen?
  ✓ Dateizugriffe: unerwartete Schreibzugriffe auf /etc?
  ✓ Netzwerk: neues unbekanntes Verbindungsziel?
  ✓ Prozesse: unerwarteter Prozessstart im Container?

  Angriffsvektor der Runtime Security braucht:
  → CVE in Anwendung → Angreifer führt Shell aus
  → Ohne Runtime: kein Alert (Shell-Start ist normaler Syscall)
  → Mit Runtime (Falco): "Neuer Prozess in Web-Container: /bin/bash" → Alert!

Falco: Kernel-Level Runtime Security

Falco - CNCF-Projekt für Runtime Security:

Wie Falco arbeitet:
  → eBPF-basiertes oder Kernel-Modul-basiertes Monitoring
  → Liest Kernel-System-Calls in Echtzeit
  → Vergleicht mit Regeln → Alert wenn Regel zutrifft

Installation via Helm:
  helm repo add falcosecurity https://falcosecurity.github.io/charts
  helm install falco falcosecurity/falco \
    --namespace falco \
    --create-namespace \
    --set driver.kind=ebpf \
    --set falco.grpc.enabled=true \
    --set falco.grpc_output.enabled=true

Falco-Regeln Syntax:
  - rule: Spawn Shell in Container
    desc: Erkennt wenn eine Shell in einem Container gestartet wird
    condition: >
      spawned_process and container and
      shell_procs and
      not container.image.repository in (allowed_shell_containers)
    output: >
      Shell gestartet in Container (user=%user.name cmd=%proc.cmdline
      container=%container.name image=%container.image.repository)
    priority: WARNING
    tags: [container, shell, attack]

Wichtige Falco-Standardregeln:
  - rule: Write below etc in container
    condition: open_write and container and fd.directory in (/etc)
    priority: ERROR

  - rule: Contact K8s API Server From Container
    condition: >
      k8s_api_server and container and
      not ka.user.name in (allowed_k8s_users)
    priority: WARNING

  - rule: Netcat Remote Code Execution in Container
    condition: >
      spawned_process and container and
      proc.name = "nc" and
      (proc.args contains "-e" or proc.args contains "-c")
    priority: CRITICAL

Falco-Ausgabe konfigurieren:
  # falco.yaml:
  outputs:
    rate: 1
    max_burst: 1000

  file_output:
    enabled: true
    filename: /var/log/falco/falco.log

  http_output:
    enabled: true
    url: https://siem.intern/api/falco  # → SIEM-Integration!

  program_output:
    enabled: true
    program: "jq '{severity:.priority,message:.output}' | curl -X POST ..."

Falco Sidekick (Alert-Routing):
  # Weiterleitung zu Slack, PagerDuty, Datadog, etc.:
  helm install falco-sidekick falcosecurity/falcosidekick \
    --set config.slack.webhookurl=https://hooks.slack.com/...
    --set config.pagerduty.routingKey=...

Seccomp Profile

Seccomp (Secure Computing Mode) - Syscall-Filterung:

Funktionsprinzip:
  → Linux-Kernel-Feature: welche Systemcalls darf ein Prozess ausführen?
  → Profile: Allowlist oder Blocklist von Syscalls
  → Nicht erlaubte Syscall → SIGKILL oder SCMP_ACT_ERRNO

Kubernetes Seccomp-Integration:
  # Pod mit RuntimeDefault-Profil:
  securityContext:
    seccompProfile:
      type: RuntimeDefault

  # Pod mit eigenem Profil:
  securityContext:
    seccompProfile:
      type: Localhost
      localhostProfile: profiles/myprofile.json

Eigenes Seccomp-Profil erstellen:
  Schritt 1: Syscalls aufzeichnen (Learning Mode):
    # Mit strace (außerhalb des Clusters zum Entwickeln):
    strace -c ./meine-anwendung
    # Ausgabe zeigt alle genutzten Syscalls

    # Oder: Falco + falco-driver-loader für Profil-Erstellung

  Schritt 2: Minimales Profil erstellen:
    # /var/lib/kubelet/seccomp/profiles/myapp.json:
    {
      "defaultAction": "SCMP_ACT_ERRNO",
      "syscalls": [
        {
          "names": [
            "read", "write", "open", "close", "stat", "fstat",
            "mmap", "mprotect", "munmap", "brk", "rt_sigaction",
            "rt_sigprocmask", "ioctl", "access", "pipe", "select",
            "sched_yield", "dup2", "pause", "nanosleep", "getpid",
            "socket", "connect", "sendto", "recvfrom", "bind",
            "listen", "accept", "getsockname", "getpeername",
            "sendmsg", "recvmsg", "shutdown", "setsockopt",
            "getsockopt", "exit_group", "futex", "set_tid_address",
            "set_robust_list", "prctl", "arch_prctl", "clone",
            "wait4", "execve", "getuid", "getgid", "getppid"
          ],
          "action": "SCMP_ACT_ALLOW"
        }
      ]
    }

  Schritt 3: Testen in Audit-Mode (Log statt Block):
    # Erstmal: SCMP_ACT_LOG statt SCMP_ACT_ERRNO
    # Logs in audit.log: welche Syscalls wurden blockiert?
    ausearch -m SECCOMP | tail -20

OCI Hook für automatische Profil-Generierung:
  # Security-Profile-Operator (SPO):
  kubectl apply -f https://github.com/kubernetes-sigs/security-profiles-operator/.../deploy.yaml
  # SPO zeichnet Syscalls auf und generiert minimale Profile automatisch!

OPA Gatekeeper und Kyverno

Policy-as-Code: Präventive Kontrollen im Admission Webhook:

OPA Gatekeeper:
  # Installation:
  kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/main/deploy/gatekeeper.yaml

  # ConstraintTemplate definieren:
  apiVersion: templates.gatekeeper.sh/v1
  kind: ConstraintTemplate
  metadata:
    name: k8srequiredlabels
  spec:
    crd:
      spec:
        names:
          kind: K8sRequiredLabels
        validation:
          openAPIV3Schema:
            type: object
            properties:
              labels:
                type: array
                items: {type: string}
    targets:
      - target: admission.k8s.gatekeeper.sh
        rego: |
          package k8srequiredlabels
          violation[{"msg": msg, "details": {"missing_labels": missing}}] {
            provided := {label | input.review.object.metadata.labels[label]}
            required := {label | label := input.parameters.labels[_]}
            missing := required - provided
            count(missing) > 0
            msg := sprintf("Missing labels: %v", [missing])
          }

  # Constraint (Constraint wird auf Cluster angewendet):
  apiVersion: constraints.gatekeeper.sh/v1beta1
  kind: K8sRequiredLabels
  metadata:
    name: require-app-label
  spec:
    match:
      kinds:
        - apiGroups: ["apps"]
          kinds: ["Deployment"]
    parameters:
      labels: ["app", "version", "owner"]
  # → Alle Deployments müssen app, version, owner Labels haben!

Kyverno (Kubernetes-native, YAML-basiert):
  # Installation:
  helm install kyverno kyverno/kyverno -n kyverno --create-namespace

  # Policy: Keine privilegierten Container:
  apiVersion: kyverno.io/v1
  kind: ClusterPolicy
  metadata:
    name: disallow-privileged-containers
  spec:
    validationFailureAction: enforce  # oder: audit
    rules:
      - name: privileged-containers
        match:
          resources:
            kinds: [Pod]
        validate:
          message: "Privilegierte Container sind nicht erlaubt!"
          pattern:
            spec:
              containers:
                - (name): "?*"
                  securityContext:
                    privileged: false  # Muss false sein!

  # Policy: Root-UID verbieten:
  - name: require-non-root
    validate:
      pattern:
        spec:
          securityContext:
            runAsNonRoot: true

  # Policy: Read-Only Filesystem erzwingen:
  - name: readonly-rootfs
    validate:
      pattern:
        spec:
          containers:
            - (name): "?*"
              securityContext:
                readOnlyRootFilesystem: true

  # Policy: Ressourcenlimits erzwingen:
  - name: require-resource-limits
    validate:
      message: "CPU und Memory Limits sind Pflicht!"
      pattern:
        spec:
          containers:
            - (name): "?*"
              resources:
                limits:
                  cpu: "?*"
                  memory: "?*"

Pod Security Admission (PSA)

Pod Security Admission - Nativer K8s-Schutz:

Drei Profile:
  privileged:  Keinerlei Einschränkungen (nur für System-Namespaces!)
  baseline:    Minimale Einschränkungen (sinnvoller Standard)
  restricted:  Strenge Einschränkungen (Produktions-Best-Practice)

PSA aktivieren (per Namespace-Label):
  # Namespace mit restricted-Profil:
  kubectl label namespace production \
    pod-security.kubernetes.io/enforce=restricted \
    pod-security.kubernetes.io/audit=restricted \
    pod-security.kubernetes.io/warn=restricted

  # Oder mit baseline:
  kubectl label namespace staging \
    pod-security.kubernetes.io/enforce=baseline

  # Enforce = blockiert unconforme Pods
  # Audit = loggt aber blockt nicht
  # Warn = Warnung im kubectl-Output

Was "restricted" einschränkt:
  → privileged: false (Pflicht)
  → allowPrivilegeEscalation: false
  → capabilities: DROP ALL (Pflicht)
  → runAsNonRoot: true
  → seccompProfile: RuntimeDefault oder Localhost
  → volumes: nur erlaubte Volume-Typen

Migrationsweg:
  1. Zuerst: warn/audit Mode → sehen was nicht konform ist
  2. Anwendungen anpassen (runAsNonRoot etc.)
  3. Dann: enforce aktivieren

Container-Forensik und Incident Response

Incident Response für kompromittierte Container:

Sofortmaßnahmen:
  # Pod isolieren (Network Policy):
  kubectl apply -f - <<EOF
  apiVersion: networking.k8s.io/v1
  kind: NetworkPolicy
  metadata:
    name: isolate-compromised-pod
    namespace: production
  spec:
    podSelector:
      matchLabels:
        app: compromised-app
    policyTypes: [Ingress, Egress]
    # Keine ingress/egress Rules = Alles blockiert!
  EOF

  # Forensic-Snapshot des Pods:
  # Container-Prozesse:
  kubectl exec -n production compromised-pod -- ps aux
  # Netzwerkverbindungen:
  kubectl exec -n production compromised-pod -- ss -tulpn
  # Crontab prüfen:
  kubectl exec -n production compromised-pod -- crontab -l

  # Container-Filesystem-Snapshot:
  kubectl cp production/compromised-pod:/tmp/malware.sh ./malware.sh

Falco-basierter Incident Response:
  Alert: "Reverse Shell von Pod 'payment-api'"
  Automated Response (via Falco Sidekick → Python-Script):

  1. Pod-Label setzen: "quarantined=true"
  2. Network Policy sofort anwenden (Isolation)
  3. Pod-Logs sichern (kubectl logs → S3)
  4. Container-Snapshot: kubectl exec -- tar czf - / → S3
  5. PagerDuty-Alert: Incident P1 öffnen
  6. SIEM: alle Events dieses Pods der letzten 24h korrelieren

Falco-Rule für Auto-Quarantine:
  - rule: Detect Reverse Shell
    condition: >
      spawned_process and container and
      proc.name in (shell_procs) and
      fd.type = ipv4 and fd.rip.name = external
    output: "Reverse Shell (pod=%k8s.pod.name ns=%k8s.ns.name)"
    priority: CRITICAL
    tags: [shell, container, reverse_shell]

  # Falco Sidekick Action:
  # Bei CRITICAL: kubectl label pod $POD_NAME quarantined=true
  # Trigger NetworkPolicy auf quarantined=true-Pods

Nächster Schritt

Unsere zertifizierten Sicherheitsexperten beraten Sie zu den Themen aus diesem Artikel — unverbindlich und kostenlos.

Kostenlos · 30 Minuten · Unverbindlich

Artikel teilen

Über den Autor

Vincent Heinen
Vincent Heinen

Abteilungsleiter Offensive Services

M.Sc. IT-Sicherheit mit über 5 Jahren Erfahrung in offensiver Sicherheitsanalyse. Leitet die Durchführung von Penetrationstests mit Spezialisierung auf Web-Applikationen, Netzwerk-Infrastruktur, Reverse Engineering und Hardware-Sicherheit. Verantwortlich für mehrere Responsible Disclosures.

OSCP+ OSCP OSWP OSWA
Zertifiziert ISO 27001ISO 9001AZAVBSI

Cookielose Analyse via Matomo (selbst gehostet, kein Tracking-Cookie). Datenschutzerklärung