PodSecurityPolicy Deprecation: Migration to Pod Security Admission

Table of Contents
Introduction
In April 2021, with Kubernetes 1.21, PodSecurityPolicy (PSP) was officially deprecated, marking the end of an era and the beginning of a new approach to pod security enforcement. The deprecation announcement gave teams until Kubernetes 1.25 (2022) to migrate to alternatives: Pod Security Admission (introduced in 1.23) or policy engines like Gatekeeper and Kyverno.
This mattered because PSP had been the primary pod security enforcement mechanism since Kubernetes 1.3, but its complexity, RBAC requirements, and performance issues made it difficult to use in production. The deprecation forced teams to evaluate alternatives and migrate their security policies, representing a significant operational change for many organizations.
Historical note: PSP had been in beta since Kubernetes 1.3 (2016) and never reached GA. Its deprecation in 1.21 and removal in 1.25 marked the end of a 5+ year beta feature that had become a security standard despite its limitations.
Why PodSecurityPolicy Was Deprecated
Complexity Issues
- RBAC Complexity: PSP required complex RBAC bindings that were difficult to understand and maintain.
- Confusing Semantics: Policy application and precedence rules were unclear.
- Performance Problems: PSP evaluation had performance overhead, especially with many policies.
- Limited Scope: PSP only applied to pods, not other Kubernetes resources.
Usability Problems
- Hard to Use Correctly: Most teams used PSP incorrectly or not at all.
- Namespace Limitations: PSP couldn’t be easily scoped to namespaces.
- Migration Challenges: Difficult to migrate existing policies to PSP.
- Documentation Gaps: Limited documentation and examples.
Migration Options
Option 1: Pod Security Admission (Kubernetes 1.23+)
Pod Security Admission provides namespace-scoped security profiles:
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted
Advantages:
- Simple, namespace-scoped
- Built into Kubernetes
- Standard security profiles
- Better performance
Limitations:
- Only three profiles (Privileged, Baseline, Restricted)
- Less flexible than PSP
- Requires Kubernetes 1.23+
Option 2: Gatekeeper
Gatekeeper provides flexible policy enforcement using OPA:
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: k8spspsecuritycontext
spec:
crd:
spec:
names:
kind: K8sPSPSecurityContext
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8spspsecuritycontext
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not container.securityContext.runAsNonRoot
msg := "Container must run as non-root"
}
Advantages:
- Flexible policy language (Rego)
- Can enforce policies on any resource
- Mutation support
- Audit mode
Limitations:
- Requires learning Rego
- Additional infrastructure
- More complex than Pod Security Admission
Option 3: Kyverno
Kyverno provides Kubernetes-native policy enforcement:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-non-root
spec:
validationFailureAction: enforce
rules:
- name: check-security-context
match:
resources:
kinds:
- Pod
validate:
message: "Container must run as non-root"
pattern:
spec:
containers:
- name: "*"
securityContext:
runAsNonRoot: true
Advantages:
- YAML-based policies (no Rego)
- Kubernetes-native
- Policy generation and mutation
- Easy to learn
Limitations:
- Less mature than Gatekeeper
- May have performance limitations
Comparison: PSP vs Alternatives
| Capability | PodSecurityPolicy | Pod Security Admission | Gatekeeper | Kyverno |
|---|---|---|---|---|
| Scope | Pods only | Pods only | Any resource | Any resource |
| Complexity | High (RBAC) | Low (labels) | Medium (Rego) | Low (YAML) |
| Performance | Poor | Good | Good | Good |
| Flexibility | Medium | Low (3 profiles) | High | High |
| Mutation | No | No | Yes | Yes |
| Kubernetes Native | Yes (deprecated) | Yes | No (add-on) | No (add-on) |
| Best For | Legacy clusters | Simple use cases | Complex policies | Kubernetes-native policies |
Migration Strategy
Phase 1: Assessment
- Inventory PSP Usage: Identify all PSP policies and their usage.
- Map to Alternatives: Map PSP policies to Pod Security Admission profiles or policy engine rules.
- Test in Staging: Test migration in staging environment.
- Plan Rollout: Plan gradual rollout to production.
Phase 2: Implementation
- Deploy Alternative: Deploy Pod Security Admission or policy engine.
- Create Policies: Create equivalent policies in new system.
- Enable Audit Mode: Enable audit mode to identify violations.
- Remediate Violations: Fix workloads that violate policies.
Phase 3: Migration
- Enable Enforcement: Enable enforcement in non-critical namespaces.
- Monitor: Monitor for issues and adjust policies.
- Gradual Rollout: Gradually enable enforcement in all namespaces.
- Disable PSP: Disable PSP once migration is complete.
Practical Migration Examples
PSP Policy
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: restricted
spec:
privileged: false
allowPrivilegeEscalation: false
requiredDropCapabilities:
- ALL
runAsUser:
rule: MustRunAsNonRoot
seLinux:
rule: RunAsAny
fsGroup:
rule: RunAsAny
volumes:
- 'configMap'
- 'emptyDir'
- 'projected'
- 'secret'
- 'downwardAPI'
- 'persistentVolumeClaim'
Pod Security Admission Equivalent
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted
Gatekeeper Equivalent
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: k8spspsecuritycontext
spec:
crd:
spec:
names:
kind: K8sPSPSecurityContext
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8spspsecuritycontext
violation[{"msg": msg}] {
input.review.object.spec.securityContext.privileged == true
msg := "Pod must not be privileged"
}
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
container.securityContext.allowPrivilegeEscalation == true
msg := "Container must not allow privilege escalation"
}
Getting Started with Migration
# 1. Identify PSP usage
kubectl get psp
kubectl get clusterrolebindings -o json | jq '.items[] | select(.roleRef.name | contains("psp"))'
# 2. Deploy Pod Security Admission (Kubernetes 1.23+)
# Enable feature gate
kube-apiserver --feature-gates=PodSecurity=true
# 3. Create namespace with Pod Security Admission
kubectl create namespace production
kubectl label namespace production pod-security.kubernetes.io/enforce=restricted
# 4. Test workloads
kubectl apply -f test-pod.yaml --dry-run=server
Caveats & Lessons Learned
- Timeline: PSP removal in 1.25 gives teams ~1 year to migrate; start early.
- Policy Mapping: Not all PSP policies map directly to alternatives; some require custom policies.
- Workload Updates: Some workloads may need updates to comply with new policies.
- Testing: Thoroughly test migration in staging before production.
Common Failure Modes
- “Workloads blocked”: New policies block existing workloads; use audit mode first.
- “Policy gaps”: Some PSP policies don’t have direct equivalents; create custom policies.
- “Migration delays”: Teams delay migration until 1.25; start early to avoid rush.
Conclusion
PodSecurityPolicy’s deprecation in 2021 marked a significant shift in Kubernetes pod security. While PSP had served the community for years, its complexity and limitations made it unsuitable for modern Kubernetes deployments. The deprecation forced teams to evaluate alternatives and adopt more modern, flexible policy enforcement mechanisms.
For organizations using PSP, the migration represented a necessary evolution toward better security practices. Pod Security Admission, Gatekeeper, and Kyverno offered different approaches to pod security, each with distinct advantages. Teams could now choose the right tool based on their complexity needs, flexibility requirements, and operational preferences.
The migration from PSP to modern policy engines demonstrated that Kubernetes security was evolving. What started as a simple pod security mechanism had become a comprehensive policy enforcement ecosystem. PSP’s deprecation wasn’t the end of pod security—it was the beginning of a more flexible, powerful approach to Kubernetes policy enforcement.