The Problem PSP Was Solving
Authentication and RBAC control who can create resources and what types of resources they can create. But they don't control the content of those resources. A developer with permission to create Pods could create a Pod that:
- Runs as
rootinside the container. - Mounts the host's
/etcdirectory. - Runs in
privilegedmode, giving it near-full access to the host kernel. - Uses
hostNetwork: true, bypassing all network policies.
These are legitimate container capabilities needed by some system components, but they are catastrophic if used by a compromised application container. Kubernetes needed a mechanism to restrict what settings a Pod is allowed to use — independent of who is creating it.
The Death of PodSecurityPolicy (PSP)
PodSecurityPolicy (PSP) was Kubernetes' original answer to this problem. It was a cluster-scoped resource that defined allowed Pod configurations. However, PSP had severe usability problems:
- Fail-open by default: If no PSP matched a Pod, the behavior depended on whether the admission controller was enabled. Many clusters had PSP enabled but misconfigured, silently allowing everything.
- Complex RBAC interaction: PSPs were enforced via RBAC — you had to create a separate
useverb binding for each PSP. This was non-obvious and error-prone. - No dry-run mode: You couldn't test a PSP without it immediately affecting production workloads.
- Confusing precedence rules: When multiple PSPs matched, the selection logic was opaque.
PSP was deprecated in Kubernetes v1.21 and completely removed in v1.25. Any cluster running v1.25+ has no PSP support at all.
The Successor: PSS + PSA
The replacement is a two-part system:
- Pod Security Standards (PSS): The policy definitions. A set of three predefined security profiles maintained by the Kubernetes project. You don't write custom rules — you choose a profile.
- Pod Security Admission (PSA): The enforcement mechanism. A built-in admission controller (no installation required) that enforces PSS profiles using Namespace labels.
This design is intentionally simpler: no CRDs, no complex RBAC interactions, no separate policy objects. The policy is expressed as a label on a Namespace.