Skip to main content
RunBook Academy

KubernetesLVII · AuthenticationAuthentication

Webhook authentication — TokenReview and external identity

Advanced⏱ ~14 minkubectlWebhook service

What you'll learn

  • Explain how the API server calls a webhook service for TokenReview
  • Identify the use cases where webhook authentication is the right choice
  • Implement a webhook authenticator (HTTPS service, TokenReview request/response)
  • Operate the webhook with the right `failurePolicy` and observability

Prerequisites

Verified against Kubernetes 1.34.x · kubeadm 1.34.x · kubectl 1.34.x · etcd 3.6.x · CoreDNS 1.11.x · containerd 1.7.x / 2.x · 2026-08-16

Not yet marked complete on this device.

Webhook authentication is the most general authentication primitive Kubernetes supports. Instead of recognising credentials itself, the API server forwards the token to an external service and asks: is this token valid? The service returns either an authenticated UserInfo or an error. This lesson covers the implementation, the use cases, and the operational risks.

How webhook authentication works

The API server is configured with a webhook endpoint:

# AuthenticationConfiguration
apiVersion: apiserver.config.k8s.io/v1
kind: AuthenticationConfiguration
webhook:
- cacheTTL: 5m
  service:
    url: https://auth.example.com/tokenreview
    caBundle: <base64-encoded CA>
  failurePolicy: Deny
  audience: kubernetes
  anonymous: false

For each bearer token presented to the API server, the authenticator POSTs a TokenReview to the webhook:

POST https://auth.example.com/tokenreview
Content-Type: application/json

{
  "apiVersion": "authentication.k8s.io/v1",
  "kind": "TokenReview",
  "spec": {
    "token": "eyJhbGciOiJSUzI1NiIs...",
    "audiences": ["kubernetes"]
  }
}

The service responds with:

{
  "apiVersion": "authentication.k8s.io/v1",
  "kind": "TokenReview",
  "status": {
    "authenticated": true,
    "user": {
      "username": "alice@example.com",
      "groups": ["dev", "oncall"]
    }
  }
}

Or, if the token is invalid:

{
  "status": {
    "authenticated": false,
    "error": "token expired"
  }
}
sequenceDiagram
    participant W as Workload
    participant AS as API server
    participant WH as Webhook service
    W->>AS: GET /api (Authorization: Bearer JWT)
    AS->>AS: Try each authenticator
    AS->>WH: POST /tokenreview (TokenReview spec)
    WH->>WH: Validate JWT signature + audience + claims
    WH->>AS: TokenReview status (authenticated: true, UserInfo)
    AS->>AS: Apply RBAC
    AS->>W: 200 OK (RBAC allowed) or 403 (denied)

The failurePolicy controls what happens when the webhook is unreachable. Deny (the safe default) means a webhook failure blocks authentication; Allow means a webhook failure accepts the token as authenticated (with system:anonymous UserInfo). Most production clusters use Deny.

Use cases

Three places webhook authentication is the right choice:

  1. Custom IdP — the cluster is in an environment with a non-OIDC IdP (e.g., a custom SAML implementation, an internal Vault, or a homegrown token issuer). The webhook service translates the IdP’s tokens into UserInfo.
  2. Vault — the cluster authenticates with Vault’s service account tokens. Vault issues a JWT, the webhook validates the JWT against Vault’s public key, and returns UserInfo.
  3. SPIFFE / SPIRE — workloads have SPIFFE identities (SVIDs). The webhook validates the SVID and returns UserInfo based on the SPIFFE ID. This is the canonical pattern for zero-trust workload identity.

A standard OIDC integration does not need a webhook — the API server handles OIDC natively. The webhook is for the cases that OIDC cannot express.

Implementing a webhook

The webhook is an HTTPS service that accepts TokenReview. A minimal implementation in Go:

func tokenReview(w http.ResponseWriter, r *http.Request) {
    var tr authv1.TokenReview
    if err := json.NewDecoder(r.Body).Decode(&tr); err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    // Validate the token against the IdP's JWKS
    userInfo, err := validateToken(tr.Spec.Token, tr.Spec.Audiences)
    if err != nil {
        json.NewEncoder(w).Encode(authv1.TokenReview{
            Status: authv1.TokenReviewStatus{
                Authenticated: false,
                Error:         err.Error(),
            },
        })
        return
    }
    json.NewEncoder(w).Encode(authv1.TokenReview{
        Status: authv1.TokenReviewStatus{
            Authenticated: true,
            User:          userInfo,
            Audiences:     tr.Spec.Audiences,
        },
    })
}

The webhook must:

  • Verify the TLS certificate of the API server (the CA bundle is in the configuration).
  • Validate the JWT against the IdP’s JWKS.
  • Check the audience claim.
  • Check the expiry.
  • Return a UserInfo with a unique username (or [email protected]-style) and the relevant groups.

Operating the webhook

Operational discipline:

  • The webhook must be highly available. A single webhook Pod is a single point of failure. Run 3+ replicas behind a Service.
  • The webhook must be observable. Every TokenReview should be logged with latency, success rate, and error reason. A slow webhook adds latency to every API request.
  • The webhook must have a cache. A webhook that re-validates every JWT on every request is a bottleneck. The API server caches for cacheTTL, but the webhook itself may need an upstream cache.
  • The webhook must be auditable. Every authentication decision should be logged. The IdP’s audit log is the source of truth for who has access.

Production failure modes

  1. failurePolicy: Allow. A webhook outage makes the cluster fully open. The fix is Deny.
  2. Webhook slow or unreachable. The API server times out the TokenReview, the request fails, the cluster is unavailable. The fix is HA + SLO on the webhook.
  3. Cache too long. A 5-minute cache means a revoked token is accepted for up to 5 minutes. The fix is to reduce cacheTTL for high-value tokens.
  4. Webhook returns wrong username. A user is authenticated but their username is system:anonymous (the default fallback). The fix is to validate the response shape in the webhook.

Cross-course references

  • The Observability course covers the audit log entries for webhook authentication failures.
  • The Linux course covers TLS and JWKS that the webhook relies on.

Quiz

Knowledge check · 4 questions

  1. Q1. Which `failurePolicy` value for webhook authentication is the dangerous default that makes the cluster fully open when the webhook is unreachable?

  2. Q2. The API server's webhook cache (`cacheTTL`) means a token revoked in the IdP is rejected immediately by the cluster.

  3. Q3. Your cluster uses webhook authentication against an internal Vault. A developer leaves the company and HR revokes their Vault token at 14:00. The developer attempts `kubectl get pods` at 14:03 and is authenticated. Why, and what is the revocation window?

    The webhook cache `cacheTTL` is set to 5 minutes. The developer authenticated at 13:58 (before revocation). The cache for that token has 4 minutes remaining. The developer attempts requests at 14:02, 14:03, 14:04, 14:05 — all succeed until the cache expires at 14:03.

  4. Q4. Name three use cases where webhook authentication is the right choice over OIDC or projected tokens.

Passing score: 75%. Answers are checked in this browser.

Production discipline

Webhook authentication is the right primitive when OIDC is insufficient — when the identity source is Vault, SPIFFE, a custom IdP, or a token-exchange system. The discipline is: failurePolicy: Deny, HA replicas, observable with latency + error metrics, audited through the SIEM, and a cache tuned to the revocation SLA. A cluster that uses webhook authentication with failurePolicy: Allow is fully open to any attacker who can disrupt the webhook. A cluster that uses webhook authentication with failurePolicy: Deny and a 1-minute cache has a revocation window of 1 minute, which is the standard production target.