Kubernetes Pod Security Standards: Admission Control, Kyverno, Gatekeeper and Runtime Hardening (2026)

Kubernetes Pod Security Standards: Admission Control, Kyverno, Gatekeeper and Runtime Hardening (2026)

Kubernetes Pod Security Standards: Admission Control, Kyverno, Gatekeeper and Runtime Hardening (2026)

Most Kubernetes clusters that get breached were not exploited through a clever kernel bug. They were breached because a pod was allowed to run as root, with a writable host mount and a full set of Linux capabilities, and nothing in the cluster had an opinion about it. The pod security standards exist to give the cluster that opinion: three named profiles that describe how much privilege a pod may request, enforced at admission time by a built-in controller.

That built-in layer is only the first of several. Since PodSecurityPolicy was removed in v1.25, teams have had to assemble their own stack from Pod Security Admission, ValidatingAdmissionPolicy, Kyverno, Gatekeeper, and runtime features such as seccomp and user namespaces. The choices overlap, and the wrong combination either blocks nothing or blocks everything on a Friday afternoon.

This article gives you a working model of that stack, the exact fields each profile checks, and a rollout sequence that does not break production.

What this covers: how the standards and Pod Security Admission work, where policy engines fit, runnable manifests, the container-escape failure modes admission cannot catch, and an audit-first rollout checklist.

Context and Background

Kubernetes has always let a pod ask for dangerous things. A container can request privileged: true, share the host network namespace, mount / from the node, or add SYS_ADMIN. The API server accepts those requests unless something rejects them, because the API is a scheduler of intent, not a judge of risk.

The first answer was PodSecurityPolicy (PSP), a cluster-wide admission controller introduced in the early days of the project. PSP was powerful but notoriously hard to use. Policies were bound to pods through RBAC, so the policy that applied depended on which service account or user created the pod, and the order of evaluation among multiple matching policies was subtle. It also mutated pods in surprising ways. PSP was deprecated in v1.21 and removed in v1.25, per the Kubernetes deprecation notes.

Its replacement split the problem in two. The Pod Security Standards (PSS) define the policy: what each profile allows. Pod Security Admission (PSA) is the built-in admission plugin that applies a profile to a namespace. PSA reached beta in v1.23 and stable in v1.25, the same release that removed PSP. The design goal was deliberate simplicity: no custom resource definitions, no per-user binding, no mutation, and no way to write a bespoke rule. You pick a level for a namespace and the plugin enforces it.

That simplicity is both the feature and the limit. PSA cannot say “images must come from our registry,” “every pod needs a team label,” or “no latest tags.” For anything beyond the three profiles you need a policy engine. In-tree, that is ValidatingAdmissionPolicy (VAP), which uses the Common Expression Language (CEL) and went stable in v1.30; we covered it in depth in ValidatingAdmissionPolicy with CEL: policy as code without webhooks. Out of tree, Kyverno and OPA Gatekeeper both run as admission webhooks, though both are now adding CEL-based paths.

A final context point: admission is a gate at the front door, not a guard inside the house. Once a pod is running, what stops a compromised process from reaching the node is the container runtime, the kernel, and the syscall filters. Hardening is therefore two problems, which this article treats separately: deciding what may be admitted, and limiting the damage of what runs.

The Pod Security Standards and Pod Security Admission, Explained

The pod security standards define three cumulative profiles. Privileged is unrestricted. Baseline blocks known privilege escalations while staying compatible with most unmodified images. Restricted follows current hardening best practice and requires explicit opt-in settings such as a non-root user and a dropped capability set. PSA applies a chosen profile per namespace using labels, in one of three modes: enforce, audit, or warn.

Layered control points for pod security standards: admission policy engines before etcd, then kubelet, container runtime, seccomp and user namespaces at runtime

Figure 1: Where each control sits in the request path. Admission plugins decide what is stored; the kubelet and runtime decide what the stored pod can actually do.

Figure 1 shows the key structural fact. Mutating admission runs first and can rewrite the object, then schema validation, then validating admission, and only then does the object land in etcd. PSA, ValidatingAdmissionPolicy, Kyverno and Gatekeeper all sit in the validating stage (Kyverno and others can also mutate earlier). After persistence the kubelet takes over, and the controls that matter there are enforced by the kernel rather than the API server.

The three profiles

Privileged is the escape hatch. It exists so that system components such as CNI plugins, storage drivers and node agents, which legitimately need host access, can live in a namespace without special-casing. A namespace labelled privileged gets no checks.

Baseline is the minimum viable guardrail. According to the official standard, it prohibits the following, among others:

  • Host namespaces: hostNetwork, hostPID and hostIPC must be unset or false.
  • Privileged containers: securityContext.privileged must be unset or false.
  • Extra capabilities: only a short allowed list may be added (for example NET_BIND_SERVICE, CHOWN, SETUID, SETGID, KILL, SYS_CHROOT); anything like SYS_ADMIN or NET_RAW is refused.
  • HostPath volumes and host ports.
  • Unsafe /proc mount types (procMount must be Default).
  • Seccomp profiles of type Unconfined, and AppArmor profiles other than the runtime default or a localhost profile.
  • SELinux options that set a custom type, user or role outside a small allowed set.
  • Sysctls outside a short list of “safe” ones.

Restricted inherits everything in Baseline and adds the settings that most people mean by “hardened”:

  • allowPrivilegeEscalation must be explicitly false, which sets the no_new_privs flag on the process.
  • runAsNonRoot must be true, and runAsUser, if set, must not be 0.
  • Containers must drop ALL capabilities and may add back only NET_BIND_SERVICE.
  • The seccomp profile type must be RuntimeDefault or Localhost, so even an unset profile is a violation.
  • Volume types are limited to configMap, csi, downwardAPI, emptyDir, ephemeral, persistentVolumeClaim, projected and secret.

The practical difference: Baseline lets most public Docker Hub images run unchanged, while Restricted usually requires editing the manifest, the image, or both. A web server that binds port 80 as root will fail Restricted until it is reconfigured to bind a high port or retain only NET_BIND_SERVICE.

Labels, modes and version pinning

PSA is configured entirely through namespace labels of the form pod-security.kubernetes.io/<mode>: <level>. The three modes behave differently, and the difference drives the rollout strategy later in this article.

Mode On violation Typical use
enforce Pod creation is rejected Final state for a namespace
audit Request allowed; violation recorded as an audit-log annotation Measuring impact without alerting developers
warn Request allowed; warning returned to the client Telling developers in kubectl and CI output

Each mode also accepts a -version label such as pod-security.kubernetes.io/enforce-version: v1.31, which pins the checks to the definition of the profile at that Kubernetes minor version. The value latest tracks the running API server. Pinning matters because the standards evolve: when a new control is added to a profile, a latest-tracking namespace can start rejecting workloads that passed yesterday after a cluster upgrade. Pinning decouples the cluster upgrade from the policy upgrade.

Here is a namespace that enforces Baseline, but warns and audits against Restricted so developers see what the next step would break:

apiVersion: v1
kind: Namespace
metadata:
  name: payments
  labels:
    pod-security.kubernetes.io/enforce: baseline
    pod-security.kubernetes.io/enforce-version: v1.31
    pod-security.kubernetes.io/audit: restricted
    pod-security.kubernetes.io/audit-version: v1.31
    pod-security.kubernetes.io/warn: restricted
    pod-security.kubernetes.io/warn-version: v1.31

PSA checks Pods directly, and it also inspects the pod template of workload resources such as Deployments, StatefulSets, DaemonSets, Jobs and CronJobs. But for those controllers the check is warn and audit only: a Deployment is not rejected, because rejecting a Deployment would not help (the ReplicaSet controller would just keep failing). The Pods themselves are rejected at creation, which you discover as a ReplicaSet with zero ready replicas and a FailedCreate event. This is the most common PSA surprise in practice.

The cluster-wide configuration and exemptions

Besides labels, PSA has a cluster-level configuration, passed to the API server via an AdmissionConfiguration file. It sets defaults for namespaces that have no labels, and exemptions by username, RuntimeClass name or namespace:

apiVersion: apiserver.config.k8s.io/v1
kind: AdmissionConfiguration
plugins:
  - name: PodSecurity
    configuration:
      apiVersion: pod-security.admission.config.k8s.io/v1
      kind: PodSecurityConfiguration
      defaults:
        enforce: "baseline"
        enforce-version: "latest"
        audit: "restricted"
        audit-version: "latest"
        warn: "restricted"
        warn-version: "latest"
      exemptions:
        usernames: []
        runtimeClasses: []
        namespaces: ["kube-system"]

Two points deserve emphasis. First, managed Kubernetes services generally do not let you edit API server flags, so on EKS, GKE and AKS you will mostly rely on namespace labels and an out-of-band way to ensure every namespace carries them. Second, exemptions are a blunt tool: a username exemption exempts everything that user creates, including anything a compromised CI credential creates. Prefer fixing the workload; if you must exempt, exempt narrowly.

What PSA does not check

PSA evaluates the pod spec against fixed rules. It does not look at image provenance, resource limits, labels, ingress rules, service accounts, or the Secrets a pod mounts. It also does not apply to resources outside the pod spec: a Service of type LoadBalancer or a ClusterRoleBinding to cluster-admin is invisible to it. Treat PSA as a floor, not a ceiling.

How a Pod Gets Judged: A Walk-through of Admission, Policy Engines and Exceptions

A pod create request is evaluated once, synchronously, inside the API server’s admission chain. PSA reads the namespace labels, compares the pod spec to the chosen profile at the pinned version, and either denies it (enforce) or lets it through with a warning header and an audit annotation (warn and audit). Everything else, such as custom rules, image checks and generated defaults, is delegated to a policy engine.

Sequence of a pod admission request through Pod Security Admission showing label lookup, evaluation and enforce versus warn outcomes

Figure 2: The Pod Security Admission decision path. Enforce returns a 403; warn and audit allow the request and leave a trail.

Figure 2 is worth internalising because it explains why a PSA failure looks different depending on where you create the object. A direct kubectl apply of a Pod gets an immediate Error from server (Forbidden) that names every violated field, for example allowPrivilegeEscalation != false and unrestricted capabilities. A Deployment applies cleanly, prints a warning, and then fails later at the ReplicaSet. In GitOps pipelines where the reconciler only reports “synced”, the second case can sit unnoticed for hours unless you alert on FailedCreate events or on ReplicaSets whose desired and ready counts diverge.

ValidatingAdmissionPolicy: in-process CEL rules

ValidatingAdmissionPolicy was introduced as a declarative alternative to validating webhooks and went stable in v1.30, according to the Kubernetes documentation. A policy declares which resources it matches and a list of CEL expressions; a separate ValidatingAdmissionPolicyBinding decides where it applies and what to do on failure (Deny, Warn or Audit validation actions). Because the expressions are compiled and evaluated inside the API server, there is no network hop, no webhook to keep available, and no certificate rotation.

Here is a policy that rejects containers using the latest tag or no tag at all, something PSA cannot express:

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
  name: disallow-latest-tag
spec:
  failurePolicy: Fail
  matchConstraints:
    resourceRules:
      - apiGroups: [""]
        apiVersions: ["v1"]
        operations: ["CREATE", "UPDATE"]
        resources: ["pods"]
  validations:
    - expression: >-
        object.spec.containers.all(c,
          c.image.contains(':') && !c.image.endsWith(':latest'))
      message: "Images must carry an explicit tag other than latest."
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
  name: disallow-latest-tag-binding
spec:
  policyName: disallow-latest-tag
  validationActions: ["Warn", "Audit"]
  matchResources:
    namespaceSelector:
      matchLabels:
        env: staging

Note the binding uses Warn and Audit only. That is the same audit-first discipline PSA encourages: ship the rule in observation mode and flip to Deny after the violations are cleaned up. This rule also has a real weakness. It matches only pods and only spec.containers; init containers, ephemeral containers and digests need explicit handling, and the colon test is crude: an untagged image from a registry with a port, such as registry:5000/app, contains a colon and passes wrongly. In production you would parse the reference more carefully.

Two extensions matter for scale. Parameterised policies (paramKind) let one policy read allowed registries or label keys from a ConfigMap or custom resource, so platform teams change data rather than code. And MutatingAdmissionPolicy, which applies the same CEL idea to defaulting and injection, is documented as stable since v1.36, having first appeared in v1.30. That removes the last common reason to run a mutating webhook for simple cases such as injecting a default seccompProfile.

Kyverno: policy as Kubernetes resources

Kyverno runs as an admission controller (and background controller) whose policies are plain Kubernetes YAML rather than Rego or CEL alone. Its distinguishing capability is that one engine covers several jobs: validate, mutate, generate resources (for example a default NetworkPolicy for each new namespace), verify image signatures, and clean up resources on a schedule. It also produces PolicyReport objects, so compliance status is queryable with kubectl.

Kyverno has been reworking its policy language. According to the release 1.16 announcement, CEL-based policy types (ValidatingPolicy, MutatingPolicy, GeneratingPolicy, DeletingPolicy and ImageValidatingPolicy) were introduced at v1beta1, with namespaced variants, and a roadmap that targets v1 in 1.17 and general availability in 1.18. The existing ClusterPolicy API keeps working and is planned to be formally deprecated only after the CEL types reach GA. If you are starting fresh, check the current release notes for the status of these types before choosing between the two APIs.

A classic ClusterPolicy that fills in the Restricted seccomp requirement for pods that omit it looks like this:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: add-default-seccomp
spec:
  rules:
    - name: set-runtime-default
      match:
        any:
          - resources:
              kinds: ["Pod"]
      mutate:
        patchStrategicMerge:
          spec:
            securityContext:
              +(seccompProfile):
                type: RuntimeDefault

The +() anchor means “add only if absent”, so existing explicit choices survive. This mutation changes the pod before validation, which can quietly turn a Restricted violation into a pass. That is useful, but it also hides drift from developers: their manifests remain non-compliant while the cluster silently fixes them. Decide deliberately which fields you auto-fix and which you want to fail loudly.

Gatekeeper: Rego constraints and audit

OPA Gatekeeper is the Open Policy Agent integration for Kubernetes. You define a ConstraintTemplate containing Rego logic, which creates a new constraint kind, and then instantiate Constraint objects that scope it. Gatekeeper’s strengths are a mature audit controller that periodically re-scans existing resources, a shared constraint library, and reuse of Rego knowledge across Terraform, API gateways and CI. Its weakness for pod hardening is verbosity: one rule means a template, a constraint, and Rego tests. Recent releases have also been adding support for CEL in templates and for synchronising ValidatingAdmissionPolicy objects; the project’s release notes are the place to confirm which versions ship those features before relying on them.

Choosing between them

The practical decision is not “which is best” but “which job is this rule.” Figure 3 turns that into a decision flow.

Decision flow for choosing Pod Security Admission, ValidatingAdmissionPolicy, Kyverno or Gatekeeper for a given pod security policy requirement

Figure 3: Pick the simplest control that can express the rule. Escalate to a webhook-based engine only when you need generation, image verification, reporting or existing Rego.

Requirement PSA VAP Kyverno Gatekeeper
Baseline / Restricted pod checks Native Hand-written Pre-built policies Library constraints
Custom field rules (labels, registries) No Yes (CEL) Yes Yes (Rego or CEL)
Mutation and defaulting No Via MutatingAdmissionPolicy Yes Yes (assign, modify set)
Generate resources No No Yes No (in core)
Verify image signatures No No Yes Via external data
Runs outside API server process No No Webhook Webhook
Failure mode if engine is down None None Depends on webhook failurePolicy Depends on webhook failurePolicy
Reports on existing resources Audit log only Audit annotations PolicyReports, background scan Audit controller

The bottom line of this matrix is operational. Anything in the API server process (PSA, VAP) cannot be “down” independently of the API server. Anything that is a webhook can be, and then your failurePolicy decides between an outage (Fail) and a silent hole (Ignore). That trade-off is the reason many teams use PSA and VAP for the non-negotiable floor and reserve Kyverno or Gatekeeper for the rules that genuinely need them.

Exceptions without losing the audit trail

Every real cluster has a handful of workloads that violate Restricted legitimately: a node-level log collector with a hostPath, a CNI agent with NET_ADMIN, a build runner that needs a privileged builder. The mistake is to answer with a namespace-wide privileged label. A better pattern is a dedicated, small, well-known namespace for each such component, labelled privileged or baseline, protected by RBAC so ordinary teams cannot deploy there, and covered by a separate policy that alerts on any new pod appearing in it.

Kyverno’s PolicyException resource, and the equivalent parameterisation in VAP bindings (an exclusion list in a ConfigMap), let you carve a named workload out of a specific rule while keeping every other rule active. Record the owner, a ticket reference and an expiry date as annotations on each exception, and review them quarterly. Exceptions without expiry dates become the permanent attack surface. We apply the same logic to the node agents discussed in Falco vs Tetragon vs Tracee runtime threat detection: the privileged detector is itself the highest-value target, so it deserves the tightest exception.

Runtime Hardening: What Admission Cannot Give You

Admission decides whether a pod spec is acceptable. It says nothing about what the processes inside do once they run. The second half of Kubernetes hardening is making the kernel an unwilling accomplice to an attacker, and the main tools are seccomp, AppArmor or SELinux, capabilities, user namespaces and sandboxed runtimes.

Seccomp: shrinking the syscall surface

Seccomp (secure computing mode) lets a process install a filter that allows or blocks individual system calls. A container that cannot call keyctl, bpf, mount or unshare has far fewer ways to turn a bug into a node compromise. The container runtime’s default profile blocks a subset of the available syscalls, chosen because they are dangerous and rarely needed by applications.

The surprise for many operators is that Kubernetes has historically run containers as Unconfined by default, which is why Restricted requires you to set seccompProfile.type: RuntimeDefault explicitly. The kubelet has a --seccomp-default option (the SeccompDefault feature) that makes RuntimeDefault the default for all workloads on a node; it graduated to stable in v1.27. Enabling it cluster-wide is one of the highest-return, lowest-effort hardening steps available, but test first: a minority of applications, typically those using unusual syscalls or older libraries, fail under the default filter.

For tighter control, a Localhost profile is a JSON file placed on each node under the kubelet’s seccomp directory and referenced by relative path. Generate these from observation (for example by recording syscalls in a test environment) rather than writing them by hand, and distribute them through a DaemonSet or the Security Profiles Operator, since a pod that references a missing profile will not start.

AppArmor, SELinux and capabilities

AppArmor and SELinux are mandatory access control systems that confine what files, sockets and operations a process may touch regardless of its UID. Kubernetes exposes AppArmor through a first-class securityContext.appArmorProfile field, which replaced the older annotation approach and is stable from v1.30. SELinux is configured with seLinuxOptions; PSS Baseline restricts which SELinux types are allowed so a pod cannot pick one that disables confinement. Which of the two is available depends on your node OS: AppArmor on Debian and Ubuntu families, SELinux on RHEL-family distributions.

Linux capabilities split root’s power into pieces. The Restricted profile’s “drop ALL, add back at most NET_BIND_SERVICE” rule is the single most effective pod-spec control for limiting a compromised process. Dangerous capabilities include SYS_ADMIN (a near-root catch-all), NET_RAW (packet crafting and ARP spoofing), SYS_PTRACE and DAC_READ_SEARCH. Dropping them breaks surprisingly little in modern applications.

User namespaces: root in the container is not root on the node

The deepest structural fix for container escape is to make container root a non-root user on the host. A user namespace maps UIDs inside the container to an unprivileged range outside it, so a process that breaks out of the container arrives on the node as a user with no special rights. Kubernetes exposes this with spec.hostUsers: false. The feature went to general availability in v1.36, per the release announcement index, and it requires a compatible kernel, container runtime and filesystem support for ID-mapped mounts. We cover the mechanics and runtime requirements in Kubernetes user namespaces and pod hardening against container escape.

User namespaces change the threat model for several high-profile escape classes. A vulnerability that depends on host root through a bind-mounted file descriptor, for instance, becomes far less useful when “root” maps to UID 65536 or above on the node. Not every volume type or storage driver supports ID-mapped mounts, so inventory your storage classes before rolling out.

Sandboxed runtimes and RuntimeClass

For untrusted or multi-tenant workloads, a shared kernel is the problem itself. RuntimeClass lets a pod request a different OCI runtime, such as gVisor (a user-space kernel that intercepts syscalls) or Kata Containers (a lightweight VM per pod). The cost is real: syscall-heavy workloads slow down under gVisor, and Kata adds memory overhead and startup latency; benchmark your own workload instead of trusting generic numbers. PSA can exempt a RuntimeClass name, which is one legitimate use of that exemption: a pod that runs inside a microVM can safely request privileges that would be dangerous on the shared kernel. Choose the runtime itself with care, as discussed in container runtime security hardening with containerd, CRI-O and Podman.

A Restricted-compliant workload, end to end

This Deployment passes Restricted and shows every field in one place. It also adds a read-only root filesystem and resource limits, which are not part of the standard but are good practice:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: orders-api
  namespace: payments
spec:
  replicas: 3
  selector:
    matchLabels: { app: orders-api }
  template:
    metadata:
      labels: { app: orders-api }
    spec:
      automountServiceAccountToken: false
      hostUsers: false
      securityContext:
        runAsNonRoot: true
        runAsUser: 10001
        runAsGroup: 10001
        fsGroup: 10001
        seccompProfile:
          type: RuntimeDefault
      containers:
        - name: api
          image: registry.example.com/orders-api:1.8.2
          ports:
            - containerPort: 8080
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            capabilities:
              drop: ["ALL"]
          resources:
            requests: { cpu: 100m, memory: 128Mi }
            limits: { memory: 256Mi }
          volumeMounts:
            - name: tmp
              mountPath: /tmp
      volumes:
        - name: tmp
          emptyDir: {}

Two details trip people up. readOnlyRootFilesystem is not checked by PSS, so a pod can pass Restricted and still be writable; that is the kind of rule you add with VAP or Kyverno. And automountServiceAccountToken: false removes a credential that attackers routinely harvest after a pod compromise, which makes it a cheap defence that has nothing to do with the standards at all. For the identity side of the same problem, see Kubernetes Pod Certificates versus SPIFFE/SPIRE and cert-manager.

Trade-offs, Gotchas, and What Goes Wrong

Hardening fails in predictable ways, and most of them are about process rather than technology.

Enforcing before measuring. The most common failure is flipping enforce: restricted on a busy namespace and discovering that a vendor Helm chart, an operator-managed workload, or a cron job that runs once a month cannot comply. Because workload controllers fail asynchronously, the outage appears as pods that never start rather than as a deploy error. Always run warn and audit first, as Figure 4 shows later.

Version drift. Namespaces labelled latest can start rejecting workloads after a control-plane upgrade if the profile gained a control. Pin versions and update the pin as a separate, reviewed change.

Webhook availability. Kyverno and Gatekeeper add a network dependency to every pod creation. If the webhook is down and failurePolicy is Fail, the cluster cannot create pods, including the pods that would restore the webhook. If it is Ignore, policy silently stops applying. Exclude the policy engine’s own namespace and kube-system from its webhooks, run multiple replicas with anti-affinity and a PodDisruptionBudget, and alert when webhook latency or error rate climbs. Our guide to graceful shutdown and PodDisruptionBudgets covers the budget side.

Mutation hiding non-compliance. As noted, auto-fixing a field means the source manifest stays wrong. GitOps drift detection may also fight the mutation, producing endless “out of sync” states. Prefer fixing source manifests and use mutation as a bridge, not a destination.

Standards are a floor, not a proof of safety. A pod that passes Restricted can still mount a service account token with cluster-admin, talk to the node metadata endpoint, run a vulnerable image, or exhaust node memory. PSS says nothing about NetworkPolicy, RBAC, resource limits or supply chain. Pair them with network segmentation (see Cilium sidecarless service mesh and eBPF) and with vulnerability triage using SBOM and VEX.

Kernel and runtime bugs bypass everything. Admission controls assume the container boundary holds. Runtime and kernel vulnerabilities (runc breakouts are a recurring example class) can defeat a perfectly compliant pod. Mitigations are patching the runtime promptly, user namespaces, seccomp, and sandboxed runtimes for untrusted code. Detection through runtime tools complements prevention.

Windows and special nodes. The standards include Windows-specific controls such as HostProcess containers, and some controls only apply meaningfully on Linux. Mixed clusters need per-node-pool thinking.

Compliance theatre. A dashboard showing 100 percent pass can mean exemptions swallowed the interesting workloads. Count exemptions as a first-class metric.

Practical Recommendations

Treat the whole stack as three layers with different owners. The platform team owns the floor: PSA labels on every namespace, enforced through automation so a new namespace cannot exist without them. Security owns the custom rules, expressed in the simplest engine that works. Application teams own their manifests and fix violations that warn and audit surface.

Start with the decision in Figure 3: use PSA for the three profiles, VAP for single-object custom rules, and Kyverno or Gatekeeper where you need generation, signature verification, reporting or existing Rego. Resist running two webhook engines in parallel; the overlap produces conflicting denials and double the latency.

Audit-first rollout flow for Kubernetes pod security standards from inventory to enforce, custom policy and runtime hardening

Figure 4: An audit-first rollout. Each loop produces data before it produces an outage.

The rollout in Figure 4 works because each step is reversible until enforcement is on. Warn and audit give two feedback channels: developers see warnings in their terminal and CI, while platform teams query audit logs for the pod-security.kubernetes.io/audit-violations annotation (visible only if your audit policy logs at Metadata level or higher). Batch the fixes by cause, because a handful of causes usually explain most violations: missing seccompProfile, missing allowPrivilegeEscalation: false, root users, and un-dropped capabilities.

Checklist to adopt this week:

  • Label every namespace with warn and audit at restricted, version-pinned to your current minor.
  • Put system components in dedicated, RBAC-protected namespaces labelled privileged and alert on new pods there.
  • Enable kubelet --seccomp-default in a staging node pool and watch for crashes.
  • Move user-facing namespaces to enforce: baseline first, then restricted per namespace as violations reach zero.
  • Add VAP bindings in Warn mode for image tags, registries and readOnlyRootFilesystem.
  • Set automountServiceAccountToken: false by default; opt in per workload.
  • Pilot hostUsers: false on a non-critical Deployment once your runtime and kernel support it.
  • Review every exception quarterly and delete expired ones.
  • Alert on ReplicaSets that cannot create pods, not only on failed deploys.

Frequently Asked Questions

What are the pod security standards in Kubernetes?

They are three cumulative policy profiles defined by the Kubernetes project: Privileged (no restrictions), Baseline (blocks known privilege escalations such as host namespaces, privileged containers and hostPath volumes) and Restricted (adds non-root execution, dropped capabilities, no privilege escalation and a seccomp profile). The standards are only a specification. The built-in Pod Security Admission plugin enforces them, and other tools such as Kyverno can implement the same checks.

What replaced PodSecurityPolicy?

PodSecurityPolicy was deprecated in v1.21 and removed in v1.25. Pod Security Admission, which enforces the Pod Security Standards through namespace labels, became stable in the same release and is its direct replacement. PSA is intentionally simpler: it has no custom policies, no mutation and no per-user binding. Teams needing more flexibility use ValidatingAdmissionPolicy, Kyverno or Gatekeeper alongside or instead of it.

Should I use Kyverno, Gatekeeper or ValidatingAdmissionPolicy?

Use the simplest tool that can express the rule. PSA covers the standard profiles, and ValidatingAdmissionPolicy handles custom validation of a single object in-process without a webhook. Choose Kyverno when you need generation, image signature verification or policy reports with YAML-native authoring. Choose Gatekeeper when you already invest in Rego or need its mature audit controller. Avoid running multiple webhook engines for the same rules.

Does Pod Security Admission block Deployments?

Not directly. For workload resources, PSA warns and audits against the pod template but does not reject the Deployment itself. The enforcement happens when the controller creates Pods, which are then denied. You see this as a ReplicaSet that never reaches its desired count and FailedCreate events naming the violated controls. Monitor for those events, and test with kubectl apply --dry-run=server to surface warnings before merging.

Is seccomp enabled by default in Kubernetes?

Not for pods by default. Without a configured profile, containers run unconfined, which is why the Restricted standard requires seccompProfile.type set to RuntimeDefault or Localhost. You can make the runtime’s default profile apply to all workloads on a node by enabling the kubelet --seccomp-default option, stable since v1.27. Test applications first, because a few fail under the default syscall filter.

Are user namespaces required for pod security?

No, they are an additional layer, not part of the standards. Setting hostUsers: false makes container root map to an unprivileged host user, reducing the impact of container escapes. The feature reached general availability in v1.36 but needs compatible kernel, runtime and ID-mapped mount support, so verify your nodes before enabling it. Combine them with Restricted settings and seccomp rather than using them as a replacement.

Further Reading

By Riju — about

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *