Skip to content

Grant CIMD clients the server's allowed audiences - #6490

Merged
blkt merged 3 commits into
stacklok:mainfrom
alex-feel:cimd-client-audience
Sep 3, 2026
Merged

Grant CIMD clients the server's allowed audiences#6490
blkt merged 3 commits into
stacklok:mainfrom
alex-feel:cimd-client-audience

Conversation

@alex-feel

@alex-feel alex-feel commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Summary

CIMD-resolved clients are built with an empty Audience list, while the token handler grants the session audience from the resource parameter (or defaults it to AllowedAudiences[0]). fosite's refresh handler validates the granted audience against the client's own list (DefaultAudienceMatchingStrategy), so every refresh_token grant from a CIMD client fails with invalid_request ("Requested audience ... has not been whitelisted by the OAuth 2.0 Client"): sign-in works, refresh never does, and every CIMD client (ChatGPT connectors, Claude Code) has to re-authorize interactively once per access-token lifespan. #3796 fixed the identical failure for DCR clients by registering them with the server's AllowedAudiences; the CIMD path never received that fix.

  • CIMDDecoratorConfig gains AllowedAudiences, threaded from the server's own AllowedAudiences at the construction site in server_impl.go.
  • buildFositeClient grants that list to every resolved client instead of nil, mirroring the DCR shape from Set audience on DCR clients for refresh token support #3796; its inputs now travel in a fositeClientParams struct (review nit).
  • Tests: the built client carries the configured audiences, an empty config preserves the prior nil behaviour, and a regression test drives the real decorator fetch() path plus fosite.DefaultAudienceMatchingStrategy exactly as the refresh handler calls it — it fails at the pre-fix behavior with fosite's verbatim production error and passes with the fix. The decorator test also asserts that the client row Persist resolved CIMD clients so Redis session rehydration finds them #6284 persists for Redis session rehydration carries the same audiences, since a refresh on a rehydrated session is checked against that row.

Fixes #6489

Type of change

  • Bug fix

Test plan

  • Unit tests (task test)
  • Linting (task lint-fix)

go test ./pkg/authserver/... (every package this change touches, including the HTTP-level integration tests) is green locally; the full task test matrix incl. -race is left to CI (cgo unavailable on the dev machine). golangci-lint run ./pkg/authserver/... reports 0 issues.

Does this introduce a user-facing change?

Yes: clients registered via Client ID Metadata Documents can now use the refresh_token grant; previously every CIMD refresh failed with invalid_request and clients had to re-authorize each access-token lifespan.

Special notes for reviewers

  • The regression test invokes fosite.DefaultAudienceMatchingStrategy directly, mirroring handler/oauth2/flow_refresh.go's call verbatim; a full HTTP-level CIMD refresh flow would need a CIMD-document test server wired into the integration harness, which felt disproportionate for a fix this size — happy to add it if you'd prefer.
  • Rebased over Persist resolved CIMD clients so Redis session rehydration finds them #6284, which landed first and touched the same buildFositeClient call: the audience is threaded into the client it persists, and the test above covers that row.

@codecov

codecov Bot commented Sep 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 78.18%. Comparing base (031675e) to head (d83cbbe).
⚠️ Report is 5 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #6490      +/-   ##
==========================================
- Coverage   78.19%   78.18%   -0.01%     
==========================================
  Files         769      769              
  Lines       75078    75087       +9     
==========================================
+ Hits        58707    58709       +2     
- Misses      16366    16373       +7     
  Partials        5        5              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

jhrozek
jhrozek previously approved these changes Sep 3, 2026

@jhrozek jhrozek left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed with security, OAuth-correctness, code-quality, and architecture passes — no blocking issues.

This correctly mirrors the DCR audience fix from #3796 for the CIMD path. Confirmed the actual audience gate is the resource-parameter validation in token.go, not client.GetAudience(), so granting the full AllowedAudiences list here doesn't widen what tokens get issued — it just unblocks fosite's refresh-grant self-check, which was the actual bug. Test coverage is solid, including a regression test that drives fosite's real DefaultAudienceMatchingStrategy.

Two minor non-blocking nits for a follow-up if you want them:

  • CIMDDecoratorConfig's doc comment says "prevents silent swaps of the two adjacent []string fields" — there are three now.
  • buildFositeClient is up to 6 params (4 []string) — might be worth a struct given the file already uses one for the same reason elsewhere, but not required.

CIMD-resolved clients were built with an empty audience list, so
fosite's DefaultAudienceMatchingStrategy rejected every refresh_token
grant with "has not been whitelisted by the OAuth 2.0 Client".
The authorization_code grant was unaffected because that path never
validates audience, so the failure only surfaced on refresh, roughly
an access-token lifetime after the first sign-in.
CIMDDecoratorConfig now carries AllowedAudiences, threaded from the
server's own AllowedAudiences at construction, and buildFositeClient
grants that list to every resolved client, mirroring how DCR clients
already inherit the server's AllowedAudiences (stacklok#3796).

Fixes: stacklok#6489
Since stacklok#6284 the decorator persists every resolved CIMD client so Redis session rehydration can find it.
A refresh_token grant on a rehydrated session is therefore checked against the persisted row's audience list rather than the in-memory client's.
The decorator-level regression test now asserts the persisted snapshot carries the configured audiences as well.
buildFositeClient had grown to six parameters, four of them []string, so a swapped pair at a call site would compile and misbehave silently.
A fositeClientParams struct names every input at the call site, for the same reason CIMDDecoratorConfig is a struct.
The CIMDDecoratorConfig comment now counts the three adjacent []string fields it guards.
@alex-feel

Copy link
Copy Markdown
Contributor Author

Rebased over #6284 (both changed the same buildFositeClient call). Since #6284 persists the resolved client for Redis session rehydration, a refresh on a rehydrated session is checked against the persisted row's audience list, so the decorator regression test now asserts that row carries the configured audiences too. Both nits are in: the CIMDDecoratorConfig comment counts three []string fields, and buildFositeClient takes a fositeClientParams struct. Tests and lint for pkg/authserver are green locally; the race run is CI's as before.

@alex-feel

Copy link
Copy Markdown
Contributor Author

The one red leg (Tests / Test Go Code) is TestPostSSE_ProgressIsolationBetweenSessions in pkg/transport/proxy/streamable, the POST-SSE progress-notification race tracked in #6349; this change only touches pkg/authserver, whose packages passed. A re-run should clear it.

@alex-feel
alex-feel requested a review from jhrozek September 3, 2026 12:06
@blkt
blkt merged commit 7f0d08f into stacklok:main Sep 3, 2026
54 of 55 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Embedded auth server: CIMD clients get no audience, so every refresh_token grant fails

3 participants