Pod Security Standards
Pod Security Standards (PSS) provide a built-in way to enforce security policies on pods at the namespace level. They replace the deprecated PodSecurityPolicy and offer three predefined security profiles: Privileged, Baseline, and Restricted.
What are Pod Security Standards?
Pod Security Standards define three security profiles with increasing levels of restrictions:
- Privileged - Unrestricted, allows almost everything (for system workloads)
- Baseline - Minimally restrictive, prevents known privilege escalations
- Restricted - Highly restrictive, follows current pod security best practices
Think of them as security levels: Privileged is like having admin access, Baseline is like standard user access, and Restricted is like a locked-down kiosk mode.
Security Profiles
Privileged
The unrestricted profile provides the widest range of capabilities. Use only for trusted, system-level workloads.
Allowed:
- All host namespaces
- Privileged containers
- Any volume type
- Any user/group IDs
- All capabilities
Baseline
The baseline profile prevents known privilege escalations while maintaining compatibility with most containerized applications.
Restrictions:
- No host namespaces (hostNetwork, hostIPC, hostPID)
- No privileged containers
- No hostPath volumes
- Must run as non-root (runAsNonRoot: true)
- No NET_RAW capability
Example - Baseline compliant pod:
apiVersion: v1
kind: Pod
metadata:
name: baseline-pod
spec:
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: nginx
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
Restricted
The restricted profile enforces the strongest security settings and requires significant configuration.
Additional restrictions beyond Baseline:
- Must drop ALL capabilities
- Must set runAsNonRoot: true
- Must set seccompProfile.type: RuntimeDefault or Localhost
- Must set allowPrivilegeEscalation: false
- Must set readOnlyRootFilesystem: true (if possible)
- Must set runAsUser to a non-zero value
Example - Restricted compliant pod:
apiVersion: v1
kind: Pod
metadata:
name: restricted-pod
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: nginx
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}
Enforcement Modes
Pod Security Standards can be enforced in three modes:
1. Enforce
Pods that violate the policy are rejected and cannot be created.
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: latest
2. Audit
Violations are logged but pods are allowed. Useful for gradual migration.
apiVersion: v1
kind: Namespace
metadata:
name: staging
labels:
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/audit-version: latest
3. Warn
Violations trigger warnings but pods are allowed. Best for development.
apiVersion: v1
kind: Namespace
metadata:
name: development
labels:
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/warn-version: latest
Applying Pod Security Standards
At Namespace Level
Set labels on the namespace:
kubectl label namespace production \
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=latest
Using Namespace Manifest
apiVersion: v1
kind: Namespace
metadata:
name: secure-apps
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: v1.28
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/audit-version: v1.28
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/warn-version: v1.28
At Cluster Level
Enable Pod Security Standards admission controller:
# kube-apiserver.yaml
apiVersion: v1
kind: Pod
metadata:
name: kube-apiserver
spec:
containers:
- name: kube-apiserver
command:
- kube-apiserver
- --enable-admission-plugins=PodSecurity
- --admission-control-config-file=/etc/kubernetes/admission-config.yaml
Create admission config:
# 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: "restricted"
enforce-version: "latest"
audit: "restricted"
audit-version: "latest"
warn: "restricted"
warn-version: "latest"
exemptions:
usernames: []
runtimeClasses: []
namespaces: ["kube-system", "kube-public"]
Migration Guide
Step 1: Audit Current State
Enable audit mode to see what would be blocked:
kubectl label namespace production \
pod-security.kubernetes.io/audit=restricted \
pod-security.kubernetes.io/audit-version=latest
Step 2: Fix Violations
Update pods to meet the standard. For example, add security context:
apiVersion: v1
kind: Pod
metadata:
name: my-app
spec:
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: my-app:latest
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
Step 3: Enable Enforcement
Once all pods are compliant, switch to enforce mode:
kubectl label namespace production \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/enforce-version=latest \
--overwrite
Common Violations and Fixes
Violation: Running as Root
Error:
runAsRoot: true is not allowed
Fix:
securityContext:
runAsNonRoot: true
runAsUser: 1000
Violation: Privileged Container
Error:
privileged: true is not allowed
Fix:
securityContext:
privileged: false
Violation: Host Network
Error:
hostNetwork: true is not allowed
Fix:
Remove hostNetwork: true or use a different networking approach.
Violation: Missing Capabilities Drop
Error:
capabilities must include drop: ["ALL"]
Fix:
securityContext:
capabilities:
drop:
- ALL
Violation: Missing Seccomp Profile
Error:
seccompProfile.type must be RuntimeDefault or Localhost
Fix:
securityContext:
seccompProfile:
type: RuntimeDefault
Best Practices
- Start with Baseline - Begin with baseline profile, then move to restricted
- Use audit mode first - Enable audit mode to identify issues before enforcing
- Exempt system namespaces - Exempt kube-system and other system namespaces
- Version pinning - Pin to specific PSS version for consistency
- Document exceptions - If exemptions are needed, document why
- Regular reviews - Periodically review and tighten security standards
Exemptions
You can exempt specific users, namespaces, or runtime classes:
exemptions:
usernames:
- "system:serviceaccount:kube-system:my-service"
namespaces:
- "kube-system"
- "kube-public"
runtimeClasses:
- "privileged"
Checking Compliance
Check namespace labels:
kubectl get namespace production -o jsonpath='{.metadata.labels}' | jq
Test pod creation:
# Try creating a pod that violates the policy
kubectl run test-pod --image=nginx --restart=Never -n production
# Check for warnings or rejections
kubectl get events -n production --field-selector involvedObject.name=test-pod
List all namespaces with PSS:
kubectl get namespaces -l pod-security.kubernetes.io/enforce
Troubleshooting
Pod rejected with PSS violation:
- Check namespace labels for enforcement mode
- Review the error message for specific violation
- Update pod spec to meet requirements
- Test in a namespace with warn mode first
System pods failing:
Exempt system namespaces from enforcement:
exemptions:
namespaces:
- "kube-system"
- "kube-public"
- "kube-node-lease"
See Also
- Admission Control - Overview of admission control
- Webhooks - Custom admission webhooks
- Workload Hardening - Additional security hardening
- SecurityContext - Pod security context