Pod Security Standards and Admission
Overview
Pod Security Standards (PSS) define three progressively restrictive security profiles for pods. The Pod Security Admission (PSA) controller enforces these standards at the namespace level, providing a built-in mechanism to prevent insecure pod configurations without external tools.
PSS Replaces PodSecurityPolicy
PodSecurityPolicy (PSP) was deprecated in Kubernetes v1.21 and removed in v1.25. Pod Security Admission is the built-in replacement. It is simpler, uses namespace labels instead of RBAC-bound policies, and is enabled by default in modern Kubernetes clusters.
Pod Security Standards Levels
Level Comparison Table
| Control | Privileged | Baseline | Restricted |
|---|---|---|---|
| HostProcess (Windows) | Allowed | Denied | Denied |
| Host Namespaces (hostNetwork, hostPID, hostIPC) | Allowed | Denied | Denied |
| Privileged Containers | Allowed | Denied | Denied |
| Capabilities | Unrestricted | Cannot add beyond defaults; cannot add SYS_ADMIN | Must drop ALL; only NET_BIND_SERVICE can be added |
| HostPort | Unrestricted | Limited range | Limited range |
| AppArmor | Any | RuntimeDefault or defined | RuntimeDefault or defined |
| SELinux | Any | Cannot set custom type | Cannot set custom type |
| /proc Mount Type | Any | Default only | Default only |
| Seccomp | Any | Any | RuntimeDefault or Localhost required |
| Sysctls | Any | Limited safe set | Limited safe set |
| Volume Types | Any | Restricted (no hostPath) | Further restricted |
| Privilege Escalation | Allowed | Allowed | Must be false |
| Running as Root | Allowed | Allowed | Must run as non-root |
Pod Security Admission Controller
The PSA controller is a built-in Kubernetes admission controller (enabled by default since v1.23 as beta, GA in v1.25) that enforces Pod Security Standards using namespace labels.
Enforcement Modes
PSA supports three modes that can be applied independently:
| Mode | Behavior | Label |
|---|---|---|
| enforce | Pods violating the policy are rejected | pod-security.kubernetes.io/enforce |
| audit | Violations are logged in the API server audit log, but pods are allowed | pod-security.kubernetes.io/audit |
| warn | Violations generate a warning shown to the user, but pods are allowed | pod-security.kubernetes.io/warn |
Combining Modes
Best practice is to use all three modes together. For example, enforce at baseline, warn and audit at restricted. This blocks dangerous pods while warning about pods that could be more secure.
Namespace Label Format
pod-security.kubernetes.io/<mode>: <level>
pod-security.kubernetes.io/<mode>-version: <version>Where:
<mode>isenforce,audit, orwarn<level>isprivileged,baseline, orrestricted<version>is a Kubernetes version likev1.30orlatest
Applying Pod Security Standards
Example 1: Enforce Baseline
# Label namespace to enforce baseline
kubectl label namespace my-namespace \
pod-security.kubernetes.io/enforce=baseline \
pod-security.kubernetes.io/enforce-version=latest# Or apply via namespace manifest
apiVersion: v1
kind: Namespace
metadata:
name: my-namespace
labels:
pod-security.kubernetes.io/enforce: baseline
pod-security.kubernetes.io/enforce-version: latestExample 2: Enforce Restricted
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/audit: restricted
pod-security.kubernetes.io/audit-version: latest
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/warn-version: latestExample 3: Gradual Enforcement (Recommended Strategy)
apiVersion: v1
kind: Namespace
metadata:
name: staging
labels:
# Enforce baseline -- block clearly dangerous pods
pod-security.kubernetes.io/enforce: baseline
pod-security.kubernetes.io/enforce-version: latest
# Warn and audit at restricted -- show what needs fixing
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: latestExample 4: Privileged Namespace (System Components)
apiVersion: v1
kind: Namespace
metadata:
name: kube-system
labels:
pod-security.kubernetes.io/enforce: privileged
pod-security.kubernetes.io/enforce-version: latestkube-system Namespace
System components in kube-system (kube-proxy, CNI plugins, etc.) often need privileged access. The kube-system namespace typically uses the privileged level. Never apply restricted to kube-system without careful testing.
Testing Pod Security Admission
Test 1: Baseline Enforcement
# Create a namespace with baseline enforcement
kubectl create namespace test-baseline
kubectl label namespace test-baseline \
pod-security.kubernetes.io/enforce=baseline
# This pod will be REJECTED (privileged: true violates baseline)
kubectl -n test-baseline run test-privileged \
--image=nginx --overrides='{
"spec": {
"containers": [{
"name": "nginx",
"image": "nginx",
"securityContext": {"privileged": true}
}]
}
}'
# Error: pods "test-privileged" is forbidden:
# violates PodSecurity "baseline:latest": privileged
# This pod will SUCCEED (no privileged settings)
kubectl -n test-baseline run test-normal --image=nginx
# pod/test-normal createdTest 2: Restricted Enforcement
# Create a namespace with restricted enforcement
kubectl create namespace test-restricted
kubectl label namespace test-restricted \
pod-security.kubernetes.io/enforce=restricted
# This simple pod will be REJECTED (doesn't meet restricted requirements)
kubectl -n test-restricted run test-nginx --image=nginx
# Error: pods "test-nginx" is forbidden: violates PodSecurity "restricted:latest":
# allowPrivilegeEscalation != false, unrestricted capabilities,
# runAsNonRoot != true, seccompProfile
# This fully-hardened pod will SUCCEED
cat <<'EOF' | kubectl apply -n test-restricted -f -
apiVersion: v1
kind: Pod
metadata:
name: restricted-pod
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: python:3.12-slim
command: ["python", "-m", "http.server", "8080"]
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
EOF
# pod/restricted-pod createdTest 3: Warning Mode
# Create namespace with warn at restricted
kubectl create namespace test-warn
kubectl label namespace test-warn \
pod-security.kubernetes.io/warn=restricted
# Create a pod that violates restricted
kubectl -n test-warn run test-nginx --image=nginx
# Warning: would violate PodSecurity "restricted:latest":
# allowPrivilegeEscalation != false, unrestricted capabilities,
# runAsNonRoot != true, seccompProfile
# pod/test-nginx created <-- Pod is still created!What Each Level Restricts in Detail
Baseline Level Restrictions
The Baseline policy prevents known privilege escalation vectors. Pods must not:
# All of these are DENIED under Baseline:
# 1. No privileged containers
securityContext:
privileged: true # DENIED
# 2. No host namespaces
spec:
hostNetwork: true # DENIED
hostPID: true # DENIED
hostIPC: true # DENIED
# 3. No hostPath volumes
volumes:
- name: host
hostPath: # DENIED
path: /
# 4. No adding SYS_ADMIN capability
securityContext:
capabilities:
add: ["SYS_ADMIN"] # DENIED
# 5. No Unmasked procMount
securityContext:
procMount: Unmasked # DENIED
# 6. Restricted hostPort range
ports:
- hostPort: 9999 # Limited (0-65535 range check)Restricted Level Restrictions (in Addition to Baseline)
# Everything from Baseline PLUS:
# 1. Must drop ALL capabilities (only NET_BIND_SERVICE allowed to add)
securityContext:
capabilities:
drop: ["ALL"] # REQUIRED
add: ["NET_RAW"] # DENIED (only NET_BIND_SERVICE allowed)
# 2. Must run as non-root
securityContext:
runAsNonRoot: true # REQUIRED
# OR runAsUser with non-zero value
# 3. Must disable privilege escalation
securityContext:
allowPrivilegeEscalation: false # REQUIRED
# 4. Must set seccomp profile
securityContext:
seccompProfile:
type: RuntimeDefault # REQUIRED (RuntimeDefault or Localhost)
# 5. Limited volume types (only these allowed):
# - configMap, csi, downwardAPI, emptyDir, ephemeral,
# persistentVolumeClaim, projected, secret
volumes:
- name: data
hostPath: # DENIED under Restricted
path: /dataPSS Enforcement Flow
Migration from PodSecurityPolicy to PSA
Migration Path
PodSecurityPolicy was removed in Kubernetes v1.25. If you are working with clusters that previously used PSP, here is the migration approach:
Step 1: Audit Current State
# Check if PSP is still in use
kubectl get psp 2>/dev/null
# Map PSP policies to PSS levels
# PSP with: privileged: false, runAsUser: MustRunAsNonRoot,
# allowPrivilegeEscalation: false
# Maps to: PSS Restricted
# PSP with: privileged: false (only)
# Maps to: PSS Baseline
# PSP with: privileged: true
# Maps to: PSS PrivilegedStep 2: Apply PSA Labels in Warn Mode First
# Apply warn labels to all namespaces
for ns in $(kubectl get ns -o jsonpath='{.items[*].metadata.name}'); do
kubectl label namespace $ns \
pod-security.kubernetes.io/warn=baseline \
pod-security.kubernetes.io/warn-version=latest \
--overwrite
doneStep 3: Check for Warnings
# Create test pods in each namespace and check for warnings
kubectl -n <namespace> run test --image=nginx --dry-run=server -o yaml
# Review audit logs for violations
kubectl logs -n kube-system kube-apiserver-<node> | grep "PodSecurity"Step 4: Enforce
# Once validated, switch to enforce mode
kubectl label namespace production \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/enforce-version=latest \
--overwriteCommon Exam Scenarios
Scenario 1: Label a Namespace to Enforce Restricted
# Task: Ensure all pods in namespace "secure-apps" meet the restricted standard
kubectl label namespace secure-apps \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/enforce-version=latest
# Verify
kubectl get namespace secure-apps --show-labelsScenario 2: Create a Namespace with Baseline Enforcement
apiVersion: v1
kind: Namespace
metadata:
name: baseline-ns
labels:
pod-security.kubernetes.io/enforce: baseline
pod-security.kubernetes.io/enforce-version: latest
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/warn-version: latestScenario 3: Fix a Pod That Violates Restricted Standard
# Original pod (violates restricted):
apiVersion: v1
kind: Pod
metadata:
name: web-app
spec:
containers:
- name: web
image: nginx:1.27
# Missing: seccompProfile, runAsNonRoot, capabilities, allowPrivilegeEscalation
---
# Fixed pod (meets restricted standard):
apiVersion: v1
kind: Pod
metadata:
name: web-app
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
seccompProfile:
type: RuntimeDefault
containers:
- name: web
image: nginx:1.27
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICEScenario 4: Identify Which PSS Level a Pod Meets
# Check with dry-run against a restricted namespace
kubectl label namespace test-ns \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/enforce-version=latest
# Try creating the pod -- if rejected, check the error message
kubectl -n test-ns apply -f pod.yaml --dry-run=server
# The error message tells you exactly what's wrong:
# Error: violates PodSecurity "restricted:latest":
# allowPrivilegeEscalation != false (container "web" ...)
# unrestricted capabilities (container "web" ...)
# runAsNonRoot != true (pod or container "web" ...)Quick Reference
Exam Speed Reference
Label namespace for restricted enforcement:
kubectl label namespace <ns> \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/enforce-version=latestLabel namespace for baseline + warn restricted:
kubectl label namespace <ns> \
pod-security.kubernetes.io/enforce=baseline \
pod-security.kubernetes.io/warn=restricted \
pod-security.kubernetes.io/warn-version=latestMinimum pod spec for Restricted level:
spec:
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
containers:
- securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]Check namespace labels:
kubectl get ns <namespace> --show-labelsDry-run test against a namespace:
kubectl apply -f pod.yaml -n <namespace> --dry-run=serverKey Exam Takeaways
- PSS has three levels: Privileged (unrestricted), Baseline (prevent escalation), Restricted (hardened)
- PSA has three modes: enforce (reject), audit (log), warn (user warning)
- Standards are applied via namespace labels -- no custom resources needed
- The label format is
pod-security.kubernetes.io/<mode>: <level> - The Restricted level requires:
runAsNonRoot,drop ALL,allowPrivilegeEscalation: false,seccompProfile - Always use
--dry-run=serverto test if a pod meets a namespace's PSS requirements - Use
warn+auditat a stricter level thanenforcefor gradual rollout - kube-system typically needs
privilegedlevel -- do not restrict it