The admission control you get without installing anything: three named profiles enforced by the built-in Pod Security admission controller, driven entirely by namespace labels. Less expressive than Kyverno or Gatekeeper, and that is its virtue.
make up secOrientation
Know exactly what each profile means and the label grammar, and these become the fastest points on the paper. PSS replaced PodSecurityPolicy, which was removed; if a scenario mentions PSP, the expected answer is "removed; use PSS, or a policy engine for anything PSS cannot express".
Profiles and modes
| Profile | Blocks / requires | Use for |
|---|---|---|
| privileged | nothing: unrestricted | system namespaces, CNI, storage drivers |
| baseline | blocks the known-bad: privileged containers, hostNetwork/hostPID/hostIPC, hostPath volumes, added capabilities beyond a safe list, unconfined seccomp/AppArmor, host ports | the sane tenant default |
| restricted | demands actively-good: runAsNonRoot, allowPrivilegeEscalation: false, all capabilities dropped (ALL, NET_BIND_SERVICE permitted back), seccompProfile: RuntimeDefault, no hostPath, restricted volume types | anything you can make comply |
The one-liner: baseline stops you being dangerous, restricted forces you to be safe.
The modes are set independently per namespace: enforce rejects at admission, audit annotates the audit log, warn prints warnings to the client. Label grammar:
pod-security.kubernetes.io/enforce: baseline
pod-security.kubernetes.io/enforce-version: v1.31 # pin, or "latest"
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/audit: restrictedVersion pinning matters more than it looks: profiles gain checks across releases, so latest means an upgrade can start rejecting pods that were fine yesterday. Pinning trades that surprise for a deliberate migration.
Build a pod spec and watch the three profiles decide:
team-a runs enforce baseline, warn and audit restricted: nothing overtly dangerous gets in, and every restricted violation is visible to both the user (a warning in their terminal) and the platform (the audit log) before anyone tightens the screw. That staged posture mirrors the Audit→Enforce choreography from 5.2; the pattern generalises to every control in this domain.
Two operational facts that decide troubleshooting tasks
- PSS evaluates pods. A Deployment whose template violates the profile is created successfully and then fails to make pods, with the evidence on the ReplicaSet, two levels below where you were looking. Same indirection as quota (1.4). Recognising the pattern is worth more than any single profile detail.
- Enforcement is admission-time. Tightening a namespace label does not evict existing violators; they run until their next write. Which is why the preview command exists:
kubectl label --dry-run=server --overwrite ns <ns> pod-security.kubernetes.io/enforce=restrictedThe API server evaluates every existing pod against the proposed profile and warns about each one that would break, without changing anything. It is the single most useful PSS command to know exists, and it turns a risky tightening into a planned migration.
What a compliant pod looks like
securityContext: # pod level
runAsNonRoot: true
runAsUser: 1000
seccompProfile: { type: RuntimeDefault }
containers:
- name: app
securityContext: # container level
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true # not required by restricted, but good practice
capabilities: { drop: ["ALL"] }The demo app's manifest (examples/demo-app/base/deployment.yaml) is a worked answer to "make this pass restricted": read its securityContext blocks line by line, because writing exactly that block from memory is a plausible task. Note the level split: runAsNonRoot and seccompProfile sit naturally at pod level, capabilities and allowPrivilegeEscalation are per container.
PSS is fixed, built in, free, and covers exactly the pod-hardening dimension. A policy engine is arbitrary, installable, and covers everything else (image registries, labels, ownership, cross-object rules) at the cost of running a webhook. The professional answer to "which should we use" is both: PSS as the always-on floor, an engine for organisation-specific rules, and the engine can also enforce PSS-like rules in namespaces where you need exceptions PSS cannot express.
Exercises
In team-a (enforce: baseline):
kubectl -n team-a run priv --image=busybox:1.37 --restart=Never \
--overrides='{"spec":{"containers":[{"name":"priv","image":"busybox:1.37","command":["sleep","300"],"securityContext":{"privileged":true}}]}}'Then run a merely-lazy pod (kubectl -n team-a run lazy --image=busybox:1.37 --restart=Never -- sleep 300) and read the warnings it prints: admitted under baseline, flagged against restricted by the warn label.
Create namespace hardened with enforce restricted. Take the lazy pod spec and make it pass: runAsNonRoot: true (busybox needs runAsUser too, e.g. 1000), allowPrivilegeEscalation: false, capabilities drop ALL, seccompProfile RuntimeDefault. Iterate against the live error messages; they name the missing control each time, which makes PSS self-documenting under exam conditions.
hardened, and your final securityContext agrees with the demo app's.In hardened, kubectl create deploy sneaky --image=busybox:1.37 -- sleep 300. The deploy is created; no pod appears. Find the rejection where it actually lives:
kubectl -n hardened get deploy sneaky # READY 0/1, no error here
kubectl -n hardened describe rs -l app=sneaky | tail -5kubectl label --dry-run=server --overwrite ns team-b pod-security.kubernetes.io/enforce=restricted and read which existing pods would violate.
audit label has been quietly recording all along.Self-check
Name the three profiles in one clause each.
Privileged: no restrictions. Baseline: blocks known privilege escalations (privileged containers, host namespaces, hostPath, extra capabilities). Restricted: additionally requires non-root, no privilege escalation, all capabilities dropped, and RuntimeDefault seccomp.
A Deployment is created and no pods appear. Where is the PSS error?
On the ReplicaSet: PSS is a pod-level admission decision, so the Deployment controller records the rejection one level down. kubectl describe rs -l … or namespace events.
You want to know who breaks if you tighten a namespace. Command?
kubectl label --dry-run=server --overwrite ns <ns> pod-security.kubernetes.io/enforce=restricted: the server evaluates existing pods and warns for each violator, changing nothing.
Why pin enforce-version?
Because profiles gain checks in new Kubernetes releases: with latest, a cluster upgrade can start rejecting pods that were compliant before. Pinning turns that into a deliberate migration you schedule rather than a surprise you discover.
PSS or Kyverno for "images must come from our registry"?
Kyverno (or Gatekeeper): PSS covers only the fixed pod-hardening dimension and has no notion of registries. PSS is the floor; the engine is for organisation-specific rules, and it is fine, indeed normal, to run both.
Docs to know your way around
- kubernetes.io: Pod Security Standards (the profile tables; do not memorise the capability lists, know where they are) and Enforce Pod Security Standards with Namespace Labels.
- Offline: the rejection messages themselves; PSS names every violated control, which makes it the most self-documenting admission mechanism in the cluster; plus
kubectl explain pod.spec.securityContext.