CKS — Pod Security Admission (PSA)
Pod Security Admission (PSA)
Pod Security Admission is the built-in admission controller that enforces Pod Security Standards. It operates at the namespace level using labels — no custom resources, no webhooks, no external dependencies. It’s been stable since Kubernetes 1.23 and is the only built-in Pod security enforcement mechanism since PSP was removed in 1.25.
1. PSA Modes
PSA supports three modes that control how violations are handled:
| Mode | Behavior | When a Pod Violates the Level |
|---|---|---|
| enforce | Reject the Pod | Pod is denied admission — it does not run |
| audit | Log the violation | Pod is allowed to run; violation recorded in the API server audit log |
| warn | Warn the user | Pod is allowed to run; user sees a warning message in kubectl output |
Pod Submission → PSA Admission Controller
├── enforce: restricted → REJECT (Pod does not run)
├── audit: restricted → ALLOW + write to audit log
└── warn: restricted → ALLOW + show warning to user
Why Three Modes?
You can combine modes for a gradual rollout strategy:
# Phase 1: See what would break without blocking anything
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/audit: restricted
# Phase 2: Enforce Baseline now, warn about Restricted
pod-security.kubernetes.io/enforce: baseline
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/audit: restricted
# Phase 3: Full Restricted enforcement
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted
Exam Tip: The CKS exam may ask you to apply PSA labels to a namespace. Know the exact label format:
pod-security.kubernetes.io/<mode>: <level>.
2. Namespace Labels
PSA is configured entirely through namespace labels. No CRDs, no YAML files, no webhook configuration.
Label Format
pod-security.kubernetes.io/<MODE>: <LEVEL>
pod-security.kubernetes.io/<MODE>-version: <VERSION>
Where:
<MODE>=enforce,audit, orwarn<LEVEL>=privileged,baseline, orrestricted<VERSION>= Kubernetes minor version (e.g.,v1.30) orlatest
Complete Namespace Example
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: v1.30
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/audit-version: v1.30
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/warn-version: v1.30
Applying Labels with kubectl
# Apply Restricted enforcement to an existing namespace
kubectl label ns production \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/audit=restricted \
pod-security.kubernetes.io/warn=restricted
# Verify labels
kubectl get ns production --show-labels
# Remove a label (if rolling back)
kubectl label ns production \
pod-security.kubernetes.io/enforce-
3. Version Pinning
Version pinning locks the PSS definitions to a specific Kubernetes version. This prevents new restrictions added in future versions from unexpectedly breaking workloads after a cluster upgrade.
# Pin to v1.30 definitions
pod-security.kubernetes.io/enforce-version: v1.30
# Use the latest definitions (whatever the cluster version supports)
pod-security.kubernetes.io/enforce-version: latest
When to Pin
| Scenario | Recommendation |
|---|---|
| Production clusters with strict change control | Pin to current version |
| Development/testing clusters | Use latest |
| After cluster upgrade | Review new PSS additions, then update pinned version |
Best Practice: Always pin in production so cluster upgrades don’t silently enforce new restrictions.
4. Cluster-Level PSA Configuration
You can set default PSA behavior for all namespaces by configuring the API server’s AdmissionConfiguration.
AdmissionConfiguration File
# /etc/kubernetes/psa/admission-config.yaml
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"
audit: "restricted"
audit-version: "latest"
warn: "restricted"
warn-version: "latest"
exemptions:
usernames: []
runtimeClasses: []
namespaces:
- kube-system
- kube-node-lease
- kube-public
Applying to the API Server
# /etc/kubernetes/manifests/kube-apiserver.yaml
apiVersion: v1
kind: Pod
metadata:
name: kube-apiserver
namespace: kube-system
spec:
containers:
- name: kube-apiserver
command:
- kube-apiserver
- --admission-control-config-file=/etc/kubernetes/psa/admission-config.yaml
# ... other flags
volumeMounts:
- name: psa-config
mountPath: /etc/kubernetes/psa
readOnly: true
volumes:
- name: psa-config
hostPath:
path: /etc/kubernetes/psa
type: DirectoryOrCreate
After modifying the static Pod manifest, the kubelet automatically restarts the API server.
# Verify the API server restarted
kubectl get pods -n kube-system | grep kube-apiserver
# Verify the admission config is loaded
kubectl logs -n kube-system kube-apiserver-controlplane | grep PodSecurity
Exam Tip: The CKS exam may ask you to configure cluster-level PSA defaults. Know the file format and the API server flag
--admission-control-config-file.
5. PSA Exemptions
Exemptions allow specific workloads to bypass PSA enforcement. This is essential for system components that need elevated privileges.
Exemption Types
| Exemption | Scope | Example |
|---|---|---|
| usernames | Specific users or service accounts | system:serviceaccount:kube-system:calico-node |
| runtimeClasses | Pods using a specific RuntimeClass | gvisor (sandbox runtime is already isolated) |
| namespaces | Entire namespaces | kube-system, kube-node-lease |
Configuration
# In AdmissionConfiguration
exemptions:
usernames:
- system:serviceaccount:kube-system:calico-node
- system:serviceaccount:kube-system:kube-proxy
runtimeClasses:
- gvisor
- kata
namespaces:
- kube-system
- kube-node-lease
- kube-public
- monitoring # Prometheus node-exporter needs hostNetwork
How Exemptions Work
Pod Admission Request
├── Is namespace exempted? → YES → Skip PSA check
├── Is user/SA exempted? → YES → Skip PSA check
├── Is runtimeClass exempted? → YES → Skip PSA check
└── None exempted → Apply PSA enforcement
Exemptions apply to all three modes (enforce, audit, warn). An exempted Pod won’t generate audit entries or warnings either.
6. Testing PSA: Rejection in Action
Step 1: Create a Restricted Namespace
kubectl create namespace secure-ns
kubectl label namespace secure-ns \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/warn=restricted
Step 2: Deploy a Privileged Pod (should be rejected)
# privileged-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: privileged-test
namespace: secure-ns
spec:
containers:
- name: test
image: nginx:1.27
kubectl apply -f privileged-pod.yaml
Expected output:
Error from server (Forbidden): error when creating "privileged-pod.yaml":
pods "privileged-test" is forbidden: violates PodSecurity "restricted:latest":
allowPrivilegeEscalation != false
(container "test" must set securityContext.allowPrivilegeEscalation=false)
unrestricted capabilities
(container "test" must set securityContext.capabilities.drop=["ALL"])
runAsNonRoot != true
(pod or container "test" must set securityContext.runAsNonRoot=true)
seccompProfile
(pod or container "test" must set securityContext.seccompProfile.type
to "RuntimeDefault" or "Localhost")
Step 3: Deploy a Compliant Pod (should succeed)
# compliant-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: compliant-test
namespace: secure-ns
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
seccompProfile:
type: RuntimeDefault
containers:
- name: test
image: nginx:1.27-alpine
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
kubectl apply -f compliant-pod.yaml
# pod/compliant-test created
7. Combining Enforce + Warn for Gradual Rollout
The recommended migration strategy uses all three modes together:
Phase 1: Audit Only (Discovery)
kubectl label ns production \
pod-security.kubernetes.io/audit=restricted \
pod-security.kubernetes.io/warn=restricted
- Existing Pods keep running
- New non-compliant Pods show warnings in kubectl
- Violations are logged in the audit log
- Review the audit log to identify all non-compliant workloads
# Check audit log for PSA violations
grep "PodSecurity" /var/log/kubernetes/audit/audit.log | \
jq '{pod: .objectRef.name, namespace: .objectRef.namespace, message: .annotations["pod-security.kubernetes.io/audit-violations"]}'
Phase 2: Enforce Baseline, Warn Restricted
kubectl label ns production \
pod-security.kubernetes.io/enforce=baseline \
pod-security.kubernetes.io/warn=restricted \
pod-security.kubernetes.io/audit=restricted
- Dangerous Pods (hostNetwork, privileged) are blocked immediately
- Pods that only violate Restricted still run but show warnings
- Developers see what needs to be fixed for Restricted compliance
Phase 3: Full Restricted Enforcement
kubectl label ns production --overwrite \
pod-security.kubernetes.io/enforce=restricted
- Only fully hardened Pods are allowed
- All violations are blocked at admission
8. PSA and Workload Controllers
PSA enforces at the Pod level, but warnings and audit entries also apply to workload controllers (Deployments, DaemonSets, Jobs, etc.) that create Pods.
# A Deployment with a non-compliant Pod template triggers a warning
kubectl apply -f deployment-non-compliant.yaml
# Warning: would violate PodSecurity "restricted:latest":
# allowPrivilegeEscalation != false ...
# deployment.apps/web created ← Deployment is created!
# But Pods will be REJECTED if enforce is set
Important: In enforce mode, the Deployment object is created, but the ReplicaSet cannot create Pods. Check ReplicaSet events:
kubectl describe rs -n production
# Events:
# Warning FailedCreate ... pods "web-abc123" is forbidden:
# violates PodSecurity "restricted:latest"
This is a common troubleshooting scenario on the CKS exam.
Summary
| Component | Detail |
|---|---|
| What is PSA | Built-in admission controller enforcing Pod Security Standards |
| Configuration | Namespace labels — no CRDs or webhooks needed |
| Modes | enforce (reject), audit (log), warn (warning) |
| Label format | pod-security.kubernetes.io/<mode>: <level> |
| Version pinning | pod-security.kubernetes.io/<mode>-version: v1.30 |
| Cluster defaults | --admission-control-config-file on API server |
| Exemptions | By username, runtimeClass, or namespace |
| Rollout strategy | Start with warn/audit → enforce Baseline → enforce Restricted |
| Key behavior | Validation only — no mutation of Pod specs |
Practice Questions
Q1: Write the kubectl command to enforce the Restricted PSS level on namespace app-ns, with warnings and audit logging also set to Restricted, pinned to version v1.30.
kubectl label namespace app-ns \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/enforce-version=v1.30 \
pod-security.kubernetes.io/audit=restricted \
pod-security.kubernetes.io/audit-version=v1.30 \
pod-security.kubernetes.io/warn=restricted \
pod-security.kubernetes.io/warn-version=v1.30
To verify:
kubectl get namespace app-ns -o yaml | grep pod-security
Expected output:
labels:
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/audit-version: v1.30
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: v1.30
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/warn-version: v1.30
Q2: You applied enforce: restricted to a namespace. A Deployment was created successfully, but no Pods are running. What's happening and how do you diagnose it?
What’s happening:
PSA enforces at the Pod level, not the Deployment level. The Deployment and ReplicaSet objects are created, but the ReplicaSet’s attempt to create Pods is blocked by PSA.
Diagnosis steps:
# 1. Check Deployment status
kubectl get deploy -n the-namespace
# NAME READY UP-TO-DATE AVAILABLE
# web 0/3 0 0
# 2. Check ReplicaSet
kubectl get rs -n the-namespace
# NAME DESIRED CURRENT READY
# web-abc123 3 0 0
# 3. Check ReplicaSet events — this shows the PSA rejection
kubectl describe rs -n the-namespace web-abc123
# Events:
# Warning FailedCreate replicaset-controller
# pods "web-abc123-xyz" is forbidden:
# violates PodSecurity "restricted:latest":
# allowPrivilegeEscalation != false ...
# 4. Fix the Deployment's Pod template to be Restricted-compliant
kubectl edit deploy web -n the-namespace
# Add required security context fields
Key insight: Always check ReplicaSet events when Pods aren’t being created. The Deployment controller won’t surface PSA errors directly.
Q3: Write an AdmissionConfiguration file that sets cluster-wide defaults to enforce Baseline and warn/audit for Restricted, exempting kube-system and monitoring namespaces.
# /etc/kubernetes/psa/admission-config.yaml
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"
audit: "restricted"
audit-version: "latest"
warn: "restricted"
warn-version: "latest"
exemptions:
usernames: []
runtimeClasses: []
namespaces:
- kube-system
- kube-node-lease
- kube-public
- monitoring
Apply by adding --admission-control-config-file=/etc/kubernetes/psa/admission-config.yaml to the API server arguments and mounting the config directory.
Q4: What is the difference between enforce and warn mode? If both are set to restricted, what happens when a non-compliant Pod is submitted?
| Mode | Action on Non-Compliant Pod |
|---|---|
| enforce | Rejects the Pod — returns a Forbidden error, Pod does not run |
| warn | Allows the Pod — returns a warning header that kubectl displays to the user |
When both are set to restricted:
kubectl apply -f non-compliant-pod.yaml
Result:
- enforce triggers first: the Pod is rejected
- warn also triggers: a warning message is shown
The user sees both the rejection error and the warning:
Warning: would violate PodSecurity "restricted:latest": ...
Error from server (Forbidden): pods "test" is forbidden:
violates PodSecurity "restricted:latest": ...
Why set both? The warn mode adds a human-readable warning for kubectl users, while the audit mode writes to the audit log for automated monitoring. Using all three modes together provides the most complete visibility:
enforce— actual enforcement (blocks Pods)warn— immediate feedback to developersaudit— historical record for security teams
Q5: A DaemonSet in kube-system runs with hostNetwork: true and privileged: true. You want to enforce Restricted on all namespaces. How do you handle kube-system?
Option 1: Exempt kube-system via cluster-level configuration (recommended)
# In AdmissionConfiguration
exemptions:
namespaces:
- kube-system
- kube-node-lease
- kube-public
This exempts kube-system entirely — all Pods in that namespace bypass PSA.
Option 2: Apply Privileged level to kube-system via namespace label
kubectl label namespace kube-system \
pod-security.kubernetes.io/enforce=privileged \
--overwrite
Namespace-level labels override cluster defaults. Setting kube-system to Privileged allows all system workloads while other namespaces enforce Restricted.
Option 3: Exempt specific service accounts
# In AdmissionConfiguration
exemptions:
usernames:
- system:serviceaccount:kube-system:calico-node
- system:serviceaccount:kube-system:kube-proxy
This is the most granular approach — only specific DaemonSet service accounts are exempted.
Recommendation: Use namespace-level exemption for kube-system. System components like CNI plugins, kube-proxy, and node-local-dns all require elevated privileges. Trying to restrict kube-system breaks core cluster functionality.
| Previous | Up | Next |
|---|---|---|
| Pod Security Standards | Pod Security | Security Contexts Deep Dive |