flex-auth/docs/adr/0004-inbound-caller-authentication.md
tegwick b2431e3cad
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Enforce caller-auth on flex-auth-user-engine and record the live A2 probe
Helm-adopted the kubectl-managed user-engine pin, promoted sha256:138aa347
warn then enforce. Reviewer volume needs fsGroup 65532; reviewer tokens
must use the API default audiences or TokenReview 401s on this k3s.
Live probe from user-engine-8569d7cb87-fp7px: 200 / 401 / 403. tenant-engine
is unchanged. tenancy.current.A stays 0 until that pin moves.
2026-08-19 14:00:53 +02:00

3.3 KiB

ADR 0004 — authenticate and bind authorization callers

Status: accepted (source implemented; production promotion pending)

Date: 2026-08-18

Context

POST /v1/check and /v1/batch_check accepted an asserted subject, tenant and protected-system name from any workload with network reach. NetworkPolicy limited reachability but did not establish caller identity. A caller could therefore represent another protected system and obtain an authoritative decision under the wrong policy package.

The boundary must authenticate workloads without turning flex-auth into an identity issuer, sharing a long-lived secret between services, or coupling authorization availability to an unrelated identity-provider round trip.

Decision

Use Kubernetes ServiceAccount tokens with audience flex-auth. flex-auth calls the Kubernetes TokenReview API through a separately projected reviewer token and binds the authenticated ServiceAccount principal to every resource.system in the request. Both the single and batch endpoints use the same choke point; health remains unauthenticated. The reviewer token uses the API server's default audiences so it can authenticate to TokenReview; a custom audience of https://kubernetes.default.svc 401s on this cluster (https://kubernetes.default.svc.cluster.local and k3s). Caller tokens keep audience flex-auth.

Each deployed policy instance has an explicit, exact binding. For example:

tenant-engine=system:serviceaccount:tenant-engine:tenant-engine
user-engine=system:serviceaccount:user-engine:user-engine

Unknown systems, missing or invalid tokens, audience mismatch and principal mismatch fail closed. TokenReview unavailability returns 503 rather than an authorization answer. Tokens and reviewer credentials are re-read rather than cached across rotation.

Three modes support promotion: disabled, warn, and enforce. Warn mode records the same authentication failures without logging credentials. It is a bounded migration aid, not a conformant steady state. The first production pin is warn on each independently rollable Deployment; enforce is the end state and is flipped per consumer after that consumer's warn logs are clean. Promotion follows FLEX-WP-0011. The two production pins must not be flipped together: user-engine can enforce while tenant-engine stays in warn.

Rejected alternatives

  • NetworkPolicy alone proves network position, not workload identity.
  • A shared header secret has broad replay and rotation blast radius and cannot bind a Kubernetes workload principal.
  • Application mTLS would add a separate certificate lifecycle where the cluster already has short-lived projected workload identity.
  • Using the caller token itself to invoke TokenReview would grant callers an unnecessary API permission. A narrow reviewer ServiceAccount holds only create on tokenreviews.authentication.k8s.io.

Consequences

  • Caller identity is bound once at the flex-auth ingress and cannot be swapped by changing request JSON.
  • flex-auth depends on the Kubernetes authentication API for uncached checks; an outage fails closed with 503.
  • Each new protected system needs an explicit binding and projected caller token. There is no wildcard binding.
  • Source and desired state reach A2, while the live declaration remains A0 until the immutable digest is promoted and probed.