-
Notifications
You must be signed in to change notification settings - Fork 207
Add MCPAuthzConfig CRD for backend-agnostic authorization #4777
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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 | ||
| 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"` | ||
| } | ||
|
|
||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [LOW] ReferencingWorkloads listMapKey=name may be insufficient (Consensus: 7/10) Using only // +listMapKey=kind
// +listMapKey=nameThis 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{}) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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)" | ||
|
|
@@ -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"` | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 // +kubebuilder:validation:XValidation:rule="!(has(self.oidcConfig) && has(self.oidcConfigRef))",message="oidcConfig and oidcConfigRef are mutually exclusive..."The same rule is needed here on // +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"` | ||
|
|
||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
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 asconfig.Validate(). This CRD uses a standalonevalidateAuthzConfigSpec()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