Kubernetes runs 96% of container-orchestration workloads surveyed by the CNCF in 2023, and every one of those clusters funnels API requests — create a pod, mount a volume, expose a service — through a request pipeline that ends at an admission controller before anything is written to etcd. Skip that layer, and a single misconfigured pod spec (privileged: true, a hostPath mount to /, no resource limits) goes live with nothing to stop it. In February 2018, researchers at RedLock found Tesla's Kubernetes dashboard exposed with no authentication and no admission-time restrictions on pod privileges, letting attackers deploy cryptomining containers directly onto Tesla's infrastructure. Admission controllers are the last programmable checkpoint between "someone asked the API server to do something" and "it happened." This post breaks down how they work, why the defaults aren't enough, and how to close the gap with policy-as-code tools like OPA Gatekeeper, Kyverno, and Kubernetes' built-in Pod Security Admission.
What is a Kubernetes admission controller, and where does it run in the request pipeline?
An admission controller is a piece of code that intercepts requests to the Kubernetes API server after authentication and authorization but before the object is persisted to etcd. The pipeline runs in a fixed order: a request hits the API server, gets authenticated (who are you), gets authorized via RBAC (are you allowed to do this), then passes through mutating admission controllers (which can rewrite the request — injecting a sidecar, adding default labels), and finally through validating admission controllers (which can only accept or reject, no changes). Kubernetes has shipped built-in admission controllers like NamespaceLifecycle and LimitRanger since early releases, but dynamic admission control — webhooks you write and register yourself — arrived as a beta feature in Kubernetes 1.9 (December 2017) and reached general availability with the admissionregistration.k8s.io/v1 API in Kubernetes 1.16 (September 2019). That's the mechanism behind OPA Gatekeeper, Kyverno, and most commercial policy engines.
Why isn't Kubernetes secure by default without admission control?
Kubernetes ships permissive by default because the API server's built-in checks stop at syntax and RBAC, not intent — a request to run a privileged, host-network pod with a hostPath mount to / is syntactically valid and, if RBAC allows pods/create, fully authorized. Nothing in a stock cluster asks whether that pod should run that way. This is why PodSecurityPolicy existed for years as an admission-time gatekeeper, and why its removal mattered: PSP was deprecated in Kubernetes 1.21 (April 2021) and fully removed in Kubernetes 1.25 (August 2022), leaving a gap that Kubernetes' own maintainers expected teams to fill with either the replacement Pod Security Admission controller or a third-party policy engine. Clusters upgraded to 1.25+ without adopting a replacement lost pod-level security enforcement entirely — not as a hypothetical, but as a documented breaking change teams had to actively remediate.
What's the difference between Pod Security Admission, OPA Gatekeeper, and Kyverno?
Pod Security Admission (PSA) is Kubernetes' built-in, no-install replacement for PSP, enforcing one of three fixed profiles — privileged, baseline, or restricted — at the namespace level via a label like pod-security.kubernetes.io/enforce=restricted. It reached stable status in Kubernetes 1.25 and covers pod-level hardening (no privilege escalation, no host namespaces, dropped capabilities) but nothing else — it can't tell you an image is unsigned or a deployment lacks an SBOM. OPA Gatekeeper, a CNCF project built on Open Policy Agent and first released as v3.0 in April 2019, uses the Rego policy language and ConstraintTemplate/Constraint CRDs to enforce arbitrary custom rules — block images not from an approved registry, require specific labels, cap replica counts. Kyverno, created by Nirmata and now a CNCF Incubating project, does the same job with native Kubernetes YAML instead of a separate policy language, which is why many platform teams adopt it first: no new syntax to learn, and it can also generate or mutate resources (auto-inject network policies, auto-add resource limits) in addition to validating them.
What happens when an admission webhook fails, and why does failurePolicy decide the outcome?
When a webhook times out or errors, failurePolicy decides whether the request is blocked (Fail) or allowed through unchecked (Ignore) — and the default value has changed across API versions in a way that catches teams off guard. Under the older admissionregistration.k8s.io/v1beta1 API, the default failurePolicy was Ignore: if your policy engine crashed, was unreachable, or hit the webhook timeout, every request sailed through with zero enforcement. The stable v1 API (GA since Kubernetes 1.16) flipped the default to Fail, but only for configurations that explicitly use v1 — older manifests, Helm charts, and copy-pasted YAML still floating around production clusters can silently retain Ignore behavior. Compounding this, the default webhook timeout is 10 seconds (set since Kubernetes 1.14, released June 2019); a policy engine under load, a slow external call inside a webhook, or a networking hiccup in the cluster can blow past that window during exactly the kind of incident — a mass deployment, a compromised CI pipeline pushing images fast — where you most need enforcement to hold.
What real-world incidents show the cost of missing or bypassed admission control?
Beyond the 2018 Tesla cryptomining breach, CVE-2019-11247 shows how admission and authorization gaps compound: the bug let users with namespace-scoped RBAC permissions read, modify, or delete cluster-scoped custom resources they should never have touched, because the API server didn't correctly re-check resource scope before acting on the request. It was fixed in Kubernetes 1.13.9, 1.14.5, and 1.15.2 (June 2019), but any cluster running an unpatched version with CRD-based tooling — which describes most clusters running Gatekeeper or Kyverno constraints, since both use CRDs — was exposed to privilege escalation regardless of how tight the RBAC policy looked on paper. The lesson repeated across incidents like this one and the Tesla breach is consistent: admission control, RBAC, and patch currency are not independent controls you can excel at individually — a gap in one undermines enforcement in the others, and attackers specifically look for clusters where the policy engine exists on paper but isn't actually blocking the request path in practice.
How Safeguard Helps
Safeguard treats admission-layer policy as part of the same pipeline it uses to secure the rest of the software supply chain, not a separate Kubernetes-only checkbox. Our reachability analysis correlates the CVEs and misconfigurations flagged in cluster manifests and container images against actual runtime call paths, so teams triage the flaws an attacker could exploit through an exposed pod instead of chasing every finding a scanner surfaces. Griffin AI reviews admission policy changes and Kubernetes manifests alongside application code, catching cases like a failurePolicy: Ignore webhook or a namespace missing a restricted Pod Security label before they merge. Safeguard generates and ingests SBOMs for the images your admission controllers are gating, giving policy engines like Gatekeeper and Kyverno accurate provenance data to enforce against rather than relying on registry metadata alone. When a gap is found — an outdated PSP reference, a missing Pod Security label, an unpinned base image — Safeguard opens an auto-fix PR with the corrected manifest, so the fix ships as fast as the finding did.