ADR-0009: App identity via projected ServiceAccount tokens¶
Status: Accepted — 003-governance-depth (2026-07) Principles served: II Source: specs/003-governance-depth/research.md G-3; specs/003-governance-depth/plan.md pinned decision 2
Context¶
The data gateway must know which app is calling before it can evaluate that app's declared scopes. Generated apps cannot be trusted to carry credentials correctly, and minting per-app secret material would create a distribution and rotation problem the platform would then own. Kubernetes already gives every workload a verifiable identity.
Decision¶
Apps authenticate to the data gateway with projected ServiceAccount tokens,
audience tend-data-gateway, mounted by the platform into each app pod.
The gateway validates each token via the Kubernetes TokenReview API
(authentication.k8s.io/v1) with a short-lived verdict cache of at most two
minutes. The reviewed username (system:serviceaccount:<ns>:<sa>) maps the
namespace to its App CR for scope lookup. No per-app secret material is
minted.
Alternatives considered¶
- OIDC/JWKS validation of SA tokens locally in the gateway — rejected: the API server owns revocation and expiry semantics, and TokenReview works identically on kind, vcluster, and GKE with zero issuer configuration.
- Per-app secrets — rejected: the pinned decision is explicit that no new secret material is minted per app; projected tokens are short-lived and platform-managed.
Consequences¶
App identity is free, rotating, and platform-issued: an app cannot claim another app's scopes without another namespace's token. The ≤2 minute verdict cache bounds both API-server load and the revocation window. TokenReview adds an API-server round trip on cache miss, accepted at internal-app volume. The audience-scoped token means it is useless against any other service. This decision covers app→gateway identity; the platform's log-query surface authenticates differently (Keycloak OIDC), and builder identity is ADR-0011.