Kubernetes Pod Security: Privilegierte Container, Root-UIDs und wie Kyverno sie blockiert
Ein Pod als root mit hostPath-Zugriff auf das Node-Dateisystem kann einen Kubernetes-Cluster in Minuten vollständig kompromittieren. Zwei Fragen — wie schlimm ist es, und wie behebt man es richtig?
Ein Kubernetes-Pod, der als root läuft und dabei hostPath-Zugriff auf das Root-Dateisystem des Nodes hat — das klingt nach einem theoretischen Worst-Case-Szenario. In der Praxis ist es erschreckend häufig: Legacy-Deployments, schnell portierte Docker-Setups, Debug-Container die nie entfernt wurden, oder einfach fehlende Security-Policies.
Zwei Fragen, die sich jeder CTO und jeder Platform Engineer stellen sollte: Wie schlimm ist es wirklich — und wie behebt man es, ohne den laufenden Betrieb zu stören?
Wie schlimm ist es wirklich?
Die Antwort ist direkt: katastrophal. Ein Pod, der als UID 0 (root) läuft und dabei hostPath: / gemountet hat, besitzt faktisch root-Zugriff auf den Node — nicht nur im Container-Namespace, sondern auf das echte Node-Dateisystem.
Was ein Angreifer damit tun kann, der diesen Container kompromittiert:
- Kubelet-Credentials stehlen — die Kubelet-Zertifikate liegen unter
/var/lib/kubelet/pki/. Mit diesen Credentials kann sich ein Angreifer direkt gegenüber dem API-Server als Node authentifizieren und von dort alle Secrets aller Pods auf diesem Node lesen. - Container Runtime Socket missbrauchen — der containerd-Socket liegt unter
/run/containerd/containerd.sock. Wer diesen Socket lesen und schreiben kann, kann beliebige privilegierte Container starten — und damit den Kubernetes-Admission-Controller vollständig umgehen. - Kompletten Cluster-Takeover — von einem kompromittierten Node aus lässt sich über gestohlene Credentials oder Service Account Tokens ein Weg zum cluster-admin-Level finden. Zeitrahmen: Minuten, nicht Stunden.
Wichtig für DSGVO-Compliance: Ein vollständiger Cluster-Takeover bedeutet Zugriff auf alle persistenten Daten aller Namespaces — Datenbanken, Secrets, Backups. Das ist ein meldepflichtiger Datenschutzvorfall nach Art. 33 DSGVO. Die 72-Stunden-Frist zur Meldung an die Aufsichtsbehörde beginnt ab Kenntnis — nicht ab Entdeckung.
Sofortmaßnahmen: Was heute noch geändert werden muss
Wenn Sie heute einen laufenden Pod mit dieser Konfiguration haben, sind das die drei Schritte in der richtigen Reihenfolge:
1. Den hostPath-Zugriff einschränken
Wenn der Pod hostPath: / wirklich braucht (z.B. für Log-Collection), beschränken Sie den Mount auf den tatsächlich benötigten Pfad und setzen Sie ihn auf read-only:
volumes:
- name: host-logs
hostPath:
path: /var/log # nur was wirklich gebraucht wird
type: Directory
containers:
- name: log-collector
volumeMounts:
- name: host-logs
mountPath: /host/var/log
readOnly: true # kein SchreibzugriffFür die meisten Workloads gibt es keinen legitimen Grund für hostPath-Zugriff. Logging-Agents wie Promtail oder Fluentd benötigen nur /var/log/podsoder /var/log/containers — nicht das gesamte Root-Dateisystem.
2. Den SecurityContext härten
Das ist die unmittelbarste Gegenmaßnahme und lässt sich in jedem bestehenden Deployment ohne Service-Unterbrechung ergänzen:
securityContext:
runAsNonRoot: true
runAsUser: 1000 # explizite UID, kein root
runAsGroup: 1000
fsGroup: 1000
seccompProfile:
type: RuntimeDefault # aktiviert seccomp-Filter
containers:
- name: app
securityContext:
allowPrivilegeEscalation: false # kein sudo, kein setUID
privileged: false
readOnlyRootFilesystem: true # kein Schreiben ins Container-FS
capabilities:
drop:
- ALL # alle Linux capabilities entfernen
add:
- NET_BIND_SERVICE # nur hinzufügen was wirklich gebraucht wirdPraxis-Hinweis: readOnlyRootFilesystem: true bricht oft Anwendungen, die in /tmp oder /var/run schreiben. Lösung: diese Pfade explizit als emptyDir-Volume mounten. Das ist ein einmaliger Aufwand — danach ist das Dateisystem unveränderbar.
3. Das privileged-Flag entfernen
privileged: true gibt einem Container faktisch alle Linux-Capabilities und deaktiviert seccomp, AppArmor und SELinux-Einschränkungen. Es gibt wenige legitime Use Cases — CNI-Plugins, bestimmte Storage-Treiber. Für Anwendungs-Container ist es immer falsch.
Langfristige Absicherung: Pod Security Admission
Einzelne Deployments zu korrigieren ist notwendig, aber nicht ausreichend. Ohne einen Mechanismus, der neue Deployments blockiert, ist die nächste fehlerhafte Konfiguration nur eine Pipeline-Ausführung entfernt.
Kubernetes 1.25+ hat Pod Security Admission (PSA) als stabiles Feature eingebaut — es ersetzt die veralteten PodSecurityPolicies. PSA definiert drei Profile:
- privileged — keine Einschränkungen (Standard)
- baseline — verhindert die offensichtlichsten Angriffsvektoren: hostPID, hostIPC, hostNetwork, privilegierte Container, gefährliche Capabilities
- restricted — maximale Einschränkung: runAsNonRoot, keine Privilege Escalation, seccomp RuntimeDefault, alle Capabilities gedroppt
PSA wird per Namespace über Labels aktiviert:
# Namespace auf "restricted" setzen — zunächst im warn-Modus
kubectl label namespace production \
pod-security.kubernetes.io/warn=restricted \
pod-security.kubernetes.io/warn-version=latest
# Nach Validierung auf enforce umstellen
kubectl label namespace production \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/enforce-version=latestPSA ist der richtige erste Schritt — aber es hat Grenzen: Es arbeitet nur auf Namespace-Ebene und bietet keine feingranulare Policy-Kontrolle. Für Produktionsumgebungen kombinieren wir PSA immer mit Kyverno.
Kyverno: Policy-as-Code für Kubernetes
In unserer Kubernetes-Beratung setzen wir auf Kyverno als primäres Policy-Engine — weil Kyverno Policies als native Kubernetes-Ressourcen definiert, kein eigenes DSL erfordert, und sich direkt in GitOps-Workflows integriert.
Eine Kyverno-Policy die UID 0 global blockiert:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-root-user
spec:
validationFailureAction: Audit # erst Audit, dann Enforce
background: true
rules:
- name: check-runasnonroot
match:
any:
- resources:
kinds: [Pod]
validate:
message: "Pods müssen als Non-Root-User laufen (runAsNonRoot: true)"
pattern:
spec:
=(securityContext):
=(runAsNonRoot): "true"
containers:
- =(securityContext):
=(runAsNonRoot): "true"
=(runAsUser): ">0"Weitere Kyverno-Policies die wir standardmäßig deployen:
disallow-privileged-containers— blockiertprivileged: truedisallow-privilege-escalation— erzwingtallowPrivilegeEscalation: falsedisallow-host-path— blockiert alle hostPath-Volumes (mit Ausnahme-Liste für System-Namespaces)require-readonly-root-filesystem— erzwingtreadOnlyRootFilesystem: truedrop-all-capabilities— erzwingtcapabilities.drop: [ALL]
Der entscheidende Schritt: Audit-Modus vor Enforcement
Hier passiert der häufigste Fehler: Policies direkt auf Enforce setzen und sich wundern, warum plötzlich Deployments in Production fehlschlagen.
Die richtige Vorgehensweise ist eine zweistufige Rollout-Strategie:
- Woche 1–2: Audit-Modus —
validationFailureAction: Auditloggt Verstöße, blockiert aber nichts. Kyverno-Reports zeigen welche Pods betroffen sind. - Woche 3+: Enforcement — erst wenn alle bestehenden Workloads angepasst wurden, auf
Enforceumstellen.
# Kyverno PolicyReport auslesen — welche Pods verstoßen aktuell?
kubectl get policyreport -A
kubectl describe policyreport -n production
# Oder via kubectl-kyverno Plugin
kubectl kyverno test . --policy-reportVersteckte Dependencies aufdecken: Der Audit-Modus zeigt oft Überraschungen — Init-Container die als root laufen, Sidecar-Injections durch Service Meshes, oder Helm-Charts von Drittanbietern mit hartcodierten Security Contexts. Ohne Audit-Phase würden diese bei Enforcement sofort Pods blockieren.
OPA Gatekeeper als Alternative
OPA Gatekeeper ist die andere weit verbreitete Option — CNCF-graduated, basiert auf der Rego-Policy-Sprache. Wir empfehlen Kyverno für die meisten Mittelstands-Setups, weil:
- Kyverno-Policies sind YAML — kein neues DSL zu lernen
- Das Kyverno-CLI ermöglicht lokales Policy-Testing ohne Cluster
- Kyverno kann auch Ressourcen mutieren (z.B. SecurityContext automatisch ergänzen)
- Gatekeeper hat mehr operationale Komplexität bei ähnlichem Ergebnis
Für Teams mit bestehender Rego-Expertise oder komplexen Cross-Ressource-Validierungen ist Gatekeeper die bessere Wahl. Beide sind produktionsreif — die Entscheidung hängt vom Team ab, nicht von der Technologie.
Vollständige Security-Baseline für Produktions-Cluster
Pod Security ist ein Layer — nicht der einzige. Eine produktionsreife Kubernetes-Security-Baseline umfasst mindestens:
- Network Policies mit Default-Deny-All — jede erlaubte Verbindung ist explizit definiert
- RBAC nach Least Privilege — kein Wildcard-Verb, kein Cluster-Admin für Anwendungs-Service-Accounts
- External Secrets Operator — keine Secrets in Git, auch nicht base64-encoded
- Image Policy — nur signierte Images aus bekannten Registries (Cosign + Kyverno)
- Admission Controller Hardening — API-Server-Flags:
--anonymous-auth=false,--audit-log-path - CIS Benchmark — regelmäßige Überprüfung mit
kube-bench
Wenn Sie wissen möchten, wo Ihr Cluster aktuell steht: unser Kubernetes-Audit deckt genau diese Punkte ab — mit einem priorisierten Bericht und konkreten Empfehlungen für Ihren Stack.
Fazit: Security ist kein Feature, sondern ein Betriebsmodus
Ein privilegierter Pod mit hostPath-Zugriff ist kein theoretisches Risiko — es ist eine offene Tür. Die Gegenmaßnahmen sind bekannt und implementierbar: SecurityContext härten, PSA aktivieren, Kyverno-Policies deployen, im Audit-Modus starten.
Der häufigste Grund warum diese Maßnahmen fehlen, ist nicht mangelndes Wissen — sondern fehlende Zeit und fehlende Policy-Infrastruktur. Ein Cluster der täglich Deployments sieht, braucht automatisierte Kontrollen. Manuelle Reviews skalieren nicht.
Wir bauen diese Security-Layer als Teil jedes Kubernetes-Plattform-Aufbaus ein — nicht als optionales Add-on, sondern als Default.
Wir schauen uns Ihre konkrete Situation an und sagen Ihnen ehrlich, ob und wie wir helfen können.