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
106 changes: 106 additions & 0 deletions cmd/thv-operator/api/v1alpha1/mcpauthzconfig_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// SPDX-FileCopyrightText: Copyright 2025 Stacklok, Inc.
// SPDX-License-Identifier: Apache-2.0

package v1alpha1

import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
)

// Condition type and reasons for MCPAuthzConfig status (RFC-0023)
const (
// ConditionTypeAuthzConfigValid indicates whether the MCPAuthzConfig configuration is valid
ConditionTypeAuthzConfigValid = ConditionTypeValid

// ConditionReasonAuthzConfigValid indicates spec validation passed
ConditionReasonAuthzConfigValid = "ConfigValid"

// ConditionReasonAuthzConfigInvalid indicates spec validation failed
ConditionReasonAuthzConfigInvalid = "ConfigInvalid"
)

// MCPAuthzConfigSpec defines the desired state of MCPAuthzConfig.
// MCPAuthzConfig resources are namespace-scoped and can only be referenced by
// MCPServer, MCPRemoteProxy, or VirtualMCPServer resources in the same namespace.
type MCPAuthzConfigSpec struct {
// Type identifies the authorizer backend (e.g., "cedarv1", "httpv1").
// Must match a registered authorizer type in the factory registry.
// +kubebuilder:validation:Required
// +kubebuilder:validation:MinLength=1
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.

[LOW] No Validate() method on MCPAuthzConfig type (Consensus: 7/10)

Other shared config CRDs (MCPOIDCConfig, MCPTelemetryConfig, MCPExternalAuthConfig) define a Validate() method on the type itself, called from the controller as config.Validate(). This CRD uses a standalone validateAuthzConfigSpec() function in the controller package instead.

The deviation is understandable (validation requires the factory registry, an external dependency), but a comment in the controller explaining why would help maintainability.

Raised by: toolhive-expert, code-reviewer

Type string `json:"type"`

// Config contains the backend-specific authorization configuration.
// The structure depends on the Type field:
// - cedarv1: policies ([]string), entities_json (string), primary_upstream_provider (string), group_claim_name (string)
// - httpv1: http ({url, timeout, insecure_skip_verify}), context ({include_args, include_operation}), claim_mapping (string)
// +kubebuilder:pruning:PreserveUnknownFields
// +kubebuilder:validation:Type=object
Config runtime.RawExtension `json:"config"`
}

// MCPAuthzConfigStatus defines the observed state of MCPAuthzConfig
type MCPAuthzConfigStatus struct {
// Conditions represent the latest available observations of the MCPAuthzConfig's state
// +listType=map
// +listMapKey=type
// +optional
Conditions []metav1.Condition `json:"conditions,omitempty"`

// ObservedGeneration is the most recent generation observed for this MCPAuthzConfig.
// +optional
ObservedGeneration int64 `json:"observedGeneration,omitempty"`

// ConfigHash is a hash of the current configuration for change detection
// +optional
ConfigHash string `json:"configHash,omitempty"`

// ReferencingWorkloads is a list of workload resources that reference this MCPAuthzConfig.
// Each entry identifies the workload by kind and name.
// +listType=map
// +listMapKey=kind
// +listMapKey=name
// +optional
ReferencingWorkloads []WorkloadReference `json:"referencingWorkloads,omitempty"`
}

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.

[LOW] ReferencingWorkloads listMapKey=name may be insufficient (Consensus: 7/10)

Using only name as the list map key could collide if an MCPServer and a VirtualMCPServer share the same name in the same namespace. The composite key should be kind + name:

// +listMapKey=kind
// +listMapKey=name

This is a pre-existing pattern copied from MCPOIDCConfig, MCPTelemetryConfig, etc. -- consider fixing across all config CRDs in a separate PR.

Raised by: kubernetes-expert

// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:resource:shortName=authzcfg,categories=toolhive
// +kubebuilder:printcolumn:name="Type",type=string,JSONPath=`.spec.type`
// +kubebuilder:printcolumn:name="Valid",type=string,JSONPath=`.status.conditions[?(@.type=='Valid')].status`
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`

// MCPAuthzConfig is the Schema for the mcpauthzconfigs API.
// MCPAuthzConfig resources are namespace-scoped and can only be referenced by
// MCPServer, MCPRemoteProxy, or VirtualMCPServer resources within the same namespace.
// Cross-namespace references are not supported for security and isolation reasons.
type MCPAuthzConfig struct {
metav1.TypeMeta `json:",inline"` // nolint:revive
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec MCPAuthzConfigSpec `json:"spec,omitempty"`
Status MCPAuthzConfigStatus `json:"status,omitempty"`
}

// +kubebuilder:object:root=true

// MCPAuthzConfigList contains a list of MCPAuthzConfig
type MCPAuthzConfigList struct {
metav1.TypeMeta `json:",inline"` // nolint:revive
metav1.ListMeta `json:"metadata,omitempty"`
Items []MCPAuthzConfig `json:"items"`
}

// MCPAuthzConfigReference references a shared MCPAuthzConfig resource.
// The referenced MCPAuthzConfig must be in the same namespace as the referencing workload.
type MCPAuthzConfigReference struct {
// Name is the name of the MCPAuthzConfig resource in the same namespace.
// +kubebuilder:validation:Required
// +kubebuilder:validation:MinLength=1
Name string `json:"name"`
}

func init() {
SchemeBuilder.Register(&MCPAuthzConfig{}, &MCPAuthzConfigList{})
}
11 changes: 10 additions & 1 deletion cmd/thv-operator/api/v1alpha1/mcpremoteproxy_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ type HeaderFromSecret struct {
// MCPRemoteProxySpec defines the desired state of MCPRemoteProxy
//
// +kubebuilder:validation:XValidation:rule="!(has(self.oidcConfig) && has(self.oidcConfigRef))",message="oidcConfig and oidcConfigRef are mutually exclusive; use oidcConfigRef to reference a shared MCPOIDCConfig"
// +kubebuilder:validation:XValidation:rule="!(has(self.authzConfig) && has(self.authzConfigRef))",message="authzConfig and authzConfigRef are mutually exclusive; use authzConfigRef to reference a shared MCPAuthzConfig"
// +kubebuilder:validation:XValidation:rule="!(has(self.telemetry) && has(self.telemetryConfigRef))",message="telemetry and telemetryConfigRef are mutually exclusive; migrate to telemetryConfigRef"
//
//nolint:lll // CEL validation rules exceed line length limit
Expand Down Expand Up @@ -88,10 +89,18 @@ type MCPRemoteProxySpec struct {
// +optional
HeaderForward *HeaderForwardConfig `json:"headerForward,omitempty"`

// AuthzConfig defines authorization policy configuration for the proxy
// AuthzConfig defines authorization policy configuration for the proxy.
// Deprecated: Use AuthzConfigRef to reference a shared MCPAuthzConfig resource instead.
// AuthzConfig and AuthzConfigRef are mutually exclusive.
// +optional
AuthzConfig *AuthzConfigRef `json:"authzConfig,omitempty"`

// AuthzConfigRef references a shared MCPAuthzConfig resource for authorization.
// The referenced MCPAuthzConfig must exist in the same namespace as this MCPRemoteProxy.
// Mutually exclusive with authzConfig.
// +optional
AuthzConfigRef *MCPAuthzConfigReference `json:"authzConfigRef,omitempty"`

// Audit defines audit logging configuration for the proxy
// +optional
Audit *AuditConfig `json:"audit,omitempty"`
Expand Down
11 changes: 10 additions & 1 deletion cmd/thv-operator/api/v1alpha1/mcpserver_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ const SessionStorageProviderRedis = "redis"
// MCPServerSpec defines the desired state of MCPServer
//
// +kubebuilder:validation:XValidation:rule="!(has(self.oidcConfig) && has(self.oidcConfigRef))",message="oidcConfig and oidcConfigRef are mutually exclusive; use oidcConfigRef to reference a shared MCPOIDCConfig"
// +kubebuilder:validation:XValidation:rule="!(has(self.authzConfig) && has(self.authzConfigRef))",message="authzConfig and authzConfigRef are mutually exclusive; use authzConfigRef to reference a shared MCPAuthzConfig"
// +kubebuilder:validation:XValidation:rule="!(has(self.telemetry) && has(self.telemetryConfigRef))",message="telemetry and telemetryConfigRef are mutually exclusive; migrate to telemetryConfigRef"
// +kubebuilder:validation:XValidation:rule="!has(self.rateLimiting) || (has(self.sessionStorage) && self.sessionStorage.provider == 'redis')",message="rateLimiting requires sessionStorage with provider 'redis'"
// +kubebuilder:validation:XValidation:rule="!(has(self.rateLimiting) && has(self.rateLimiting.perUser)) || has(self.oidcConfig) || has(self.oidcConfigRef) || has(self.externalAuthConfigRef)",message="rateLimiting.perUser requires authentication (oidcConfig, oidcConfigRef, or externalAuthConfigRef)"
Expand Down Expand Up @@ -276,10 +277,18 @@ type MCPServerSpec struct {
// +optional
OIDCConfigRef *MCPOIDCConfigReference `json:"oidcConfigRef,omitempty"`

// AuthzConfig defines authorization policy configuration for the MCP server
// AuthzConfig defines authorization policy configuration for the MCP server.
// Deprecated: Use AuthzConfigRef to reference a shared MCPAuthzConfig resource instead.
// AuthzConfig and AuthzConfigRef are mutually exclusive.
// +optional
AuthzConfig *AuthzConfigRef `json:"authzConfig,omitempty"`

// AuthzConfigRef references a shared MCPAuthzConfig resource for authorization.
// The referenced MCPAuthzConfig must exist in the same namespace as this MCPServer.
// Mutually exclusive with authzConfig.
// +optional
AuthzConfigRef *MCPAuthzConfigReference `json:"authzConfigRef,omitempty"`
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.

[HIGH] Missing CEL XValidation for authzConfig/authzConfigRef mutual exclusivity (Consensus: 10/10)

The AuthzConfig and AuthzConfigRef fields are documented as "mutually exclusive" but no CEL validation rule enforces this at the API server level. The existing OIDC pattern has:

// +kubebuilder:validation:XValidation:rule="!(has(self.oidcConfig) && has(self.oidcConfigRef))",message="oidcConfig and oidcConfigRef are mutually exclusive..."

The same rule is needed here on MCPServerSpec, MCPRemoteProxySpec, and IncomingAuthConfig:

// +kubebuilder:validation:XValidation:rule="!(has(self.authzConfig) && has(self.authzConfigRef))",message="authzConfig and authzConfigRef are mutually exclusive; use authzConfigRef to reference a shared MCPAuthzConfig"

Without this, users can set both fields simultaneously and the controller must decide which takes precedence.

Raised by: kubernetes-expert, code-reviewer, toolhive-expert


// Audit defines audit logging configuration for the MCP server
// +optional
Audit *AuditConfig `json:"audit,omitempty"`
Expand Down
12 changes: 10 additions & 2 deletions cmd/thv-operator/api/v1alpha1/virtualmcpserver_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ type EmbeddingServerRef struct {
//
// +kubebuilder:validation:XValidation:rule="self.type == 'oidc' ? (has(self.oidcConfig) || has(self.oidcConfigRef)) : true",message="spec.incomingAuth.oidcConfig or oidcConfigRef is required when type is oidc"
// +kubebuilder:validation:XValidation:rule="!(has(self.oidcConfig) && has(self.oidcConfigRef))",message="oidcConfig and oidcConfigRef are mutually exclusive; use oidcConfigRef to reference a shared MCPOIDCConfig"
// +kubebuilder:validation:XValidation:rule="!(has(self.authzConfig) && has(self.authzConfigRef))",message="authzConfig and authzConfigRef are mutually exclusive; use authzConfigRef to reference a shared MCPAuthzConfig"
//
//nolint:lll // CEL validation rules exceed line length limit
type IncomingAuthConfig struct {
Expand All @@ -133,10 +134,17 @@ type IncomingAuthConfig struct {
// +optional
OIDCConfigRef *MCPOIDCConfigReference `json:"oidcConfigRef,omitempty"`

// AuthzConfig defines authorization policy configuration
// Reuses MCPServer authz patterns
// AuthzConfig defines authorization policy configuration.
// Deprecated: Use AuthzConfigRef to reference a shared MCPAuthzConfig resource instead.
// AuthzConfig and AuthzConfigRef are mutually exclusive.
// +optional
AuthzConfig *AuthzConfigRef `json:"authzConfig,omitempty"`

// AuthzConfigRef references a shared MCPAuthzConfig resource for authorization.
// The referenced MCPAuthzConfig must exist in the same namespace as this VirtualMCPServer.
// Mutually exclusive with authzConfig.
// +optional
AuthzConfigRef *MCPAuthzConfigReference `json:"authzConfigRef,omitempty"`
}

// OutgoingAuthConfig configures authentication from Virtual MCP to backend MCPServers
Expand Down
132 changes: 132 additions & 0 deletions cmd/thv-operator/api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading