KubernetesLXII · Pod Security StandardsPod Security Standards
PSS migration — moving workloads to restricted
What you'll learn
- Apply the systematic migration from no PSS to `enforce: restricted`
- Use audit tools (kube-linter, Kyverno, polaris) to identify violations
- Fix the common violations (root, hostPath, capabilities, seccomp)
- Recognise the failure modes (over-restriction, undocumented exceptions)
Prerequisites
Verified against Kubernetes 1.34.x · kubeadm 1.34.x · kubectl 1.34.x · etcd 3.6.x · CoreDNS 1.11.x · containerd 1.7.x / 2.x · 2026-08-16
Migrating workloads to Pod Security Standards
restricted is a multi-quarter exercise. The
discipline is per-namespace, multi-step, and backed by
audit tools. This lesson covers the systematic
approach, the tools, the common fixes, and the
production failure modes.
The migration phases
Five phases, in order:
- Audit. Identify the violations across every namespace and workload.
- Categorise. Group the violations by type and effort to fix.
- Fix. Address each violation in the workload’s manifest.
- Test. Run the fixed workload in a staging
namespace with
enforce: restricted. - Enforce. Switch the namespace’s label to
enforce: restricted.
flowchart LR
A[Audit] --> B[Categorise]
B --> C[Fix]
C --> D[Test]
D --> E[Enforce]
E --> F{Next namespace}
F -->|yes| A
Each phase is per-namespace; the team tracks progress in a spreadsheet.
The audit tools
Three tools support the audit:
- kube-linter. Lints Kubernetes manifests against
a set of rules including PSS.
kube-linter lint manifest.yamlreturns violations.
kube-linter lint manifest.yaml
# error: host-network-share (deployment "api" uses host network)
# error: privilege-escalation (container "api" allows privilege escalation)
# error: run-as-non-root (container "api" does not have runAsNonRoot set)
- Kyverno. Has a built-in policy for each PSS
profile.
kubectl apply -f pss-restricted.yamlruns the policy inAuditmode.
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: pss-restricted
spec:
validationFailureAction: Audit # warn but allow
rules:
- name: restricted
match:
resources:
kinds: ["Pod"]
validate:
message: "violates PodSecurity 'restricted:latest'"
pattern:
spec:
containers:
- securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
readOnlyRootFilesystem: true
runAsNonRoot: true
- polaris. Dashboard for workload configuration; flags PSS violations alongside other best practices.
polaris audit --audit-format=pretty
# api: 8/12 checks passing
# - runAsNonRoot: fail
# - readOnlyRootFilesystem: fail
# - allowPrivilegeEscalation: pass
The common fixes
Five patterns recur:
- Run as non-root. Add
securityContext.runAsNonRoot: trueand setrunAsUserto a non-zero UID. Update the container image’sUSERdirective if necessary.
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
- Replace
hostPathwith CSI oremptyDir. A workload that useshostPathfor cache mounts can use a CSI cache volume; for ephemeral data, useemptyDir.
volumes:
- name: cache
csi:
driver: csi.example.com
volumeAttributes:
type: cache
- Drop ALL capabilities. Add
capabilities.drop: ["ALL"]and onlyaddwhat is needed (typicallyNET_BIND_SERVICEfor non-root binding).
securityContext:
capabilities:
drop: ["ALL"]
add: ["NET_BIND_SERVICE"] # only if needed
- Add seccompProfile. Set
seccompProfile.typetoRuntimeDefault(the runtime’s default profile) orLocalhost(a custom profile).
securityContext:
seccompProfile:
type: RuntimeDefault
- Disallow privilege escalation. Set
allowPrivilegeEscalation: falseon every container, whichrestrictedrequires and which no other fix implies.
securityContext:
allowPrivilegeEscalation: false
The migration runbook
A production migration runbook:
- Audit.
kube-linter lintevery manifest in the namespace. Categorise violations by type. - Prioritise. Fix the small violations first (root, seccomp). Save the medium violations for the next sprint.
- Test. Run the fixed manifest in a staging
namespace with
enforce: restricted. - Promote. Apply the fixed manifest to prod.
- Verify. Run the audit again; verify no violations remain.
- Enforce. Set the enforce label and pin
pod-security.kubernetes.io/enforce-versionto the current release, so a later cluster upgrade cannot tighten the profile underneath a running workload.
#!/bin/bash
# migrate-pss.sh — migrate one namespace
NS="$1"
PROFILE="restricted"
# 1. Audit
echo "=== Audit for $NS ==="
kubectl get pods -n "$NS" -o yaml | kube-linter lint /dev/stdin
# 2. Set warn
kubectl label ns "$NS" "pod-security.kubernetes.io/warn=$PROFILE" --overwrite
# 3. Set audit
kubectl label ns "$NS" "pod-security.kubernetes.io/audit=$PROFILE" --overwrite
# 4. Wait for fixes (manual)
# 5. Set enforce
kubectl label ns "$NS" "pod-security.kubernetes.io/enforce=$PROFILE" --overwrite
echo "=== Migration complete for $NS ==="
Production failure modes
- Migration deadline is missed. A namespace
stays on
warn: restrictedindefinitely; the violations are not fixed. The fix is a per-quarter audit with a deadline. - A workload cannot be fixed. The migration
encounters a workload that genuinely cannot meet
restricted(e.g., a legacy binary that requires root). The fix is to document the exception, pin theenforce-version, and migrate the binary. - Over-restriction breaks a workload. The
workload is migrated to
restrictedbut fails to start. The fix is to roll back the manifest change and investigate. - The CI audit is not enforced. The CI pipeline
does not run
kube-linter; new manifests with violations are deployed. The fix is to add the audit step to CI.
The operational discipline
- Audit in CI/CD. Every manifest applied to the cluster passes the audit.
- Per-namespace migration. The team tracks each namespace’s progress.
- Quarterly review. Every namespace’s PSA labels are reviewed.
- Documented exceptions. A list of namespaces
that cannot meet
restrictedis maintained. - Auditor reports. The SIEM alerts on violations; the security team reviews the alerts weekly.
Cross-course references
- The Observability course covers the audit log and SIEM integration.
- The Linux course covers the kernel features that PSS enforces.
Quiz
Knowledge check · 4 questions
Q1. Which is the right fix for a workload that uses `hostPath: /var/cache/app` for cache mounts?
Q2. The PSS audit should run only quarterly; running it in CI/CD adds latency without security value.
Q3. Your migration to `enforce: restricted` is blocked by a legacy binary that requires root to write `/etc/myapp.conf`. The binary cannot be modified. Walk the response.
The binary is a third-party application; the vendor requires root. The manifest sets `runAsUser: 0` and writes to `/etc/myapp.conf`. The migration to `restricted` would require changing the binary or mounting `/etc` as writable (which would still be on the root filesystem). The team is stuck.
Q4. Name three of the five migration phases and explain what each one does.
Passing score: 75%. Answers are checked in this browser.
Production discipline
The PSS migration is a multi-quarter exercise. The
discipline is per-namespace, multi-step, and
audit-driven. The CI pipeline catches new violations
before they reach the cluster; the namespace label
enforces the profile at admission; the audit log
records every violation. A cluster whose namespaces
are mostly restricted and whose migration is
documented has a PSS programme that is auditable; a
cluster whose namespaces are mostly privileged or
baseline and whose migration is stalled has a
programme that is not.