Kubernetes Pod Security Admission (PSA) is the built-in, recommended way to enforce Pod Security Standards (PSS) across your cluster, replacing the deprecated PodSecurityPolicy (PSP).
Let’s see PSA in action. Imagine we have a Deployment that tries to create a privileged Pod:
apiVersion: apps/v1
kind: Deployment
metadata:
name: privileged-deployment
spec:
replicas: 1
selector:
matchLabels:
app: privileged
template:
metadata:
labels:
app: privileged
spec:
containers:
- name: nginx
image: nginx:latest
securityContext:
privileged: true # This is the problematic part
If PSA is configured to Enforce the privileged security context at the namespace level, this Deployment will fail to create its Pods. The API server will reject the Pod creation request before it even hits the scheduler.
The Problem PSA Solves
Before PSA, enforcing security best practices for Pods was a pain. PodSecurityPolicy (PSP) was the tool, but it was complex, hard to get right, and eventually deprecated. PSA provides a simpler, more declarative approach by leveraging the Pod Security Standards (PSS) that are now built into Kubernetes. These standards define three profiles: Privileged, Baseline, and Restricted, each with increasing levels of security. PSA allows you to enforce these profiles at the namespace level, ensuring that all Pods deployed within that namespace adhere to a defined security posture.
How PSA Works Internally
PSA operates as a validating admission controller built directly into the Kubernetes API server. When a request to create, update, or even kubectl exec into a Pod comes in, the API server intercepts it. If a namespace has a PSA configuration applied, the API server checks the incoming Pod against the rules of the specified PSS profile. If the Pod violates any of the enforced rules, the API server rejects the request with a clear error message, preventing the insecure Pod from being admitted to the cluster.
The key to PSA is its configuration at the namespace level. You apply labels to namespaces that tell PSA which PSS profile to enforce and what action to take when a violation occurs.
There are three PSS profiles:
privileged: Unrestricted. Allows all possible Pod configurations. Generally not recommended for production.baseline: Minimally restrictive. Prevents known privilege escalations. Suitable for most applications.restricted: Heavily restricted. Pods are run in an hardened context, similar to a general-purpose minimal container runtime.
For each profile, you can configure two modes:
enforce: Violations cause the Pod to be rejected. This is the mode you want for strict security.audit: Violations are recorded as audit events but the Pod is still allowed to run. Useful for understanding what would be blocked.warn: Violations are returned as a warning to the user but the Pod is still allowed to run. Good for gradual rollouts.
Enforcing Policies with PSA
To enforce policies, you label the target namespace. For example, to enforce the baseline profile in its enforce mode for all Pods in the default namespace, you would run:
kubectl label --overwrite namespace default pod-security.kubernetes.io/enforce=baseline
To enforce the restricted profile in audit mode:
kubectl label --overwrite namespace default pod-security.kubernetes.io/audit=restricted
And for warn mode:
kubectl label --overwrite namespace default pod-security.kubernetes.io/warn=restricted
You can also combine these. A common pattern is to use enforce for a strict profile, audit for a slightly less strict one, and warn for the most permissive, allowing you to catch potential violations at different stages.
For instance, to enforce restricted and audit baseline in the production namespace:
kubectl label --overwrite namespace production pod-security.kubernetes.io/enforce=restricted
kubectl label --overwrite namespace production pod-security.kubernetes.io/audit=baseline
When you try to create the privileged-deployment from the beginning in a namespace labeled pod-security.kubernetes.io/enforce=baseline or pod-security.kubernetes.io/enforce=restricted, you’ll get an error like this:
Error from server (Forbidden): error when creating "privileged-deployment.yaml": admission webhook "pod-security-policy.kubernetes.io/" denied the request: context.securityContext.privileged: Invalid value: true: Privileged containers are not allowed
This error clearly indicates that the Pod’s securityContext.privileged: true violates the enforced baseline policy.
The Levers You Control
The primary levers you control are the labels applied to your namespaces. These labels determine:
- Which PSS profile (
privileged,baseline,restricted) is being checked. - What action (
enforce,audit,warn) is taken when a violation occurs.
You can apply multiple labels to a single namespace to layer policies. For example, pod-security.kubernetes.io/enforce=restricted and pod-security.kubernetes.io/audit=baseline means that any Pod violating the restricted profile will be rejected, while any Pod violating only the baseline profile (but not restricted) will be audited.
The order of evaluation for layered policies is: enforce policies are checked first, then audit, then warn. A Pod must satisfy the strictest applicable policy across all modes to be admitted.
What Most People Don’t Know
The most surprising part for many is how PSA interacts with other admission controllers, particularly mutating controllers. While PSA is a validating controller (it only says "yes" or "no"), other controllers might mutate Pods before they reach PSA. If a mutating controller modifies a Pod in a way that then violates the PSA policy, PSA will reject it. Conversely, if a mutating controller fixes a potential violation (e.g., by setting a default readOnlyRootFilesystem: true if the user didn’t specify it and the policy requires it), the Pod might then be admitted. This means the order of admission controllers matters, and you need to be aware of how your mutating webhooks might indirectly affect PSA’s decisions.
The next thing you’ll likely encounter is managing PSA across a large cluster and ensuring consistency, which often leads to exploring GitOps tools for label management and policy-as-code solutions.