RBAC controls who can create Pods. Pod Security Standards control what those Pods can do. Even if a user has permission to create Pods, the PodSecurity admission controller can reject Pods that request dangerous capabilities (privileged, hostNetwork, root). This is the built-in defense against container escape.
1. The Three Security Levels
| Level | Description | Use Case |
|---|---|---|
| Privileged | No restrictions — anything goes | System-level workloads (CNI, CSI, kube-system) |
| Baseline | Prevents known privilege escalations while remaining easy to adopt | Most application workloads |
| Restricted | Maximum hardening — heavily restricted Pod capabilities | Security-sensitive, multi-tenant, CKS-aligned |
What Each Level Restricts
| Control | Privileged | Baseline | Restricted |
|---|---|---|---|
| hostNetwork/hostPID/hostIPC | ✅ Allowed | ❌ Blocked | ❌ Blocked |
| Privileged containers | ✅ Allowed | ❌ Blocked | ❌ Blocked |
| Host ports | ✅ Allowed | ❌ Blocked | ❌ Blocked |
| hostPath volumes | ✅ Allowed | ❌ Blocked | ❌ Blocked |
| Capabilities (NET_RAW, SYS_ADMIN...) | ✅ Allowed | Blocks dangerous ones | Only NET_BIND_SERVICE allowed |
| Run as root (UID 0) | ✅ Allowed | ✅ Allowed | ❌ Must run as non-root |
| Privilege escalation | ✅ Allowed | ✅ Allowed | ❌ allowPrivilegeEscalation: false |
| Seccomp profile | Not required | Not required | Must be RuntimeDefault or Localhost |
| Volume types | All | All except hostPath | Only: configMap, emptyDir, projected, secret, PVC, downwardAPI, ephemeral |
Restricted blocks everything a compromised container could abuse. Non-root only, no privilege escalation, mandatory seccomp, minimal capabilities. This is the CKS target and multi-tenant standard.
2. Enforcement Modes
The PodSecurity admission controller (built-in since K8s 1.25) enforces standards at three modes per namespace:
| Mode | Behavior | Use Case |
|---|---|---|
enforce | Reject Pods that violate the standard | Production — hard block |
warn | Allow but display a warning to the user | Migration — see what would break |
audit | Allow but log to audit log | Monitoring — track violations |
Configuration via Namespace Labels
# Apply Pod Security Standards to a namespace using labels: kubectl label namespace production \ pod-security.kubernetes.io/enforce=restricted \ pod-security.kubernetes.io/enforce-version=latest \ pod-security.kubernetes.io/warn=restricted \ pod-security.kubernetes.io/audit=restricted
# Or in the namespace manifest:
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: latest
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/warn-version: latest
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/audit-version: latest
Label Format
# pod-security.kubernetes.io/{mode}: {level}
# pod-security.kubernetes.io/{mode}-version: {version}
# mode: enforce | warn | audit
# level: privileged | baseline | restricted
# version: latest | v1.28 | v1.27 ... (pin to specific K8s version's rules)
•
enforce: baseline (hard-block the worst violations)•
warn: restricted (show developers what would break under restricted)•
audit: restricted (log all violations for security team review)This lets you tighten security incrementally.
kubectl label ns X pod-security.kubernetes.io/enforce=restricted. Quick and easy once you know the label path.
3. Practical: What Gets Blocked
Pod Rejected by "Restricted" Level
# This Pod violates Restricted in multiple ways:
apiVersion: v1
kind: Pod
metadata:
name: bad-pod
namespace: production # enforce=restricted
spec:
containers:
- name: app
image: nginx
securityContext:
runAsUser: 0 # ❌ Root (must be non-root)
allowPrivilegeEscalation: true # ❌ Must be false
# Missing seccompProfile # ❌ Must have RuntimeDefault or Localhost
# Result:
# Error: pods "bad-pod" is forbidden: violates PodSecurity "restricted:latest":
# allowPrivilegeEscalation != false
# runAsNonRoot != true
# seccompProfile not set
Pod That Passes "Restricted"
apiVersion: v1
kind: Pod
metadata:
name: good-pod
namespace: production
spec:
securityContext:
runAsNonRoot: true # ✓ Pod-level: must be non-root
seccompProfile:
type: RuntimeDefault # ✓ Seccomp required
containers:
- name: app
image: nginx:1.25
securityContext:
allowPrivilegeEscalation: false # ✓ Must be false
capabilities:
drop: ["ALL"] # ✓ Drop all capabilities
add: ["NET_BIND_SERVICE"] # ✓ Only this one is allowed
readOnlyRootFilesystem: true # Good practice (not required by restricted)
ports:
- containerPort: 8080
USER 1000 in Dockerfile). (2) Override with securityContext.runAsUser: 1000 in the Pod spec. Most production-grade images now support non-root — check image documentation.
4. Migration Strategy
Step-by-Step Rollout
- Audit first: Label namespaces with
audit: restricted. Check audit logs for violations. - Warn next: Add
warn: restricted. Developers see warnings onkubectl apply. - Fix violations: Update Deployments to pass Restricted (add securityContext fields).
- Enforce: Once clean, add
enforce: restricted. Non-compliant Pods are blocked.
# Dry-run: check what would be violated without enforcing: kubectl label ns production --dry-run=server --overwrite \ pod-security.kubernetes.io/enforce=restricted # Shows warnings for existing Pods that would violate # Or use kubectl-pss plugin to scan: kubectl auth can-i --list -n production # (no direct tool — use warn mode)
Exemptions
Some system Pods (kube-system, monitoring) need elevated privileges. Configure exemptions in the PodSecurity admission configuration:
# In the admission configuration (AdmissionConfiguration):
apiVersion: apiserver.config.k8s.io/v1
kind: AdmissionConfiguration
plugins:
- name: PodSecurity
configuration:
apiVersion: pod-security.admission.config.k8s.io/v1
kind: PodSecurityConfiguration
defaults:
enforce: baseline
enforce-version: latest
exemptions:
usernames: []
runtimeClasses: []
namespaces:
- kube-system # Exempt kube-system from enforcement
- kube-node-lease
- monitoring # Prometheus needs elevated access
enforce: privileged (allows everything for system Pods). Reserve Restricted for application namespaces. This is more explicit than blanket exemptions.
5. Pod Security Standards vs PodSecurityPolicy (Legacy)
| PodSecurityPolicy (removed in 1.25) | Pod Security Standards (current) | |
|---|---|---|
| Implementation | Custom resources + RBAC binding | Namespace labels (simple!) |
| Complexity | High (confusing interaction with RBAC) | Low (just label the namespace) |
| Granularity | Per-field customization (very flexible) | Three predefined levels (less flexible) |
| Migration | N/A (removed) | Audit → warn → enforce |
| Custom policies | Yes (but complex) | No — use Gatekeeper/Kyverno for custom rules |
Summary
| Concept | Key Point |
|---|---|
| Three levels | Privileged (no restrictions), Baseline (blocks escapes), Restricted (maximum hardening) |
| Three modes | enforce (reject), warn (display warning), audit (log only) |
| Configuration | Namespace labels: pod-security.kubernetes.io/{mode}={level} |
| Baseline blocks | hostNetwork, hostPID, privileged, hostPath, dangerous capabilities |
| Restricted requires | Non-root, no privilege escalation, seccomp, drop ALL capabilities, limited volumes |
| Migration path | audit → warn → fix → enforce (incremental tightening) |
| kube-system | Label as enforce: privileged (system Pods need elevated access) |
| PSP → PSS | PSP removed in 1.25. Use PSS + Gatekeeper/Kyverno for custom rules. |
📝 Quiz: Pod Security Standards
Q1: You label a namespace with pod-security.kubernetes.io/enforce=baseline. A user tries to create a Pod with hostNetwork: true. What happens?
enforce, the Pod creation is denied with an error: "violates PodSecurity baseline: hostNetwork=true." The Pod is not created.Q2: A namespace has enforce: baseline and warn: restricted. A Pod runs as root (UID 0). Is it created?
enforce: baseline passes. But warn: restricted shows a warning to the user: "would violate restricted: runAsNonRoot != true." The Pod is created but the user is alerted it wouldn't pass Restricted.Q3: What's the minimum securityContext a Pod needs to pass the Restricted level?
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
containers:
- securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]Optional but recommended: readOnlyRootFilesystem: true. If adding capabilities, only NET_BIND_SERVICE is allowed.Q4: The kube-system namespace needs to run privileged Pods (CNI, kube-proxy). How do you handle this with Pod Security Standards?
pod-security.kubernetes.io/enforce=privileged. The Privileged level allows everything — system Pods won't be blocked. This is more explicit than exempting the namespace entirely. Each namespace gets the level appropriate for its workloads.Q5: You want custom policies beyond the three built-in levels (e.g., "only allow images from our private registry"). Can Pod Security Standards do this?
Q6: What's the recommended migration strategy for moving a cluster from no Pod Security to Restricted enforcement?
audit: restricted on all namespaces → check audit logs for violations. (2) Add warn: restricted → developers see warnings. (3) Fix all Pod specs (add securityContext, drop capabilities, set non-root). (4) Enforce baseline first (enforce: baseline) as an intermediate step. (5) Finally, enforce: restricted once all Pods are compliant. Never jump directly to enforce — you'll break running workloads.