Safeguard
Container Security

What Are Kubernetes Admission Controllers

Kubernetes admission controllers intercept every API request before it hits etcd. Here's how validating and mutating webhooks work, what's enabled by default, and where they fail.

Vikram Iyer
Cloud Security Engineer
6 min read

Every object that reaches Kubernetes' etcd store — a Pod, a Deployment, a Service — passes through a checkpoint most engineers never look at directly: the admission control chain. Admission controllers are plugins compiled into (or registered with) the kube-apiserver that intercept requests after authentication and authorization but before persistence, and they can reject a request outright or silently rewrite it. Kubernetes shipped its first admission plugins in 2015 with v1.0, made dynamic, webhook-based admission control generally available in v1.9 (December 2017), and by v1.25 (August 2022) had removed PodSecurityPolicy entirely in favor of the built-in Pod Security Admission controller. Today a default kubeadm install runs roughly a dozen admission plugins before your YAML ever lands in the cluster, and tools like OPA Gatekeeper and Kyverno add dozens more via webhooks. Misconfigure this layer and you either block legitimate deployments or, worse, let unrestricted workloads straight through.

What Is a Kubernetes Admission Controller?

A Kubernetes admission controller is a piece of code that intercepts requests to the kube-apiserver after they've been authenticated and authorized, but before the object is written to etcd. It runs as one of two types: compiled-in controllers that ship with the API server binary (like NamespaceLifecycle or LimitRanger), and dynamic admission webhooks that call out to an external HTTP service you register via a ValidatingWebhookConfiguration or MutatingWebhookConfiguration object. Compiled-in controllers are enabled with the --enable-admission-plugins flag on kube-apiserver; webhooks are cluster resources any user with sufficient RBAC permissions can create. Both act on the same request lifecycle event — a create, update, delete, or connect call to the API — and both can only affect objects going forward, not resources already stored in the cluster.

How Do Admission Controllers Intercept a Request?

Admission controllers sit at a fixed point in the API request pipeline: authentication, then authorization (typically RBAC), then admission control, then schema validation and persistence to etcd. Within that admission stage, mutating webhooks run first, in the order they're registered, and can modify the object body — injecting a sidecar container, setting default resource limits, or adding labels. Validating webhooks run second, against the already-mutated object, and can only allow or deny the request; they cannot change it. Since Kubernetes v1.14, the default webhook timeout is 10 seconds (down from 30 seconds in earlier releases), and each webhook config sets a failurePolicy of either Ignore (fail open) or Fail (fail closed) for when the webhook endpoint is unreachable. A single slow or crashed webhook server with failurePolicy: Fail on a broad NAMESPACE=* selector can block every deployment in a cluster — a failure mode that has taken down production clusters more than once when teams rolled out policy webhooks without scoping namespaceSelector or objectSelector fields.

Which Admission Controllers Are Enabled by Default?

The Kubernetes project ships a specific default list documented in the kube-apiserver reference, and it includes CertificateApproval, CertificateSigning, CertificateSubjectRestriction, DefaultIngressClass, DefaultStorageClass, DefaultTolerationSeconds, LimitRanger, MutatingAdmissionWebhook, NamespaceLifecycle, PersistentVolumeClaimResize, PodSecurity, Priority, ResourceQuota, RuntimeClass, ServiceAccount, StorageObjectInUseProtection, TaintNodesByCondition, and ValidatingAdmissionWebhook — 18 plugins as of the v1.30 reference docs. Managed offerings differ: EKS, GKE, and AKS each preconfigure slightly different subsets, and GKE's Autopilot mode adds its own mandatory validating webhooks for node-level hardening that aren't present in standard GKE. PodSecurityPolicy, which governed privileged containers, host namespace access, and volume types from v1.3 through v1.20, was deprecated in v1.21 (April 2021) and removed in v1.25 (August 2022), replaced by the namespace-labeled Pod Security Admission controller with its three built-in levels: privileged, baseline, and restricted.

Why Did PodSecurityPolicy Get Removed?

PodSecurityPolicy was removed because its authorization model was fundamentally hard to reason about: policies were selected implicitly through RBAC bindings rather than applied explicitly to a namespace, so predicting which policy would apply to a given pod required tracing every RoleBinding and ClusterRoleBinding a service account touched. SIG Auth flagged this design as a long-term maintenance and usability problem as early as 2019, froze new PSP features, and shipped Pod Security Admission as a built-in, namespace-label-driven replacement in v1.22 (August 2021), moving it to stable in v1.25. The replacement model requires only a pod-security.kubernetes.io/enforce label on a namespace set to privileged, baseline, or restricted — no RBAC indirection. Clusters that never migrated off PSP before v1.25 lost pod-level security enforcement entirely on upgrade unless they'd already adopted Pod Security Admission, OPA Gatekeeper, or Kyverno, which is why the Kubernetes deprecation guide gave operators a full four-minor-version runway (1.21 to 1.25, roughly 16 months) to migrate.

What Security Risks Come From Misconfigured Admission Controllers?

Misconfigured admission controllers create two opposite failure modes: overly permissive webhooks that let dangerous workloads through, and overly broad fail-closed policies that take down unrelated deployments. On the permissive side, a restricted Pod Security Admission label that's applied only in enforce mode without audit and warn still allows privileged pods in any namespace that lacks the label at all — and PSA is opt-in per namespace, so a newly created namespace with no label runs unrestricted by default. CVE-2019-11247 illustrates the intercept-point risk directly: a bug in the API server let cluster-scoped requests operate on namespaced custom resources across namespace boundaries, bypassing the RBAC checks admission controllers are supposed to see enforced correctly upstream of them. On the availability side, teams running Gatekeeper or Kyverno with failurePolicy: Fail and no namespaceSelector exclusion for kube-system have bricked their own clusters when the policy engine's pod itself failed to schedule, since the webhook couldn't admit its own replacement pod. OPA Gatekeeper's own documentation explicitly recommends excluding the Gatekeeper namespace from its webhook's scope for this reason.

How Safeguard Helps

Safeguard extends admission control with runtime and supply-chain context that policy engines alone don't have. Instead of writing static Rego or Kyverno rules and hoping they cover every risky image property, teams pair admission control with Safeguard's reachability analysis to determine whether a vulnerable package flagged in an image's SBOM is actually invoked by the running application, cutting false-positive blocks at the gate. Safeguard generates and ingests SBOMs at build time so the admission layer has an accurate, signed component inventory to evaluate rather than a best-effort scan at deploy time. Griffin AI, Safeguard's security reasoning engine, triages the specific CVEs and misconfigurations surfaced during that evaluation and ranks them by exploitability. When a policy violation is real, Safeguard can open an auto-fix PR against the source Dockerfile or manifest — such as pinning a base image digest or adding a missing securityContext — so the fix ships before the next admission check runs, rather than after a deployment is blocked in production.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.