Skip to content

Commit 6bea254

Browse files
jhrozekclaudeJAORMX
committed
Add MCPExternalAuthConfig CRD and controller
Implement external authentication configuration for MCP servers via a new MCPExternalAuthConfig custom resource. This enables MCP servers to exchange incoming authentication tokens for tokens that can be used with external services via RFC-8693 OAuth 2.0 Token Exchange. The MCPExternalAuthConfig is namespace-scoped and can only be referenced by MCPServers in the same namespace. The controller implements a finalizer to prevent deletion while referenced, and uses hash-based change detection to efficiently trigger MCPServer reconciliation when configuration changes. Configuration is injected into MCPServer deployments via RunConfig ConfigMap with the OAuth client secret provided through a TOOLHIVE_TOKEN_EXCHANGE_CLIENT_SECRET environment variable that references a Kubernetes Secret, following security best practices. Includes comprehensive unit tests (83% coverage), integration tests, E2E Chainsaw tests, and example manifests. Co-Authored-By: Jakub Hrozek <[email protected]> Co-authored-by: Claude <[email protected]> Co-authored-by: Juan Antonio Osorio <[email protected]> Signed-off-by: Juan Antonio Osorio <[email protected]>
1 parent bca5e7a commit 6bea254

23 files changed

+2752
-0
lines changed
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package v1alpha1
2+
3+
import (
4+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
5+
)
6+
7+
// External auth configuration types
8+
const (
9+
// ExternalAuthTypeTokenExchange is the type for RFC-8693 token exchange
10+
ExternalAuthTypeTokenExchange = "tokenExchange"
11+
)
12+
13+
// MCPExternalAuthConfigSpec defines the desired state of MCPExternalAuthConfig.
14+
// MCPExternalAuthConfig resources are namespace-scoped and can only be referenced by
15+
// MCPServer resources in the same namespace.
16+
type MCPExternalAuthConfigSpec struct {
17+
// Type is the type of external authentication to configure
18+
// +kubebuilder:validation:Enum=tokenExchange
19+
// +kubebuilder:validation:Required
20+
Type string `json:"type"`
21+
22+
// TokenExchange configures RFC-8693 OAuth 2.0 Token Exchange
23+
// Only used when Type is "tokenExchange"
24+
// +optional
25+
TokenExchange *TokenExchangeConfig `json:"tokenExchange,omitempty"`
26+
}
27+
28+
// TokenExchangeConfig holds configuration for RFC-8693 OAuth 2.0 Token Exchange.
29+
// This configuration is used to exchange incoming authentication tokens for tokens
30+
// that can be used with external services.
31+
// The structure matches the tokenexchange.Config from pkg/auth/tokenexchange/middleware.go
32+
type TokenExchangeConfig struct {
33+
// TokenURL is the OAuth 2.0 token endpoint URL for token exchange
34+
// +kubebuilder:validation:Required
35+
TokenURL string `json:"token_url"`
36+
37+
// ClientID is the OAuth 2.0 client identifier
38+
// +kubebuilder:validation:Required
39+
ClientID string `json:"client_id"`
40+
41+
// ClientSecretRef is a reference to a secret containing the OAuth 2.0 client secret
42+
// +kubebuilder:validation:Required
43+
ClientSecretRef SecretKeyRef `json:"client_secret_ref"`
44+
45+
// Audience is the target audience for the exchanged token
46+
// +kubebuilder:validation:Required
47+
Audience string `json:"audience"`
48+
49+
// Scope is the scope to request for the exchanged token (space-separated string)
50+
// +optional
51+
Scope string `json:"scope,omitempty"`
52+
53+
// ExternalTokenHeaderName is the name of the custom header to use for the exchanged token.
54+
// If set, the exchanged token will be added to this custom header (e.g., "X-Upstream-Token").
55+
// If empty or not set, the exchanged token will replace the Authorization header (default behavior).
56+
// +optional
57+
ExternalTokenHeaderName string `json:"external_token_header_name,omitempty"`
58+
}
59+
60+
// SecretKeyRef is a reference to a key within a Secret
61+
type SecretKeyRef struct {
62+
// Name is the name of the secret
63+
// +kubebuilder:validation:Required
64+
Name string `json:"name"`
65+
66+
// Key is the key within the secret
67+
// +kubebuilder:validation:Required
68+
Key string `json:"key"`
69+
}
70+
71+
// MCPExternalAuthConfigStatus defines the observed state of MCPExternalAuthConfig
72+
type MCPExternalAuthConfigStatus struct {
73+
// ObservedGeneration is the most recent generation observed for this MCPExternalAuthConfig.
74+
// It corresponds to the MCPExternalAuthConfig's generation, which is updated on mutation by the API Server.
75+
// +optional
76+
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
77+
78+
// ConfigHash is a hash of the current configuration for change detection
79+
// +optional
80+
ConfigHash string `json:"configHash,omitempty"`
81+
}
82+
83+
// +kubebuilder:object:root=true
84+
// +kubebuilder:subresource:status
85+
// +kubebuilder:resource:shortName=extauth;mcpextauth
86+
// +kubebuilder:printcolumn:name="Type",type=string,JSONPath=`.spec.type`
87+
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
88+
89+
// MCPExternalAuthConfig is the Schema for the mcpexternalauthconfigs API.
90+
// MCPExternalAuthConfig resources are namespace-scoped and can only be referenced by
91+
// MCPServer resources within the same namespace. Cross-namespace references
92+
// are not supported for security and isolation reasons.
93+
type MCPExternalAuthConfig struct {
94+
metav1.TypeMeta `json:",inline"` // nolint:revive
95+
metav1.ObjectMeta `json:"metadata,omitempty"`
96+
97+
Spec MCPExternalAuthConfigSpec `json:"spec,omitempty"`
98+
Status MCPExternalAuthConfigStatus `json:"status,omitempty"`
99+
}
100+
101+
// +kubebuilder:object:root=true
102+
103+
// MCPExternalAuthConfigList contains a list of MCPExternalAuthConfig
104+
type MCPExternalAuthConfigList struct {
105+
metav1.TypeMeta `json:",inline"` // nolint:revive
106+
metav1.ListMeta `json:"metadata,omitempty"`
107+
Items []MCPExternalAuthConfig `json:"items"`
108+
}
109+
110+
func init() {
111+
SchemeBuilder.Register(&MCPExternalAuthConfig{}, &MCPExternalAuthConfigList{})
112+
}

cmd/thv-operator/api/v1alpha1/mcpserver_types.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,11 @@ type MCPServerSpec struct {
116116
// +optional
117117
ToolConfigRef *ToolConfigRef `json:"toolConfigRef,omitempty"`
118118

119+
// ExternalAuthConfigRef references a MCPExternalAuthConfig resource for external authentication.
120+
// The referenced MCPExternalAuthConfig must exist in the same namespace as this MCPServer.
121+
// +optional
122+
ExternalAuthConfigRef *ExternalAuthConfigRef `json:"externalAuthConfigRef,omitempty"`
123+
119124
// Telemetry defines observability configuration for the MCP server
120125
// +optional
121126
Telemetry *TelemetryConfig `json:"telemetry,omitempty"`
@@ -480,6 +485,14 @@ type ToolConfigRef struct {
480485
Name string `json:"name"`
481486
}
482487

488+
// ExternalAuthConfigRef defines a reference to a MCPExternalAuthConfig resource.
489+
// The referenced MCPExternalAuthConfig must be in the same namespace as the MCPServer.
490+
type ExternalAuthConfigRef struct {
491+
// Name is the name of the MCPExternalAuthConfig resource
492+
// +kubebuilder:validation:Required
493+
Name string `json:"name"`
494+
}
495+
483496
// InlineAuthzConfig contains direct authorization configuration
484497
type InlineAuthzConfig struct {
485498
// Policies is a list of Cedar policy strings
@@ -587,6 +600,10 @@ type MCPServerStatus struct {
587600
// +optional
588601
ToolConfigHash string `json:"toolConfigHash,omitempty"`
589602

603+
// ExternalAuthConfigHash is the hash of the referenced MCPExternalAuthConfig spec
604+
// +optional
605+
ExternalAuthConfigHash string `json:"externalAuthConfigHash,omitempty"`
606+
590607
// URL is the URL where the MCP server can be accessed
591608
// +optional
592609
URL string `json:"url,omitempty"`

cmd/thv-operator/api/v1alpha1/zz_generated.deepcopy.go

Lines changed: 145 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)