Home / Writing / k8s-admission-controllers
Kubernetes Admission Controllers
Admission controllers are a full request-processing layer inside the Kubernetes API server — not just a simple on/off setting.
If you’ve ever wondered how tools like OPA/Gatekeeper, Kyverno, or Istio’s sidecar injection actually work, admission controllers are the answer. They intercept every write request to the API server and get to either modify it or reject it before anything hits etcd.
Where they sit in the request flow
When you run kubectl apply -f pod.yaml, the API server processes the request in this order:
That third step is the interesting one. You’ve already proven who you are and that you have RBAC permission — admission controllers are the last chance to ask should this actually happen the way it was requested?
Two types: mutating and validating
Mutating admission controllers
These can rewrite the request before it’s stored. Common uses:
- Inject a sidecar container (how Istio works)
- Add default labels or annotations automatically
- Fill in missing resource limits
The request that lands in etcd may not look exactly like what you submitted.
Validating admission controllers
These can only approve or reject — no modifications. Common uses:
- Block containers that request
privileged: true - Enforce naming conventions
- Require specific labels on all deployments
- Reject images not from your approved registry
Order of execution matters
Mutating runs first, validating runs second. This is deliberate: you want all mutations to be settled before the final policy check fires.
Request
↓
Mutating Admission Controllers (can rewrite)
↓
Validating Admission Controllers (approve or deny)
↓
Stored in etcd
Built-in vs webhook-based
Built-in controllers
These ship with Kubernetes and are enabled via API server flags:
--enable-admission-plugins=NamespaceLifecycle,LimitRanger,ServiceAccount,ResourceQuota
Common built-ins you’ll see in production:
| Controller | What it does |
|---|---|
NamespaceLifecycle | Rejects requests into terminating namespaces |
LimitRanger | Enforces CPU/memory limits from LimitRange objects |
ServiceAccount | Auto-mounts service account tokens |
ResourceQuota | Blocks requests that would exceed namespace quotas |
The fact that you enable them with a flag is why people sometimes think admission controllers are “just a switch.” The built-ins are. Custom ones are not.
Webhook-based controllers
This is where the real power lives. Kubernetes can call out to an external HTTP service — your code — and let it decide.
Two webhook types map to the two controller types:
MutatingWebhookConfiguration— your service can mutate the objectValidatingWebhookConfiguration— your service approves or rejects
The flow becomes:
API Server
→ HTTPS POST to your webhook
→ your code inspects the AdmissionReview request
→ returns allowed: true/false (+ optional patch for mutating)
→ API server continues or rejects
Tools like OPA/Gatekeeper and Kyverno are just well-packaged validating webhook implementations.
Building a validating webhook
The demo repo lives at github.com/heinlinaung/k8s-webhook-demo. Here’s the shape of what you’re building.
What the API server sends you
Every webhook call is an AdmissionReview object:
{
"apiVersion": "admission.k8s.io/v1",
"kind": "AdmissionReview",
"request": {
"uid": "abc-123",
"kind": { "group": "", "version": "v1", "kind": "Pod" },
"operation": "CREATE",
"object": {
"metadata": { "name": "my-pod", "namespace": "default" },
"spec": {
"containers": [{ "name": "app", "image": "nginx:latest" }]
}
}
}
}
What you return
{
"apiVersion": "admission.k8s.io/v1",
"kind": "AdmissionReview",
"response": {
"uid": "abc-123",
"allowed": false,
"status": {
"message": "Image tag 'latest' is not allowed. Use a specific version."
}
}
}
Set allowed: true to let it through, false to block it. The message is what the user sees when their kubectl apply is rejected.
Registering the webhook
You tell Kubernetes about your webhook via a ValidatingWebhookConfiguration:
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: image-tag-validator
webhooks:
- name: validate-image-tags.example.com
rules:
- apiGroups: [""]
apiVersions: ["v1"]
resources: ["pods"]
operations: ["CREATE", "UPDATE"]
clientConfig:
service:
name: my-webhook
namespace: default
path: /validate
caBundle: <base64-encoded-ca-cert>
admissionReviewVersions: ["v1"]
sideEffects: None
failurePolicy: Fail
failurePolicy: Fail means if your webhook is down, requests are rejected. Ignore means they go through. Pick based on how critical your policy is — security policies should fail closed.
The TLS requirement
Kubernetes requires webhook endpoints to use TLS. For local development with kind, the easiest path is cert-manager to issue a self-signed cert and auto-inject the caBundle.
Real-world use cases
| Policy | Implementation |
|---|---|
Block latest image tags | Validating webhook |
| Require team labels on all resources | Validating webhook |
| Auto-inject monitoring sidecar | Mutating webhook |
| Enforce approved image registries | Validating webhook |
| Set default resource limits | Mutating webhook |
| Prevent privileged containers | Built-in + validating webhook |
The pattern: anything you’d want enforced at apply time rather than discovered later in a CI scan is a candidate for an admission webhook.
What I’d keep in mind
- TLS is non-negotiable — Kubernetes won’t call HTTP-only endpoints. Budget time for cert setup.
failurePolicyis a security decision — fail open or fail closed? Know which you’re choosing.- Webhook latency adds up — every
kubectl applywaits for your webhook to respond. Keep handlers fast. - Test with dry runs first — the
--dry-run=serverflag sends the request through admission but doesn’t persist. Use it. - Kyverno or Gatekeeper before rolling your own — custom webhooks are the right call when you need logic that policy engines can’t express. For standard enforcement rules, use the existing tools.
The demo repo has a working local setup with kind, cert-manager, and a simple image-tag validation webhook if you want to see the full wiring.
Comments