PodSecurityPolicy Migration Leftover Disables PodSecurity
A single stray plugin name in a kube-apiserver flag can strip PodSecurity enforcement from every namespace at once, including kube-system, letting container specs carrying privileged: true, hostPID or hostNetwork pass admission unchecked. RBAC and NetworkPolicy provide no backstop, since neither governs container capability grants on the control plane.
Operational summary
At a glance
- Symptom
- A pod spec anywhere in the cluster sets privileged: true, hostPID: true or hostNetwork: true and is admitted without any pod-security.kubernetes.io check.
- Likely cause
- kube-apiserver started with --disable-admission-plugins=PodSecurityPolicy,PodSecurity, removing the PodSecurity admission plugin from the enabled set.
- Impact
- Every namespace, including kube-system, accepts privileged containers with no admission-time enforcement or audit trail.
- Verification signal
- Re-run the detection test and a controlled negative-path test.
- Safe correction
- Audit static kube-apiserver manifests directly for stray PodSecurity entries in --disable-admission-plugins and confirm denial with a synthetic privileged pod test.
- Rollback or recovery
- Restore the exported configuration if the new control blocks required production traffic, then narrow the policy before redeployment.
The Trap
A stray plugin name in kube-apiserver’s –disable-admission-plugins flag, carried over from a PodSecurityPolicy migration script, that removes the PodSecurity admission plugin from the enabled set cluster-wide.
The Default State
During the 1.21–1.25 PodSecurityPolicy deprecation window, migration runbooks instructed operators to strip PodSecurityPolicy from –enable-admission-plugins and add it to –disable-admission-plugins on every control-plane node. Engineers copy-pasted this instruction into the rollout that introduced the PodSecurity admission plugin and, assuming the two names referred to the same retiring feature, appended “PodSecurity” to the identical disable list. The static pod manifest at /etc/kubernetes/manifests/kube-apiserver.yaml ends up with –disable-admission-plugins=PodSecurityPolicy,PodSecurity, and kube-apiserver restarts cleanly with no warning that a second, unrelated plugin has just been switched off.
The Blast Radius
With PodSecurity absent from the enabled plugin set, kube-apiserver stops evaluating pod-security.kubernetes.io/enforce labels on any namespace, including kube-system and default. Every PodSpec is admitted unchanged: privileged: true, hostPID: true, hostNetwork: true and arbitrary hostPath mounts all pass validation. There is no rejection, no webhook denial, no distinct audit entry, because the checks that would generate them never execute. Existing NetworkPolicy and RBAC controls do not compensate, since they govern traffic and API access rather than container capability grants. The usual first symptom is a compromised sidecar escaping to the host node via a privileged mount, discovered only after lateral movement has already reached every node in the cluster.
The Lead Mechanic Fix
Audit every control-plane node’s static manifest directly rather than trusting kubeadm’s stored config: grep /etc/kubernetes/manifests/kube-apiserver.yaml for –disable-admission-plugins and confirm PodSecurity is absent from the value. Where kubeadm-managed, correct the ClusterConfiguration apiServer.extraArgs and re-run kubeadm upgrade apply to regenerate the manifest atomically; do not hand-edit the running file, since kubelet will restart kube-apiserver mid-write and can leave a truncated YAML. Confirm restoration by submitting a synthetic pod spec with privileged: true against a namespace labelled pod-security.kubernetes.io/enforce=restricted using kubectl apply –dry-run=server; the request must be denied with a PodSecurity violation message before the fix is considered complete.
Apply the safer control
Before you change production
Confirm the affected scope, export the current configuration, and test the replacement control in a non-production environment first.
Audit static kube-apiserver manifests directly for stray PodSecurity entries in --disable-admission-plugins and confirm denial with a synthetic privileged pod test.
Validate the vendor-specific syntax in official documentation before applying it.
Verify, roll back or escalate
Verify
Re-run the detection test and a controlled negative-path test. Confirm the unsafe behaviour is blocked while approved traffic still succeeds.
Rollback
Restore the exported configuration if the new control blocks required production traffic, then narrow the policy before redeployment.
Escalate
Escalate when the blast radius is uncertain, the control cannot be tested safely, or remediation requires an outage or security exception.