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

LevelDescriptionUse Case
PrivilegedNo restrictions — anything goesSystem-level workloads (CNI, CSI, kube-system)
BaselinePrevents known privilege escalations while remaining easy to adoptMost application workloads
RestrictedMaximum hardening — heavily restricted Pod capabilitiesSecurity-sensitive, multi-tenant, CKS-aligned

What Each Level Restricts

ControlPrivilegedBaselineRestricted
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...)✅ AllowedBlocks dangerous onesOnly NET_BIND_SERVICE allowed
Run as root (UID 0)✅ Allowed✅ Allowed❌ Must run as non-root
Privilege escalation✅ Allowed✅ AllowedallowPrivilegeEscalation: false
Seccomp profileNot requiredNot requiredMust be RuntimeDefault or Localhost
Volume typesAllAll except hostPathOnly: configMap, emptyDir, projected, secret, PVC, downwardAPI, ephemeral
Baseline blocks container escape vectors. It prevents the most dangerous capabilities (hostPID, hostNetwork, privileged, hostPath) while still allowing Pods to run as root and without seccomp. It's the minimum security posture for any non-system workload.

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:

ModeBehaviorUse Case
enforceReject Pods that violate the standardProduction — hard block
warnAllow but display a warning to the userMigration — see what would break
auditAllow but log to audit logMonitoring — 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)
You can use different levels for different modes. A common migration pattern:
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.
CKS exam: know the label format by heart. The most common task: "Enforce the Restricted Pod Security Standard on namespace X." → 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
Many popular container images run as root by default (nginx, redis, postgres). Under Restricted, you'll need either: (1) Images built with a non-root user (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

  1. Audit first: Label namespaces with audit: restricted. Check audit logs for violations.
  2. Warn next: Add warn: restricted. Developers see warnings on kubectl apply.
  3. Fix violations: Update Deployments to pass Restricted (add securityContext fields).
  4. 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
Don't exempt — use the right level per namespace. Rather than exempting kube-system entirely, label it with 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)
ImplementationCustom resources + RBAC bindingNamespace labels (simple!)
ComplexityHigh (confusing interaction with RBAC)Low (just label the namespace)
GranularityPer-field customization (very flexible)Three predefined levels (less flexible)
MigrationN/A (removed)Audit → warn → enforce
Custom policiesYes (but complex)No — use Gatekeeper/Kyverno for custom rules
PodSecurityPolicy was removed in K8s 1.25. If you see PSP in exam questions, it's likely asking you to migrate TO Pod Security Standards. For custom policies beyond the three levels (e.g., "only allow images from registry.company.com"), use OPA/Gatekeeper or Kyverno — covered in Chapter 8.

Summary

ConceptKey Point
Three levelsPrivileged (no restrictions), Baseline (blocks escapes), Restricted (maximum hardening)
Three modesenforce (reject), warn (display warning), audit (log only)
ConfigurationNamespace labels: pod-security.kubernetes.io/{mode}={level}
Baseline blockshostNetwork, hostPID, privileged, hostPath, dangerous capabilities
Restricted requiresNon-root, no privilege escalation, seccomp, drop ALL capabilities, limited volumes
Migration pathaudit → warn → fix → enforce (incremental tightening)
kube-systemLabel as enforce: privileged (system Pods need elevated access)
PSP → PSSPSP 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?

Rejected. The Baseline level blocks hostNetwork. Since the mode is 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?

Yes, with a warning. Running as root is allowed by Baseline (only Restricted blocks it). So 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?

At minimum:
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?

Label kube-system with 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?

No. Pod Security Standards are limited to three fixed levels. For custom policies (image allowlists, label requirements, resource constraints), use OPA/Gatekeeper or Kyverno — policy engines that run as admission webhooks. They complement PSS: PSS handles baseline Pod hardening, Gatekeeper/Kyverno handles organization-specific rules.

Q6: What's the recommended migration strategy for moving a cluster from no Pod Security to Restricted enforcement?

Incremental: (1) Start with 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.