fix(vault): authenticate auth-api and agents-api with Kubernetes auth - #758
Conversation
auth-api and agents-api took their Vault session token from a file the vault-agent injector wrote once at pod init (agent-pre-populate-only, no renewing sidecar), read it into SPRING_CLOUD_VAULT_TOKEN in the container command, and ran Spring Cloud Vault with the default TOKEN authentication. Vault's kubernetes mount issues a 1 h token, which the Spring Cloud Vault session manager renewed hourly — but only up to the mount's max_lease_ttl of 2764800 s (32 days). Past that cap renewal fails and Spring Vault discards the token and attempts a fresh login; TOKEN authentication has no login mechanism, so the token stayed dead for the remaining life of the pod and every Vault call returned "403 Forbidden: permission denied / invalid token". The visible failure was auth-api's /api/v1/auth/verify, which mints the agents assertion token through Vault transit (TokenService.createAgentsAssertionToken -> VaultTransitJwtEncoder -> transit sign). Forward-auth for agents.jorisjonkers.dev 500s while jorisjonkers.dev keeps working, because only the agents assertion path signs through transit. Both services now use KUBERNETES authentication against their existing kubernetes auth roles, so the session manager can re-login indefinitely from the projected service-account token. The injected token file and the SPRING_CLOUD_VAULT_TOKEN export are removed; the vault-agent still renders the secret env file under its own role. agents-api is configured through the canonical SPRING_CLOUD_VAULT_* property names because its application.yml lives in the agents-api repository.
|
Post-merge verification on the live cluster. The new auth-api pod runs with Transit signing failures went from 4 on the outgoing pod to 0 on the new one, and the previously 500ing endpoints now answer their correct unauthenticated codes: The "follow-up" note in the description is now stale: Two |
Symptom
Every request through forward-auth to
agents.jorisjonkers.devreturned 500:jorisjonkers.devkept working throughout, which is what made the fault look partial rather than systemic.Root cause
auth-api and agents-api took their Vault session token from a file the vault-agent injector wrote once at pod init:
vault.hashicorp.com/agent-inject-token+vault.hashicorp.com/agent-pre-populate-onlyrun the agent as an init container only, with no renewing sidecarexport SPRING_CLOUD_VAULT_TOKEN="$(cat /vault/secrets/token)"— a one-time read into an env varapplication.ymlleftspring.cloud.vault.authenticationat itsTOKENdefaultVault's kubernetes mount issues a 1 h token (
token_ttl=3600,token_max_ttl=0), which the Spring Cloud Vault session manager renewed hourly. Renewal is capped by the mount'smax_lease_ttlof2764800s — exactly 32 days. Past that cap renewal fails; Spring Vault then discards the token and forces a fresh login, butTOKENauthentication has no login mechanism. The token stayed dead for the remaining life of the pod, and every Vault call returnedpermission denied / invalid token.The live auth-api pod started 2026-06-21, so the cap was reached on 2026-07-23.
lookup-selfon its injected token confirmed the token was rejected while the pod itself stayed Ready — the readiness probe does not touch Vault.The split between hosts follows from which code path signs through Vault transit:
Only the agents assertion token is minted through transit, so
/api/v1/auth/me(app-ui) kept answering 200 while/api/v1/auth/verify(agents forward-auth) 500d.Change
Both services now authenticate with
KUBERNETESagainst the kubernetes auth roles they already have, so the session manager can re-login indefinitely from the projected service-account token instead of holding one unrenewable token. The injected token file and theSPRING_CLOUD_VAULT_TOKENexport are removed; the vault-agent still renders the secret env file under its own role.agents-api is configured through the canonical
SPRING_CLOUD_VAULT_*property names, since itsapplication.ymllives in the agents-api repository rather than here.Validation
Both service accounts were confirmed to authenticate against their roles before the change was written:
./gradlew :services:auth-api:test :services:auth-api:integrationTest— passcd platform/tests && node --test— passkubectl kustomize platform/cluster/flux/clusters/production— buildsRestarting auth-api restored service immediately, which corroborates the diagnosis; this change stops the failure from returning 32 days after every rollout.
Follow-up, not in this PR
auth-api's
rabbithealth indicator is DOWN withACCESS_REFUSEDand did not recover on restart with freshly rendered credentials, so therabbitmq/creds/app-consumerdynamic role is independently broken. Tracked separately.