Skip to content

Fall back to request-token claims for opaque upstream tokens#5147

Open
cjohnhanson wants to merge 2 commits intostacklok:mainfrom
cjohnhanson:fix/cedar-opaque-token-fallback
Open

Fall back to request-token claims for opaque upstream tokens#5147
cjohnhanson wants to merge 2 commits intostacklok:mainfrom
cjohnhanson:fix/cedar-opaque-token-fallback

Conversation

@cjohnhanson
Copy link
Copy Markdown

Summary

  • VirtualMCPServer's incoming Cedar authorizer denied every request when the embedded auth server's upstream provider issues opaque OAuth 2.0 access tokens (Google's ya29.*, GitHub's gho_*). resolveClaims JWT-parsed the upstream token unconditionally and returned the parse error verbatim, so every authorization check failed and the gateway skipped every tool.
  • Discriminate by token shape: if the upstream token is not three dot-separated segments it cannot be a JWT (per JOSE compact serialization), so fall back to identity.Claims (the request-token claims). The embedded auth server already mirrors the upstream OIDC sub/email/name into its issued AS token (see pkg/authserver/server/session/session.go), so policies referencing standard OIDC claims continue to evaluate correctly.
  • JWT-shaped tokens (three segments) that fail to parse still return the error: a tampered or corrupted upstream JWT must not silently degrade to fallback claims. The added looksLikeJWT(tokenStr) = strings.Count(tokenStr, ".") == 2 discriminator keeps that boundary explicit.
  • The auto-binding of PrimaryUpstreamProvider added in Fix Cedar upstream-claim evaluation on VirtualMCPServer #5002 (operator side, cmd/thv-operator/pkg/vmcpconfig/converter.go) doesn't expose an opt-out CRD field, so users with opaque-token providers had no in-config workaround. This change unblocks them without changing operator behavior.

Closes #5146

Type of change

  • Bug fix
  • New feature
  • Refactoring (no behavior change)
  • Dependency update
  • Documentation
  • Other (describe):

Test plan

  • Unit tests (go test ./pkg/authz/...)
  • go vet ./pkg/authz/...
  • gofmt -l pkg/authz/authorizers/cedar/ (clean)
  • End-to-end verified on a GKE staging cluster: VirtualMCPServer with accounts.google.com upstream, Cedar policy referencing claim_email. Pre-fix: tools/list returns empty with Authorization check failed for tool, skipping per tool. Post-fix: full aggregated tool list returned, single WARN line upstream token is not a JWT; falling back to request-token claims for Cedar evaluation provider=google.

The existing TestParseUpstreamJWTClaims/opaque_token_returns_error row stays unchanged — the helper still returns the same error; only the caller's recovery behavior changes.

API Compatibility

  • This PR does not break the v1beta1 API. No CRD or ConfigOptions schema change.

Changes

File Change
pkg/authz/authorizers/cedar/core.go resolveClaims discriminates looksLikeJWT(tokenStr) before falling back. Opaque (≠3 segments) → fall back to identity.Claims with a WARN. JWT-shaped but unparseable → return the original parse error (deny). Adds the looksLikeJWT helper.
pkg/authz/authorizers/cedar/core_test.go Replaces the upstream_token_opaque_not_parseable row in TestAuthorizeWithJWTClaims_UpstreamProvider with three rows: opaque-fallback-denied (policy mismatch via fallback claims), opaque-fallback-permitted (policy matches via fallback claims), and JWT-shaped-but-malformed-still-errors (security-regression guard for tampered JWTs).

Does this introduce a user-facing change?

Yes — bug-fix only. Operators with Google (or any opaque-access-token) upstream OIDC provider now receive the aggregated tools list instead of an empty one. No CRD or configuration change required. A WARN log surfaces each fallback so operators can observe the path their identity claims take.

Tampered or corrupted upstream JWTs still hard-deny — there is no behavior change for JWT-access-token providers (Okta, Azure AD with JWT access tokens, etc.).

Special notes for reviewers

A complementary operator-side change — only auto-set PrimaryUpstreamProvider when the user hasn't asked for the explicit fallback — would expose the documented escape hatch via a CRD field, since the docstring on PrimaryUpstreamProvider already says "When empty, claims from the ToolHive-issued token are used." That route is more invasive (CRD + types + converter + tests). Happy to PR it separately if you'd prefer the opt-in surface over the implicit fallback in this PR. Either approach unblocks the failing case.

The fallback could also be argued to belong behind a ConfigOptions.AllowOpaqueTokenFallback knob (default false). I left it implicit here because (a) the discriminator preserves the deny for tampered JWTs, which is the security-relevant case, and (b) for opaque-token providers the previous behavior was always-deny — there's no operator who relied on it doing anything useful. Open to adding the gate if you'd prefer the more conservative surface.

VirtualMCPServer (Cedar incoming authz) denied every request when the
embedded auth servers upstream provider issues opaque OAuth 2.0 access
tokens (Googles ya29.*, GitHubs gho_*). resolveClaims tried to JWT-parse
the upstream token unconditionally and returned the parse error verbatim,
so every authorization check failed and the gateway skipped every tool.

Discriminate by token shape: if the upstream token is not three dot-
separated segments it cannot be a JWT, so fall back to identity.Claims
(the request-token claims). The embedded auth server already mirrors
the upstream OIDC sub, email and name into its issued AS token (see
pkg/authserver/server/session/session.go), so policies referencing
standard OIDC claims continue to evaluate correctly.

JWT-shaped tokens (three segments) that fail to parse still return the
error: a tampered or corrupted upstream JWT must not silently degrade
to fallback claims.

Closes stacklok#5146

Signed-off-by: Cody J. Hanson <cjohnhanson@users.noreply.github.com>
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.

VirtualMCPServer Cedar authorization rejects all requests when upstream OIDC provider issues opaque access tokens

1 participant