· Kubernetes · 8 Min. Lesezeit
PodSecurity restricted: der Namespace, in dem keine Pods starten
Der enforce-Modus der Pod Security Admission prüft keine Deployments, sondern nur die daraus entstehenden Pods. Das Deployment wird fehlerfrei angelegt und trotzdem startet nichts. Wie Sie Namespaces sauber auf restricted heben: Trockenlauf, Version-Pin, securityContext und in GitOps verankerte Labels.

Pod Security Admission ist laut Doku zur Pod Security Admission seit Kubernetes v1.25 stabil und standardmäßig aktiviert. Sie brauchen nichts zu installieren, sondern eine Entscheidung darüber, welches Profil in welchem Namespace gilt, und einen Migrationspfad, der bestehende Workloads nicht überrascht.
Eine Eigenschaft bestimmt dabei den gesamten Ablauf: enforce wird nicht auf Workload-Ressourcen angewendet, sondern ausschließlich auf die resultierenden Pod-Objekte. Ein Deployment passiert die Admission ungeprüft, die Ablehnung greift erst beim Pod. audit und warn greifen dagegen auch auf die Workload-Ressource. Das ist der Grund, warum Sie warn immer mitsetzen.
Welches Profil wofür
Die Profile sind kumulativ, restricted enthält alles aus baseline.
| Profil | Charakter laut Doku | Praktische Folge |
|---|---|---|
privileged | Uneingeschränkt, erlaubt bekannte Rechteausweitungen | Eingebauter Default, für Infrastruktur-Namespaces |
baseline | Verhindert bekannte Rechteausweitungen | Ein Pod ohne securityContext läuft weiterhin |
restricted | Folgt den Best Practices zur Pod-Härtung | Jeder Workload muss seinen securityContext mitliefern |
Dazu drei Modi. enforce weist den Pod ab, audit erzeugt eine Annotation im Audit-Log, warn gibt dem Aufrufer eine Warnung zurück. Das Labelformat ist pod-security.kubernetes.io/<MODE>: <LEVEL>, dazu optional je Modus pod-security.kubernetes.io/<MODE>-version: <VERSION> mit einer gültigen Minor-Version oder latest.
restricted verlangt laut Feldtabelle der Pod Security Standards im Wesentlichen fünf Dinge. Achten Sie auf die Wildcards, die Anforderungen gelten für jeden Container aller drei Typen. Erfüllt auch nur einer davon sie nicht, scheitert die Validierung des gesamten Pods.
allowPrivilegeEscalation: falsefürspec.containers[*],spec.initContainers[*]undspec.ephemeralContainers[*].runAsNonRoot: true, auf Pod-Ebene oder je Container. Seit v1.23 gilt zusätzlich die Kontrolle Running as Non-root user,runAsUserdarf also nicht0sein.- Ein seccomp-Profil vom Typ
RuntimeDefaultoderLocalhost(seit v1.19), nichtUnconfined. - Seit v1.22 muss jeder Container
capabilities.dropsetzen und darinALLenthalten. Nachladen ist nur fürNET_BIND_SERVICEerlaubt. - Acht erlaubte Volume-Typen:
configMap,csi,downwardAPI,emptyDir,ephemeral,persistentVolumeClaim,projected,secret.hostPathundnfsfallen heraus.
Seit v1.25 wertet restricted zudem pod.spec.os.name aus. Rechteausweitung, seccomp und Capabilities werden nur verlangt, wenn dieser Wert nicht windows ist.
Schritt 1: Bestandsaufnahme
Zuerst stellen Sie fest, welche Namespaces kein enforce-Label tragen. Die Anleitung zu den Namespace-Labels nennt dafür einen Selektor auf das nicht vorhandene Label:
kubectl get namespaces --selector='!pod-security.kubernetes.io/enforce'Prüfung: Eine leere Ausgabe bedeutet, dass jeder Namespace explizit gelabelt ist. Jeder gelistete Namespace fällt auf den Cluster-Default zurück, und der ist ohne eigene Konfiguration privileged. Den Ist-Stand aller Labels zeigt kubectl get ns --show-labels.
Schritt 2: Erst beobachten, nichts blockieren
Setzen Sie clusterweit nur audit und warn. Diese Modi lehnen nichts ab, greifen aber auf Workload-Ressourcen und machen sichtbar, was später scheitern würde:
kubectl label --overwrite ns --all \
pod-security.kubernetes.io/audit=baseline \
pod-security.kubernetes.io/warn=baselinePrüfung: Rollen Sie ein bestehendes Deployment erneut aus und lesen Sie die Warnungen beim apply.
Schritt 3: Trockenlauf gegen die laufenden Pods
Wird ein enforce-Label gesetzt oder geändert, prüft das Plugin jeden Pod des Namespace gegen die neue Richtlinie und gibt die Verstöße als Warnungen zurück. Mit --dry-run=server bekommen Sie diese Liste, ohne das Label zu setzen:
kubectl label --dry-run=server --overwrite ns --all \
pod-security.kubernetes.io/enforce=restrictedPrüfung: Die Ausgabe enthält je Namespace die Pods, die unter dem neuen Level nicht mehr zulässig wären. Solange dort Einträge stehen, gehören diese Workloads in Schritt 4, nicht in eine Ausnahme. Denselben Lauf fahren Sie vorher gegen enforce=baseline.
Schritt 4: Workloads restricted-fähig machen
Das folgende Manifest ist gegen die obige Feldliste zusammengesetzt. capabilities existiert nur auf Container-Ebene und muss deshalb je Container wiederholt werden, auch im initContainer.
apiVersion: apps/v1
kind: Deployment
metadata:
name: abrechnung-api
namespace: abrechnung-prod
spec:
replicas: 2
selector:
matchLabels: { app: abrechnung-api }
template:
metadata:
labels: { app: abrechnung-api }
spec:
securityContext:
runAsNonRoot: true
runAsUser: 10001
seccompProfile:
type: RuntimeDefault
initContainers:
- name: schema-migration
image: registry.lab.rz.jabi-it.de/abrechnung/migrate:1.4.0
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
containers:
- name: api
image: registry.lab.rz.jabi-it.de/abrechnung/api:1.4.0
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
volumeMounts:
- name: cache
mountPath: /tmp
volumes:
- name: cache
emptyDir: {}Prüfung: Kontrollieren Sie securityContext und Volume-Typen, bevor Sie das Level anziehen. Die tatsächliche Kennung im Container zeigt kubectl exec <name> -n <ns> -- id.
kubectl get pod <name> -n <namespace> \
-o jsonpath='{.spec.initContainers[*].securityContext}{"\n"}{.spec.containers[*].securityContext}{"\n"}{.spec.volumes[*]}{"\n"}'Schritt 5: enforce setzen und die Version pinnen
Jetzt wird scharf geschaltet. Die Doku pinnt in ihren Beispielen auf eine Minor-Version, statt implizit latest zu verwenden:
kubectl label --overwrite ns abrechnung-prod \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/enforce-version=v1.36Für Namespaces, die heute nur baseline erreichen, empfiehlt die Doku ein Zwischenmuster: enforce bleibt auf baseline, audit und warn stehen bereits auf restricted. Der Namespace ist abgesichert und meldet laufend, was noch fehlt.
Prüfung: Die Labels gegenlesen, danach ein echter Rollout. Er muss ohne Warnungen durchlaufen.
kubectl get ns abrechnung-prod -o jsonpath='{.metadata.labels}{"\n"}'Schritt 6: Den Cluster-Default festlegen
Der eingebaute Default lautet enforce: privileged. Ein neu angelegter Namespace ohne Label ist damit nicht geschützt. Die Anleitung zum Admission-Controller ändert das über eine AdmissionConfiguration, die dem kube-apiserver über --admission-control-config-file übergeben wird:
apiVersion: apiserver.config.k8s.io/v1
kind: AdmissionConfiguration
plugins:
- name: PodSecurity
configuration:
apiVersion: pod-security.admission.config.k8s.io/v1
kind: PodSecurityConfiguration
defaults:
enforce: "baseline"
enforce-version: "latest"
audit: "restricted"
warn: "restricted"Prüfung: Legen Sie testweise einen Namespace ohne Labels an und rollen Sie dort einen Pod aus, der gegen das neue Default-Level verstößt. Er muss abgewiesen werden. Die Liste aus Schritt 1 enthält danach nur noch Namespaces, bei denen Sie den Default bewusst greifen lassen.
Schritt 7: Labels in GitOps verankern
Erzeugt Argo CD die Namespaces, gehören die PodSecurity-Labels in die Application. Laut Argo-CD-Doku zu den Sync-Optionen wirkt managedNamespaceMetadata nur mit gesetzter Sync-Option CreateNamespace=true. Ein eigenes Namespace-Manifest in derselben Application hat Vorrang und überschreibt die Werte, liefern Sie also keines mit.
spec:
syncPolicy:
managedNamespaceMetadata:
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: v1.36
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted
syncOptions:
- CreateNamespace=truePrüfung: Nach dem Sync die Labels am Namespace gegenlesen, mit dem Befehl aus Schritt 5. Wie eine Application aufgebaut ist, steht im Beitrag Argo CD deployen und die erste App ausrollen.
Die drei folgenreichsten Stolpersteine
1. Das Deployment wird angelegt, es entsteht kein Pod
Symptom: kubectl apply des Deployments läuft fehlerfrei durch. kubectl get deploy zeigt dauerhaft 0/2, ohne dass am Deployment ein Grund steht.
Ursache: enforce greift nur auf die resultierenden Pod-Objekte. Abgewiesen wird erst, wenn der ReplicaSet-Controller den Pod erzeugen will. Der Status des Deployments ist dafür der falsche Ort.
Prüfung:
kubectl describe rs -n <namespace>kubectl get events -n <namespace> --sort-by=.lastTimestampLösung: Setzen Sie zu enforce immer auch warn und audit auf demselben Level. warn greift auf die Workload-Ressource, dadurch sehen Sie den Verstoß schon beim apply.
2. Der ungelabelte Namespace ist nicht streng, sondern offen
Symptom: Ein frisch angelegter Namespace ohne PodSecurity-Label verhält sich nicht restriktiv, unsichere Pods laufen dort durch.
Ursache: Der eingebaute Default der PodSecurityConfiguration ist privileged. Ohne eigene AdmissionConfiguration fällt ein ungelabelter Namespace auf diese offenste Stufe zurück, nicht auf eine strenge.
Prüfung:
kubectl get namespaces --selector='!pod-security.kubernetes.io/enforce'Lösung: Entweder den Default über die AdmissionConfiguration aus Schritt 6 anheben, oder jeden Namespace explizit labeln. Nur die zweite Variante funktioniert auch in Clustern, in denen Sie die apiserver-Konfiguration nicht anfassen können.
3. Der Pod erfüllt scheinbar alles und wird trotzdem abgewiesen
Symptom: Der Anwendungscontainer trägt einen vollständigen securityContext, der Pod wird dennoch nicht zugelassen.
Ursache: Die Kontrollen gelten mit Wildcard für containers[*], initContainers[*] und ephemeralContainers[*]. Ein vergessener initContainer oder ein injizierter Sidecar reißt den ganzen Pod mit. Dazu zwei leicht übersehene Punkte: runAsUser darf nicht 0 sein, und spec.volumes[*] ist auf acht Typen begrenzt.
Prüfung: derselbe Befehl wie in Schritt 4.
Lösung: Setzen Sie runAsNonRoot und seccompProfile auf Pod-Ebene, damit sie für alle Container-Listen gelten. capabilities.drop: ["ALL"] muss je Container stehen. hostPath ersetzen Sie durch emptyDir, configMap, secret, projected oder persistentVolumeClaim.
Die übrigen Fälle
Diese drei Punkte blockieren, bevor überhaupt ein Pod läuft. Sie sind nicht die einzigen. Die vollständige Sammlung der Stolpersteine steht im Beitrag PodSecurity restricted: die vollständige Sammlung der Stolpersteine, im gleichen Schema aus Symptom, Ursache, Prüfung und Lösung. Dort kommen Pods dazu, die nach einem Cluster-Upgrade unverändert abgewiesen werden, die fehlende Mutation, die RBAC-Umgehung über das Namespace-Objekt, zu breite Exemptions, Argo CD und Windows-Workloads. Wenn Sie eine bestehende Umgebung übernehmen, arbeiten Sie diese Liste vor dem ersten enforce-Label durch.
Grenzen
Pod Security Admission ist bewusst schmal gebaut, und die Doku benennt die Grenzen selbst. Es gibt genau drei Stufen. Zwischen baseline und privileged existiert kein Zwischenprofil, weil Rechte oberhalb von baseline laut Doku typischerweise sehr anwendungsspezifisch sind. Die Granularität endet am Namespace, unterschiedliche Richtlinien je ServiceAccount wie unter PodSecurityPolicy gibt es nicht. Und der Controller mutiert nicht. Er verschiebt Arbeit in die Workloads, und das ist die eigentliche Kostenstelle einer Härtung auf restricted.
Für sandboxed Workloads spricht die Doku kein Profil aus. Die FAQ der Pod Security Standards nennt zwei Gründe: Es gibt keine standardisierte API, um einen Pod als sandboxed zu kennzeichnen, und keine gemeinsame Definition eines sandboxed Runtime. Weil der Schutz stark von der Methode der Sandbox abhängt, wird kein einzelnes Profil für alle empfohlen. Dieselbe Stelle hält fest, dass die Notwendigkeit, privilegierte Rechte einzuschränken, geringer ausfällt, wenn der Workload vom darunterliegenden Kernel isoliert ist.
Bestimmte Pod-Updates sind zudem von der Prüfung ausgenommen: Metadaten-Updates mit Ausnahme von Änderungen an den seccomp- und AppArmor-Annotationen sowie gültige Updates an .spec.activeDeadlineSeconds und .spec.tolerations. Ein laufender Pod wird also nicht bei jeder Änderung erneut vollständig geprüft.
Ich halte die Namespace-Labels trotzdem für den richtigen Einstieg in die Cluster-Härtung, gerade weil sie so wenig können. Sie sind eingebaut, sie brauchen keinen zusätzlichen Webhook im kritischen Pfad, und sie zwingen die Workloads zu einem securityContext, der ohnehin dorthin gehört.
Sie wollen einen Cluster, der sicher ist und trotzdem zuverlässig ausrollt? Ich härte Kubernetes-Umgebungen so, dass die Absicherung greift, ohne dass Deployments still liegen bleiben. Mehr unter IT-Sicherheit & ISMS, das Erstgespräch ist unverbindlich.

Alex Jabi
Ich betreue IT, Informationssicherheit und Datenschutz für KMU und Praxen an der Bergstraße und im Odenwald, persönlich, dokumentiert und ohne Cloud-Zwang.
Mehr über mich

