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 @@ -44,7 +44,7 @@ projects:

- id: toolhive
repo: stacklok/toolhive
version: v0.43.0
version: v0.44.0
# 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
16 changes: 12 additions & 4 deletions static/api-specs/toolhive-api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -976,6 +976,14 @@ components:
instead of Extra (assignClaim routes it to that field) — it is still
the external token's client_id claim, not a ToolHive one.
type: string
allow_may_act:
description: |-
AllowMayAct permits this external issuer's may_act claim to authorize
delegation. It defaults to false; external issuers must be opted in
explicitly because may_act bypasses AllowedActors. It does not affect
self-issued subject tokens. When enabled, AllowedDelegateClients must
name specific ToolHive clients rather than use the wildcard.
type: boolean
allow_private_ips:
description: |-
AllowPrivateIPs permits OIDC discovery and JWKS fetches for THIS
Expand All @@ -986,10 +994,10 @@ components:
description: |-
AllowedActors is the allowlist of ActorClaim values authorized to
exchange a subject token from this issuer when it carries no
"may_act" claim; empty means only may_act-bearing tokens are
accepted. By itself names no ToolHive client — see
AllowedDelegateClients and docs/arch/17-token-exchange-delegation.md
("Accepted limitations" #1).
"may_act" claim. Empty denies every token unless AllowMayAct is true
and the token carries a permitted may_act claim. By itself names no
ToolHive client — see AllowedDelegateClients and
docs/arch/17-token-exchange-delegation.md ("Accepted limitations" #1).
items:
type: string
type: array
Expand Down
93 changes: 93 additions & 0 deletions static/api-specs/toolhive-crds/mcpexternalauthconfigs.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,99 @@
},
"type": "object"
},
"trustedIssuers": {
"description": "TrustedIssuers configures external OIDC issuers whose tokens are\naccepted as RFC 8693 subject tokens during token exchange, in addition\nto self-issued subject tokens. Empty (the default) means only\nself-issued subject tokens are accepted. See\ndocs/arch/17-token-exchange-delegation.md for the trust model.",
"items": {
"description": "TrustedIssuerConfig configures an external OIDC issuer whose tokens are\naccepted as RFC 8693 subject tokens during token exchange. It mirrors\ntokenexchange.TrustedIssuer (pkg/authserver/server/tokenexchange), the\nruntime type the operator converts this into directly — no secret is\nreferenced by this type, so no SecretKeyRef indirection is needed, unlike\nDelegateClientConfig.",
"properties": {
"actorClaim": {
"description": "ActorClaim names the claim identifying the client that requested the\nsubject token from this external issuer (used by allowedActors below).\nDefaults to \"azp\" when empty; use \"appid\" for Microsoft Entra v1, \"cid\"\nfor Okta. The special value \"client_id\" reads the subject token's\nclient_id claim instead.",
"maxLength": 64,
"type": "string"
},
"allowMayAct": {
"default": false,
"description": "AllowMayAct permits this external issuer's may_act claim to authorize\ndelegation. Defaults to false; external issuers must be opted in\nexplicitly because may_act bypasses allowedActors. Does not affect\nself-issued subject tokens. The wildcard is never permitted alongside\nspecific allowedDelegateClients, regardless of this setting.",
"type": "boolean"
},
"allowPrivateIPs": {
"description": "AllowPrivateIPs permits OIDC discovery and JWKS fetches for THIS issuer\nto resolve to a private or loopback address. Use only when the issuer\nis hosted inside the same cluster and has no public endpoint. Requires\njwksUrl to be set explicitly (enforced at reconcile time), since\notherwise OIDC discovery — fetched from the external issuer itself —\nwould choose the private dial target.",
"type": "boolean"
},
"allowedActors": {
"description": "AllowedActors is the allowlist of actorClaim values authorized to\nexchange a subject token from this issuer when it carries no\n\"may_act\" claim. Empty denies every token unless allowMayAct is true\nand the token carries a permitted may_act claim.",
"items": {
"maxLength": 256,
"minLength": 1,
"type": "string"
},
"maxItems": 50,
"type": "array",
"x-kubernetes-list-type": "atomic"
},
"allowedDelegateClients": {
"description": "AllowedDelegateClients restricts which ToolHive client IDs may\nexchange a subject token from this issuer. Required; set it to [\"*\"]\nto permit any confidential client holding the token-exchange grant. The\nwildcard must be the only entry; otherwise list specific client IDs to\nbind delegation to them.",
"items": {
"maxLength": 256,
"minLength": 1,
"type": "string"
},
"maxItems": 50,
"minItems": 1,
"type": "array",
"x-kubernetes-list-type": "atomic"
},
"expectedAudience": {
"description": "ExpectedAudience is the expected \"aud\" claim value that must appear in\nthe token's audience list. This should be a resource/API identifier\n(e.g. a URI), not a client ID.",
"maxLength": 2048,
"minLength": 1,
"type": "string"
},
"insecureAllowHTTP": {
"description": "InsecureAllowHTTP permits plain-HTTP OIDC discovery and JWKS fetches\nfor THIS issuer only. Development and testing only — never set in\nproduction.",
"type": "boolean"
},
"issuerUrl": {
"description": "IssuerURL is the expected \"iss\" claim value (exact match).",
"maxLength": 2048,
"minLength": 1,
"type": "string"
},
"jwksUrl": {
"description": "JWKSURL is the URL to fetch the issuer's JSON Web Key Set from. If\nempty, it is resolved via OIDC discovery at\n{issuerUrl}/.well-known/openid-configuration.",
"maxLength": 2048,
"type": "string"
}
},
"required": [
"allowedDelegateClients",
"expectedAudience",
"issuerUrl"
],
"type": "object",
"x-kubernetes-validations": [
{
"message": "allowedDelegateClients must not combine the wildcard \"*\" with specific client IDs",
"rule": "!('*' in self.allowedDelegateClients) || size(self.allowedDelegateClients) == 1"
},
{
"message": "allowMayAct must not be enabled when allowedDelegateClients contains the wildcard \"*\"",
"rule": "!(has(self.allowMayAct) && self.allowMayAct && '*' in self.allowedDelegateClients)"
},
{
"message": "actorClaim must name a readable claim; use client_id or a non-reserved claim such as azp, appid, or cid",
"rule": "!has(self.actorClaim) || !(self.actorClaim in ['sub', 'iss', 'aud', 'exp', 'iat', 'nbf', 'jti', 'name', 'email', 'scope', 'scp', 'may_act'])"
},
{
"message": "allowPrivateIPs requires jwksUrl to be set explicitly",
"rule": "!(has(self.allowPrivateIPs) && self.allowPrivateIPs) || (has(self.jwksUrl) && self.jwksUrl != \"\")"
}
]
},
"maxItems": 20,
"type": "array",
"x-kubernetes-list-type": "atomic"
},
"upstreamProviders": {
"description": "UpstreamProviders configures connections to upstream Identity Providers.\nThe embedded auth server delegates authentication to these providers.\nMCPServer and MCPRemoteProxy support a single upstream; VirtualMCPServer supports multiple.",
"items": {
Expand Down
93 changes: 93 additions & 0 deletions static/api-specs/toolhive-crds/virtualmcpservers.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,99 @@
},
"type": "object"
},
"trustedIssuers": {
"description": "TrustedIssuers configures external OIDC issuers whose tokens are\naccepted as RFC 8693 subject tokens during token exchange, in addition\nto self-issued subject tokens. Empty (the default) means only\nself-issued subject tokens are accepted. See\ndocs/arch/17-token-exchange-delegation.md for the trust model.",
"items": {
"description": "TrustedIssuerConfig configures an external OIDC issuer whose tokens are\naccepted as RFC 8693 subject tokens during token exchange. It mirrors\ntokenexchange.TrustedIssuer (pkg/authserver/server/tokenexchange), the\nruntime type the operator converts this into directly — no secret is\nreferenced by this type, so no SecretKeyRef indirection is needed, unlike\nDelegateClientConfig.",
"properties": {
"actorClaim": {
"description": "ActorClaim names the claim identifying the client that requested the\nsubject token from this external issuer (used by allowedActors below).\nDefaults to \"azp\" when empty; use \"appid\" for Microsoft Entra v1, \"cid\"\nfor Okta. The special value \"client_id\" reads the subject token's\nclient_id claim instead.",
"maxLength": 64,
"type": "string"
},
"allowMayAct": {
"default": false,
"description": "AllowMayAct permits this external issuer's may_act claim to authorize\ndelegation. Defaults to false; external issuers must be opted in\nexplicitly because may_act bypasses allowedActors. Does not affect\nself-issued subject tokens. The wildcard is never permitted alongside\nspecific allowedDelegateClients, regardless of this setting.",
"type": "boolean"
},
"allowPrivateIPs": {
"description": "AllowPrivateIPs permits OIDC discovery and JWKS fetches for THIS issuer\nto resolve to a private or loopback address. Use only when the issuer\nis hosted inside the same cluster and has no public endpoint. Requires\njwksUrl to be set explicitly (enforced at reconcile time), since\notherwise OIDC discovery — fetched from the external issuer itself —\nwould choose the private dial target.",
"type": "boolean"
},
"allowedActors": {
"description": "AllowedActors is the allowlist of actorClaim values authorized to\nexchange a subject token from this issuer when it carries no\n\"may_act\" claim. Empty denies every token unless allowMayAct is true\nand the token carries a permitted may_act claim.",
"items": {
"maxLength": 256,
"minLength": 1,
"type": "string"
},
"maxItems": 50,
"type": "array",
"x-kubernetes-list-type": "atomic"
},
"allowedDelegateClients": {
"description": "AllowedDelegateClients restricts which ToolHive client IDs may\nexchange a subject token from this issuer. Required; set it to [\"*\"]\nto permit any confidential client holding the token-exchange grant. The\nwildcard must be the only entry; otherwise list specific client IDs to\nbind delegation to them.",
"items": {
"maxLength": 256,
"minLength": 1,
"type": "string"
},
"maxItems": 50,
"minItems": 1,
"type": "array",
"x-kubernetes-list-type": "atomic"
},
"expectedAudience": {
"description": "ExpectedAudience is the expected \"aud\" claim value that must appear in\nthe token's audience list. This should be a resource/API identifier\n(e.g. a URI), not a client ID.",
"maxLength": 2048,
"minLength": 1,
"type": "string"
},
"insecureAllowHTTP": {
"description": "InsecureAllowHTTP permits plain-HTTP OIDC discovery and JWKS fetches\nfor THIS issuer only. Development and testing only — never set in\nproduction.",
"type": "boolean"
},
"issuerUrl": {
"description": "IssuerURL is the expected \"iss\" claim value (exact match).",
"maxLength": 2048,
"minLength": 1,
"type": "string"
},
"jwksUrl": {
"description": "JWKSURL is the URL to fetch the issuer's JSON Web Key Set from. If\nempty, it is resolved via OIDC discovery at\n{issuerUrl}/.well-known/openid-configuration.",
"maxLength": 2048,
"type": "string"
}
},
"required": [
"allowedDelegateClients",
"expectedAudience",
"issuerUrl"
],
"type": "object",
"x-kubernetes-validations": [
{
"message": "allowedDelegateClients must not combine the wildcard \"*\" with specific client IDs",
"rule": "!('*' in self.allowedDelegateClients) || size(self.allowedDelegateClients) == 1"
},
{
"message": "allowMayAct must not be enabled when allowedDelegateClients contains the wildcard \"*\"",
"rule": "!(has(self.allowMayAct) && self.allowMayAct && '*' in self.allowedDelegateClients)"
},
{
"message": "actorClaim must name a readable claim; use client_id or a non-reserved claim such as azp, appid, or cid",
"rule": "!has(self.actorClaim) || !(self.actorClaim in ['sub', 'iss', 'aud', 'exp', 'iat', 'nbf', 'jti', 'name', 'email', 'scope', 'scp', 'may_act'])"
},
{
"message": "allowPrivateIPs requires jwksUrl to be set explicitly",
"rule": "!(has(self.allowPrivateIPs) && self.allowPrivateIPs) || (has(self.jwksUrl) && self.jwksUrl != \"\")"
}
]
},
"maxItems": 20,
"type": "array",
"x-kubernetes-list-type": "atomic"
},
"upstreamProviders": {
"description": "UpstreamProviders configures connections to upstream Identity Providers.\nThe embedded auth server delegates authentication to these providers.\nMCPServer and MCPRemoteProxy support a single upstream; VirtualMCPServer supports multiple.",
"items": {
Expand Down