Veröffentlicht am

Seccomp und AppArmor: Container-Syscalls einschränken

Teilen:
Authors

TL;DR

Seccomp filtert Syscalls die ein Container ausfuehren darf, AppArmor beschränkt den Zugriff auf Dateien und Netzwerkressourcen. Beide Mechanismen arbeiten auf Kernel-Ebene und ergänzen sich. RuntimeDefault als Seccomp-Profil ist der Mindeststandard für jeden Produktions-Cluster. Custom-Profile lohnen sich für sicherheitskritische Workloads.


Warum Kernel-Level-Security wichtig ist

Container teilen sich den Kernel des Host-Systems. Ein Container der beliebige Syscalls ausfuehren darf, kann Schwachstellen im Kernel ausnutzen. Seccomp und AppArmor setzen genau hier an -- sie begrenzen, was ein Prozess im Container auf Kernel-Ebene tun darf.

Kubernetes unterstützt beide Mechanismen direkt über die securityContext-Konfiguration. Seit Kubernetes 1.27 ist Seccomp mit RuntimeDefault als Standard-Profil verfügbar.

# Minimaler Pod mit Seccomp RuntimeDefault
apiVersion: v1
kind: Pod
metadata:
  name: secure-pod
spec:
  securityContext:
    seccompProfile:
      type: RuntimeDefault
  containers:
    - name: app
      image: nginx:1.27

Dieses eine Feld blockiert bereits über 40 potenziell gefährliche Syscalls.


Seccomp: Syscall-Filtering im Detail

Seccomp (Secure Computing Mode) filtert Linux-Syscalls auf Prozessebene. Kubernetes bietet drei Profil-Typen:

TypBeschreibungSicherheitsniveau
UnconfinedKein Filtering (Standard ohne Konfiguration)Keines
RuntimeDefaultContainer-Runtime-Profil (containerd/CRI-O)Gut
LocalhostEigenes Profil vom NodeIndividuell

RuntimeDefault als Baseline

Das RuntimeDefault-Profil der Container-Runtime blockiert Syscalls wie reboot, mount, ptrace und kexec_load. Für die meisten Anwendungen reicht das aus.

# Prüfen ob Seccomp auf dem Node aktiv ist
grep CONFIG_SECCOMP /boot/config-$(uname -r)
# CONFIG_SECCOMP=y
# CONFIG_SECCOMP_FILTER=y

# Seccomp-Status eines laufenden Containers prüfen
kubectl exec secure-pod -- cat /proc/1/status | grep Seccomp
# Seccomp:    2
# Seccomp_filters:    1
# (2 = SECCOMP_MODE_FILTER = aktiv)

Custom Seccomp-Profil erstellen

Wenn RuntimeDefault zu permissiv ist, erstellt ein eigenes Profil. Die Strategie: Alles blockieren, dann gezielt erlauben.

{
  "defaultAction": "SCMP_ACT_ERRNO",
  "architectures": [
    "SCMP_ARCH_X86_64",
    "SCMP_ARCH_AARCH64"
  ],
  "syscalls": [
    {
      "names": [
        "accept4", "bind", "close", "connect",
        "epoll_ctl", "epoll_wait", "exit", "exit_group",
        "fcntl", "fstat", "futex", "getpid", "getuid",
        "listen", "mmap", "mprotect", "nanosleep",
        "openat", "read", "recvfrom", "sendto",
        "socket", "write", "writev", "brk",
        "clock_gettime", "sigaltstack", "set_tid_address"
      ],
      "action": "SCMP_ACT_ALLOW"
    }
  ]
}

Das Profil auf dem Node platzieren und im Pod referenzieren:

# Profil auf den Node kopieren
sudo mkdir -p /var/lib/kubelet/seccomp/profiles
sudo cp nginx-strict.json /var/lib/kubelet/seccomp/profiles/nginx-strict.json
# Pod mit Custom Seccomp-Profil
apiVersion: v1
kind: Pod
metadata:
  name: nginx-strict
spec:
  securityContext:
    seccompProfile:
      type: Localhost
      localhostProfile: profiles/nginx-strict.json
  containers:
    - name: nginx
      image: nginx:1.27
      securityContext:
        allowPrivilegeEscalation: false
        capabilities:
          drop: ["ALL"]

Wichtig: Der localhostProfile-Pfad ist relativ zu /var/lib/kubelet/seccomp/. Schreibt keinen absoluten Pfad.


AppArmor: Mandatory Access Control

AppArmor beschränkt den Zugriff eines Prozesses auf Dateien, Netzwerk und andere Ressourcen. Anders als Seccomp arbeitet AppArmor nicht auf Syscall-Ebene, sondern auf Ressourcen-Ebene.

AppArmor-Status prüfen

# AppArmor-Status auf dem Node
sudo aa-status
# apparmor module is loaded.
# 47 profiles are loaded.
# 47 profiles are in enforce mode.

# Kubernetes-relevante Profile anzeigen
sudo aa-status | grep k8s

Eigenes AppArmor-Profil

Ein restriktives Profil für einen Webserver-Container:

# /etc/apparmor.d/k8s-nginx-restricted
cat << 'PROFILE' | sudo tee /etc/apparmor.d/k8s-nginx-restricted
#include <tunables/global>

profile k8s-nginx-restricted flags=(attach_disconnected,mediate_deleted) {
  #include <abstractions/base>

  # Netzwerk: nur TCP und UDP
  network inet tcp,
  network inet udp,

  # Nginx-spezifische Pfade
  /usr/sbin/nginx mr,
  /etc/nginx/** r,
  /var/log/nginx/** rw,
  /var/cache/nginx/** rw,
  /run/nginx.pid rw,

  # Sensible Dateien blockieren
  deny /etc/shadow r,
  deny /etc/passwd w,
  deny /proc/sys/** w,
  deny /sys/** w,

  # Alles andere: lesen erlaubt
  /** r,
}
PROFILE

# Profil laden
sudo apparmor_parser -r /etc/apparmor.d/k8s-nginx-restricted

Pod mit AppArmor-Profil

Seit Kubernetes 1.30 gibt es ein dediziertes appArmorProfile-Feld im securityContext. Für ältere Versionen verwendet Annotations.

# Kubernetes 1.30+: securityContext-Feld
apiVersion: v1
kind: Pod
metadata:
  name: nginx-apparmor
spec:
  containers:
    - name: nginx
      image: nginx:1.27
      securityContext:
        appArmorProfile:
          type: Localhost
          localhostProfile: k8s-nginx-restricted

---
# Kubernetes < 1.30: Annotation
apiVersion: v1
kind: Pod
metadata:
  name: nginx-apparmor-legacy
  annotations:
    container.apparmor.security.beta.kubernetes.io/nginx: localhost/k8s-nginx-restricted
spec:
  containers:
    - name: nginx
      image: nginx:1.27

Achtung: Der Container-Name im Annotations-Key muss exakt mit dem Container-Namen in der Pod-Spec übereinstimmen.


Seccomp und AppArmor kombinieren

Beide Mechanismen ergänzen sich. Seccomp filtert Syscalls, AppArmor beschränkt Ressourcenzugriffe. In Produktionsumgebungen aktiviert ihr am besten beides:

apiVersion: v1
kind: Pod
metadata:
  name: hardened-app
spec:
  securityContext:
    runAsNonRoot: true
    seccompProfile:
      type: RuntimeDefault
  containers:
    - name: app
      image: myapp:v2.1
      securityContext:
        allowPrivilegeEscalation: false
        capabilities:
          drop: ["ALL"]
        readOnlyRootFilesystem: true
        appArmorProfile:
          type: Localhost
          localhostProfile: k8s-nginx-restricted
      volumeMounts:
        - name: tmp
          mountPath: /tmp
  volumes:
    - name: tmp
      emptyDir: {}

Debugging: Wenn Profile zu restriktiv sind

Container starten nicht oder crashen nach dem Start? Wahrscheinlich blockiert ein Profil einen benötigten Syscall oder Dateizugriff.

# Seccomp-Verstösse im Kernel-Log finden
sudo dmesg | grep -i seccomp
# audit: type=1326 audit(...): auid=4294967295 uid=0 gid=0
#   ses=4294967295 pid=12345 comm="nginx" exe="/usr/sbin/nginx"
#   sig=0 arch=c000003e syscall=262 ...

# Syscall-Nummer auflösen
ausyscall --dump | grep 262
# 262    newfstatat

# AppArmor-Verstösse finden
sudo dmesg | grep -i apparmor
# apparmor="DENIED" operation="open" profile="k8s-nginx-restricted"
#   name="/etc/resolv.conf" pid=12345

Der Workflow fuer Custom-Profile: Startet im complain-Modus (AppArmor) oder mit SCMP_ACT_LOG (Seccomp), beobachtet die Logs, und passt das Profil iterativ an.

# AppArmor-Profil in complain-Modus setzen (loggt statt blockiert)
sudo aa-complain /etc/apparmor.d/k8s-nginx-restricted

# Nach dem Testen zurück in enforce-Modus
sudo aa-enforce /etc/apparmor.d/k8s-nginx-restricted

FAQ

Brauche ich Seccomp wenn ich schon AppArmor nutze?

Ja. AppArmor und Seccomp arbeiten auf verschiedenen Ebenen. AppArmor kontrolliert Dateizugriffe und Netzwerk, Seccomp filtert direkt Syscalls. Ein Angreifer der einen Kernel-Exploit nutzt, umgeht möglicherweise AppArmor -- Seccomp blockiert den dafür nötigen Syscall trotzdem.

Funktioniert AppArmor auf allen Kubernetes-Nodes?

Nein. AppArmor ist spezifisch für Debian-basierte Distributionen (Ubuntu, Debian). RHEL, CentOS und Fedora verwenden SELinux statt AppArmor. Prüft mit sudo aa-status ob AppArmor verfügbar ist.

Was ist der Unterschied zwischen RuntimeDefault und Unconfined?

RuntimeDefault verwendet das von der Container-Runtime (containerd, CRI-O) bereitgestellte Seccomp-Profil und blockiert ca. 40-60 gefährliche Syscalls. Unconfined deaktiviert Seccomp komplett -- jeder Syscall ist erlaubt. Seit Kubernetes 1.27 könnt ihr RuntimeDefault clusterweit als Standard setzen.

Wie teste ich ob mein Custom-Profil funktioniert?

Deployt eure Anwendung mit dem Profil in einem Staging-Namespace. Führt alle normalen Operationen durch und prüft dmesg auf DENIED-Einträge. Für Seccomp nutzt strace um die benötigten Syscalls eurer Anwendung zu ermitteln.


Fazit

Seccomp und AppArmor sind keine optionalen Extras -- sie sind grundlegende Sicherheitsmechanismen für Container auf Kernel-Ebene. RuntimeDefault als Seccomp-Profil sollte in jedem Cluster der Mindeststandard sein. Wer sicherheitskritische Workloads betreibt, erstellt zusätzlich Custom-Profile für beide Mechanismen und kombiniert sie mit Pod Security Admission für eine durchgängige Defense-in-Depth-Strategie.


Verwandte Artikel

Kubernetes-Security & Compliance?

Security-Audits, Penetration Tests und Compliance-Beratung für Ihre Container-Infrastruktur. BSI-Grundschutz, DSGVO, ISO 27001.

Kubernetes-Beratung gesucht?

Wir helfen deutschen Unternehmen bei der Kubernetes-Implementierung, Migration und Optimierung. DSGVO-konform und praxiserprobt.

📖 Verwandte Artikel

Weitere interessante Beiträge zu ähnlichen Themen