Skip to content

Commit 47f42e4

Browse files
tgrunnagleclaude
andcommitted
Document Cedar primary upstream provider selection
Clarify how Cedar resolves its claim source when the embedded auth server is active: it reads upstream IDP claims only when the runtime config sets primary_upstream_provider, otherwise it falls back to claims on the original client request. Document the operator's default-to-first-upstream behavior on VirtualMCPServer, the new primaryUpstreamProvider override, and the rejection conditions that guard misconfiguration. Note that the field is a no-op on MCPServer and MCPRemoteProxy and surfaces an advisory condition. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 3313da6 commit 47f42e4

3 files changed

Lines changed: 109 additions & 7 deletions

File tree

docs/toolhive/concepts/cedar-policies.mdx

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -567,20 +567,49 @@ groups or roles using the same `principal in THVGroup::"..."` syntax.
567567
### How it works
568568

569569
1. The embedded authorization server authenticates the user with your upstream
570-
identity provider and issues a ToolHive JWT.
571-
2. The Cedar authorizer reads claims from the upstream token (not just the
572-
ToolHive-issued JWT).
573-
3. Group claims are extracted and used to build `THVGroup` parent entities for
574-
the principal.
570+
identity provider, stores the upstream access token, and issues a ToolHive
571+
JWT for the client.
572+
2. The authorizer resolves which token's claims to evaluate:
573+
- When `primaryUpstreamProvider` is set, it reads claims from the named
574+
upstream's stored access token.
575+
- When `primaryUpstreamProvider` is unset and an embedded auth server is
576+
configured, it reads claims from the first upstream provider declared on
577+
the auth server.
578+
- When `primaryUpstreamProvider` is unset and no embedded auth server is
579+
configured, it reads claims from the bearer token on the original client
580+
request.
581+
3. Group and role claims are extracted from the resolved claim source and used
582+
to build `THVGroup` parent entities for the principal.
575583
4. Policies using `principal in THVGroup::"<group>"` evaluate correctly.
576584

577585
:::note
578586

579-
If the upstream token is opaque (not a JWT), the authorizer denies the request.
580-
There is no silent fallback to ToolHive-issued claims only.
587+
If the resolved upstream token is opaque (not a JWT), the authorizer denies the
588+
request. There is no silent fallback to the client request's claims.
581589

582590
:::
583591

592+
### How the upstream provider is chosen
593+
594+
In Kubernetes, how you configure `primaryUpstreamProvider` depends on the
595+
resource type:
596+
597+
- **VirtualMCPServer:** the embedded auth server can declare multiple upstream
598+
providers. Set `incomingAuth.authzConfig.inline.primaryUpstreamProvider` on
599+
the `VirtualMCPServer` to choose which upstream's access token Cedar
600+
evaluates. When the field is empty, the operator defaults to the first entry
601+
in `authServerConfig.upstreamProviders` and emits an
602+
`AuthzUpstreamSelectionWarning` status condition naming the chosen provider.
603+
See
604+
[Cedar authorization claim source](../guides-vmcp/authentication.mdx#cedar-authorization-claim-source)
605+
in the vMCP authentication guide for the override syntax and validation
606+
behavior.
607+
- **MCPServer and MCPRemoteProxy:** the inline `primaryUpstreamProvider` field
608+
is accepted but has no effect on these resources. Setting it surfaces an
609+
`AuthzPrimaryUpstreamProviderIgnored` advisory status condition. The embedded
610+
auth server for these resources runs through a referenced
611+
`MCPExternalAuthConfig` and supports a single upstream provider.
612+
584613
## Policy evaluation and secure defaults
585614

586615
Understanding how Cedar evaluates policies helps you write more effective and

docs/toolhive/guides-k8s/auth-k8s.mdx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -849,6 +849,19 @@ membership. See
849849
[Upstream identity provider claims](../concepts/cedar-policies.mdx#upstream-identity-provider-claims)
850850
for details.
851851

852+
:::note[`primaryUpstreamProvider` on MCPServer and MCPRemoteProxy]
853+
854+
The embedded authorization server for `MCPServer` and `MCPRemoteProxy` resources
855+
is configured through a referenced `MCPExternalAuthConfig` and supports a single
856+
upstream provider, so there is no provider selection to make. The
857+
`primaryUpstreamProvider` field on the inline authz config is accepted on these
858+
resources but has no effect; setting it surfaces an
859+
`AuthzPrimaryUpstreamProviderIgnored` advisory status condition. Use a
860+
[`VirtualMCPServer`](../guides-vmcp/authentication.mdx#cedar-authorization-claim-source)
861+
when you need Cedar to choose between multiple upstream providers.
862+
863+
:::
864+
852865
**Step 1: Create authorization configuration**
853866

854867
<BasicCedarConfig />

docs/toolhive/guides-vmcp/authentication.mdx

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,66 @@ spec:
491491
# highlight-end
492492
```
493493

494+
### Cedar authorization claim source
495+
496+
When you configure Cedar policies under `incomingAuth.authzConfig.inline`, the
497+
operator binds Cedar's claim source to one of the providers in
498+
`authServerConfig.upstreamProviders` so that group and role policies evaluate
499+
against upstream IDP claims rather than the ToolHive-issued JWT.
500+
501+
By default, the operator selects the first entry in
502+
`authServerConfig.upstreamProviders`. With two or more upstreams declared the
503+
choice is ambiguous, so the operator additionally emits an
504+
`AuthzUpstreamSelectionWarning` status condition naming the chosen provider so
505+
you can verify the default matches your intent.
506+
507+
To pin Cedar to a specific upstream, set `primaryUpstreamProvider` on the inline
508+
authz config:
509+
510+
```yaml title="VirtualMCPServer resource"
511+
spec:
512+
incomingAuth:
513+
type: oidc
514+
oidcConfigRef:
515+
name: my-oidc-config
516+
audience: https://mcp.example.com/mcp
517+
authzConfig:
518+
type: inline
519+
inline:
520+
# highlight-next-line
521+
primaryUpstreamProvider: github
522+
policies:
523+
- 'permit(principal in THVGroup::"engineering", action, resource);'
524+
entitiesJson: '[]'
525+
authServerConfig:
526+
issuer: https://auth.example.com
527+
upstreamProviders:
528+
- name: github
529+
type: oauth2
530+
oauth2Config: { ... }
531+
- name: google
532+
type: oidc
533+
oidcConfig: { ... }
534+
```
535+
536+
The value must match an entry in `authServerConfig.upstreamProviders`. Setting
537+
the field on a single-upstream config is allowed but redundant: the default
538+
already resolves to that upstream.
539+
540+
Rejection behavior at admission:
541+
542+
- **Name does not match a declared upstream:** the `VirtualMCPServer` is
543+
rejected with `AuthServerConfigValidated=False` and `AuthzUpstreamUnknown`.
544+
Cedar would otherwise deny every request at runtime, so the operator rejects
545+
at admission instead.
546+
- **Field set without an embedded auth server:** the `VirtualMCPServer` is
547+
rejected with `AuthzPrimaryProviderRequiresAuthServer`. Either remove the
548+
field or configure `authServerConfig`.
549+
550+
For background on how Cedar resolves claims from the upstream token versus the
551+
ToolHive-issued JWT, see
552+
[Upstream identity provider claims](../concepts/cedar-policies.mdx#upstream-identity-provider-claims).
553+
494554
### Session storage
495555

496556
By default, upstream tokens are stored in memory and lost on pod restart. For

0 commit comments

Comments
 (0)