Skip to content
Draft
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
29 changes: 29 additions & 0 deletions cmd/thv-operator/api/v1beta1/mcpexternalauthconfig_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,10 @@ type EmbeddedAuthServerConfig struct {
// +listType=atomic
// +optional
BaselineClientScopes []string `json:"baselineClientScopes,omitempty"`

// CIMD configures Client ID Metadata Document support. When omitted, CIMD is disabled.
// +optional
CIMD *EmbeddedAuthServerCIMDConfig `json:"cimd,omitempty"`
}

// TokenLifespanConfig holds configuration for token lifetimes.
Expand All @@ -325,6 +329,31 @@ type TokenLifespanConfig struct {
AuthCodeLifespan string `json:"authCodeLifespan,omitempty"`
}

// EmbeddedAuthServerCIMDConfig configures Client ID Metadata Document (CIMD) support
// on the embedded authorization server. When enabled, the AS accepts HTTPS URLs as
// client_id values and resolves them via the CIMD protocol, allowing clients such as
// VS Code to authenticate without prior Dynamic Client Registration.
type EmbeddedAuthServerCIMDConfig struct {
// Enabled activates CIMD client lookup. When false (the default), the AS only
// accepts client_id values that were registered via DCR.
// +kubebuilder:default=false
Enabled bool `json:"enabled"`

// CacheMaxSize is the maximum number of CIMD documents held in the LRU cache.
// Defaults to 256 when Enabled is true and this field is omitted.
// +kubebuilder:validation:Minimum=1
// +optional
CacheMaxSize int `json:"cacheMaxSize,omitempty"`

// CacheFallbackTTL is the fixed TTL applied to every cached CIMD document.
// Cache-Control header parsing is not yet implemented; all entries use this value.
// Format: Go duration string (e.g. "5m", "10m", "1h").
// Defaults to 5 minutes when Enabled is true and this field is omitted.
// +kubebuilder:validation:Pattern=`^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$`
// +optional
CacheFallbackTTL string `json:"cacheFallbackTtl,omitempty"`
}

// UpstreamProviderType identifies the type of upstream Identity Provider.
type UpstreamProviderType string

Expand Down
20 changes: 20 additions & 0 deletions cmd/thv-operator/api/v1beta1/zz_generated.deepcopy.go

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

10 changes: 10 additions & 0 deletions cmd/thv-operator/pkg/controllerutil/authserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,16 @@ func BuildAuthServerRunConfig(
}
config.Storage = storageCfg

// Build CIMD configuration. CacheFallbackTTL is passed as-is (string);
// resolveCIMDConfig in the runner parses it to time.Duration at startup.
if authConfig.CIMD != nil && authConfig.CIMD.Enabled {
config.CIMD = &authserver.CIMDRunConfig{
Enabled: authConfig.CIMD.Enabled,
CacheMaxSize: authConfig.CIMD.CacheMaxSize,
CacheFallbackTTL: authConfig.CIMD.CacheFallbackTTL,
}
}

return config, nil
}

Expand Down
129 changes: 129 additions & 0 deletions cmd/thv-operator/pkg/controllerutil/authserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2645,3 +2645,132 @@ func TestValidateAndAddAuthServerRefOptions(t *testing.T) {
})
}
}

// TestBuildAuthServerRunConfig_CIMD verifies that BuildAuthServerRunConfig
// correctly converts the CRD EmbeddedAuthServerCIMDConfig into
// authserver.CIMDRunConfig. The four cases cover the nil path (CIMD off
// by default), explicit values (fields are mapped and TTL is parsed), zero
// optional fields (authserver applies its own defaults at startup), and an
// invalid TTL string (returns a parse error).
func TestBuildAuthServerRunConfig_CIMD(t *testing.T) {
t.Parallel()

// baseAuthConfig returns a minimal EmbeddedAuthServerConfig that is valid
// enough for BuildAuthServerRunConfig to proceed past signing-key and
// upstream validation without requiring real secrets.
baseAuthConfig := func(cimd *mcpv1beta1.EmbeddedAuthServerCIMDConfig) *mcpv1beta1.EmbeddedAuthServerConfig {
return &mcpv1beta1.EmbeddedAuthServerConfig{
Issuer: "https://auth.example.com",
SigningKeySecretRefs: []mcpv1beta1.SecretKeyRef{
{Name: "signing-key", Key: "private.pem"},
},
HMACSecretRefs: []mcpv1beta1.SecretKeyRef{
{Name: "hmac-secret", Key: "hmac"},
},
CIMD: cimd,
}
}

defaultAudiences := []string{"https://mcp.example.com"}
defaultScopes := []string{"openid", "offline_access"}

tests := []struct {
name string
cimd *mcpv1beta1.EmbeddedAuthServerCIMDConfig
wantCIMD bool
wantErr bool
errContains string
checkFunc func(t *testing.T, got *authserver.CIMDRunConfig)
}{
{
name: "nil CIMD leaves config.CIMD nil",
cimd: nil,
wantCIMD: false,
},
{
name: "CIMD disabled leaves config.CIMD nil",
cimd: &mcpv1beta1.EmbeddedAuthServerCIMDConfig{
Enabled: false,
CacheMaxSize: 100,
CacheFallbackTTL: "10m",
},
wantCIMD: false,
},
{
name: "CIMD enabled with explicit values maps all fields",
cimd: &mcpv1beta1.EmbeddedAuthServerCIMDConfig{
Enabled: true,
CacheMaxSize: 512,
CacheFallbackTTL: "10m",
},
wantCIMD: true,
checkFunc: func(t *testing.T, got *authserver.CIMDRunConfig) {
t.Helper()
assert.True(t, got.Enabled)
assert.Equal(t, 512, got.CacheMaxSize)
assert.Equal(t, "10m", got.CacheFallbackTTL)
},
},
{
name: "CIMD enabled with zero optional fields leaves defaults to authserver",
cimd: &mcpv1beta1.EmbeddedAuthServerCIMDConfig{
Enabled: true,
},
wantCIMD: true,
checkFunc: func(t *testing.T, got *authserver.CIMDRunConfig) {
t.Helper()
assert.True(t, got.Enabled)
assert.Zero(t, got.CacheMaxSize, "zero means authserver applies its own default at startup")
assert.Zero(t, got.CacheFallbackTTL, "zero means authserver applies its own default at startup")
},
},
{
name: "invalid CacheFallbackTTL passes through to runner for validation",
cimd: &mcpv1beta1.EmbeddedAuthServerCIMDConfig{
Enabled: true,
CacheFallbackTTL: "not-a-duration",
},
wantCIMD: true,
checkFunc: func(t *testing.T, got *authserver.CIMDRunConfig) {
t.Helper()
// The converter passes the string through; parse errors are caught
// by CIMDRunConfig.Validate() or resolveCIMDConfig in the runner.
assert.Equal(t, "not-a-duration", got.CacheFallbackTTL)
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

cfg, err := BuildAuthServerRunConfig(
"default", "test-server",
baseAuthConfig(tt.cimd),
defaultAudiences, defaultScopes,
"https://mcp.example.com",
)

if tt.wantErr {
require.Error(t, err)
if tt.errContains != "" {
assert.Contains(t, err.Error(), tt.errContains)
}
return
}

require.NoError(t, err)
require.NotNil(t, cfg)

if !tt.wantCIMD {
assert.Nil(t, cfg.CIMD, "expected config.CIMD to be nil")
return
}

require.NotNil(t, cfg.CIMD, "expected config.CIMD to be set")
if tt.checkFunc != nil {
tt.checkFunc(t, cfg.CIMD)
}
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,33 @@ spec:
maxItems: 10
type: array
x-kubernetes-list-type: atomic
cimd:
description: CIMD configures Client ID Metadata Document support.
When omitted, CIMD is disabled.
properties:
cacheFallbackTtl:
description: |-
CacheFallbackTTL is the fixed TTL applied to every cached CIMD document.
Cache-Control header parsing is not yet implemented; all entries use this value.
Format: Go duration string (e.g. "5m", "10m", "1h").
Defaults to 5 minutes when Enabled is true and this field is omitted.
pattern: ^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$
type: string
cacheMaxSize:
description: |-
CacheMaxSize is the maximum number of CIMD documents held in the LRU cache.
Defaults to 256 when Enabled is true and this field is omitted.
minimum: 1
type: integer
enabled:
default: false
description: |-
Enabled activates CIMD client lookup. When false (the default), the AS only
accepts client_id values that were registered via DCR.
type: boolean
required:
- enabled
type: object
hmacSecretRefs:
description: |-
HMACSecretRefs references Kubernetes Secrets containing symmetric secrets for signing
Expand Down Expand Up @@ -1516,6 +1543,33 @@ spec:
maxItems: 10
type: array
x-kubernetes-list-type: atomic
cimd:
description: CIMD configures Client ID Metadata Document support.
When omitted, CIMD is disabled.
properties:
cacheFallbackTtl:
description: |-
CacheFallbackTTL is the fixed TTL applied to every cached CIMD document.
Cache-Control header parsing is not yet implemented; all entries use this value.
Format: Go duration string (e.g. "5m", "10m", "1h").
Defaults to 5 minutes when Enabled is true and this field is omitted.
pattern: ^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$
type: string
cacheMaxSize:
description: |-
CacheMaxSize is the maximum number of CIMD documents held in the LRU cache.
Defaults to 256 when Enabled is true and this field is omitted.
minimum: 1
type: integer
enabled:
default: false
description: |-
Enabled activates CIMD client lookup. When false (the default), the AS only
accepts client_id values that were registered via DCR.
type: boolean
required:
- enabled
type: object
hmacSecretRefs:
description: |-
HMACSecretRefs references Kubernetes Secrets containing symmetric secrets for signing
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,33 @@ spec:
maxItems: 10
type: array
x-kubernetes-list-type: atomic
cimd:
description: CIMD configures Client ID Metadata Document support.
When omitted, CIMD is disabled.
properties:
cacheFallbackTtl:
description: |-
CacheFallbackTTL is the fixed TTL applied to every cached CIMD document.
Cache-Control header parsing is not yet implemented; all entries use this value.
Format: Go duration string (e.g. "5m", "10m", "1h").
Defaults to 5 minutes when Enabled is true and this field is omitted.
pattern: ^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$
type: string
cacheMaxSize:
description: |-
CacheMaxSize is the maximum number of CIMD documents held in the LRU cache.
Defaults to 256 when Enabled is true and this field is omitted.
minimum: 1
type: integer
enabled:
default: false
description: |-
Enabled activates CIMD client lookup. When false (the default), the AS only
accepts client_id values that were registered via DCR.
type: boolean
required:
- enabled
type: object
hmacSecretRefs:
description: |-
HMACSecretRefs references Kubernetes Secrets containing symmetric secrets for signing
Expand Down Expand Up @@ -3034,6 +3061,33 @@ spec:
maxItems: 10
type: array
x-kubernetes-list-type: atomic
cimd:
description: CIMD configures Client ID Metadata Document support.
When omitted, CIMD is disabled.
properties:
cacheFallbackTtl:
description: |-
CacheFallbackTTL is the fixed TTL applied to every cached CIMD document.
Cache-Control header parsing is not yet implemented; all entries use this value.
Format: Go duration string (e.g. "5m", "10m", "1h").
Defaults to 5 minutes when Enabled is true and this field is omitted.
pattern: ^([0-9]+(\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$
type: string
cacheMaxSize:
description: |-
CacheMaxSize is the maximum number of CIMD documents held in the LRU cache.
Defaults to 256 when Enabled is true and this field is omitted.
minimum: 1
type: integer
enabled:
default: false
description: |-
Enabled activates CIMD client lookup. When false (the default), the AS only
accepts client_id values that were registered via DCR.
type: boolean
required:
- enabled
type: object
hmacSecretRefs:
description: |-
HMACSecretRefs references Kubernetes Secrets containing symmetric secrets for signing
Expand Down
Loading
Loading