pod security standards: baseline vs restricted
Explains the Kubernetes Pod Security Standards (privileged, baseline, restricted), what each one blocks, and how to enforce them with the PodSecurity admission plugin via namespace labels. Use when pods get rejected by admission, you are writing cluster policy, or deciding whether restricted is worth the friction. Triggers: 'pod security admission', 'baseline vs restricted'. Not for: custom OPA or Kyverno policies, node hardening, or RBAC issues.
pod security standards: baseline vs restricted
TL;DR
Kubernetes ships three built-in Pod Security Standards: privileged (no restrictions, for system pods), baseline (blocks the known-bad stuff like privileged containers and host namespaces), and restricted (hardened, requires non-root, dropped capabilities, seccomp). Most teams enforce baseline everywhere and push new workloads toward restricted. Enforce them with the PodSecurity admission plugin by labeling namespaces, audit first, then fix violations one by one.
pod security standards: baseline vs restrictedUse this when
- A pod is rejected with a "violates PodSecurity" admission error and you need to fix the spec
- You are setting up a new cluster and deciding what posture to enforce
- Someone asks whether your workloads meet the restricted standard
- You are replacing the old PodSecurityPolicy setup with the built-in standards
Not for this skill when
- You need custom rules beyond the three built-in profiles (use OPA Gatekeeper or Kyverno)
- You are hardening the node OS or the kubelet (different layer, different checklist)
- The violation is about RBAC or network policy, not the pod spec
Steps
1. See what is enforced where
kubectl get ns -L pod-security.kubernetes.io/enforceExpected: each namespace listed with its enforce level, or blank for none. Blank means the standard is not enforced there yet.
The three profiles: privileged allows everything and exists for system-level pods like CNI and CSI drivers. Baseline blocks the greatest hits: privileged containers, host namespaces and hostPath volumes, and a few other known escape paths. Restricted goes further and requires a non-root user, a read-only root filesystem, all capabilities dropped, and a seccomp profile set.
2. Audit before you enforce
kubectl label namespace my-app pod-security.kubernetes.io/audit=restricted pod-security.kubernetes.io/audit-version=latestExpected: the label is applied. Violating pods now generate warnings in the audit log and on kubectl apply, but nothing is blocked. Let normal deploys run for a day or two to collect the full violation list before you enforce anything.
3. Fix the usual violations
The same handful of fields fix almost every restricted violation. Add them to your deployment:
securityContext:
runAsNonRoot: true
runAsUser: 10001
seccompProfile:
type: RuntimeDefault
containers:
- name: app
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]kubectl apply -f deployment.yamlExpected: the deploy succeeds under audit mode, and no new warnings appear for the namespace. If your container genuinely needs a capability, add it back explicitly under capabilities add instead of dropping the whole profile.
4. Enforce, then prove it bites
kubectl label namespace my-app pod-security.kubernetes.io/enforce=restricted pod-security.kubernetes.io/enforce-version=latestExpected: the label is applied. Prove enforcement works with a deliberately bad pod (server-side dry run, nothing is created):
kubectl run pss-test --image=nginx -n my-app --dry-run=server --overrides='{"spec":{"containers":[{"name":"pss-test","image":"nginx","securityContext":{"privileged":true}}]}}'Expected: a Forbidden error naming the PodSecurity violation. If the bad pod is admitted, the label did not apply.
5. Exempt what legitimately cannot comply
kubectl label namespace kube-system pod-security.kubernetes.io/enforce=privileged pod-security.kubernetes.io/enforce-version=latestExpected: system pods stop tripping the checks. Keep the exemption list short and write down why each one exists. Every exemption is a hole you chose, so review them quarterly.
Variant: fix one rejected deploy
Read the admission error, it names the exact field (usually runAsNonRoot, seccompProfile, or capabilities). Add the missing securityContext fields to that pod or deployment and re-apply. One field at a time beats guessing.
Variant: migrate off PodSecurityPolicy
PSP is removed in modern Kubernetes. Map each PSP rule to the nearest standard (most PSPs land on baseline), label namespaces in audit mode, fix the violations, then enable the PodSecurity admission plugin and delete the PSPs.
Variant: baseline for the whole cluster
Set cluster-wide defaults in the admission plugin configuration so new namespaces get baseline enforce automatically, then opt specific namespaces up to restricted with labels.
Why this happens
Containers share the host kernel, so a container running as root with full capabilities is uncomfortably close to root on the node. Almost every container escape in the wild traces back to the same short list: privileged mode, host namespaces, and writeable host paths. Baseline kills the escape routes. Restricted also kills the common privilege-escalation paths inside the container, which is why it breaks more apps and needs the audit-first approach.
Edge cases and pitfalls
- Older images assume they run as root and break under restricted. Test with the audit label before enforcing.
- readOnlyRootFilesystem breaks apps that write to /tmp. Mount an emptyDir volume there.
- runAsUser on an image whose files are owned by root causes permission errors. Check file ownership inside the image.
- Seccomp RuntimeDefault is stricter than Unconfined. Some databases and runtimes need specific syscalls allowed.
- Enforcement is per-namespace via labels. New namespaces default to nothing unless you set cluster-wide defaults in the admission config.
- The standards only check the pod spec. They say nothing about image vulnerabilities or RBAC.
Tool notes: the PodSecurity admission plugin is built into Kubernetes 1.23 and later (stable in 1.25). Works with any CNI.
Provenance
Resolved from the public thread: https://vectle.com/posts/pst_Mgo0LTDltr9Nr2I3hcuQ
Maintainer review
No maintainer verification is recorded for this version.
This records the version a maintainer checked. It does not assert that the version is the latest upstream release.