CKS — Pod Security Standards (PSS)

9 minute read

Pod Security Standards (PSS)

Pod Security Standards define three progressively restrictive security levels for Pods. They replaced PodSecurityPolicy (PSP), which was removed in Kubernetes 1.25, and are now the built-in mechanism for controlling what Pods are allowed to do.

Every CKS candidate must know all three levels, what each restricts, and when to apply them.


1. The Three Levels

Security Spectrum:

  Privileged ←————————————————————————→ Restricted
  (No restrictions)                      (Maximum lockdown)

  ┌────────────┐   ┌────────────┐   ┌─────────────┐
  │ Privileged │   │  Baseline  │   │ Restricted  │
  │            │   │            │   │             │
  │ Unrestricted   │ Prevents   │   │ Hardened    │
  │ access for │   │ known      │   │ workloads   │
  │ system     │   │ privilege  │   │ with full   │
  │ workloads  │   │ escalation │   │ lockdown    │
  └────────────┘   └────────────┘   └─────────────┘

Privileged

  • No restrictions whatsoever
  • Allows hostNetwork, hostPID, hostIPC, privileged containers, all capabilities
  • Intended for system-level workloads: CNI plugins, storage drivers, logging agents
  • Should only be used in kube-system or dedicated infrastructure namespaces

Baseline

  • Prevents known privilege escalation vectors
  • Blocks hostNetwork, hostPID, hostIPC, privileged containers
  • Blocks host path volumes, host ports, and certain dangerous capabilities
  • Still allows running as root, allows some capabilities
  • Suitable for most general workloads during migration

Restricted

  • Maximum security for untrusted or multi-tenant workloads
  • Must run as non-root (runAsNonRoot: true)
  • Must drop ALL capabilities (can only add back NET_BIND_SERVICE)
  • Must set allowPrivilegeEscalation: false
  • Must have a Seccomp profile (RuntimeDefault or Localhost)
  • Volume types restricted to safe set (configMap, secret, emptyDir, PVC, etc.)
  • This is the target level for production workloads on the CKS exam

2. Complete Comparison Table

This table shows every field controlled by Pod Security Standards:

Control Privileged Baseline Restricted
hostNetwork Allowed Blocked Blocked
hostPID Allowed Blocked Blocked
hostIPC Allowed Blocked Blocked
hostPorts Allowed Blocked Blocked
privileged containers Allowed Blocked Blocked
Capabilities — add Any Only: AUDIT_WRITE, CHOWN, DAC_OVERRIDE, FOWNER, FSETID, KILL, MKNOD, NET_BIND_SERVICE, SETFCAP, SETGID, SETPCAP, SETUID, SYS_CHROOT Only: NET_BIND_SERVICE
Capabilities — drop ALL Not required Not required Required
hostPath volumes Allowed Blocked Blocked
Allowed volume types All All except hostPath configMap, csi, downwardAPI, emptyDir, ephemeral, persistentVolumeClaim, projected, secret
procMount Any Default only Default only
seccompProfile No restriction No restriction Must be RuntimeDefault or Localhost
runAsNonRoot No restriction No restriction Must be true
runAsUser Any Any Must not be 0 (root)
allowPrivilegeEscalation Allowed Allowed Must be false
sysctls Any Only safe sysctls Only safe sysctls
appArmorProfile Any Not unconfined (if set) Not unconfined (if set)
seLinuxOptions type Any Only allowed types Only allowed types
windowsOptions — hostProcess Allowed Blocked Blocked

Exam Tip: Memorize what Restricted requires: runAsNonRoot: true, drop: ["ALL"], allowPrivilegeEscalation: false, and seccompProfile.type: RuntimeDefault. You’ll need to write compliant Pods under time pressure.


3. Pod Examples for Each Level

Privileged Pod (system workload)

apiVersion: v1
kind: Pod
metadata:
  name: cni-installer
  namespace: kube-system
spec:
  hostNetwork: true
  hostPID: true
  containers:
  - name: install-cni
    image: calico/cni:v3.27
    securityContext:
      privileged: true
    volumeMounts:
    - name: cni-bin
      mountPath: /host/opt/cni/bin
    - name: cni-conf
      mountPath: /host/etc/cni/net.d
  volumes:
  - name: cni-bin
    hostPath:
      path: /opt/cni/bin
  - name: cni-conf
    hostPath:
      path: /etc/cni/net.d

This Pod uses hostNetwork, hostPID, privileged, and hostPath — it only passes the Privileged level.

Baseline Pod (general workload)

apiVersion: v1
kind: Pod
metadata:
  name: web-app
spec:
  containers:
  - name: app
    image: nginx:1.27
    ports:
    - containerPort: 80
    # Runs as root (UID 0) — allowed under Baseline
    # No Seccomp — allowed under Baseline
    # No explicit capabilities drop — allowed under Baseline

This Pod has no host-level access but runs as root without dropping capabilities. It passes Baseline but fails Restricted.

Restricted Pod (hardened workload)

apiVersion: v1
kind: Pod
metadata:
  name: secure-web
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    runAsGroup: 1000
    fsGroup: 1000
    seccompProfile:
      type: RuntimeDefault
  containers:
  - name: app
    image: nginx:1.27-alpine
    ports:
    - containerPort: 8080
    securityContext:
      allowPrivilegeEscalation: false
      readOnlyRootFilesystem: true
      capabilities:
        drop: ["ALL"]
    volumeMounts:
    - name: tmp
      mountPath: /tmp
    - name: cache
      mountPath: /var/cache/nginx
    - name: run
      mountPath: /var/run
  volumes:
  - name: tmp
    emptyDir: {}
  - name: cache
    emptyDir: {}
  - name: run
    emptyDir: {}

This Pod passes the Restricted level — non-root, all capabilities dropped, privilege escalation disabled, Seccomp enabled, read-only root filesystem with writable directories via emptyDir.


4. Migration from PodSecurityPolicy (PSP) to PSS

PodSecurityPolicy was deprecated in Kubernetes 1.21 and removed in 1.25. Pod Security Standards (enforced via Pod Security Admission) replaced it.

Migration Timeline

Kubernetes 1.21: PSP deprecated, PSA introduced (alpha)
Kubernetes 1.22: PSA beta
Kubernetes 1.23: PSA stable
Kubernetes 1.25: PSP REMOVED — PSA is the only built-in option

PSP → PSS Mapping

PSP Field PSS Equivalent
spec.privileged: false Baseline level (blocks privileged)
spec.hostNetwork: false Baseline level (blocks hostNetwork)
spec.hostPID: false Baseline level (blocks hostPID)
spec.runAsUser: MustRunAsNonRoot Restricted level (runAsNonRoot: true)
spec.requiredDropCapabilities: [ALL] Restricted level (drop: ["ALL"])
spec.allowPrivilegeEscalation: false Restricted level
spec.volumes: [configMap, secret, ...] Restricted level (limited volume types)
spec.allowedCapabilities Use PSA exemptions or OPA/Gatekeeper for fine-grained control

Key Differences

Feature PSP PSS/PSA
Enforcement Admission controller (cluster-wide) Namespace labels
Granularity Per-policy, RBAC-bound Three fixed levels + exemptions
Custom rules Extensive per-field control Fixed levels — use OPA/Gatekeeper for custom
Status Removed in 1.25 Built-in, stable
Mutation Could modify Pods (inject defaults) No mutation — only validation

Warning: PSA does not mutate Pods. Unlike PSP, it won’t inject runAsNonRoot: true into your Pod spec. Your manifests must already be compliant.


5. When to Use Each Level

Level Use Case Example Namespaces
Privileged System infrastructure that needs host access kube-system, kube-node-lease, monitoring (for node exporters)
Baseline General workloads during migration or development staging, dev, namespaces being migrated from PSP
Restricted Production workloads, multi-tenant clusters, CKS exam production, tenant-a, tenant-b, any security-sensitive namespace

Decision Flowchart

Does the workload need host access (hostNetwork, hostPID, privileged)?
  ├── YES → Privileged (only in infrastructure namespaces)
  └── NO
       ├── Can it run as non-root with all caps dropped?
       │    ├── YES → Restricted ✓ (target state)
       │    └── NO → Baseline (temporary — plan migration to Restricted)
       └── Is it a third-party image you can't modify?
            ├── YES → Baseline + plan to rebuild image
            └── NO → Restricted ✓

6. Identifying Non-Compliant Pods

Check Which Level a Running Pod Violates

# Dry-run check: will this Pod pass the Restricted level?
kubectl label --dry-run=server --overwrite ns default \
  pod-security.kubernetes.io/enforce=restricted

# Check all Pods in a namespace against Restricted
kubectl get pods -n my-namespace -o json | \
  jq -r '.items[] | select(
    .spec.containers[].securityContext.allowPrivilegeEscalation != false or
    .spec.securityContext.runAsNonRoot != true
  ) | .metadata.name'

Common Violations and Fixes

Violation Root Cause Fix
pods "x" is forbidden: violates ... runAsNonRoot Container runs as root Add runAsNonRoot: true + runAsUser: 1000
violates ... capabilities Capabilities not dropped Add capabilities: drop: ["ALL"]
violates ... allowPrivilegeEscalation Default is true Set allowPrivilegeEscalation: false
violates ... seccompProfile No Seccomp profile Add seccompProfile: type: RuntimeDefault
violates ... hostPath Using hostPath volume Switch to PVC or emptyDir

7. CKS Exam Patterns

Pattern 1: Identify the PSS Level

Task: Given a Pod spec, determine which PSS level it satisfies.

Strategy:
  1. Check for hostNetwork, hostPID, privileged → if present, only Privileged
  2. Check for runAsNonRoot, drop ALL, allowPrivilegeEscalation: false,
     Seccomp profile → if all present, Restricted
  3. Otherwise → Baseline

Pattern 2: Fix a Pod to Meet Restricted

Task: A Pod is being rejected. Make it compliant with the Restricted level.

Checklist:
  □ spec.securityContext.runAsNonRoot: true
  □ spec.securityContext.seccompProfile.type: RuntimeDefault
  □ containers[*].securityContext.allowPrivilegeEscalation: false
  □ containers[*].securityContext.capabilities.drop: ["ALL"]
  □ No hostPath volumes
  □ No hostNetwork/hostPID/hostIPC
  □ No privileged: true

Summary

Concept Detail
What is PSS Three built-in security levels for Pods: Privileged, Baseline, Restricted
Privileged No restrictions — for system workloads only
Baseline Blocks known escalation vectors (hostNetwork, privileged, hostPID)
Restricted Full lockdown — non-root, drop ALL, no privilege escalation, Seccomp required
Replaces PodSecurityPolicy (removed in 1.25)
Enforcement Via Pod Security Admission (PSA) — namespace labels
Mutation PSA does NOT mutate Pods — manifests must be compliant up front
CKS target Restricted level for production namespaces

Practice Questions

Q1: A Pod has hostNetwork: true and privileged: true. Which PSS levels will accept this Pod?

Only the Privileged level.

  • Baseline rejects hostNetwork: true and privileged: true
  • Restricted also rejects them (Restricted is a superset of Baseline restrictions)
  • Only Privileged has no restrictions and will accept this Pod

These settings should only appear in infrastructure namespaces like kube-system.

Q2: What are the four mandatory security context settings required by the Restricted PSS level?

The Restricted level requires:

  1. runAsNonRoot: true — Pod or container level
  2. allowPrivilegeEscalation: false — each container
  3. capabilities.drop: ["ALL"] — each container (only NET_BIND_SERVICE can be added back)
  4. seccompProfile.type: RuntimeDefault (or Localhost) — Pod or container level

Example:

spec:
  securityContext:
    runAsNonRoot: true
    seccompProfile:
      type: RuntimeDefault
  containers:
  - name: app
    image: myapp:1.0
    securityContext:
      allowPrivilegeEscalation: false
      capabilities:
        drop: ["ALL"]

Missing any one of these four causes the Pod to be rejected under Restricted enforcement.

Q3: Your team is migrating from PodSecurityPolicy to Pod Security Standards. The existing PSP allows running as root but blocks hostNetwork and privileged containers. Which PSS level matches this policy?

Baseline.

  • Baseline blocks hostNetwork, hostPID, hostIPC, and privileged containers — matching the existing PSP
  • Baseline still allows running as root, which the PSP also allowed
  • Restricted would be too strict because it requires runAsNonRoot: true, which the current workloads don’t satisfy

Migration path:

  1. Apply Baseline enforcement to match current PSP behavior
  2. Apply Restricted in warn mode to identify non-compliant Pods
  3. Fix all workloads to be Restricted-compliant
  4. Switch enforcement to Restricted
kubectl label ns my-namespace \
  pod-security.kubernetes.io/enforce=baseline \
  pod-security.kubernetes.io/warn=restricted
Q4: A colleague says "We set PSA to Restricted, so our Pods automatically run as non-root now." Is this correct?

No, this is incorrect.

Pod Security Admission (PSA) performs validation only — it does not mutate Pods. This is a key difference from the old PodSecurityPolicy.

  • PSA rejects Pods that don’t meet the Restricted level
  • PSA does not inject runAsNonRoot: true or any other field into your Pod spec
  • If your manifest doesn’t already have runAsNonRoot: true, the Pod will be rejected, not fixed

This means all manifests, Helm charts, and CI/CD pipelines must be updated to include the required security context fields before enabling Restricted enforcement.

The workflow is:

  1. Start with warn mode to identify non-compliant manifests
  2. Fix all manifests to include required security context settings
  3. Then enable enforce mode
Q5: Which volume types are allowed under the Restricted PSS level? Why is hostPath blocked?

Allowed volume types under Restricted:

Volume Type Why Allowed
configMap Read-only configuration data, controlled by RBAC
csi CSI-managed volumes with access control
downwardAPI Pod metadata — no host access
emptyDir Ephemeral, Pod-scoped — no host access
ephemeral Generic ephemeral volumes with PVC backend
persistentVolumeClaim PVC-managed with access modes, no direct host access
projected Combines configMap, secret, downwardAPI, serviceAccountToken
secret Sensitive data, controlled by RBAC

Why hostPath is blocked:

hostPath mounts a directory from the host node’s filesystem directly into the Pod. This is dangerous because:

  1. Container escape — A Pod can mount / from the host and gain full read/write access to the node
  2. Cross-Pod data access — Pods on the same node can share data through host directories, bypassing network policies
  3. Credential theft — Mounting /etc/kubernetes/pki/ exposes cluster CA keys and certificates
  4. Persistence — Attackers can write backdoors to the host filesystem that survive Pod deletion
# DANGEROUS — blocked by Baseline and Restricted
volumes:
- name: host-root
  hostPath:
    path: /           # Full host filesystem access!

Previous Up Next
Pod Security Pod Security Pod Security Admission

Updated: