Skip to content

Commit 0bc95a8

Browse files
committed
changes from review
1 parent fcaf921 commit 0bc95a8

File tree

4 files changed

+47
-49
lines changed

4 files changed

+47
-49
lines changed

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

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,26 @@ var _ webhook.CustomValidator = &MCPExternalAuthConfig{}
2424

2525
// ValidateCreate implements webhook.CustomValidator
2626
func (r *MCPExternalAuthConfig) ValidateCreate(_ context.Context, _ runtime.Object) (admission.Warnings, error) {
27-
return nil, r.validate()
27+
var warnings admission.Warnings
28+
if r.Spec.Type == ExternalAuthTypeUnauthenticated {
29+
warnings = append(warnings,
30+
"'unauthenticated' type disables authentication to the backend. "+
31+
"Only use for backends on trusted networks or when authentication is handled by network-level security.")
32+
}
33+
return warnings, r.validate()
2834
}
2935

3036
// ValidateUpdate implements webhook.CustomValidator
3137
func (r *MCPExternalAuthConfig) ValidateUpdate(
3238
_ context.Context, _ runtime.Object, _ runtime.Object,
3339
) (admission.Warnings, error) {
34-
return nil, r.validate()
40+
var warnings admission.Warnings
41+
if r.Spec.Type == ExternalAuthTypeUnauthenticated {
42+
warnings = append(warnings,
43+
"'unauthenticated' type disables authentication to the backend. "+
44+
"Only use for backends on trusted networks or when authentication is handled by network-level security.")
45+
}
46+
return warnings, r.validate()
3547
}
3648

3749
// ValidateDelete implements webhook.CustomValidator

cmd/thv-operator/controllers/virtualmcpserver_controller.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"fmt"
99
"maps"
1010
"reflect"
11+
"strings"
1112
"time"
1213

1314
appsv1 "k8s.io/api/apps/v1"
@@ -64,10 +65,6 @@ type VirtualMCPServerReconciler struct {
6465
PlatformDetector *ctrlutil.SharedPlatformDetector
6566
}
6667

67-
var (
68-
envVarSanitizeRegex = regexp.MustCompile(`[^A-Z0-9_]`)
69-
)
70-
7168
// +kubebuilder:rbac:groups=toolhive.stacklok.dev,resources=virtualmcpservers,verbs=get;list;watch;create;update;patch;delete
7269
// +kubebuilder:rbac:groups=toolhive.stacklok.dev,resources=virtualmcpservers/status,verbs=get;update;patch
7370
// +kubebuilder:rbac:groups=toolhive.stacklok.dev,resources=mcpgroups,verbs=get;list;watch

cmd/thv-operator/pkg/controllerutil/externalauth.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ import (
77
"strings"
88
)
99

10+
var (
11+
envVarSanitizer = regexp.MustCompile(`[^A-Z0-9_]`)
12+
)
13+
1014
// GenerateUniqueTokenExchangeEnvVarName generates a unique environment variable name for token exchange
1115
// client secrets, incorporating the ExternalAuthConfig name to ensure uniqueness.
1216
// This function is used by both the converter and deployment controller to ensure consistent
@@ -18,7 +22,7 @@ func GenerateUniqueTokenExchangeEnvVarName(configName string) string {
1822
// Sanitize config name for use in env var (uppercase, replace invalid chars with underscore)
1923
sanitized := strings.ToUpper(strings.ReplaceAll(configName, "-", "_"))
2024
// Remove any remaining invalid characters (keep only alphanumeric and underscore)
21-
sanitized = regexp.MustCompile(`[^A-Z0-9_]`).ReplaceAllString(sanitized, "_")
25+
sanitized = envVarSanitizer.ReplaceAllString(sanitized, "_")
2226
return fmt.Sprintf("TOOLHIVE_TOKEN_EXCHANGE_CLIENT_SECRET_%s", sanitized)
2327
}
2428

@@ -33,6 +37,6 @@ func GenerateUniqueHeaderInjectionEnvVarName(configName string) string {
3337
// Sanitize config name for use in env var (uppercase, replace invalid chars with underscore)
3438
sanitized := strings.ToUpper(strings.ReplaceAll(configName, "-", "_"))
3539
// Remove any remaining invalid characters (keep only alphanumeric and underscore)
36-
sanitized = regexp.MustCompile(`[^A-Z0-9_]`).ReplaceAllString(sanitized, "_")
40+
sanitized = envVarSanitizer.ReplaceAllString(sanitized, "_")
3741
return fmt.Sprintf("TOOLHIVE_HEADER_INJECTION_VALUE_%s", sanitized)
3842
}

cmd/thv-operator/pkg/vmcpconfig/converter.go

Lines changed: 26 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"github.com/stacklok/toolhive/cmd/thv-operator/pkg/controllerutil"
1919
"github.com/stacklok/toolhive/cmd/thv-operator/pkg/oidc"
2020
"github.com/stacklok/toolhive/cmd/thv-operator/pkg/spectoconfig"
21+
"github.com/stacklok/toolhive/pkg/vmcp/auth/converters"
2122
authtypes "github.com/stacklok/toolhive/pkg/vmcp/auth/types"
2223
vmcpconfig "github.com/stacklok/toolhive/pkg/vmcp/config"
2324
)
@@ -300,53 +301,37 @@ func (c *Converter) convertBackendAuthConfig(
300301
return nil, fmt.Errorf("backend %s: unknown auth type %q", backendName, crdConfig.Type)
301302
}
302303

303-
// convertExternalAuthConfigToStrategy converts MCPExternalAuthConfig to BackendAuthStrategy
304+
// convertExternalAuthConfigToStrategy converts MCPExternalAuthConfig to BackendAuthStrategy.
305+
// This uses the converter registry to consolidate conversion logic and apply token type normalization consistently.
306+
// The registry pattern makes adding new auth types easier and ensures conversion happens in one place.
304307
func (*Converter) convertExternalAuthConfigToStrategy(
305308
_ context.Context,
306309
externalAuthConfig *mcpv1alpha1.MCPExternalAuthConfig,
307310
) (*authtypes.BackendAuthStrategy, error) {
308-
strategy := &authtypes.BackendAuthStrategy{}
309-
310-
switch externalAuthConfig.Spec.Type {
311-
case mcpv1alpha1.ExternalAuthTypeUnauthenticated:
312-
strategy.Type = authtypes.StrategyTypeUnauthenticated
313-
314-
case mcpv1alpha1.ExternalAuthTypeHeaderInjection:
315-
if externalAuthConfig.Spec.HeaderInjection == nil {
316-
return nil, fmt.Errorf("headerInjection config is required when type is headerInjection")
317-
}
318-
319-
strategy.Type = authtypes.StrategyTypeHeaderInjection
320-
strategy.HeaderInjection = &authtypes.HeaderInjectionConfig{
321-
HeaderName: externalAuthConfig.Spec.HeaderInjection.HeaderName,
322-
// The secret value will be mounted as an environment variable by the deployment controller
323-
// Use the same env var naming convention as the deployment controller
324-
HeaderValueEnv: controllerutil.GenerateUniqueHeaderInjectionEnvVarName(externalAuthConfig.Name),
325-
}
326-
327-
case mcpv1alpha1.ExternalAuthTypeTokenExchange:
328-
if externalAuthConfig.Spec.TokenExchange == nil {
329-
return nil, fmt.Errorf("tokenExchange config is required when type is tokenExchange")
330-
}
331-
332-
strategy.Type = authtypes.StrategyTypeTokenExchange
333-
strategy.TokenExchange = &authtypes.TokenExchangeConfig{
334-
TokenURL: externalAuthConfig.Spec.TokenExchange.TokenURL,
335-
ClientID: externalAuthConfig.Spec.TokenExchange.ClientID,
336-
Audience: externalAuthConfig.Spec.TokenExchange.Audience,
337-
Scopes: externalAuthConfig.Spec.TokenExchange.Scopes,
338-
SubjectTokenType: externalAuthConfig.Spec.TokenExchange.SubjectTokenType,
339-
}
311+
// Use the converter registry to convert to typed strategy
312+
registry := converters.DefaultRegistry()
313+
converter, err := registry.GetConverter(externalAuthConfig.Spec.Type)
314+
if err != nil {
315+
return nil, err
316+
}
340317

341-
// If client secret ref is set, use an environment variable
342-
if externalAuthConfig.Spec.TokenExchange.ClientSecretRef != nil {
343-
// The secret value will be mounted as an environment variable by the deployment controller
344-
// Use the same env var naming convention as the deployment controller
345-
strategy.TokenExchange.ClientSecretEnv = controllerutil.GenerateUniqueTokenExchangeEnvVarName(externalAuthConfig.Name)
346-
}
318+
// Convert to typed BackendAuthStrategy (applies token type normalization)
319+
strategy, err := converter.ConvertToStrategy(externalAuthConfig)
320+
if err != nil {
321+
return nil, fmt.Errorf("failed to convert external auth config to strategy: %w", err)
322+
}
347323

348-
default:
349-
return nil, fmt.Errorf("unknown external auth type: %s", externalAuthConfig.Spec.Type)
324+
// Enrich with unique env var names per ExternalAuthConfig to avoid conflicts
325+
// when multiple configs of the same type reference different secrets
326+
if strategy.TokenExchange != nil &&
327+
externalAuthConfig.Spec.TokenExchange != nil &&
328+
externalAuthConfig.Spec.TokenExchange.ClientSecretRef != nil {
329+
strategy.TokenExchange.ClientSecretEnv = controllerutil.GenerateUniqueTokenExchangeEnvVarName(externalAuthConfig.Name)
330+
}
331+
if strategy.HeaderInjection != nil &&
332+
externalAuthConfig.Spec.HeaderInjection != nil &&
333+
externalAuthConfig.Spec.HeaderInjection.ValueSecretRef != nil {
334+
strategy.HeaderInjection.HeaderValueEnv = controllerutil.GenerateUniqueHeaderInjectionEnvVarName(externalAuthConfig.Name)
350335
}
351336

352337
return strategy, nil

0 commit comments

Comments
 (0)