Pod Security Standards & admission

Baseline, restricted, and OPA/Gatekeeper.

Advanced14 min · lesson 14 of 24

Pod Security Admission is the built-in admission controller (it replaced the deprecated PodSecurityPolicy) that enforces the three Pod Security Standards: privileged (no restrictions), baseline (blocks the known escalation vectors — hostPath, hostNetwork, privileged, added dangerous capabilities), and restricted (the hardened profile: non-root, no privilege escalation, all capabilities dropped, seccomp on, no host namespaces). It is configured entirely with namespace labels, so there is no CRD or webhook to install.

THE THREE POD SECURITY STANDARDS
privileged
No restrictions
Open profile for trusted infra workloads
baseline
Blocks hostPath / hostNetwork
Blocks privileged
Blocks added dangerous capabilities
restricted
runAsNonRoot, no privilege escalation
Drop ALL capabilities
seccomp profile set
No host namespaces, safe volumes only
PSA sets one enforced level per namespace via labels; roll out with warn and audit before flipping enforce=restricted.

Each namespace can carry three independent modes: enforce (reject violating pods), audit (allow but record in the audit log), and warn (allow but return a warning to the client). You also pin the standard to a Kubernetes version so a future relaxation of a rule does not silently change your posture. The independence of the modes is the key to a safe rollout.

terminal
$ kubectl label ns payments \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/enforce-version=v1.30 \
pod-security.kubernetes.io/warn=restricted \
pod-security.kubernetes.io/audit=restricted
Audit before you enforce
Flipping a busy namespace straight to enforce=restricted rejects every non-compliant pod on the next deploy — and existing running pods are not re-checked, so the breakage shows up later, mid-incident. Set warn and audit to restricted first, read who fails, fix the workloads, then turn on enforce.

What restricted actually requires

A pod that passes restricted is the security context lesson made mandatory: runAsNonRoot, allowPrivilegeEscalation false, all capabilities dropped, a seccomp profile set, no host namespaces, and only the safe volume types. It is worth being able to write one from memory, because the fastest way to fix a rejected pod is to know exactly which field it is missing.

restricted-pod.yaml
spec:
securityContext:
runAsNonRoot: true
seccompProfile: { type: RuntimeDefault }
containers:
- name: app
image: registry.internal/app:1.4.2
securityContext:
allowPrivilegeEscalation: false
capabilities: { drop: ["ALL"] }

When three profiles are not enough

PSA only knows those three profiles; it cannot express “images must come from our registry,” “every pod needs a team label,” or “no :latest tags.” For rules specific to your org you add a policy engine — OPA/Gatekeeper (constraints written in Rego via ConstraintTemplates) or Kyverno (policies written in plain YAML). Both run as admission webhooks and can validate or even mutate. PSA for the standard baseline, a policy engine for everything bespoke.

kyverno-disallow-latest.yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata: { name: disallow-latest-tag }
spec:
validationFailureAction: Enforce
rules:
- name: require-explicit-tag
match: { any: [{ resources: { kinds: [Pod] } }] }
validate:
message: "images must use a specific, non-latest tag"
pattern:
spec:
containers:
- image: "!*:latest"
PSA is namespace-wide, not per-pod
Because the control is a namespace label, one namespace has one enforced level for every pod in it. A workload that genuinely needs more (a privileged node agent) belongs in its own namespace with a looser label and a very tight RBAC boundary — not as an exemption that quietly weakens a restricted namespace.