Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/upstream-projects.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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/,
Expand Down
38 changes: 38 additions & 0 deletions docs/toolhive/concepts/cedar-policies.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
34 changes: 22 additions & 12 deletions docs/toolhive/reference/authz-policy-reference.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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::"<sub_claim>"` | The authenticated user, identified by the `sub` claim from the access token |
| `Action` | `Action::"<action_id>"` | The MCP operation being performed |
| `Tool` | `Tool::"<tool_name>"` | A tool resource (used for `tools/call`) |
| `Prompt` | `Prompt::"<prompt_name>"` | A prompt resource (used for `prompts/get`) |
| `Resource` | `Resource::"<sanitized_uri>"` | A data resource (used for `resources/read`). The URI is [sanitized](#resource-uri-sanitization) for Cedar compatibility |
| `FeatureType` | `FeatureType::"<feature>"` | 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::"<group_name>"` | A group membership entity. Used with Cedar's `in` operator for [group-based policies](#group-membership) |
| Entity type | Format | Description |
| ------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Client` | `Client::"<sub_claim>"` | The authenticated user, identified by the `sub` claim from the access token |
| `Action` | `Action::"<action_id>"` | The MCP operation being performed |
| `Tool` | `Tool::"<tool_name>"` | A tool resource (used for `tools/call`) |
| `Prompt` | `Prompt::"<prompt_name>"` | A prompt resource (used for `prompts/get`) |
| `Resource` | `Resource::"<sanitized_uri>"` | A data resource (used for `resources/read`). The URI is [sanitized](#resource-uri-sanitization) for Cedar compatibility |
| `FeatureType` | `FeatureType::"<feature>"` | 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::"<group_name>"` | 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

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
53 changes: 51 additions & 2 deletions static/api-specs/toolhive-api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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: |-
Expand All @@ -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.
Expand Down