Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
37 changes: 37 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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rdimitrov these docs are autogenerated right? This knob is more of an internal one and I'd actually prefer to not have it documented. SHould I push to the PR directly?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes 👍 Or ask claude here (with an @) to do it for you 😃

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@claude the group_entity_type parameter should not be documented, can you remove it from the concepts and other places it's been mentioned at?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, Claude gave the googly-eyes reaction but didn't actually do anything... 🤔

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I pushed to the PR, but I guess I can't review anymore? Verbal LGTM now for my parts anyway

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,39 @@ 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's `in` operator compares entity UIDs by type name, so the
synthesized parents won't match those stale entities and policies that reference
them silently deny every request. 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