Pod Security Admission: labels, levels, and the manifest that gets rejected
PSP is dead
PodSecurityPolicy was removed in 1.25. Anything you read that tells you to create
a PSP is wrong. The replacement is built in: Pod Security Admission, driven
entirely by namespace labels, enforcing the Pod Security Standards.
Three levels
- privileged — no restrictions (system namespaces)
- baseline — blocks known privilege escalations: hostNetwork/hostPID/hostIPC, privileged, hostPath volumes, added capabilities beyond a small allow-list,
sysctlsoutside the safe set,procMount, seccompUnconfined - restricted — everything in baseline plus: non-root required,
allowPrivilegeEscalation: false, all capabilities dropped, seccompRuntimeDefaultorLocalhost, no host ports
Four labels
apiVersion: v1
kind: Namespace
metadata:
name: prod
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: v1.35
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted
enforce= reject non-compliant Pods (403)audit= allow, record in the audit logwarn= allow, return a warning to the client (kubectlprints it)-version= which standard version to apply (latestby default)
enforce: baseline + warn: restricted is the sane migration setting: nothing
breaks, and you see what would break.
The minimal Pod that passes restricted
apiVersion: v1
kind: Pod
metadata: {name: ok, namespace: prod}
spec:
securityContext:
runAsNonRoot: true
runAsUser: 2000
runAsGroup: 3000
seccompProfile: {type: RuntimeDefault}
containers:
- name: ok
image: registry.k8s.io/pause:3.10
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
Memorise this. It is the answer to half the PSA questions, and you can paste it
faster than you can reason about it.
Reading a rejection
Error from server (Forbidden): error when creating "pod.yaml": pods "bad" is
forbidden: violates PodSecurity "restricted:latest":
allowPrivilegeEscalation != false (container "bad" must set
securityContext.allowPrivilegeEscalation=false),
unrestricted capabilities (container "bad" must set securityContext.capabilities.drop=["ALL"]),
runAsNonRoot != true (pod or container "bad" must set securityContext.runAsNonRoot=true),
seccompProfile (pod or container "bad" must set securityContext.seccompProfile.type
to "RuntimeDefault" or "Localhost")
Four lines, four fixes. The message is a checklist — read it instead of guessing.
"Which of these three manifests will be rejected, and why"
kubectl label ns prod pod-security.kubernetes.io/enforce=restricted --overwrite
kubectl apply -f one.yaml --dry-run=server # the fastest way to find out
--dry-run=server runs the real admission chain without writing. Use it beforeapply whenever PSA is involved; it costs one second and tells you exactly which
field is wrong.
Exempting a namespace (and when not to)
The API server flags:
--admission-control-config-file=/etc/kubernetes/psa/config.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: AdmissionConfiguration
plugins:
- name: PodSecurity
configuration:
defaults:
enforce: baseline
exemptions:
namespaces: ["kube-system", "monitoring"]
The exam task is nearly always "label the namespace", not "configure exemptions".
Know that exemptions exist so you do not try to label kube-system.