diff --git a/.github/upstream-projects.yaml b/.github/upstream-projects.yaml index b6a8b797..481674d5 100644 --- a/.github/upstream-projects.yaml +++ b/.github/upstream-projects.yaml @@ -35,7 +35,7 @@ projects: - id: toolhive repo: stacklok/toolhive - version: v0.26.0 + version: v0.26.1 # 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/, diff --git a/docs/toolhive/concepts/cedar-policies.mdx b/docs/toolhive/concepts/cedar-policies.mdx index e4a0d90a..1ef86041 100644 --- a/docs/toolhive/concepts/cedar-policies.mdx +++ b/docs/toolhive/concepts/cedar-policies.mdx @@ -114,6 +114,10 @@ cedar: 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 the parent + entities synthesized from group and role claims. Defaults to `THVGroup` when + empty. Set this when your policy vocabulary uses a different name such as + `OrgRole` or `Group`. ## Writing effective policies @@ -564,6 +568,40 @@ 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, ToolHive maps group and role claims to a Cedar entity type named +`THVGroup`. If your policy vocabulary uses a different name, set the +`group_entity_type` field to override the default: + +```json +{ + "version": "1.0", + "type": "cedarv1", + "cedar": { + "policies": [ + "permit(principal in OrgRole::\"engineering\", action, resource);" + ], + "entities_json": "[]", + "group_entity_type": "OrgRole" + } +} +``` + +With this configuration, group and role claims are mapped to `OrgRole` parent +entities, so policies must reference the same type. The value must be a valid +Cedar identifier; namespaced names (for example, `Platform::Group`) aren't +supported. ToolHive validates the value at startup and rejects invalid +identifiers with a descriptive error. + +If your `entities_json` still contains `THVGroup` entities while +`group_entity_type` is set to a different value, ToolHive logs a warning at +startup. Cedar compares entity UIDs by type name, so the synthesized parents +don't match the stale `THVGroup` entities. Any permit that references those +stale entities silently fails to match, leaving the request denied by default. +Update `entities_json` to use the new entity type name, or remove the obsolete +entities. + ### How it works 1. The embedded authorization server authenticates the user with your upstream diff --git a/docs/toolhive/reference/authz-policy-reference.mdx b/docs/toolhive/reference/authz-policy-reference.mdx index ca3d29af..b283e094 100644 --- a/docs/toolhive/reference/authz-policy-reference.mdx +++ b/docs/toolhive/reference/authz-policy-reference.mdx @@ -19,15 +19,15 @@ Every Cedar authorization request involves three entity types: a principal, an action, and a resource. ToolHive maps MCP concepts to these Cedar entities automatically. -| Entity type | Format | Description | -| ------------- | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `Client` | `Client::""` | The authenticated user, identified by the `sub` claim from the access token | -| `Action` | `Action::""` | The MCP operation being performed | -| `Tool` | `Tool::""` | A tool resource (used for `tools/call`) | -| `Prompt` | `Prompt::""` | A prompt resource (used for `prompts/get`) | -| `Resource` | `Resource::""` | A data resource (used for `resources/read`). The URI is [sanitized](#resource-uri-sanitization) for Cedar compatibility | -| `FeatureType` | `FeatureType::""` | A feature category entity. Values: `tool`, `prompt`, `resource`. Not currently used for authorization; list operations are handled via [response filtering](#list-operation-filtering) | -| `THVGroup` | `THVGroup::""` | A group membership entity. Used with Cedar's `in` operator for [group-based policies](#group-membership) | +| Entity type | Format | Description | +| ------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `Client` | `Client::""` | The authenticated user, identified by the `sub` claim from the access token | +| `Action` | `Action::""` | The MCP operation being performed | +| `Tool` | `Tool::""` | A tool resource (used for `tools/call`) | +| `Prompt` | `Prompt::""` | A prompt resource (used for `prompts/get`) | +| `Resource` | `Resource::""` | A data resource (used for `resources/read`). The URI is [sanitized](#resource-uri-sanitization) for Cedar compatibility | +| `FeatureType` | `FeatureType::""` | A feature category entity. Values: `tool`, `prompt`, `resource`. Not currently used for authorization; list operations are handled via [response filtering](#list-operation-filtering) | +| `THVGroup` | `THVGroup::""` | A group membership entity. Used with Cedar's `in` operator for [group-based policies](#group-membership). `THVGroup` is the default type name; configurable via [`group_entity_type`](#customizing-the-group-entity-type) | ## Cedar actions @@ -266,9 +266,10 @@ context.arg_location == "New York" ## Group membership -ToolHive automatically extracts group claims from JWT tokens and creates -`THVGroup` parent entities for the principal. This lets you write group-based -policies using Cedar's `in` operator. +ToolHive automatically extracts group claims from JWT tokens and creates parent +entities for the principal. This lets you write group-based policies using +Cedar's `in` operator. The parent entity type defaults to `THVGroup` and is +configurable via the `group_entity_type` Cedar config field. ### How groups are resolved @@ -331,6 +332,15 @@ When `role_claim_name` is set, ToolHive extracts the claim value and creates `THVGroup` parent entities for the principal, following the same pattern as group claims. Both groups and roles use the `THVGroup` entity type. +### Customizing the group entity type + +The `THVGroup` entity type name is the default. To use a different name in your +policy vocabulary (for example, `OrgRole` or `Group`), set `group_entity_type` +in the Cedar config. The value must be a valid Cedar identifier and applies to +both group and role claims. See +[Customizing the group entity type](../concepts/cedar-policies.mdx#customizing-the-group-entity-type) +in the Cedar policies guide for details. + ### Server-scoped resources Each MCP server is automatically registered as an `MCP` entity. You can use this diff --git a/static/api-specs/toolhive-api.yaml b/static/api-specs/toolhive-api.yaml index 570e4d63..6d973f5c 100644 --- a/static/api-specs/toolhive-api.yaml +++ b/static/api-specs/toolhive-api.yaml @@ -405,6 +405,51 @@ components: This is required and must match a configured upstream provider name. type: string type: object + github_com_stacklok_toolhive_pkg_authserver.DCRUpstreamConfig: + description: |- + DCRConfig enables RFC 7591 Dynamic Client Registration against the + upstream authorization server. When set, the client credentials are + obtained at runtime rather than being pre-provisioned via ClientID / + ClientSecretFile / ClientSecretEnvVar, and ClientID must be left empty. + Mutually exclusive with ClientID. + properties: + discovery_url: + description: |- + DiscoveryURL is the RFC 8414 / OIDC Discovery URL from which the + registration_endpoint is resolved at runtime. Mutually exclusive with + RegistrationEndpoint. + type: string + initial_access_token_env_var: + description: |- + InitialAccessTokenEnvVar is the name of an environment variable + containing the RFC 7591 initial access token. Mutually exclusive with + InitialAccessTokenFile. + type: string + initial_access_token_file: + description: |- + InitialAccessTokenFile is the path to a file containing the RFC 7591 + initial access token presented to the registration endpoint. Mutually + exclusive with InitialAccessTokenEnvVar. Both may be omitted for open + registration endpoints. + type: string + registration_endpoint: + description: |- + RegistrationEndpoint is the RFC 7591 registration endpoint URL used + directly, bypassing discovery. Mutually exclusive with DiscoveryURL. + type: string + software_id: + description: |- + SoftwareID is the RFC 7591 "software_id" registration metadata value, + identifying the client software independent of any particular + registration instance. + type: string + software_statement: + description: |- + SoftwareStatement is the RFC 7591 "software_statement" JWT asserting + metadata about the client software, signed by a party the authorization + server trusts. + type: string + type: object github_com_stacklok_toolhive_pkg_authserver.OAuth2UpstreamRunConfig: description: |- OAuth2Config contains OAuth 2.0-specific configuration. @@ -423,8 +468,10 @@ components: endpoint. type: string client_id: - description: ClientID is the OAuth 2.0 client identifier registered with - the upstream IDP. + description: |- + ClientID is the OAuth 2.0 client identifier registered with the upstream IDP. + Mutually exclusive with DCRConfig: when DCRConfig is set, ClientID is obtained + at runtime via RFC 7591 Dynamic Client Registration and must be left empty. type: string client_secret_env_var: description: |- @@ -436,6 +483,8 @@ components: ClientSecretFile is the path to a file containing the OAuth 2.0 client secret. Mutually exclusive with ClientSecretEnvVar. Optional for public clients using PKCE. type: string + dcr_config: + $ref: '#/components/schemas/github_com_stacklok_toolhive_pkg_authserver.DCRUpstreamConfig' redirect_uri: description: |- RedirectURI is the callback URL where the upstream IDP will redirect after authentication.