The PodSecurityPolicy Deprecation
PodSecurityPolicy (PSP) was removed in Kubernetes 1.25. The replacement: Pod Security Standards (PSS) β built-in, no extra controllers needed.
Three Security Levels
| Level | What It Allows | Use Case |
|---|---|---|
| Privileged | Everything | System/infra pods (CNI, storage drivers) |
| Baseline | Sane defaults, blocks known escalations | General workloads |
| Restricted | Strictest, runs as non-root, no capabilities | Security-sensitive |
Enable Per-Namespace
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
# Enforce restricted (reject violations)
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: latest
# Warn on baseline violations (log but allow)
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/audit: restricted
---
apiVersion: v1
kind: Namespace
metadata:
name: kube-system
labels:
# System namespace needs privileged
pod-security.kubernetes.io/enforce: privilegedWhat Restricted Blocks
# β This pod will be REJECTED in a restricted namespace
apiVersion: v1
kind: Pod
spec:
containers:
- name: app
image: myapp
securityContext:
privileged: true # β Blocked
runAsUser: 0 # β Must be non-root
allowPrivilegeEscalation: true # β Blocked
volumeMounts:
- name: host
mountPath: /host
volumes:
- name: host
hostPath: # β Blocked
path: /# β
This pod PASSES restricted
apiVersion: v1
kind: Pod
spec:
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: myapp
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
readOnlyRootFilesystem: true
runAsUser: 1000Migration from PSP
# 1. Audit what would break
kubectl label ns production pod-security.kubernetes.io/warn=restricted --dry-run=server
# 2. Check violations
kubectl get events --field-selector reason=FailedCreate -A | grep "violates PodSecurity"
# 3. Fix pods, then enforce
kubectl label ns production pod-security.kubernetes.io/enforce=restrictedKyverno (Policy-as-Code Alternative)
For more granular control than built-in PSS:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-non-root
spec:
validationFailureAction: Enforce
rules:
- name: check-non-root
match:
resources:
kinds: [Pod]
namespaces: [production]
validate:
message: "Containers must run as non-root"
pattern:
spec:
containers:
- securityContext:
runAsNonRoot: true
allowPrivilegeEscalation: falseOPA Gatekeeper
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredSecurityContext
metadata:
name: must-run-as-nonroot
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
namespaces: ["production"]
parameters:
runAsNonRoot: true
allowPrivilegeEscalation: falseComparison
| Feature | Built-in PSS | Kyverno | OPA Gatekeeper |
|---|---|---|---|
| No extra install | β | β | β |
| Granularity | 3 levels only | Any policy | Any policy |
| Mutation | β | β (auto-fix) | β οΈ |
| Generate resources | β | β | β |
| Learning curve | Low | Medium | High (Rego) |
| Performance | Native (fastest) | Fast | Medium |
Recommended Strategy
- Start with PSS β label all namespaces (baseline for dev, restricted for prod)
- Add Kyverno β for policies PSS canβt express (image allowlists, label requirements)
- Audit mode first β warn before enforce
- Exempt system namespaces β kube-system, monitoring, cert-manager need privileged