DevOps · Kubernetes

Kubernetes Pod Security Standards

Pod Security Admission replaced PodSecurityPolicy in Kubernetes 1.25 with three namespace-level profiles — privileged, baseline, restricted — and three enforcement modes worth understanding before you flip one on.

John Kihiu12 min read

PodSecurityPolicy was removed in Kubernetes 1.25 after years of being deprecated and, frankly, disliked — the admission logic was confusing to reason about and harder to debug when it rejected a pod for reasons that weren't obvious from the error message. Pod Security Admission is what replaced it: a built-in admission controller that enforces one of three predefined profiles per namespace, using nothing more than a label. No CRDs, no separate policy objects to manage — just labels on the namespace itself.

The three profiles

Pod Security Standards define three profiles, each stricter than the last. Privileged is unrestricted — it exists so cluster-admin workloads (CNI plugins, node agents) that genuinely need broad host access have somewhere to live without special-casing every namespace. Baseline blocks the most obviously dangerous settings — privileged containers, host namespaces, most custom capabilities — while staying compatible with common container images that haven't been hardened. Restricted is the one you actually want for application workloads: it enforces current pod hardening best practice, including running as non-root, dropping all Linux capabilities by default, and requiring a defined seccomp profile.

yaml · namespace labels
apiVersion: v1
kind: Namespace
metadata:
  name: payments
  labels:
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/enforce-version: latest
    pod-security.kubernetes.io/audit: restricted
    pod-security.kubernetes.io/warn: restricted

enforce, audit, and warn are independent

Each profile can be applied in three modes at once, and they don't have to match. enforce actually rejects non-compliant pods at admission time. audit allows the pod but logs a policy violation to the audit log, useful for seeing what would break without breaking anything yet. warn allows the pod but returns a warning to whoever ran kubectl apply, visible in their terminal immediately. The practical pattern is to set warn and audit to restricted while leaving enforce at baseline or unset, watch the audit log for a release cycle to see what would actually get rejected, then flip enforce once you know nothing legitimate breaks.

Roll it out in audit mode first

Namespaces with existing workloads should not go straight to enforce: restricted. Set audit and warn first, review the audit log or your CI's kubectl output for violations over a real deploy cycle, fix what surfaces, then move enforce up. Skipping this step means finding out your restricted policy blocks a legitimate workload during an incident, not during a planned migration.

What restricted actually blocks

The restricted profile's checks map to real attack surface, not arbitrary rules. It blocks: containers running as root (runAsNonRoot must be true), any path to privilege escalation (allowPrivilegeEscalation: false is required), use of host namespaces (hostNetwork, hostPID, hostIPC must all be false), and any Linux capability beyond a small allowed set — capabilities.drop: ["ALL"] is required, with only narrowly scoped additions like NET_BIND_SERVICE permitted. It also requires a seccompProfile to be set, typically RuntimeDefault. Most of this maps directly onto pod-escape and privilege-escalation techniques that have shown up in real container breakout CVEs — the profile isn't guessing at best practice, it's closing specific doors.

yaml · restricted-compliant pod spec
apiVersion: v1
kind: Pod
metadata:
  name: api
  namespace: payments
spec:
  securityContext:
    runAsNonRoot: true
    seccompProfile:
      type: RuntimeDefault
  containers:
    - name: api
      image: registry.example.com/api:1.4.0
      securityContext:
        allowPrivilegeEscalation: false
        capabilities:
          drop: ["ALL"]

Migrating off PodSecurityPolicy

Teams still running clusters below 1.25, or that upgraded and left PSP admission webhooks half-removed, are the ones most likely to get bitten here — PSP is gone in 1.25+, full stop, and there's no compatibility shim. The Pod Security Standards profiles cover the same intent as a reasonably strict PSP but with a much smaller surface to configure: three labels per namespace instead of a cluster-wide policy object with role bindings to manage which pods can use which policy. If you're migrating an existing PSP setup, expect to spend most of the effort auditing which workloads were quietly relying on PSP gaps (a policy that was more permissive than anyone realized) rather than on the Pod Security Admission configuration itself.

Wrapping up

Pod Security Admission is a smaller, simpler mechanism than PodSecurityPolicy ever was — three profiles, three modes, applied through labels instead of a separate policy resource. The restricted profile is the one worth enforcing on any namespace running application workloads, but it's worth rolling out through audit and warn first, since the checks it enforces (non-root, no privilege escalation, no host namespaces, dropped capabilities) are exactly the settings that older or unhardened images tend to violate.

John Kihiu
Acumatica ERP Developer · Laravel Engineer

Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.