Admission Flow
Overview
Every write request to the Kubernetes API server passes through the admission chain before it is persisted to etcd. This chain enforces policy, injects defaults, and validates correctness. Understanding it is essential for debugging mysterious rejections and for building admission webhooks.
Full Admission Sequence
kubectl / controller / service
│
│ POST/PUT/PATCH /api/v1/...
▼
┌─────────────────────────────────────────────────────────┐
│ API Server │
│ │
│ 1. AUTHENTICATION │
│ ├── Bearer token (JWT) → TokenReview │
│ ├── Client certificate → x509 validation │
│ └── Anonymous (if enabled) │
│ │ identity: username, groups, extra │
│ ▼ │
│ 2. AUTHORIZATION (RBAC) │
│ ├── SubjectAccessReview: can <identity> <verb> │
│ │ <resource> in <namespace>? │
│ └── Result: Allow / Deny │
│ │ allowed │
│ ▼ │
│ 3. MUTATING ADMISSION WEBHOOKS │
│ ├── API server looks up MutatingWebhookConfiguration│
│ ├── For each matching webhook (parallel): │
│ │ ├── POST https://webhook-svc/mutate │
│ │ │ {AdmissionReview request} │
│ │ └── Response: {allowed, patch (JSONPatch)} │
│ ├── Patches applied in webhook order │
│ └── Result: mutated object │
│ │ │
│ ▼ │
│ 4. OBJECT SCHEMA VALIDATION │
│ └── OpenAPI v3 schema check (unknown fields, types) │
│ │ │
│ ▼ │
│ 5. VALIDATING ADMISSION WEBHOOKS │
│ ├── For each matching webhook (parallel): │
│ │ ├── POST https://webhook-svc/validate │
│ │ └── Response: {allowed, status (error message)} │
│ └── ANY denial → entire request rejected │
│ │ │
│ ▼ │
│ 6. PERSIST TO etcd │
│ └── Object written with resourceVersion │
└─────────────────────────────────────────────────────────┘
│
▼
201 Created / 200 OK / 422 Unprocessable Entity
Authentication Methods
# Check how a request authenticated
kubectl auth whoami
# Name: alice
# Groups: [system:authenticated]
# Service account inside cluster
# Bearer: token from /var/run/secrets/kubernetes.io/serviceaccount/token
# → API server calls TokenReview to validate JWT signature
# Client certificate (kubeconfig)
# API server validates against cluster CA
# CN = username, O = groups
# Anonymous
# Only if --anonymous-auth=true on API server (disabled in hardened clusters)
Mutating Webhook — Istio Sidecar Injection
The most common mutating webhook in production is the Istio sidecar injector. Here is the exact sequence:
kubectl apply pod.yaml
│
▼
API Server receives POST /api/v1/namespaces/production/pods
│
│ Check MutatingWebhookConfiguration "istio-sidecar-injector"
│ Rules: pods CREATE, namespaces with label istio-injection=enabled
│
▼
POST https://istiod.istio-system.svc:443/inject
Body: AdmissionReview {
request: {
uid: "abc-123",
kind: {group:"", version:"v1", kind:"Pod"},
object: <original pod JSON>,
operation: "CREATE"
}
}
▼
istiod responds:
AdmissionReview {
response: {
uid: "abc-123",
allowed: true,
patchType: "JSONPatch",
patch: base64(<JSONPatch array>)
}
}
JSONPatch adds:
- initContainers: [istio-init] (iptables rules)
- containers: [istio-proxy] (Envoy sidecar)
- volumes: [istio-envoy, istio-token]
- annotations: {sidecar.istio.io/status: ...}
MutatingWebhookConfiguration
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
name: istio-sidecar-injector
webhooks:
- name: sidecar-injector.istio.io
clientConfig:
service:
name: istiod
namespace: istio-system
path: /inject
port: 443
caBundle: <base64-encoded-CA> # must match webhook server's TLS cert
rules:
- apiGroups: [""]
apiVersions: ["v1"]
resources: ["pods"]
operations: ["CREATE"]
namespaceSelector:
matchLabels:
istio-injection: "enabled" # only inject in labelled namespaces
objectSelector:
matchExpressions:
- key: sidecar.istio.io/inject
operator: NotIn
values: ["false"]
failurePolicy: Fail # Fail = reject pod if webhook unreachable
sideEffects: None
admissionReviewVersions: ["v1"]
reinvocationPolicy: IfNeeded # re-run if another webhook modified the object
ValidatingWebhookConfiguration — Kyverno Policy
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: kyverno-resource-validating-webhook-cfg
webhooks:
- name: validate.kyverno.svc
clientConfig:
service:
name: kyverno-svc
namespace: kyverno
path: /validate/fail
caBundle: <base64-encoded-CA>
rules:
- apiGroups: ["*"]
apiVersions: ["*"]
resources: ["pods", "deployments", "statefulsets"]
operations: ["CREATE", "UPDATE"]
failurePolicy: Ignore # allow if Kyverno is down (production safety)
namespaceSelector:
matchExpressions:
- key: kubernetes.io/metadata.name
operator: NotIn
values: ["kube-system", "kyverno"]
sideEffects: None
timeoutSeconds: 10
Failure Modes and Debugging
failurePolicy: Fail vs Ignore
failurePolicy: Fail (default for most webhooks)
→ If the webhook server is unreachable or returns 5xx:
the original request is REJECTED
→ Protects against policy bypass when webhook is down
→ Risk: webhook outage blocks all pod creation
failurePolicy: Ignore
→ If webhook is unreachable: request proceeds as if webhook approved
→ Safer for availability, but policy can be bypassed during webhook downtime
→ Use for non-critical webhooks (metrics, labelling)
Common Webhook Failures
# Error: "failed calling webhook ... connection refused"
# → Webhook server pod is down
kubectl get pods -n <webhook-namespace>
kubectl logs -n <webhook-namespace> -l app=<webhook> --tail=50
# Error: "x509: certificate signed by unknown authority"
# → caBundle in WebhookConfiguration doesn't match webhook server's TLS cert
# cert-manager cainjector should keep this in sync automatically
kubectl get mutatingwebhookconfiguration <name> \
-o jsonpath='{.webhooks[0].clientConfig.caBundle}' | \
base64 -d | openssl x509 -noout -subject -issuer
# Error: "admission webhook ... timed out"
# → Webhook server is too slow (default timeout: 10s)
# Check webhook server resource usage; increase timeoutSeconds
# Dry-run to test what webhooks would do without persisting
kubectl apply -f pod.yaml --dry-run=server
# See which webhooks fired for a resource
kubectl get events -n <namespace> | grep webhook
Admission Chain Timing
Total admission chain overhead (typical):
Authentication: 1–5ms
RBAC authorization: 1–3ms
Mutating webhooks: 5–50ms per webhook (network call)
Schema validation: 1–5ms
Validating webhooks: 5–50ms per webhook (network call)
etcd write: 5–50ms (fsync)
─────────────────────────────────
Total: 20–200ms (1–3 webhooks)
Total (many webhooks): 200ms–1s
Impact: latency is additive for each webhook in the chain.
Keep webhook logic fast (<10ms). Use caching inside webhook servers.
Related
- 06 — Admission Controllers — built-in admission plugins
- 06 — Policy Enforcement — Kyverno policies
- 01 — Pod Creation Flow — admission runs in step 1
- 12 — Webhook Flow — webhook server implementation