Skip to content

controller: rotate OpenAICredentials access tokens - #2815

Open
junfeiliu531 wants to merge 10 commits into
theagentrouter:mainfrom
junfeiliu531:spiffe-token-provider-3
Open

junfeiliu531 wants to merge 10 commits into
theagentrouter:mainfrom
junfeiliu531:spiffe-token-provider-3

Conversation

@junfeiliu531

Copy link
Copy Markdown

Description

Second part of #2791: makes the OpenAICredentials BackendSecurityPolicy added in #2806 functional. The controller obtains a subject token (SPIFFE JWT-SVID or OIDC), exchanges it for an OpenAI access token via OAuth 2.0 Token Exchange (RFC 8693), and rotates it before it expires.

Controller (internal/controller)

  • tokenprovider.NewTokenExchangeProvider: an RFC 8693 client wrapping any subject TokenProvider. It sends subject_token, subject_token_type, and the optional audience and scope. The expiry comes from expires_in, falling back to the subject token's expiry when absent.
  • rotators.NewOpenAITokenRotator: stores the access token under openAIAccessToken in the generated ai-eg-bsp-<name> secret, mirroring the Azure token rotator.
  • The BSP controller builds the subject token provider from subjectToken.spiffeJWTSVID or subjectToken.oidcExchangeToken. OIDC client secrets referenced from other namespaces go through the same ReferenceGrant check as AWS/Azure/GCP.
  • The gateway translates the policy into filterapi.OpenAIAuth. credentialOverride defaults to the x-aigw-openai-access-token header.

Data plane (internal/backendauth)

  • An OpenAIAuth handler sets Authorization: Bearer <token>, plus OpenAI-Organization and OpenAI-Project when configured. The same headers are set when the token comes from a credential override.

Docs and example

  • examples/basic/openai-spiffe.yaml.
  • The spiffeJWTSVID API doc notes that such policies act as the controller's SPIFFE identity, so only trusted cluster admins should create them.

Related Issues/PRs (if applicable)

Part of #2791
Depends on #2806 (this branch includes its commits until it merges)

Special notes for reviewers (if applicable)

  • Mounting the SPIFFE Workload API socket into the controller (with SPIFFE_ENDPOINT_SOCKET pointing at it) is left to the deployment. This PR makes no Helm changes.
  • The token exchange request sends no client credentials and no requested_token_type.
  • Tests: unit tests for the provider, rotator, handler and controller wiring. An envtest test in tests/controller runs the BSP controller against a fake SPIFFE Workload API and a fake token exchange endpoint. There is no kind e2e: that would require SPIRE in CI, and no other provider's token rotation has one.
  • The OpenAI token exchange endpoint is still open (see api: add OpenAICredentials BackendSecurityPolicy #2806), so this has not been tested against OpenAI.

🤖 Generated with Claude Code [1]

1: https://claude.com/claude-code

Junfei Liu and others added 10 commits October 6, 2026 14:18
Add a tokenprovider.TokenProvider backed by the SPIFFE Workload API.
It fetches a JWT-SVID for a fixed audience; the socket address is
passed explicitly or falls back to SPIFFE_ENDPOINT_SOCKET.

First step of theagentrouter#2791. Not wired into any rotator yet.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Junfei Liu <jliu531@bloomberg.net>
Add an OpenAICredentials type to v1beta1 for secretless access to
OpenAI via OAuth 2.0 Token Exchange (RFC 8693). It carries optional
organization/project IDs and a tokenExchange block (tokenURL,
subjectTokenType defaulting to ...:token-type:jwt, audience, scopes).

The subject token comes from exactly one of an OIDC client-credentials
flow or a SPIFFE JWT-SVID (audience plus optional socketPath).

v1beta1 only, following the CredentialOverride precedent. Part of
theagentrouter#2791; the controller does not act on the new type yet.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Junfei Liu <jliu531@bloomberg.net>
Replace subjectToken.oidc (*egv1a1.OIDC) with oidcExchangeToken
(*BackendSecurityPolicyOIDC), matching the field name and nesting of
the AWS, Azure and GCP credential types. The controller can then read
the OIDC config the same way for all four types.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Junfei Liu <jliu531@bloomberg.net>
Drop the subjectToken wrapper: oidcExchangeToken and the SPIFFE source
now sit directly under tokenExchange, so "OIDC vs SPIFFE" can be
spelled the same way if AWS/GCP/Azure gain a SPIFFE option later.

Rename spiffe to spiffeJWTSVID (type BackendSecurityPolicySPIFFEJWTSVID)
since nothing is exchanged when fetching it, and to leave room for an
X.509-SVID source. Clarify which hop each audience/scopes field
applies to.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Junfei Liu <jliu531@bloomberg.net>
Group the subject token sources under tokenExchange.subjectToken
again, keeping the oidcExchangeToken and spiffeJWTSVID names. The
wrapper keeps the sources separate from the exchange parameters and
gives new sources an obvious home.

Write the one-of rule as a list count so a new source only adds an
entry to it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Junfei Liu <jliu531@bloomberg.net>
The controller does not act on OpenAICredentials yet. Return a
"not yet supported" error from credential rotation and the gateway
auth translation instead of the generic unsupported-type errors, and
handle the type in getBSPGeneratedSecretName so it cannot panic once
rotation reaches it.

Document which hop each audience field applies to.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Junfei Liu <jliu531@bloomberg.net>
Add filterapi.OpenAIAuth carrying an access token plus optional
organization and project IDs. The handler sets Authorization: Bearer
and, when configured, OpenAI-Organization and OpenAI-Project. The same
headers are set when the token comes from a credential override.

Part of theagentrouter#2791.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Junfei Liu <jliu531@bloomberg.net>
Add an RFC 8693 token exchange provider that wraps a subject token
provider (SPIFFE JWT-SVID or OIDC), and an OpenAI token rotator that
stores the exchanged access token in the generated BSP secret. Rotation
follows the exchanged token's expires_in, falling back to the subject
token's expiry when absent.

Wire OpenAICredentials into the BSP controller and the gateway's
filterapi translation, replacing the "not yet supported" errors, and
default the credential override header to x-aigw-openai-access-token.

Part of theagentrouter#2791.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Junfei Liu <jliu531@bloomberg.net>
Run the BackendSecurityPolicy controller against a fake SPIFFE
Workload API and a fake token exchange endpoint, and check that the
JWT-SVID is exchanged and the access token lands in the generated
secret.

Part of theagentrouter#2791.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Junfei Liu <jliu531@bloomberg.net>
Add an OpenAICredentials example using a SPIFFE JWT-SVID, and note in
the API docs that such policies act as the controller's SPIFFE
identity. Mounting the Workload API socket into the controller is left
to the deployment.

Part of theagentrouter#2791.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Junfei Liu <jliu531@bloomberg.net>
@junfeiliu531
junfeiliu531 requested a review from a team as a code owner October 8, 2026 03:44
@netlify

netlify Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for theagentrouter ready!

Name Link
🔨 Latest commit 3e3d2ff
🔍 Latest deploy log https://app.netlify.com/projects/theagentrouter/deploys/6ac711bcb54cc90008629626
😎 Deploy Preview https://deploy-preview-2815--theagentrouter.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@aabchoo aabchoo self-assigned this Oct 8, 2026
@aabchoo aabchoo added the area/controller Controller and reconciliation label Oct 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/controller Controller and reconciliation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants