From 47f42e41efa37bce3c6fe74fbdef39c852b48d30 Mon Sep 17 00:00:00 2001 From: Trey Date: Thu, 7 May 2026 09:36:10 -0700 Subject: [PATCH] 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) --- docs/toolhive/concepts/cedar-policies.mdx | 43 +++++++++++--- docs/toolhive/guides-k8s/auth-k8s.mdx | 13 +++++ docs/toolhive/guides-vmcp/authentication.mdx | 60 ++++++++++++++++++++ 3 files changed, 109 insertions(+), 7 deletions(-) diff --git a/docs/toolhive/concepts/cedar-policies.mdx b/docs/toolhive/concepts/cedar-policies.mdx index e4a0d90a..67b11320 100644 --- a/docs/toolhive/concepts/cedar-policies.mdx +++ b/docs/toolhive/concepts/cedar-policies.mdx @@ -567,20 +567,49 @@ groups or roles using the same `principal in THVGroup::"..."` syntax. ### How it works 1. The embedded authorization server authenticates the user with your upstream - identity provider and issues a ToolHive JWT. -2. The Cedar authorizer reads claims from the upstream token (not just the - ToolHive-issued JWT). -3. Group claims are extracted and used to build `THVGroup` parent entities for - the principal. + identity provider, stores the upstream access token, and issues a ToolHive + JWT for the client. +2. The authorizer resolves which token's claims to evaluate: + - When `primaryUpstreamProvider` is set, it reads claims from the named + upstream's stored access token. + - When `primaryUpstreamProvider` is unset and an embedded auth server is + configured, it reads claims from the first upstream provider declared on + the auth server. + - When `primaryUpstreamProvider` is unset and no embedded auth server is + configured, it reads claims from the bearer token on the original client + request. +3. Group and role claims are extracted from the resolved claim source and used + to build `THVGroup` parent entities for the principal. 4. Policies using `principal in THVGroup::""` evaluate correctly. :::note -If the upstream token is opaque (not a JWT), the authorizer denies the request. -There is no silent fallback to ToolHive-issued claims only. +If the resolved upstream token is opaque (not a JWT), the authorizer denies the +request. There is no silent fallback to the client request's claims. ::: +### How the upstream provider is chosen + +In Kubernetes, how you configure `primaryUpstreamProvider` depends on the +resource type: + +- **VirtualMCPServer:** the embedded auth server can declare multiple upstream + providers. Set `incomingAuth.authzConfig.inline.primaryUpstreamProvider` on + the `VirtualMCPServer` to choose which upstream's access token Cedar + evaluates. When the field is empty, the operator defaults to the first entry + in `authServerConfig.upstreamProviders` and emits an + `AuthzUpstreamSelectionWarning` status condition naming the chosen provider. + See + [Cedar authorization claim source](../guides-vmcp/authentication.mdx#cedar-authorization-claim-source) + in the vMCP authentication guide for the override syntax and validation + behavior. +- **MCPServer and MCPRemoteProxy:** the inline `primaryUpstreamProvider` field + is accepted but has no effect on these resources. Setting it surfaces an + `AuthzPrimaryUpstreamProviderIgnored` advisory status condition. The embedded + auth server for these resources runs through a referenced + `MCPExternalAuthConfig` and supports a single upstream provider. + ## Policy evaluation and secure defaults Understanding how Cedar evaluates policies helps you write more effective and diff --git a/docs/toolhive/guides-k8s/auth-k8s.mdx b/docs/toolhive/guides-k8s/auth-k8s.mdx index fab1673a..078d777e 100644 --- a/docs/toolhive/guides-k8s/auth-k8s.mdx +++ b/docs/toolhive/guides-k8s/auth-k8s.mdx @@ -849,6 +849,19 @@ membership. See [Upstream identity provider claims](../concepts/cedar-policies.mdx#upstream-identity-provider-claims) for details. +:::note[`primaryUpstreamProvider` on MCPServer and MCPRemoteProxy] + +The embedded authorization server for `MCPServer` and `MCPRemoteProxy` resources +is configured through a referenced `MCPExternalAuthConfig` and supports a single +upstream provider, so there is no provider selection to make. The +`primaryUpstreamProvider` field on the inline authz config is accepted on these +resources but has no effect; setting it surfaces an +`AuthzPrimaryUpstreamProviderIgnored` advisory status condition. Use a +[`VirtualMCPServer`](../guides-vmcp/authentication.mdx#cedar-authorization-claim-source) +when you need Cedar to choose between multiple upstream providers. + +::: + **Step 1: Create authorization configuration** diff --git a/docs/toolhive/guides-vmcp/authentication.mdx b/docs/toolhive/guides-vmcp/authentication.mdx index 14479db5..72bf19f5 100644 --- a/docs/toolhive/guides-vmcp/authentication.mdx +++ b/docs/toolhive/guides-vmcp/authentication.mdx @@ -491,6 +491,66 @@ spec: # highlight-end ``` +### Cedar authorization claim source + +When you configure Cedar policies under `incomingAuth.authzConfig.inline`, the +operator binds Cedar's claim source to one of the providers in +`authServerConfig.upstreamProviders` so that group and role policies evaluate +against upstream IDP claims rather than the ToolHive-issued JWT. + +By default, the operator selects the first entry in +`authServerConfig.upstreamProviders`. With two or more upstreams declared the +choice is ambiguous, so the operator additionally emits an +`AuthzUpstreamSelectionWarning` status condition naming the chosen provider so +you can verify the default matches your intent. + +To pin Cedar to a specific upstream, set `primaryUpstreamProvider` on the inline +authz config: + +```yaml title="VirtualMCPServer resource" +spec: + incomingAuth: + type: oidc + oidcConfigRef: + name: my-oidc-config + audience: https://mcp.example.com/mcp + authzConfig: + type: inline + inline: + # highlight-next-line + primaryUpstreamProvider: github + policies: + - 'permit(principal in THVGroup::"engineering", action, resource);' + entitiesJson: '[]' + authServerConfig: + issuer: https://auth.example.com + upstreamProviders: + - name: github + type: oauth2 + oauth2Config: { ... } + - name: google + type: oidc + oidcConfig: { ... } +``` + +The value must match an entry in `authServerConfig.upstreamProviders`. Setting +the field on a single-upstream config is allowed but redundant: the default +already resolves to that upstream. + +Rejection behavior at admission: + +- **Name does not match a declared upstream:** the `VirtualMCPServer` is + rejected with `AuthServerConfigValidated=False` and `AuthzUpstreamUnknown`. + Cedar would otherwise deny every request at runtime, so the operator rejects + at admission instead. +- **Field set without an embedded auth server:** the `VirtualMCPServer` is + rejected with `AuthzPrimaryProviderRequiresAuthServer`. Either remove the + field or configure `authServerConfig`. + +For background on how Cedar resolves claims from the upstream token versus the +ToolHive-issued JWT, see +[Upstream identity provider claims](../concepts/cedar-policies.mdx#upstream-identity-provider-claims). + ### Session storage By default, upstream tokens are stored in memory and lost on pod restart. For