From 638f97037486cc5a298e20b1dc42f5922836a5c9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 2 Jun 2026 23:35:37 +0000 Subject: [PATCH 1/5] Update stacklok/toolhive to v0.28.3 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/upstream-projects.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/upstream-projects.yaml b/.github/upstream-projects.yaml index 6bc6b4a8..e3caa280 100644 --- a/.github/upstream-projects.yaml +++ b/.github/upstream-projects.yaml @@ -35,7 +35,7 @@ projects: - id: toolhive repo: stacklok/toolhive - version: v0.28.2 + version: v0.28.3 # toolhive is a monorepo covering the CLI, the Kubernetes # operator, and the vMCP gateway. It also introduces cross- # cutting features that land in concepts/, integrations/, From 4c3d6b4a928f619e9538ce09f7f1ed78812dbd06 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 2 Jun 2026 23:36:24 +0000 Subject: [PATCH 2/5] Refresh reference assets for toolhive v0.28.3 --- .../crds/mcpexternalauthconfigs.schema.json | 7 +++ .../crds/mcpremoteproxies.schema.json | 20 ++++++++- static/api-specs/crds/mcpservers.schema.json | 20 ++++++++- .../crds/virtualmcpservers.schema.json | 43 ++++++++++++++++++- 4 files changed, 84 insertions(+), 6 deletions(-) diff --git a/static/api-specs/crds/mcpexternalauthconfigs.schema.json b/static/api-specs/crds/mcpexternalauthconfigs.schema.json index 25cc8243..775ccea3 100644 --- a/static/api-specs/crds/mcpexternalauthconfigs.schema.json +++ b/static/api-specs/crds/mcpexternalauthconfigs.schema.json @@ -171,6 +171,13 @@ "pattern": "^https?://[^\\s?#]+[^/\\s?#]$", "type": "string" }, + "primaryUpstreamProvider": { + "description": "PrimaryUpstreamProvider names the upstream IDP whose access token Cedar\nshould read claims from when authorising a request. Must match the name\nof one of the entries in UpstreamProviders. When empty, the controller\nauto-selects the first entry of UpstreamProviders.\n\nOnly meaningful on VirtualMCPServer, where multiple upstream providers\ncan be configured and Cedar needs to pick which token's claims to\nevaluate. The VirtualMCPServer controller validates this field against\nUpstreamProviders at admission and rejects unresolvable values.\n\nOn MCPServer and MCPRemoteProxy this field is structurally present (the\nEmbeddedAuthServerConfig struct is shared) but has no runtime effect:\nthose CRDs are restricted to a single upstream so there is no choice to\nmake. Setting it on those CRDs is silently ignored.", + "maxLength": 63, + "minLength": 1, + "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$", + "type": "string" + }, "signingKeySecretRefs": { "description": "SigningKeySecretRefs references Kubernetes Secrets containing signing keys for JWT operations.\nSupports key rotation by allowing multiple keys (oldest keys are used for verification only).\nIf not specified, an ephemeral signing key will be auto-generated (development only -\nJWTs will be invalid after restart).", "items": { diff --git a/static/api-specs/crds/mcpremoteproxies.schema.json b/static/api-specs/crds/mcpremoteproxies.schema.json index 3c0ca6c5..f9fc0c53 100644 --- a/static/api-specs/crds/mcpremoteproxies.schema.json +++ b/static/api-specs/crds/mcpremoteproxies.schema.json @@ -70,12 +70,23 @@ ], "type": "object" }, + "groupClaimName": { + "description": "GroupClaimName is the JWT claim key that contains group membership for the\nprincipal. When set, takes priority over the well-known defaults\n(\"groups\", \"roles\", \"cognito:groups\"). Use this for IDPs that place\ngroups under a URI-style claim (e.g. \"https://example.com/groups\"). When\nType is \"configMap\", a group_claim_name entry in the referenced ConfigMap\nis overridden by this field if both are set.", + "maxLength": 253, + "type": "string" + }, + "groupEntityType": { + "description": "GroupEntityType is the Cedar entity type name used for principal parent\nUIDs synthesised from JWT group/role claims. Defaults to \"THVGroup\" when\nempty. Must match the entity type used in the static entity store for\ntransitive `in` checks (e.g. `ClaimGroup → PlatformRole`) to resolve.\nNamespaced names (`Foo::Bar`) are not yet supported. When Type is\n\"configMap\", a group_entity_type entry in the referenced ConfigMap is\noverridden by this field if both are set.", + "maxLength": 63, + "pattern": "^[A-Za-z_][A-Za-z0-9_]*$", + "type": "string" + }, "inline": { "description": "Inline contains direct authorization configuration\nOnly used when Type is \"inline\"", "properties": { "entitiesJson": { "default": "[]", - "description": "EntitiesJSON is a JSON string representing Cedar entities", + "description": "EntitiesJSON is a JSON string representing Cedar entities. Required when\ntransitive policies (e.g. `ClaimGroup → PlatformRole`) need a static\nentity store; defaults to \"[]\".", "type": "string" }, "policies": { @@ -88,7 +99,7 @@ "x-kubernetes-list-type": "atomic" }, "primaryUpstreamProvider": { - "description": "PrimaryUpstreamProvider names the upstream IDP whose access token's claims\nCedar should evaluate. Currently honored only when the parent\nAuthzConfigRef.Type is \"inline\"; configMap-sourced policies will support\nthis in a future release (see #5208). Only meaningful for VirtualMCPServer\nwith an embedded auth server. When empty and an embedded auth server has\nupstreams configured, the controller defaults to the first upstream\nprovider. The name must match one of the upstreams declared on\nspec.authServerConfig.upstreamProviders; otherwise the VirtualMCPServer is\nrejected with AuthServerConfigValidated=False. MCPServer and MCPRemoteProxy\nhave no embedded auth server; setting this field on those CRs surfaces an\nAuthzPrimaryUpstreamProviderIgnored advisory condition on the resource.", + "description": "PrimaryUpstreamProvider names the upstream IDP whose access token's\nclaims Cedar should evaluate.\n\nDeprecated: on VirtualMCPServer this field has moved to\nspec.authServerConfig.primaryUpstreamProvider. The old location is\nstill read for one release for backward compatibility; the\nVirtualMCPServer controller emits an AuthzPrimaryUpstreamProviderDeprecated\nWarning event whenever it is consumed, and removal is planned for the\nrelease after the deprecation cycle.\n\nOn MCPServer and MCPRemoteProxy this field has always been a structural\nno-op (those CRDs do not run an embedded auth server). Setting it\ncontinues to surface the AuthzPrimaryUpstreamProviderIgnored advisory\ncondition; the deprecation does not change that behaviour.", "maxLength": 63, "minLength": 1, "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$", @@ -100,6 +111,11 @@ ], "type": "object" }, + "roleClaimName": { + "description": "RoleClaimName is the JWT claim key that contains role membership for the\nprincipal. When set, the claim is extracted separately from GroupClaimName\nand both are mapped to the configured GroupEntityType. When Type is\n\"configMap\", a role_claim_name entry in the referenced ConfigMap is\noverridden by this field if both are set.", + "maxLength": 253, + "type": "string" + }, "type": { "default": "configMap", "description": "Type is the type of authorization configuration", diff --git a/static/api-specs/crds/mcpservers.schema.json b/static/api-specs/crds/mcpservers.schema.json index a8ce366a..61ef8899 100644 --- a/static/api-specs/crds/mcpservers.schema.json +++ b/static/api-specs/crds/mcpservers.schema.json @@ -78,12 +78,23 @@ ], "type": "object" }, + "groupClaimName": { + "description": "GroupClaimName is the JWT claim key that contains group membership for the\nprincipal. When set, takes priority over the well-known defaults\n(\"groups\", \"roles\", \"cognito:groups\"). Use this for IDPs that place\ngroups under a URI-style claim (e.g. \"https://example.com/groups\"). When\nType is \"configMap\", a group_claim_name entry in the referenced ConfigMap\nis overridden by this field if both are set.", + "maxLength": 253, + "type": "string" + }, + "groupEntityType": { + "description": "GroupEntityType is the Cedar entity type name used for principal parent\nUIDs synthesised from JWT group/role claims. Defaults to \"THVGroup\" when\nempty. Must match the entity type used in the static entity store for\ntransitive `in` checks (e.g. `ClaimGroup → PlatformRole`) to resolve.\nNamespaced names (`Foo::Bar`) are not yet supported. When Type is\n\"configMap\", a group_entity_type entry in the referenced ConfigMap is\noverridden by this field if both are set.", + "maxLength": 63, + "pattern": "^[A-Za-z_][A-Za-z0-9_]*$", + "type": "string" + }, "inline": { "description": "Inline contains direct authorization configuration\nOnly used when Type is \"inline\"", "properties": { "entitiesJson": { "default": "[]", - "description": "EntitiesJSON is a JSON string representing Cedar entities", + "description": "EntitiesJSON is a JSON string representing Cedar entities. Required when\ntransitive policies (e.g. `ClaimGroup → PlatformRole`) need a static\nentity store; defaults to \"[]\".", "type": "string" }, "policies": { @@ -96,7 +107,7 @@ "x-kubernetes-list-type": "atomic" }, "primaryUpstreamProvider": { - "description": "PrimaryUpstreamProvider names the upstream IDP whose access token's claims\nCedar should evaluate. Currently honored only when the parent\nAuthzConfigRef.Type is \"inline\"; configMap-sourced policies will support\nthis in a future release (see #5208). Only meaningful for VirtualMCPServer\nwith an embedded auth server. When empty and an embedded auth server has\nupstreams configured, the controller defaults to the first upstream\nprovider. The name must match one of the upstreams declared on\nspec.authServerConfig.upstreamProviders; otherwise the VirtualMCPServer is\nrejected with AuthServerConfigValidated=False. MCPServer and MCPRemoteProxy\nhave no embedded auth server; setting this field on those CRs surfaces an\nAuthzPrimaryUpstreamProviderIgnored advisory condition on the resource.", + "description": "PrimaryUpstreamProvider names the upstream IDP whose access token's\nclaims Cedar should evaluate.\n\nDeprecated: on VirtualMCPServer this field has moved to\nspec.authServerConfig.primaryUpstreamProvider. The old location is\nstill read for one release for backward compatibility; the\nVirtualMCPServer controller emits an AuthzPrimaryUpstreamProviderDeprecated\nWarning event whenever it is consumed, and removal is planned for the\nrelease after the deprecation cycle.\n\nOn MCPServer and MCPRemoteProxy this field has always been a structural\nno-op (those CRDs do not run an embedded auth server). Setting it\ncontinues to surface the AuthzPrimaryUpstreamProviderIgnored advisory\ncondition; the deprecation does not change that behaviour.", "maxLength": 63, "minLength": 1, "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$", @@ -108,6 +119,11 @@ ], "type": "object" }, + "roleClaimName": { + "description": "RoleClaimName is the JWT claim key that contains role membership for the\nprincipal. When set, the claim is extracted separately from GroupClaimName\nand both are mapped to the configured GroupEntityType. When Type is\n\"configMap\", a role_claim_name entry in the referenced ConfigMap is\noverridden by this field if both are set.", + "maxLength": 253, + "type": "string" + }, "type": { "default": "configMap", "description": "Type is the type of authorization configuration", diff --git a/static/api-specs/crds/virtualmcpservers.schema.json b/static/api-specs/crds/virtualmcpservers.schema.json index 6024caaf..6b2b7af2 100644 --- a/static/api-specs/crds/virtualmcpservers.schema.json +++ b/static/api-specs/crds/virtualmcpservers.schema.json @@ -62,6 +62,13 @@ "pattern": "^https?://[^\\s?#]+[^/\\s?#]$", "type": "string" }, + "primaryUpstreamProvider": { + "description": "PrimaryUpstreamProvider names the upstream IDP whose access token Cedar\nshould read claims from when authorising a request. Must match the name\nof one of the entries in UpstreamProviders. When empty, the controller\nauto-selects the first entry of UpstreamProviders.\n\nOnly meaningful on VirtualMCPServer, where multiple upstream providers\ncan be configured and Cedar needs to pick which token's claims to\nevaluate. The VirtualMCPServer controller validates this field against\nUpstreamProviders at admission and rejects unresolvable values.\n\nOn MCPServer and MCPRemoteProxy this field is structurally present (the\nEmbeddedAuthServerConfig struct is shared) but has no runtime effect:\nthose CRDs are restricted to a single upstream so there is no choice to\nmake. Setting it on those CRDs is silently ignored.", + "maxLength": 63, + "minLength": 1, + "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$", + "type": "string" + }, "signingKeySecretRefs": { "description": "SigningKeySecretRefs references Kubernetes Secrets containing signing keys for JWT operations.\nSupports key rotation by allowing multiple keys (oldest keys are used for verification only).\nIf not specified, an ephemeral signing key will be auto-generated (development only -\nJWTs will be invalid after restart).", "items": { @@ -1207,6 +1214,18 @@ "authz": { "description": "Authz contains authorization configuration (optional).", "properties": { + "entitiesJson": { + "description": "EntitiesJSON is a JSON string representing Cedar entities. Required for\nenterprise policies that rely on transitive relationships (e.g.\n`ClaimGroup → PlatformRole`) — without it the Cedar authorizer is\nconstructed with an empty entity store and `in` checks against absent\nentities silently evaluate to false. Defaults to \"[]\" when empty.", + "type": "string" + }, + "groupClaimName": { + "description": "GroupClaimName is the JWT claim key that contains group membership for\nthe principal. When set, takes priority over the well-known defaults\n(\"groups\", \"roles\", \"cognito:groups\"). Use this for IDPs that place\ngroups under a URI-style claim (e.g. \"https://example.com/groups\").\nWhen empty, only the well-known claim names are checked.", + "type": "string" + }, + "groupEntityType": { + "description": "GroupEntityType is the Cedar entity type name used for principal parent\nUIDs synthesised from JWT group/role claims. Defaults to \"THVGroup\" when\nempty. Must match the entity type used in EntitiesJSON for transitive\n`in` checks to resolve. Namespaced names (`Foo::Bar`) are not yet supported.", + "type": "string" + }, "policies": { "description": "Policies contains Cedar policy definitions (when Type = \"cedar\").", "items": { @@ -1218,6 +1237,10 @@ "description": "PrimaryUpstreamProvider names the upstream IDP provider whose access\ntoken should be used as the source of JWT claims for Cedar evaluation.\nWhen empty, claims from the ToolHive-issued token are used.\nMust match an upstream provider name configured in the embedded auth server\n(e.g. \"default\", \"github\"). Only relevant when the embedded auth server is active.", "type": "string" }, + "roleClaimName": { + "description": "RoleClaimName is the JWT claim key that contains role membership for the\nprincipal. When set, the claim is extracted separately from GroupClaimName\nand both are mapped to the configured group entity type. When empty, no\nrole extraction is performed.", + "type": "string" + }, "type": { "description": "Type is the authz type: \"cedar\", \"none\"", "type": "string" @@ -2072,12 +2095,23 @@ ], "type": "object" }, + "groupClaimName": { + "description": "GroupClaimName is the JWT claim key that contains group membership for the\nprincipal. When set, takes priority over the well-known defaults\n(\"groups\", \"roles\", \"cognito:groups\"). Use this for IDPs that place\ngroups under a URI-style claim (e.g. \"https://example.com/groups\"). When\nType is \"configMap\", a group_claim_name entry in the referenced ConfigMap\nis overridden by this field if both are set.", + "maxLength": 253, + "type": "string" + }, + "groupEntityType": { + "description": "GroupEntityType is the Cedar entity type name used for principal parent\nUIDs synthesised from JWT group/role claims. Defaults to \"THVGroup\" when\nempty. Must match the entity type used in the static entity store for\ntransitive `in` checks (e.g. `ClaimGroup → PlatformRole`) to resolve.\nNamespaced names (`Foo::Bar`) are not yet supported. When Type is\n\"configMap\", a group_entity_type entry in the referenced ConfigMap is\noverridden by this field if both are set.", + "maxLength": 63, + "pattern": "^[A-Za-z_][A-Za-z0-9_]*$", + "type": "string" + }, "inline": { "description": "Inline contains direct authorization configuration\nOnly used when Type is \"inline\"", "properties": { "entitiesJson": { "default": "[]", - "description": "EntitiesJSON is a JSON string representing Cedar entities", + "description": "EntitiesJSON is a JSON string representing Cedar entities. Required when\ntransitive policies (e.g. `ClaimGroup → PlatformRole`) need a static\nentity store; defaults to \"[]\".", "type": "string" }, "policies": { @@ -2090,7 +2124,7 @@ "x-kubernetes-list-type": "atomic" }, "primaryUpstreamProvider": { - "description": "PrimaryUpstreamProvider names the upstream IDP whose access token's claims\nCedar should evaluate. Currently honored only when the parent\nAuthzConfigRef.Type is \"inline\"; configMap-sourced policies will support\nthis in a future release (see #5208). Only meaningful for VirtualMCPServer\nwith an embedded auth server. When empty and an embedded auth server has\nupstreams configured, the controller defaults to the first upstream\nprovider. The name must match one of the upstreams declared on\nspec.authServerConfig.upstreamProviders; otherwise the VirtualMCPServer is\nrejected with AuthServerConfigValidated=False. MCPServer and MCPRemoteProxy\nhave no embedded auth server; setting this field on those CRs surfaces an\nAuthzPrimaryUpstreamProviderIgnored advisory condition on the resource.", + "description": "PrimaryUpstreamProvider names the upstream IDP whose access token's\nclaims Cedar should evaluate.\n\nDeprecated: on VirtualMCPServer this field has moved to\nspec.authServerConfig.primaryUpstreamProvider. The old location is\nstill read for one release for backward compatibility; the\nVirtualMCPServer controller emits an AuthzPrimaryUpstreamProviderDeprecated\nWarning event whenever it is consumed, and removal is planned for the\nrelease after the deprecation cycle.\n\nOn MCPServer and MCPRemoteProxy this field has always been a structural\nno-op (those CRDs do not run an embedded auth server). Setting it\ncontinues to surface the AuthzPrimaryUpstreamProviderIgnored advisory\ncondition; the deprecation does not change that behaviour.", "maxLength": 63, "minLength": 1, "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$", @@ -2102,6 +2136,11 @@ ], "type": "object" }, + "roleClaimName": { + "description": "RoleClaimName is the JWT claim key that contains role membership for the\nprincipal. When set, the claim is extracted separately from GroupClaimName\nand both are mapped to the configured GroupEntityType. When Type is\n\"configMap\", a role_claim_name entry in the referenced ConfigMap is\noverridden by this field if both are set.", + "maxLength": 253, + "type": "string" + }, "type": { "default": "configMap", "description": "Type is the type of authorization configuration", From 715b8d5e1e01f3285fab8b9bcb859d58cc39cda5 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Tue, 2 Jun 2026 23:44:39 +0000 Subject: [PATCH 3/5] Document toolhive v0.28.3 authz and condition changes Adds the new spec.authServerConfig.primaryUpstreamProvider on vMCP and the deprecation of the previous inline location, the group_entity_type Cedar config field with a transitive policy example, AuthzConfigMap condition reasons, and MCPExternalAuthConfig Valid=False mirroring onto consumer CRs. --- SUMMARY.md | 21 ++++++++++ docs/toolhive/concepts/cedar-policies.mdx | 39 ++++++++++++++++++- docs/toolhive/guides-k8s/auth-k8s.mdx | 33 ++++++++++++++++ docs/toolhive/guides-vmcp/authentication.mdx | 32 +++++++++++++++ .../reference/authz-policy-reference.mdx | 31 +++++++++++++++ 5 files changed, 155 insertions(+), 1 deletion(-) create mode 100644 SUMMARY.md diff --git a/SUMMARY.md b/SUMMARY.md new file mode 100644 index 00000000..6b08ee1f --- /dev/null +++ b/SUMMARY.md @@ -0,0 +1,21 @@ +# Summary of changes + +- Added `primaryUpstreamProvider` documentation (new + `spec.authServerConfig.primaryUpstreamProvider` field on `VirtualMCPServer`) + in `docs/toolhive/guides-vmcp/authentication.mdx`, including the deprecation + notice for the previous `spec.incomingAuth.authzConfig.inline.primaryUpstreamProvider` + location (stacklok/toolhive#5290). +- Added the `group_entity_type` (Kubernetes `groupEntityType`) Cedar config + field with a worked transitive-policy example in + `docs/toolhive/concepts/cedar-policies.mdx` and + `docs/toolhive/reference/authz-policy-reference.mdx`. Documents the enterprise + `ClaimGroup in PlatformRole` pattern that motivated stacklok/toolhive#5290. +- Updated `entities_json` description in `docs/toolhive/concepts/cedar-policies.mdx` + to call out the `"[]"` default and its role for transitive policies. +- Added a Kubernetes-troubleshooting note for `AuthzConfigMapNotFound` and + `AuthzConfigMapInvalid` condition reasons on the `AuthConfigured` condition + in `docs/toolhive/guides-k8s/auth-k8s.mdx`. +- Added a troubleshooting note for `ExternalAuthConfigValidated=False` mirrored + onto consumer CRs (`MCPServer`/`MCPRemoteProxy`/`VirtualMCPServer`) from the + source `MCPExternalAuthConfig` in `docs/toolhive/guides-k8s/auth-k8s.mdx` + (stacklok/toolhive#5354). diff --git a/docs/toolhive/concepts/cedar-policies.mdx b/docs/toolhive/concepts/cedar-policies.mdx index e4a0d90a..f8f704f4 100644 --- a/docs/toolhive/concepts/cedar-policies.mdx +++ b/docs/toolhive/concepts/cedar-policies.mdx @@ -107,13 +107,20 @@ cedar: supported) - `cedar`: The Cedar-specific configuration - `policies`: An array of Cedar policy strings - - `entities_json`: A JSON string representing Cedar entities + - `entities_json`: A JSON string representing Cedar entities. Defaults to + `"[]"` when empty. Required when transitive policies (for example, + `ClaimGroup in PlatformRole`) need a static entity store. - `group_claim_name`: Optional custom JWT claim name for group membership (for example, `https://example.com/groups`) - `role_claim_name`: Optional custom JWT claim name for role membership, separate from group claims. Use this when your identity provider provides roles in a different claim than groups (for example, Entra ID `roles` claim). If not set, roles are extracted from the same claims as groups. + - `group_entity_type`: Optional Cedar entity type name used for principal + parent UIDs synthesized from JWT group and role claims. Defaults to + `THVGroup`. Set this when your enterprise entity store uses a different type + name (for example, `ClaimGroup`) so transitive `in` checks resolve against + the entities you provide in `entities_json`. ## Writing effective policies @@ -564,6 +571,36 @@ With both fields configured, ToolHive extracts group membership from the `THVGroup` parent entities, so you can write policies that reference either groups or roles using the same `principal in THVGroup::"..."` syntax. +### Customizing the group entity type + +By default, group and role claims are mapped to `THVGroup` parent entities. If +your authorization model uses a static entity store (provided through +`entities_json`) with a different entity type -- for example, an enterprise +hierarchy where `ClaimGroup` entities belong to `PlatformRole` entities -- set +`group_entity_type` so the synthesized principal parents match the entities +defined in your store: + +```json +{ + "version": "1.0", + "type": "cedarv1", + "cedar": { + "policies": [ + "permit(principal in PlatformRole::\"admin\", action, resource);" + ], + "entities_json": "[{\"uid\": {\"type\": \"ClaimGroup\", \"id\": \"engineering\"}, \"parents\": [{\"type\": \"PlatformRole\", \"id\": \"admin\"}]}]", + "group_claim_name": "groups", + "group_entity_type": "ClaimGroup" + } +} +``` + +Without `group_entity_type`, Cedar synthesizes `THVGroup::"engineering"` +principal parents, which do not match the `ClaimGroup` entities in +`entities_json`. Transitive `in` checks against `PlatformRole` silently evaluate +to false, so every request is denied. Setting `group_entity_type` aligns the +synthesized parents with your entity store. + ### How it works 1. The embedded authorization server authenticates the user with your upstream diff --git a/docs/toolhive/guides-k8s/auth-k8s.mdx b/docs/toolhive/guides-k8s/auth-k8s.mdx index e145570f..5a819fcb 100644 --- a/docs/toolhive/guides-k8s/auth-k8s.mdx +++ b/docs/toolhive/guides-k8s/auth-k8s.mdx @@ -1147,6 +1147,22 @@ kubectl logs -n toolhive-system -l app.kubernetes.io/name=weather-server-k8s - Check the ConfigMap content: `kubectl get configmap authz-config -n toolhive-system -o yaml` +**Authorization ConfigMap not resolved:** + +When `spec.authzConfig.type: configMap`, the controller pre-validates the +referenced ConfigMap and surfaces failures on the `AuthConfigured` condition +with one of two reasons: + +- `AuthzConfigMapNotFound`: the ConfigMap does not exist in the namespace. + Create it before reconciling, or correct the `name`/`namespace` reference. +- `AuthzConfigMapInvalid`: the ConfigMap exists but the payload is missing the + configured `key`, is empty, has malformed YAML or JSON, or fails Cedar + validation. Inspect the payload and the Cedar configuration shape. + +```bash +kubectl describe mcpserver -n toolhive-system +``` + **OIDC configuration issues:** - For external IdP: Ensure the issuer URL is accessible from within the cluster @@ -1182,6 +1198,23 @@ kubectl logs -n toolhive-system -l app.kubernetes.io/name=weather-server-k8s - Verify the upstream provider's client ID and redirect URI are correctly configured in the `MCPExternalAuthConfig` +**Consumer reports `ExternalAuthConfigValidated=False`:** + +When the referenced `MCPExternalAuthConfig` has its own `Valid` condition set to +`False`, the consumer resource (`MCPServer`, `MCPRemoteProxy`, or +`VirtualMCPServer`) mirrors that condition onto its own status with the same +reason and message. Check both objects: + +```bash +kubectl describe mcpserver -n toolhive-system +kubectl describe mcpexternalauthconfig -n toolhive-system +``` + +A reason like `EnterpriseRequired` on the consumer indicates the source +`MCPExternalAuthConfig` is using a type (such as OBO) that requires Stacklok +Enterprise. Fix the configuration on the `MCPExternalAuthConfig` and the +consumer's mirrored condition clears on the next reconcile. + **Token validation failures after restart:** - Ensure you have configured `signingKeySecretRefs` and `hmacSecretRefs` with diff --git a/docs/toolhive/guides-vmcp/authentication.mdx b/docs/toolhive/guides-vmcp/authentication.mdx index 9978d1dc..00e8e824 100644 --- a/docs/toolhive/guides-vmcp/authentication.mdx +++ b/docs/toolhive/guides-vmcp/authentication.mdx @@ -548,6 +548,38 @@ providers, see The [complete example](#complete-example) below shows full provider configurations. +When multiple upstream providers are configured, Cedar reads JWT claims from the +first upstream's access token by default. Pin a specific provider with +`spec.authServerConfig.primaryUpstreamProvider`. The value must match one of the +names in `upstreamProviders`; unresolvable values are rejected at admission with +`AuthServerConfigValidated=False`: + +```yaml +spec: + authServerConfig: + issuer: https://auth.example.com + primaryUpstreamProvider: okta + upstreamProviders: + - name: okta + type: oidc + # ... + - name: github + type: oauth2 + # ... +``` + +:::note[Deprecated location] + +`primaryUpstreamProvider` previously lived under +`spec.incomingAuth.authzConfig.inline.primaryUpstreamProvider`. The old location +is still read for backward compatibility, but the VirtualMCPServer controller +emits a Warning event with reason `AuthzPrimaryUpstreamProviderDeprecated` +whenever it consumes it. Move the value to +`spec.authServerConfig.primaryUpstreamProvider` to clear the warning; the +deprecated field is planned for removal one release after the deprecation cycle. + +::: + :::tip[Non-standard token responses] Some OAuth 2.0 providers nest tokens under non-standard paths instead of diff --git a/docs/toolhive/reference/authz-policy-reference.mdx b/docs/toolhive/reference/authz-policy-reference.mdx index b1ca599f..bc2571ab 100644 --- a/docs/toolhive/reference/authz-policy-reference.mdx +++ b/docs/toolhive/reference/authz-policy-reference.mdx @@ -319,6 +319,37 @@ cedar: entities_json: '[]' ``` +### Customizing the group entity type + +By default, group and role claims become `THVGroup` parent entities. If your +authorization model defines a static entity store (provided through +`entities_json`) using a different entity type, set `group_entity_type` so the +synthesized parents match. This is required for transitive policies that walk a +hierarchy such as `ClaimGroup in PlatformRole`: + +```yaml title="authz-config.yaml" +version: '1.0' +type: cedarv1 +cedar: + group_claim_name: 'groups' + group_entity_type: 'ClaimGroup' + policies: + - 'permit(principal in PlatformRole::"admin", action, resource);' + entities_json: | + [ + { + "uid": {"type": "ClaimGroup", "id": "engineering"}, + "parents": [{"type": "PlatformRole", "id": "admin"}] + } + ] +``` + +Without `group_entity_type`, Cedar synthesizes `THVGroup::"engineering"` for the +principal's parent. Because the entity store contains `ClaimGroup` entries, the +transitive `in PlatformRole` check evaluates to false and every request is +denied. Namespaced names (`Foo::Bar`) are not supported; the type must be a bare +Cedar identifier. + ### How roles are resolved In addition to group claims, ToolHive can extract role claims from a separate From db9b241f4bcb1d886936a49228e12e46bf929af1 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Tue, 2 Jun 2026 23:49:03 +0000 Subject: [PATCH 4/5] Clarify wording in v0.28.3 authz docs --- docs/toolhive/concepts/cedar-policies.mdx | 18 +++++++++--------- docs/toolhive/guides-vmcp/authentication.mdx | 6 +++--- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/toolhive/concepts/cedar-policies.mdx b/docs/toolhive/concepts/cedar-policies.mdx index f8f704f4..1966740a 100644 --- a/docs/toolhive/concepts/cedar-policies.mdx +++ b/docs/toolhive/concepts/cedar-policies.mdx @@ -108,8 +108,8 @@ cedar: - `cedar`: The Cedar-specific configuration - `policies`: An array of Cedar policy strings - `entities_json`: A JSON string representing Cedar entities. Defaults to - `"[]"` when empty. Required when transitive policies (for example, - `ClaimGroup in PlatformRole`) need a static entity store. + `"[]"` when empty. Required when policies use transitive `in` checks against + a static entity store (for example, `ClaimGroup in PlatformRole`). - `group_claim_name`: Optional custom JWT claim name for group membership (for example, `https://example.com/groups`) - `role_claim_name`: Optional custom JWT claim name for role membership, @@ -118,9 +118,9 @@ cedar: claim). If not set, roles are extracted from the same claims as groups. - `group_entity_type`: Optional Cedar entity type name used for principal parent UIDs synthesized from JWT group and role claims. Defaults to - `THVGroup`. Set this when your enterprise entity store uses a different type - name (for example, `ClaimGroup`) so transitive `in` checks resolve against - the entities you provide in `entities_json`. + `THVGroup`. Set this when your entity store uses a different type name (for + example, `ClaimGroup`) so transitive `in` checks resolve against the + entities you provide in `entities_json`. ## Writing effective policies @@ -575,10 +575,10 @@ groups or roles using the same `principal in THVGroup::"..."` syntax. By default, group and role claims are mapped to `THVGroup` parent entities. If your authorization model uses a static entity store (provided through -`entities_json`) with a different entity type -- for example, an enterprise -hierarchy where `ClaimGroup` entities belong to `PlatformRole` entities -- set -`group_entity_type` so the synthesized principal parents match the entities -defined in your store: +`entities_json`) with a different entity type, set `group_entity_type` so the +synthesized principal parents match the entities defined in your store. For +example, you might want `ClaimGroup` entities synthesized from JWT claims to +belong to `PlatformRole` parents defined in your entity store: ```json { diff --git a/docs/toolhive/guides-vmcp/authentication.mdx b/docs/toolhive/guides-vmcp/authentication.mdx index 00e8e824..ecea981e 100644 --- a/docs/toolhive/guides-vmcp/authentication.mdx +++ b/docs/toolhive/guides-vmcp/authentication.mdx @@ -574,9 +574,9 @@ spec: `spec.incomingAuth.authzConfig.inline.primaryUpstreamProvider`. The old location is still read for backward compatibility, but the VirtualMCPServer controller emits a Warning event with reason `AuthzPrimaryUpstreamProviderDeprecated` -whenever it consumes it. Move the value to -`spec.authServerConfig.primaryUpstreamProvider` to clear the warning; the -deprecated field is planned for removal one release after the deprecation cycle. +whenever it reads the value. Move the field to +`spec.authServerConfig.primaryUpstreamProvider` to clear the warning. Removal of +the deprecated location is planned for the release after the deprecation cycle. ::: From e6eea5f8397f562f89006a26adfc780cbbac25cb Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Tue, 2 Jun 2026 23:49:44 +0000 Subject: [PATCH 5/5] Add upstream-release-docs content for toolhive v0.28.3 --- SUMMARY.md | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 SUMMARY.md diff --git a/SUMMARY.md b/SUMMARY.md deleted file mode 100644 index 6b08ee1f..00000000 --- a/SUMMARY.md +++ /dev/null @@ -1,21 +0,0 @@ -# Summary of changes - -- Added `primaryUpstreamProvider` documentation (new - `spec.authServerConfig.primaryUpstreamProvider` field on `VirtualMCPServer`) - in `docs/toolhive/guides-vmcp/authentication.mdx`, including the deprecation - notice for the previous `spec.incomingAuth.authzConfig.inline.primaryUpstreamProvider` - location (stacklok/toolhive#5290). -- Added the `group_entity_type` (Kubernetes `groupEntityType`) Cedar config - field with a worked transitive-policy example in - `docs/toolhive/concepts/cedar-policies.mdx` and - `docs/toolhive/reference/authz-policy-reference.mdx`. Documents the enterprise - `ClaimGroup in PlatformRole` pattern that motivated stacklok/toolhive#5290. -- Updated `entities_json` description in `docs/toolhive/concepts/cedar-policies.mdx` - to call out the `"[]"` default and its role for transitive policies. -- Added a Kubernetes-troubleshooting note for `AuthzConfigMapNotFound` and - `AuthzConfigMapInvalid` condition reasons on the `AuthConfigured` condition - in `docs/toolhive/guides-k8s/auth-k8s.mdx`. -- Added a troubleshooting note for `ExternalAuthConfigValidated=False` mirrored - onto consumer CRs (`MCPServer`/`MCPRemoteProxy`/`VirtualMCPServer`) from the - source `MCPExternalAuthConfig` in `docs/toolhive/guides-k8s/auth-k8s.mdx` - (stacklok/toolhive#5354).