Skip to content

Commit f00d37e

Browse files
authored
Merge branch 'main' into worktree-redis-cluster-support-to-auth-server-storage
2 parents 358d801 + 9572f6a commit f00d37e

108 files changed

Lines changed: 7351 additions & 442 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/security-scan.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ jobs:
3232
fail-build: false
3333

3434
- name: Upload Grype scan results to GitHub Security tab
35-
uses: github/codeql-action/upload-sarif@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
35+
uses: github/codeql-action/upload-sarif@e46ed2cbd01164d986452f91f178727624ae40d7 # v4
3636
if: always()
3737
with:
3838
sarif_file: ${{ steps.grype-scan.outputs.sarif }}

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

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
// SPDX-FileCopyrightText: Copyright 2025 Stacklok, Inc.
22
// SPDX-License-Identifier: Apache-2.0
33

4-
// Package v1alpha1 contains the deprecated v1alpha1 API types for the
5-
// toolhive.stacklok.dev group. These types exist solely to enable seamless
6-
// CRD graduation from v1alpha1 → v1beta1: the CRD serves both versions
7-
// (with conversion strategy "None"), so existing v1alpha1 resources continue
8-
// to work while users migrate their manifests to v1beta1.
4+
// Package v1alpha1 contains the v1alpha1 API types for the
5+
// toolhive.stacklok.dev group. Most types in this package exist to enable
6+
// seamless CRD graduation from v1alpha1 to v1beta1: the CRD serves both
7+
// versions (with conversion strategy "None"), so existing v1alpha1 resources
8+
// continue to work while users migrate their manifests to v1beta1.
9+
//
10+
// MCPWebhookConfig starts in v1alpha1 and is not deprecated.
911
//
1012
// All Spec and Status types are imported from v1beta1 — the schemas are
1113
// identical. Only the root resource types and their List companions are
1214
// defined here so that controller-gen produces a multi-version CRD.
1315
//
14-
// This package will be removed in a future release once the v1alpha1
15-
// deprecation period ends.
16+
// The deprecated compatibility types in this package will be removed in a
17+
// future release once the v1alpha1 deprecation period ends.
1618
//
1719
// +kubebuilder:object:generate=true
1820
// +groupName=toolhive.stacklok.dev
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// SPDX-FileCopyrightText: Copyright 2025 Stacklok, Inc.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package v1alpha1
5+
6+
import (
7+
"testing"
8+
"time"
9+
10+
"github.com/stretchr/testify/assert"
11+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
12+
13+
mcpv1beta1 "github.com/stacklok/toolhive/cmd/thv-operator/api/v1beta1"
14+
)
15+
16+
func TestMCPWebhookConfig_Creation(t *testing.T) {
17+
t.Parallel()
18+
19+
timeout := metav1.Duration{Duration: 5 * time.Second}
20+
21+
config := &MCPWebhookConfig{
22+
ObjectMeta: metav1.ObjectMeta{
23+
Name: "test-webhook-config",
24+
Namespace: "default",
25+
},
26+
Spec: mcpv1beta1.MCPWebhookConfigSpec{
27+
Validating: []mcpv1beta1.WebhookSpec{
28+
{
29+
Name: "test-validator",
30+
URL: "https://example.com/validate",
31+
Timeout: &timeout,
32+
FailurePolicy: mcpv1beta1.WebhookFailurePolicyFail,
33+
TLSConfig: &mcpv1beta1.WebhookTLSConfig{
34+
InsecureSkipVerify: true,
35+
},
36+
},
37+
},
38+
Mutating: []mcpv1beta1.WebhookSpec{
39+
{
40+
Name: "test-mutator",
41+
URL: "https://example.com/mutate",
42+
Timeout: &timeout,
43+
FailurePolicy: mcpv1beta1.WebhookFailurePolicyIgnore,
44+
HMACSecretRef: &mcpv1beta1.SecretKeyRef{
45+
Name: "hmac-secret",
46+
Key: "key",
47+
},
48+
},
49+
},
50+
},
51+
}
52+
53+
assert.NotNil(t, config)
54+
assert.Equal(t, "test-webhook-config", config.Name)
55+
assert.Len(t, config.Spec.Validating, 1)
56+
assert.Len(t, config.Spec.Mutating, 1)
57+
58+
assert.Equal(t, "test-validator", config.Spec.Validating[0].Name)
59+
assert.Equal(t, mcpv1beta1.WebhookFailurePolicyFail, config.Spec.Validating[0].FailurePolicy)
60+
assert.True(t, config.Spec.Validating[0].TLSConfig.InsecureSkipVerify)
61+
62+
assert.Equal(t, "test-mutator", config.Spec.Mutating[0].Name)
63+
assert.Equal(t, mcpv1beta1.WebhookFailurePolicyIgnore, config.Spec.Mutating[0].FailurePolicy)
64+
assert.Equal(t, "hmac-secret", config.Spec.Mutating[0].HMACSecretRef.Name)
65+
}

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,34 @@ type MCPTelemetryConfigList struct {
276276
Items []MCPTelemetryConfig `json:"items"`
277277
}
278278

279+
// ─── MCPWebhookConfig ────────────────────────────────────────────────────────
280+
281+
//+kubebuilder:object:root=true
282+
//+kubebuilder:storageversion
283+
//+kubebuilder:subresource:status
284+
//+kubebuilder:resource:shortName=mwc,categories=toolhive
285+
//+kubebuilder:printcolumn:name="Valid",type=string,JSONPath=`.status.conditions[?(@.type=='Valid')].status`
286+
//+kubebuilder:printcolumn:name="References",type=string,JSONPath=`.status.referencingWorkloads`
287+
//+kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
288+
289+
// MCPWebhookConfig is the Schema for the mcpwebhookconfigs API.
290+
type MCPWebhookConfig struct {
291+
metav1.TypeMeta `json:",inline"` // nolint:revive
292+
metav1.ObjectMeta `json:"metadata,omitempty"`
293+
294+
Spec v1beta1.MCPWebhookConfigSpec `json:"spec,omitempty"`
295+
Status v1beta1.MCPWebhookConfigStatus `json:"status,omitempty"`
296+
}
297+
298+
//+kubebuilder:object:root=true
299+
300+
// MCPWebhookConfigList contains a list of MCPWebhookConfig.
301+
type MCPWebhookConfigList struct {
302+
metav1.TypeMeta `json:",inline"` // nolint:revive
303+
metav1.ListMeta `json:"metadata,omitempty"`
304+
Items []MCPWebhookConfig `json:"items"`
305+
}
306+
279307
// ─── MCPToolConfig ───────────────────────────────────────────────────────────
280308

281309
//+kubebuilder:object:root=true
@@ -378,6 +406,7 @@ func init() {
378406
&MCPServer{}, &MCPServerList{},
379407
&MCPServerEntry{}, &MCPServerEntryList{},
380408
&MCPTelemetryConfig{}, &MCPTelemetryConfigList{},
409+
&MCPWebhookConfig{}, &MCPWebhookConfigList{},
381410
&MCPToolConfig{}, &MCPToolConfigList{},
382411
&VirtualMCPCompositeToolDefinition{}, &VirtualMCPCompositeToolDefinitionList{},
383412
&VirtualMCPServer{}, &VirtualMCPServerList{},

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

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

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,18 @@ const (
8686
const (
8787
// ConditionTypeExternalAuthConfigValidated indicates whether the ExternalAuthConfig is valid
8888
ConditionTypeExternalAuthConfigValidated = "ExternalAuthConfigValidated"
89+
90+
// ConditionTypeWebhookConfigValidated indicates whether the WebhookConfig is valid
91+
ConditionTypeWebhookConfigValidated = "WebhookConfigValidated"
8992
)
9093

9194
const (
9295
// ConditionReasonExternalAuthConfigMultiUpstream indicates the ExternalAuthConfig has multiple upstreams,
9396
// which is not supported for MCPServer (use VirtualMCPServer for multi-upstream).
9497
ConditionReasonExternalAuthConfigMultiUpstream = "MultiUpstreamNotSupported"
98+
99+
// ConditionReasonWebhookConfigInvalid indicates the referenced webhook config is invalid or missing
100+
ConditionReasonWebhookConfigInvalid = "WebhookConfigInvalid"
95101
)
96102

97103
const (
@@ -287,6 +293,11 @@ type MCPServerSpec struct {
287293
// +optional
288294
ExternalAuthConfigRef *ExternalAuthConfigRef `json:"externalAuthConfigRef,omitempty"`
289295

296+
// WebhookConfigRef references a MCPWebhookConfig resource for webhook middleware configuration.
297+
// The referenced MCPWebhookConfig must exist in the same namespace as this MCPServer.
298+
// +optional
299+
WebhookConfigRef *WebhookConfigRef `json:"webhookConfigRef,omitempty"`
300+
290301
// AuthServerRef optionally references a resource that configures an embedded
291302
// OAuth 2.0/OIDC authorization server to authenticate MCP clients.
292303
// Currently the only supported kind is MCPExternalAuthConfig (type: embeddedAuthServer).
@@ -717,6 +728,14 @@ type AuthServerRef struct {
717728
Name string `json:"name"`
718729
}
719730

731+
// WebhookConfigRef defines a reference to a MCPWebhookConfig resource.
732+
// The referenced MCPWebhookConfig must be in the same namespace as the MCPServer.
733+
type WebhookConfigRef struct {
734+
// Name is the name of the MCPWebhookConfig resource
735+
// +kubebuilder:validation:Required
736+
Name string `json:"name"`
737+
}
738+
720739
// ToolConfigRef defines a reference to a MCPToolConfig resource.
721740
// The referenced MCPToolConfig must be in the same namespace as the MCPServer.
722741
type ToolConfigRef struct {
@@ -828,6 +847,10 @@ type MCPServerStatus struct {
828847
// +optional
829848
TelemetryConfigHash string `json:"telemetryConfigHash,omitempty"`
830849

850+
// WebhookConfigHash is the hash of the referenced MCPWebhookConfig spec
851+
// +optional
852+
WebhookConfigHash string `json:"webhookConfigHash,omitempty"`
853+
831854
// URL is the URL where the MCP server can be accessed
832855
// +optional
833856
URL string `json:"url,omitempty"`
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// SPDX-FileCopyrightText: Copyright 2025 Stacklok, Inc.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package v1beta1
5+
6+
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
7+
8+
// WebhookFailurePolicy defines how webhook errors are handled.
9+
type WebhookFailurePolicy string
10+
11+
const (
12+
// WebhookFailurePolicyFail denies the request on webhook error.
13+
WebhookFailurePolicyFail WebhookFailurePolicy = "fail"
14+
// WebhookFailurePolicyIgnore allows the request on webhook error.
15+
WebhookFailurePolicyIgnore WebhookFailurePolicy = "ignore"
16+
)
17+
18+
// WebhookTLSConfig contains TLS configuration for secure webhook connections
19+
type WebhookTLSConfig struct {
20+
// CASecretRef references a Secret containing the CA certificate bundle used to verify the webhook server's certificate.
21+
// Contains a bundle of PEM-encoded X.509 certificates.
22+
// +optional
23+
CASecretRef *SecretKeyRef `json:"caSecretRef,omitempty"`
24+
25+
// ClientCertSecretRef references a Secret containing the client certificate for mTLS authentication.
26+
// The referenced key must contain a PEM-encoded client certificate.
27+
// Use ClientKeySecretRef to provide the corresponding private key.
28+
// +optional
29+
ClientCertSecretRef *SecretKeyRef `json:"clientCertSecretRef,omitempty"`
30+
31+
// ClientKeySecretRef references a Secret containing the private key for the client certificate.
32+
// Required when ClientCertSecretRef is set to enable mTLS.
33+
// +optional
34+
ClientKeySecretRef *SecretKeyRef `json:"clientKeySecretRef,omitempty"`
35+
36+
// InsecureSkipVerify disables server certificate verification.
37+
// WARNING: This should only be used for development/testing and not in production environments.
38+
// +optional
39+
InsecureSkipVerify bool `json:"insecureSkipVerify,omitempty"`
40+
}
41+
42+
// WebhookSpec defines the configuration for a single webhook middleware
43+
type WebhookSpec struct {
44+
// Name is a unique identifier for this webhook
45+
// +kubebuilder:validation:MinLength=1
46+
// +kubebuilder:validation:MaxLength=63
47+
Name string `json:"name"`
48+
49+
// URL is the endpoint to call for this webhook. Must be an HTTP/HTTPS URL.
50+
// +kubebuilder:validation:Format=uri
51+
URL string `json:"url"`
52+
53+
// Timeout configures the maximum time to wait for the webhook to respond.
54+
// Defaults to 10s if not specified. Maximum is 30s.
55+
// +kubebuilder:validation:Type=string
56+
// +kubebuilder:validation:Format=duration
57+
// +optional
58+
Timeout *metav1.Duration `json:"timeout,omitempty"`
59+
60+
// FailurePolicy defines how to handle errors when communicating with the webhook.
61+
// Supported values: "fail", "ignore". Defaults to "fail".
62+
// +kubebuilder:validation:Enum=fail;ignore
63+
// +kubebuilder:default=fail
64+
// +optional
65+
FailurePolicy WebhookFailurePolicy `json:"failurePolicy,omitempty"`
66+
67+
// TLSConfig contains optional TLS configuration for the webhook connection.
68+
// +optional
69+
TLSConfig *WebhookTLSConfig `json:"tlsConfig,omitempty"`
70+
71+
// HMACSecretRef references a Kubernetes Secret containing the HMAC signing key
72+
// used to sign the webhook payload. If set, the X-Toolhive-Signature header will be injected.
73+
// +optional
74+
HMACSecretRef *SecretKeyRef `json:"hmacSecretRef,omitempty"`
75+
}
76+
77+
// MCPWebhookConfigSpec defines the desired state of MCPWebhookConfig
78+
// +kubebuilder:validation:XValidation:rule="(has(self.validating) ? size(self.validating) : 0) + (has(self.mutating) ? size(self.mutating) : 0) > 0",message="at least one validating or mutating webhook must be defined"
79+
//
80+
//nolint:lll // CEL validation rules exceed line length limit
81+
type MCPWebhookConfigSpec struct {
82+
// Validating webhooks are called to approve or deny MCP requests.
83+
// +optional
84+
Validating []WebhookSpec `json:"validating,omitempty"`
85+
86+
// Mutating webhooks are called to transform MCP requests before processing.
87+
// +optional
88+
Mutating []WebhookSpec `json:"mutating,omitempty"`
89+
}
90+
91+
// MCPWebhookConfigStatus defines the observed state of MCPWebhookConfig
92+
type MCPWebhookConfigStatus struct {
93+
// Conditions represent the latest available observations
94+
// +listType=map
95+
// +listMapKey=type
96+
// +optional
97+
Conditions []metav1.Condition `json:"conditions,omitempty"`
98+
99+
// ObservedGeneration is the last observed generation corresponding to the current status
100+
// +optional
101+
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
102+
103+
// ConfigHash is a hash of the spec, used for detecting changes
104+
// +optional
105+
ConfigHash string `json:"configHash,omitempty"`
106+
107+
// ReferencingWorkloads is a list of workload resources that reference this MCPWebhookConfig.
108+
// Each entry identifies the workload by kind and name.
109+
// +listType=map
110+
// +listMapKey=name
111+
// +optional
112+
ReferencingWorkloads []WorkloadReference `json:"referencingWorkloads,omitempty"`
113+
}

0 commit comments

Comments
 (0)