Inhoudsopgave
- Doel
- Vereisten
- De PSP-verwijderingstijdlijn
- Drie profielen: privileged, baseline, restricted
- Drie handhavingsmodi: enforce, audit, warn
- Stap 1: huidige PSPs inventariseren
- Stap 2: namespaces labelen met audit en warn
- Stap 3: workload-violations fixen
- Stap 4: enforce mode inschakelen per namespace
- Stap 5: PSP-objecten en RBAC-bindings verwijderen
- Stap 6: cluster-level defaults instellen
- Stap 7: automatisering bijwerken voor nieuwe namespaces
- Eindresultaat verifiëren
- Exemptions en de risico's ervan
- Wanneer PSA niet genoeg is
- Veelvoorkomende problemen
- Wanneer escaleren
Doel
Na het doorlopen van deze gids heb je je cluster gemigreerd van PodSecurityPolicy naar Pod Security Admission, met gelabelde namespaces, een cluster-level vangnet, en geen gat in de pod security enforcement.
Vereisten
- Een Kubernetes-cluster op versie 1.25 of nieuwer (PSA is GA sinds 1.25)
kubectl-toegang met rechten om namespaces te labelen, PSPs te bekijken en RBAC-bindings te beheren- Draai je nog op Kubernetes 1.22 tot en met 1.24, dan is PSA beschikbaar als beta admission plugin; de labelsyntax is identiek maar de AdmissionConfiguration API-versie verschilt (zie stap 6)
- Bekendheid met RBAC, want namespace-labelpermissies bepalen wie de enforcement kan verzwakken
De PSP-verwijderingstijdlijn
PodSecurityPolicy werd deprecated in Kubernetes 1.21 (april 2021) en volledig verwijderd in 1.25 (augustus 2022). Dat was niet zomaar een labeltje. PSP had fundamentele ontwerpfouten die niet opgelost konden worden zonder breaking changes: een verwarrend toepassingsmechanisme waardoor je onbedoeld te brede rechten kon verlenen, geen audit-modus voor veilige uitrol, en een mutatie-eigenschap die het onmogelijk maakte om te achterhalen wat er precies met een pod spec was gebeurd.
Wanneer PSP verdwijnt (door verwijdering of een clusterupgrade naar 1.25), heeft elke namespace zonder PSA-labels helemaal geen pod security enforcement meer. Pods kunnen als root draaien, hostpaden mounten en willekeurige capabilities aanvragen. Dit is het gevaarlijkste moment in de hele migratie.
PSP-verwijdering raakt securityContext niet. Het securityContext-veld in pod specs is de security-configuratie op podniveau; het staat los van het admission-beleid dat het valideert.
Drie profielen: privileged, baseline, restricted
Pod Security Standards definiëren drie profielen die cumulatief strenger worden:
Privileged is volledig onbeperkt. Geen securitybeperkingen. Gebruik dit voor infrastructuur-namespaces (kube-system, monitoring, CNI-plugins) waar workloads legitiem hosttoegang nodig hebben.
Baseline blokkeert bekende privilege-escalatiepaden terwijl het adopteerbaar blijft voor de meeste applicatieworkloads. Het verbiedt hostNetwork, hostPID, hostIPC, privileged containers, hostPath-volumes, en beperkt capabilities tot een veilige subset (NET_BIND_SERVICE, CHOWN, SETUID, SETGID en andere). Seccomp mag niet Unconfined zijn. SELinux-types zijn beperkt tot container_t, container_init_t en container_kvm_t.
Restricted voegt alles van Baseline toe plus: runAsNonRoot moet true zijn, allowPrivilegeEscalation moet false zijn, capabilities moeten ALL droppen (alleen NET_BIND_SERVICE mag weer worden toegevoegd), en een seccompProfile van RuntimeDefault of Localhost is verplicht.
Elke container in de pod moet voldoen. Als een enkele init-container de validatie niet haalt, wordt de hele pod geweigerd.
Welk profiel voor welke namespace
| Type namespace | Aanbevolen profiel |
|---|---|
| Applicatieworkloads (gehard) | restricted |
| Applicatieworkloads (standaard) | baseline |
| Infrastructuur (CNI, logging, monitoring, ingress) | privileged |
kube-system, kube-public, kube-node-lease |
Exempt via AdmissionConfiguration |
Het Restricted-profiel faalt voor het overgrote deel van de workloads die niet expliciet gehard zijn. Standaard nginx-images draaien als root. Calico, Flannel en Cilium hebben NET_ADMIN, SYS_ADMIN en host-namespaces nodig. Logshippers hebben hostPath-volumes nodig om /var/log te lezen. Begin enforcement op Baseline voor applicatie-namespaces en verscherp naar Restricted nadat je de securityContext-velden van je workloads hebt gefixed.
Drie handhavingsmodi: enforce, audit, warn
PSA heeft drie modi die je onafhankelijk per namespace instelt:
| Modus | Blokkeert pods? | Wat het rapporteert | Geldt voor |
|---|---|---|---|
enforce |
Ja | Rejection-fout bij pod-creatie | Alleen pod-objecten |
audit |
Nee | Annotatie in Kubernetes audit log | Workload-resources + pods |
warn |
Nee | Warning:-melding in kubectl-output |
Workload-resources + pods |
De asymmetrie tussen enforce en de andere twee modi is het meest verkeerd begrepen gedrag. enforce evalueert alleen het resulterende Pod-object, niet de Deployment of StatefulSet die het aanmaakte. Je kunt gewoon kubectl apply doen op een Deployment met een violating pod template en het lukt. De blokkade komt pas wanneer de controller de daadwerkelijke Pod aanmaakt. De Deployment staat dan in je cluster terwijl het ReplicaSet pods in een loop faalt.
audit en warn evalueren workload-resources (Deployments, Jobs, StatefulSets) op templateniveau en vangen violations dus al op voordat pods worden aangemaakt. Daarom is audit en warn eerst draaien niet optioneel; het is de enige manier om template-level problemen te vangen voordat enforce pods blokkeert.
warn blokkeert niks. Het stuurt een Warning:-prefix naar de kubectl-client. Operators zien soms warn-output en nemen aan dat hun workloads beschermd zijn. Dat zijn ze pas wanneer enforce is ingeschakeld.
Stap 1: huidige PSPs inventariseren
Bepaal voordat je migreert wat je PSPs daadwerkelijk afdwingen. PSA kan niet alle PSP-functionaliteit repliceren. PSA is namelijk non-mutating: het valideert maar wijzigt nooit pod specs. Als je leunde op PSP om defaults in te stellen (zoals defaultAllowPrivilegeEscalation of runAsGroup: MustRunAs), heb je een mutating admission webhook of een policy engine zoals Kyverno nodig als aanvulling.
Bekijk je huidige PSPs en identificeer muterende velden:
# Alle PSPs bekijken (werkt alleen voor 1.25)
kubectl get psp
# Exporteren voor analyse
kubectl get psp -o yaml > current-psps.yaml
Verwijder deze mutatie-velden uit je analyse (PSA heeft geen equivalent):
defaultAllowPrivilegeEscalationdefaultAddCapabilitiesruntimeClass.defaultRuntimeClassName- Seccomp- en AppArmor-default-profielannotaties
Let ook op velden die PSA's drie vaste profielen niet valideren: allowedHostPaths (padspecifieke restricties), allowedFlexVolumes, allowedCSIDrivers, forbiddenSysctls, runAsGroup (MustRunAs-strategie), supplementalGroups en fsGroup. Als je security-posture van deze velden afhangt, is PSA alleen niet genoeg.
Stap 2: namespaces labelen met audit en warn
Pas niet-blokkerende labels toe op alle namespaces om violations te observeren zonder workloads te verstoren:
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/audit-version: latest # vooruitkijkend
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/warn-version: latest
Toepassen op alle namespaces tegelijk:
# Alle niet-systeem namespaces labelen met audit + warn op restricted-niveau
for ns in $(kubectl get ns -o jsonpath='{.items[*].metadata.name}'); do
kubectl label --overwrite ns "$ns" \
pod-security.kubernetes.io/audit=restricted \
pod-security.kubernetes.io/audit-version=latest \
pod-security.kubernetes.io/warn=restricted \
pod-security.kubernetes.io/warn-version=latest
done
Verwachte output: elke kubectl label-opdracht print namespace/<naam> labeled.
Stap 3: workload-violations fixen
Rol je workloads opnieuw uit om evaluatie te triggeren tegen de nieuwe labels. Let op Warning:-meldingen in de kubectl-output:
kubectl rollout restart deployment -n production
# Verwacht: Warning: would violate PodSecurity "restricted:latest":
# allowPrivilegeEscalation != false, ...
Controleer het Kubernetes audit log op pod-security-annotaties die violations in audit-modus identificeren.
De typische fixes bestaan uit het toevoegen of aanscherpen van securityContext op zowel pod- als containerniveau:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
namespace: production
spec:
template:
spec:
securityContext:
runAsNonRoot: true # podniveau
seccompProfile:
type: RuntimeDefault # vereist door restricted
containers:
- name: web
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"] # alles droppen, dan toevoegen wat nodig is
add: ["NET_BIND_SERVICE"] # alleen als de container bindt onder 1024
Itereer totdat kubectl apply geen warnings meer produceert. Systeem-namespaces (waar CNI, logging en monitoring draaien) zullen wel warnings tonen onder het restricted-profiel. Dat is verwacht; die namespaces krijgen het privileged-profiel in de volgende stap.
Stap 4: enforce mode inschakelen per namespace
Zodra audit en warn geen violations meer tonen voor een namespace, pas je het enforce-label toe:
kubectl label --overwrite ns production \
pod-security.kubernetes.io/enforce=baseline \
pod-security.kubernetes.io/enforce-version=v1.31
Pin enforce-version op een specifieke Kubernetes-minor-versie. Dit voorkomt dat een clusterupgrade nieuwe beleidschecks introduceert die eerder conforme pods plotseling blokkeren. Gebruik latest voor audit- en warn-versies zodat je vroegtijdig zicht hebt op toekomstige vereisten.
Het aanbevolen patroon voor applicatie-namespaces die volledig gehard zijn:
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: v1.31 # vastgezet
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/audit-version: latest # vooruitkijkend
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/warn-version: latest
Voor namespaces die nog niet volledig gehard zijn: enforce baseline en audit/warn restricted. Rol namespace voor namespace uit. Zet niet alles in een keer op enforce.
Stap 5: PSP-objecten en RBAC-bindings verwijderen
Nadat PSA enforce actief is op alle namespaces, ruim je PSP-artefacten op:
# Controleer op resterende PSP RBAC-dependencies
kubectl get clusterrolebinding -o json | jq '.items[] | select(.roleRef.name | test("psp")) | .metadata.name'
kubectl get rolebinding -A -o json | jq '.items[] | select(.roleRef.name | test("psp")) | {ns: .metadata.namespace, name: .metadata.name}'
# Verwijder PSP RBAC-bindings
kubectl delete clusterrolebinding <psp-binding-naam>
kubectl delete rolebinding <psp-binding-naam> -n <namespace>
# Verwijder PSPs (alleen op clusters nog onder 1.25)
kubectl delete psp --all
Op Kubernetes 1.25+ bestaan PSP-objecten niet meer. Het risico is dat de upgrade stilletjes PSP-enforcement verwijderde terwijl je namespaces geen PSA-labels hadden. Lees je dit na een upgrade, controleer dan of je namespaces gelabeld zijn. Ongelabelde namespaces hebben geen enforcement.
Stap 6: cluster-level defaults instellen
Namespace-labels beschermen gelabelde namespaces. Nieuwe namespaces zonder labels hebben geen bescherming. Een cluster-level AdmissionConfiguration vangt dit gat op:
# /etc/kubernetes/psa-config.yaml
apiVersion: apiserver.config.k8s.io/v1 # v1 voor K8s 1.25+
kind: AdmissionConfiguration
plugins:
- name: PodSecurity
configuration:
apiVersion: pod-security.admission.config.k8s.io/v1
kind: PodSecurityConfiguration
defaults:
enforce: "baseline"
enforce-version: "latest"
audit: "restricted"
audit-version: "latest"
warn: "restricted"
warn-version: "latest"
exemptions:
usernames: []
runtimeClasses: []
namespaces:
- kube-system
- kube-public
- kube-node-lease
Verwijs naar dit bestand in de API-serverconfiguratie:
# Voor kubeadm-clusters, toevoegen aan ClusterConfiguration:
# apiServer:
# extraArgs:
# admission-control-config-file: /etc/kubernetes/psa-config.yaml
# extraVolumes:
# - name: psa-config
# hostPath: /etc/kubernetes/psa-config.yaml
# mountPath: /etc/kubernetes/psa-config.yaml
# readOnly: true
De AdmissionConfiguration API-versie hangt af van je Kubernetes-versie: v1 voor 1.25+, v1beta1 voor 1.23 tot en met 1.24, v1alpha1 voor 1.22.
Op managed clusters (EKS, GKE, AKS): alle drie de providers schakelen PSA standaard in maar zetten de cluster-level default op privileged voor alle modi. Dat betekent dat er geen restricties gelden tenzij je expliciet namespaces labelt. De AdmissionConfiguration kun je op managed clusters doorgaans niet direct aanpassen. Namespace-labels zijn dan je primaire configuratie-oppervlak.
Stap 7: automatisering bijwerken voor nieuwe namespaces
Elk namespace-provisioning-tool (Helm-charts, Kustomize-bases, Terraform-modules, CI/CD-templates) moet PSA-labels toepassen op nieuwe namespaces. Zonder automatisering draait de eerstvolgende namespace die iemand via kubectl create ns aanmaakt zonder bescherming.
Gebruik je een GitOps-tool zoals ArgoCD, voeg de labels dan toe aan je namespace-manifesten in de Git-repository. Gebruik je Terraform met de Kubernetes-provider, voeg de labels toe aan je kubernetes_namespace-resources.
Eindresultaat verifiëren
Controleer of alle namespaces enforcement-labels hebben:
kubectl get ns --show-labels | grep pod-security
Verwachte output: elke namespace toont pod-security.kubernetes.io/enforce met waarde baseline of restricted. Systeem-namespaces tonen of privileged of staan in de AdmissionConfiguration-exemptions.
Test dat enforcement daadwerkelijk violations blokkeert:
kubectl run test-privileged --image=nginx \
--overrides='{"spec":{"containers":[{"name":"nginx","image":"nginx","securityContext":{"privileged":true}}]}}' \
-n production --dry-run=server
# Verwacht: Error from server (Forbidden): ... violates PodSecurity "baseline:..."
Als de testpod wordt geaccepteerd, ontbreken de namespace-labels of staan ze op privileged.
Exemptions en de risico's ervan
Exemptions worden geconfigureerd in de AdmissionConfiguration en omzeilen alle PSA-modi (enforce, audit en warn). Er zijn drie dimensies:
- Namespaces (meestal
kube-system) - Usernames (bijvoorbeeld CI-systeemaccounts)
- RuntimeClassNames (pods die specifieke runtimeklassen gebruiken)
Exempt geen controller-service-accounts zoals system:serviceaccount:kube-system:replicaset-controller. Een controller exempten exempt in feite elke gebruiker die de bijbehorende workload-resource kan aanmaken, want de pod wordt uiteindelijk door de identiteit van de controller aangemaakt.
Iedereen met update- of patch-rechten op namespace-objecten kan PSA-labels verwijderen en daarmee enforcement uitschakelen. Vergrendel namespace-labelpermissies via RBAC of voeg een validating admission webhook toe om ongeautoriseerde labelwijzigingen te voorkomen.
Wanneer PSA niet genoeg is
PSA's drie vaste profielen dekken de meeste situaties. Je hebt een third-party policy engine nodig wanneer je het volgende vereist:
- Per-ServiceAccount beleid binnen een namespace (PSA is alleen namespace-scoped)
- Mutatie (automatisch security-defaults instellen op pods die ze niet zelf zetten)
- Beleid op niet-pod-resources (NetworkPolicies, ConfigMaps, custom resources)
- Gedetailleerde aangepaste regels die verder gaan dan de drie vaste profielen
Kyverno (CNCF Incubating) schrijft beleid in YAML en ondersteunt validatie, mutatie en generatie. OPA/Gatekeeper (CNCF Graduate) gebruikt de Rego-taal en is flexibeler maar heeft een steilere leercurve.
De aanbevolen aanpak: gebruik PSA als ingebouwde baseline (geen dependencies, altijd ingeschakeld) en voeg Kyverno of Gatekeeper toe voor regels die PSA niet kan uitdrukken. Vervang PSA niet door een policy engine; bouw erop voort.
Veelvoorkomende problemen
Pods geweigerd maar de Deployment is wel geaccepteerd. Dit is de enforce-modus-asymmetrie die hierboven beschreven staat. Controleer de ReplicaSet-events: kubectl describe rs <replicaset-naam> -n <namespace>. De events tonen de rejection op podniveau. Fix de securityContext van de pod template.
Geen warnings na het labelen. De warn- en audit-labels evalueren workloads op het moment van apply. Bestaande pods worden niet opnieuw geëvalueerd. Herstart of herinstalleer je workloads: kubectl rollout restart deployment -n <namespace>.
Workloads in kube-system breken na labelen. Systeemcomponenten hebben het privileged-profiel nodig. Label kube-system als privileged of exempt het in de AdmissionConfiguration.
Pods halen warn-modus maar falen op enforce-modus. Dit kan gebeuren wanneer de pod template en de daadwerkelijke pod verschillen (admission webhooks die sidecars injecteren, bijvoorbeeld). De geïnjecteerde sidecar kan het beleid schenden ook al haalde de template de check wel. Bekijk de daadwerkelijke pod spec, niet alleen de Deployment-template.
Wanneer escaleren
Als pods geweigerd worden en je kunt niet identificeren welk veld het beleid schendt, verzamel dan:
- De volledige foutmelding uit
kubectl describeop het ReplicaSet of de pod - De namespace-labels:
kubectl get ns <namespace> --show-labels - De pod spec:
kubectl get pod <pod-naam> -n <namespace> -o yaml - Kubernetes-versie:
kubectl version - Of er admission webhooks zijn die pod specs muteren:
kubectl get mutatingwebhookconfigurations - De cluster-level AdmissionConfiguration (als je toegang hebt tot de API-serverconfiguratie)