Pod Security Admission
Pod Security Admission applies built-in Pod Security Standards at namespace level: privileged, baseline, or restricted. It replaced the old PodSecurityPolicy admission controller. Use warn and audit first, then enforce restricted for normal app namespaces. Keep privileged platform agents isolated in dedicated namespaces.
Mental Model
RBAC controls who can ask Kubernetes to create Pods. Pod Security Admission controls whether the Pod spec is acceptable at admission time. It checks runtime-sensitive fields such as privileged mode, host namespace access, hostPath mounts, Linux capabilities, seccomp, root user settings, and privilege escalation.
PSA is intentionally simple: it uses namespace labels and built-in profiles. If a client needs custom policy logic, they usually add Kyverno, Gatekeeper, Kubewarden, or a platform admission controller alongside PSA.
Pod Security Admission blocks unsafe Pod specs at API admission time.
Security Levels
| Level | Meaning | Use |
|---|---|---|
privileged | Unrestricted profile. | Dedicated infrastructure namespaces for CNI, CSI, node agents, or debug tooling. |
baseline | Blocks known privilege escalation paths while allowing common app patterns. | Migration step for legacy workloads. |
restricted | Strongest built-in profile: non-root, no privilege escalation, dropped capabilities, seccomp, tight volumes. | Target for normal production app namespaces. |
Namespace Labels
PSA reads labels on the Namespace. enforce rejects violating Pods, warn shows warnings to clients such as kubectl, and audit records violations in audit logs.
apiVersion: v1
kind: Namespace
metadata:
name: app
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: latest
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/audit-version: latest
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/warn-version: latestkubectl label namespace app \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/enforce-version=latest \
pod-security.kubernetes.io/audit=restricted \
pod-security.kubernetes.io/audit-version=latest \
pod-security.kubernetes.io/warn=restricted \
pod-security.kubernetes.io/warn-version=latest --overwrite
kubectl get namespace app --show-labelsRestricted-Friendly Workload
This pattern works for many web workloads: non-root image, no privilege escalation, all Linux capabilities dropped, runtime seccomp, read-only root filesystem, and writable emptyDir only where needed.
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
namespace: app
spec:
replicas: 2
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
containers:
- name: web
image: nginxinc/nginx-unprivileged:1.27
ports:
- containerPort: 8080
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}securityContext Fields
| Field | Why It Matters | Restricted-Friendly Default |
|---|---|---|
runAsNonRoot | Prevents UID 0 containers. | true |
allowPrivilegeEscalation | Blocks gaining more privileges than parent process. | false |
capabilities.drop | Removes Linux capabilities. | ["ALL"] |
seccompProfile | Limits syscalls. | RuntimeDefault |
readOnlyRootFilesystem | Reduces writeable attack surface. | true when app supports it. |
privileged | Gives broad host-level access. | Do not use in app namespaces. |
Common Violations
| Violation | Why It Fails | Fix |
|---|---|---|
privileged: true | Privileged containers are not allowed in baseline/restricted. | Move to platform namespace or remove privileged need. |
hostNetwork, hostPID, hostIPC | Accesses host namespaces. | Use normal pod networking/process isolation. |
hostPath | Mounts host filesystem. | Use PVC, ConfigMap, Secret, or emptyDir where possible. |
| Runs as root | Restricted expects non-root. | Use non-root image or set valid non-root UID. |
| Added capabilities | Restricted requires all capabilities dropped. | Drop all; redesign if special capability is needed. |
| No seccomp | Restricted expects seccomp profile. | Set RuntimeDefault. |
Debug Admission Failures
NS=<namespace>
kubectl get ns "$NS" --show-labels
kubectl apply --dry-run=server -f deployment.yaml
kubectl get events -n "$NS" --sort-by=.lastTimestamp | grep -Ei 'forbidden|violates|podsecurity|admission'
kubectl describe rs -n "$NS" <replicaset>
# Find security-sensitive fields in a rendered workload.
kubectl get deploy <deploy> -n "$NS" -o yaml | grep -E 'privileged|hostNetwork|hostPID|hostIPC|hostPath|runAs|allowPrivilege|capabilities|seccomp' -nPlatform Exceptions
Some workloads genuinely need privileged access: CNI plugins, CSI drivers, node exporters, log collectors, backup agents, security agents, and low-level troubleshooting tools. Do not weaken app namespaces for them. Put them in dedicated namespaces with clear ownership and labels.
apiVersion: v1
kind: Namespace
metadata:
name: platform-agents
labels:
pod-security.kubernetes.io/enforce: privileged
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted
owner: platform-teamSafe Rollout Pattern
- Inventory namespaces and workloads that need privileged, hostPath, hostNetwork, or root access.
- Set
warn=restrictedandaudit=restrictedfirst while keepingenforce=baseline. - Fix workload specs and Helm values to become restricted-compatible.
- Move platform agents into dedicated exception namespaces.
- Switch app namespaces to
enforce=restrictedafter warnings are clean.
Symptom To Cause
| Symptom | Likely Cause | Check First |
|---|---|---|
Forbidden: violates PodSecurity | Pod spec violates namespace enforce level. | Admission message and namespace labels. |
| Helm install fails only in prod namespace | Prod namespace enforces stricter PSA labels. | Rendered chart securityContext and namespace labels. |
| App cannot write temp files | readOnlyRootFilesystem enabled without writable mount. | Add emptyDir for required temp/cache path. |
| Container fails as non-root | Image expects root-owned paths or privileged port. | Use non-root image, change port, fix file ownership. |
| DaemonSet blocked | Needs hostPath/hostNetwork/privileged access. | Dedicated privileged platform namespace. |
Production Patterns
- Default app namespaces to restricted: make exceptions explicit instead of broad.
- Use audit/warn during migration: prevent surprise outages.
- Patch Helm values: most charts expose securityContext knobs.
- Pair with policy tools: use Kyverno/Gatekeeper for custom rules not covered by PSA.
- Document exceptions: privileged namespaces should have owner and purpose labels.