feat: authenticate callers with short-lived signed tokens - #41
Merged
Merged
Conversation
Verify bearer tokens against the identity provider's published keys, refuse any token that outlives the configured lifetime, and take the tenant from the token. Static keys remain for development; the cluster manifests and the Helm chart now require tokens and mount no static key. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Closes the first line of the spec's security design (§14): "Authenticate users and services with short-lived credentials." Until now the gateway accepted only static API keys.
How
llm_router.credentials.TokenVerifieraccepts a bearer token only when it is:iat,expandsub, with a lifetime no longer thanROUTER_JWT_MAX_LIFETIME_SECONDS;401with the reason and is never retried as a static key.ROUTER_REQUIRE_SHORT_LIVED_CREDENTIALS=true.ROUTER_API_KEYSis no longer mounted, and the issuer's keys come from the secret manager.auth.issuerandauth.audienceare chart values.Tests
tests/integration/test_credentials_api.py(30 tests), using real RSA and EC keys generated in the test: a valid token; twelve kinds of token that must be refused (expired, too long-lived, wrong key, unknown key, wrong audience, wrong issuer, no tenant, no subject, HS256,alg: none, malformed, noiat); key rotation without a restart; issuer outage; the gateway end to end, including tenant entitlement from the token, static-key refusal when tokens are required, and quota shared across a renewed token.Local: ruff and mypy clean, full suite passes, Playwright 9/9, rebuilt image scans clean with Trivy.
Limits
🤖 Generated with Claude Code