- Authors

- Name
- Phillip Pham
- @ddppham
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:
| Typ | Beschreibung | Sicherheitsniveau |
|---|---|---|
Unconfined | Kein Filtering (Standard ohne Konfiguration) | Keines |
RuntimeDefault | Container-Runtime-Profil (containerd/CRI-O) | Gut |
Localhost | Eigenes Profil vom Node | Individuell |
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
- Pod Security Admission: PSA richtig konfigurieren
- CKS Vorbereitung: Security-Tools die du beherrschen musst
- Kubernetes Security Hardening
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
API Server Hardening: Kubernetes absichern
Kubernetes API Server härten mit Audit-Logging, Encryption at Rest, OIDC und Rate Limiting. Praxisnahe Konfiguration für sichere Cluster.
CIS Benchmark: Kubernetes-Cluster härten
CIS Kubernetes Benchmark mit kube-bench prüfen und kritische Findings an API-Server, etcd und Kubelet systematisch beheben.
BSI-Grundschutz für Kubernetes-Cluster umsetzen
BSI IT-Grundschutz für Kubernetes umsetzen: Baustein SYS.1.6 Containerisierung mit Pod Security Standards, Audit-Logging und Netzwerksegmentierung.
etcd Encryption at Rest: Kubernetes-Secrets verschlüsseln
Kubernetes-Secrets liegen standardmäßig unverschlüsselt in etcd. So aktivieren Sie Encryption at Rest mit EncryptionConfiguration und rotieren Schlüssel sicher.
Pod Security Admission: PSA richtig konfigurieren
Pod Security Admission ersetzt PodSecurityPolicy seit Kubernetes 1.25. So konfiguriert ihr PSA-Profile und Enforcement-Modi für eure Namespaces.