Skip to content
Merged
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
52 changes: 32 additions & 20 deletions cmd/help/dedupe-enums/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
// Command dedupe-enums works around a swag v2.0.0-rc5 nondeterminism bug:
// loadExternalPackage (in swag's packages.go) reparses an external module's
// package without memoizing by import path, which can non-deterministically
// double the "enum"/"x-enum-varnames" arrays it emits for types defined
// repeat the "enum"/"x-enum-varnames" arrays it emits for types defined
// outside this module (model.ArgumentType, model.Format, model.Icon from
// github.com/modelcontextprotocol/registry). When an array is exactly two
// identical halves back to back, this collapses it to one.
// github.com/modelcontextprotocol/registry). The repeat count varies by
// machine — 2x and 3x have both been seen. When an array is the same block
// repeated back to back, this collapses it to one copy.
//
// Run after `swag init`, on each generated docs/server file:
//
Expand Down Expand Up @@ -62,41 +63,52 @@ func dedupeJSONBlock(match string) string {
trimmed[i] = strings.TrimSuffix(l, ",")
}

half, ok := firstHalfIfDoubled(trimmed)
period, ok := firstPeriodIfRepeated(trimmed)
if !ok {
return match
}
return header + strings.Join(half, ",\n") + "\n" + footer
return header + strings.Join(period, ",\n") + "\n" + footer
}

func dedupeYAMLBlock(match string) string {
g := yamlArray.FindStringSubmatch(match)
header, body := g[1], g[2]

lines := strings.Split(strings.TrimRight(body, "\n"), "\n")
half, ok := firstHalfIfDoubled(lines)
period, ok := firstPeriodIfRepeated(lines)
if !ok {
return match
}
return header + strings.Join(half, "\n") + "\n"
return header + strings.Join(period, "\n") + "\n"
}

// firstHalfIfDoubled reports whether lines is exactly two identical halves
// back to back, returning the first half if so. It only catches this
// specific shape (contiguous, order-preserved duplication), not any 2x
// multiset — if swag's bug ever reordered values within the second copy,
// this wouldn't fire. That matches the upstream bug: it re-appends the same
// const list a second time, never reorders it.
func firstHalfIfDoubled(lines []string) ([]string, bool) {
// firstPeriodIfRepeated reports whether lines is the same block repeated
// back to back some whole number of times, returning one copy of that block
// if so. It only catches this shape (contiguous, order-preserved
// duplication), not any repeated multiset — if swag's bug ever reordered
// values within a later copy, this wouldn't fire. That matches the upstream
// bug: it re-appends the same const list, never reorders it. The repeat
// count varies between machines (2x and 3x have both been observed), so
// this collapses any count rather than only a doubling.
func firstPeriodIfRepeated(lines []string) ([]string, bool) {
n := len(lines)
if n == 0 || n%2 != 0 {
if n < 2 {
return nil, false
}
half := n / 2
for i := 0; i < half; i++ {
if lines[i] != lines[half+i] {
return nil, false
for period := 1; period <= n/2; period++ {
if n%period != 0 {
continue
}
repeated := true
for i := period; i < n; i++ {
if lines[i] != lines[i-period] {
repeated = false
break
}
}
if repeated {
return lines[:period], true
}
}
return lines[:half], true
return nil, false
}
17 changes: 14 additions & 3 deletions cmd/help/dedupe-enums/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"testing"
)

func TestFirstHalfIfDoubled(t *testing.T) {
func TestFirstPeriodIfRepeated(t *testing.T) {
t.Parallel()

tests := []struct {
Expand All @@ -24,8 +24,19 @@ func TestFirstHalfIfDoubled(t *testing.T) {
ok: true,
},
{
name: "tripled is not a doubled shape",
name: "tripled collapses to one period",
lines: []string{"a", "b", "a", "b", "a", "b"},
want: []string{"a", "b"},
ok: true,
},
{
name: "partial repeat is left alone",
lines: []string{"a", "b", "a", "b", "a"},
ok: false,
},
{
name: "non-repeating list is left alone",
lines: []string{"a", "b", "c", "d"},
ok: false,
},
{
Expand Down Expand Up @@ -53,7 +64,7 @@ func TestFirstHalfIfDoubled(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got, ok := firstHalfIfDoubled(tt.lines)
got, ok := firstPeriodIfRepeated(tt.lines)
if ok != tt.ok {
t.Fatalf("ok = %v, want %v", ok, tt.ok)
}
Expand Down
78 changes: 78 additions & 0 deletions cmd/thv-operator/api/v1beta1/mcpexternalauthconfig_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -1048,6 +1048,19 @@ type OIDCUpstreamConfig struct {
// +kubebuilder:validation:MaxLength=128
// +kubebuilder:validation:Pattern=`^([a-zA-Z_][a-zA-Z0-9_]*)?$`
SubjectClaim string `json:"subjectClaim,omitempty"`

// CABundleRef references a ConfigMap containing a CA bundle added to the
// system roots when connecting to this upstream; it does not restrict trust
// to this bundle or disable public-root trust. The selected key is projected
// as ca.crt.
// +optional
CABundleRef *CABundleSource `json:"caBundleRef,omitempty"`

// AllowPrivateIPs permits the upstream provider's HTTP client to connect to
// private IP ranges (RFC-1918, link-local). Use only when the upstream is
// hosted inside the same cluster and has no public endpoint.
// +optional
AllowPrivateIPs bool `json:"allowPrivateIPs,omitempty"`
}

// OAuth2UpstreamConfig contains configuration for pure OAuth 2.0 providers.
Expand Down Expand Up @@ -1149,6 +1162,13 @@ type OAuth2UpstreamConfig struct {
// +optional
AdditionalAuthorizationParams map[string]string `json:"additionalAuthorizationParams,omitempty"`

// CABundleRef references a ConfigMap containing a CA bundle added to the
// system roots when connecting to this upstream; it does not restrict trust
// to this bundle or disable public-root trust. The selected key is projected
// as ca.crt.
// +optional
CABundleRef *CABundleSource `json:"caBundleRef,omitempty"`

// InsecureAllowHTTP permits plain-HTTP authorization and token endpoint URLs
// for this upstream. Only for in-cluster development environments (e.g. an
// OAuth2 provider served over HTTP in a kind cluster) where TLS is not
Expand Down Expand Up @@ -1717,6 +1737,17 @@ const (
// Validate() with an error other than the enterprise-required sentinel.
// Used by out-of-tree handlers; unreachable in upstream-only builds.
ConditionReasonInvalidConfig = "InvalidConfig"

// ConditionReasonInvalidCABundle: a referenced upstream CA bundle ConfigMap
// is missing its key or holds content that is not a PEM certificate.
//
// Distinct from ConditionReasonInvalidConfig because the failing input is
// ConfigMap *content*, which is covered by neither metadata.generation nor
// the referenced config's spec hash. The guards that hold a terminal
// ConditionReasonInvalidConfig steady across reconciles key off those two
// values, so reusing that reason here would pin the failure in place even
// after the ConfigMap is repaired.
ConditionReasonInvalidCABundle = "InvalidCABundle"
)

// XAASpec holds configuration for the XAA (Cross-Application Access) auth strategy.
Expand Down Expand Up @@ -2079,6 +2110,10 @@ func (*MCPExternalAuthConfig) validateUpstreamProvider(index int, provider *Upst
"and oauth2Config must be set when type is 'oauth2' (and the other must not be set)", prefix)
}

if err := validateUpstreamProviderCABundle(prefix, provider); err != nil {
return err
}

// Validate OAuth2-specific constraints (defense-in-depth with CEL).
// The discriminator above guarantees OAuth2Config != nil when type is oauth2.
if provider.Type == UpstreamProviderTypeOAuth2 {
Expand All @@ -2094,6 +2129,17 @@ func (*MCPExternalAuthConfig) validateUpstreamProvider(index int, provider *Upst
return ValidateAdditionalAuthorizationParams(prefix, provider.AdditionalAuthorizationParams())
}

func validateUpstreamProviderCABundle(prefix string, provider *UpstreamProviderConfig) error {
field := "oidcConfig.caBundleRef"
if provider.Type == UpstreamProviderTypeOAuth2 {
field = "oauth2Config.caBundleRef"
}
if err := validateUpstreamCABundleRef(provider.CABundleRef()); err != nil {
return fmt.Errorf("%s: %s: %w", prefix, field, err)
}
return nil
}

// Length caps for DCR-related string fields. Mirror the
// +kubebuilder:validation:MaxLength markers on DCRUpstreamConfig so that
// ValidateOAuth2DCRConfig is a true reconcile-time backstop for length
Expand All @@ -2108,6 +2154,22 @@ const (
MaxSoftwareStatementLength = 16384
)

// validateUpstreamCABundleRef validates the source shape shared by OIDC and
// OAuth2 upstream CA bundles. ConfigMap existence and key contents are
// resolved by Kubernetes when the generated Pod is scheduled.
func validateUpstreamCABundleRef(ref *CABundleSource) error {
if ref == nil {
return nil
}
if ref.ConfigMapRef == nil {
return fmt.Errorf("configMapRef must be specified")
}
if ref.ConfigMapRef.Name == "" {
return fmt.Errorf("configMapRef.name must not be empty")
}
return nil
}

// ValidateOAuth2DCRConfig enforces the mutual exclusivity between ClientID and
// DCRConfig, between ClientSecretRef and DCRConfig, and (when DCRConfig is
// present) between DiscoveryURL and RegistrationEndpoint. It also enforces the
Expand Down Expand Up @@ -2186,6 +2248,22 @@ func (p *UpstreamProviderConfig) AdditionalAuthorizationParams() map[string]stri
return nil
}

// CABundleRef returns the CA bundle reference for the provider's configured
// type, or nil when the type-matched config or the reference is absent.
func (p *UpstreamProviderConfig) CABundleRef() *CABundleSource {
switch p.Type {
case UpstreamProviderTypeOIDC:
if p.OIDCConfig != nil {
return p.OIDCConfig.CABundleRef
}
case UpstreamProviderTypeOAuth2:
if p.OAuth2Config != nil {
return p.OAuth2Config.CABundleRef
}
}
return nil
}

// SyntheticIdentityUpstreams returns the names of OAuth2 upstreams running
// in synthesis mode (neither userInfo nor identityFromToken configured),
// sorted lexically for deterministic condition messages. OIDC upstreams are
Expand Down
8 changes: 7 additions & 1 deletion cmd/thv-operator/api/v1beta1/mcpserver_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,11 @@ const (
// which is not supported for MCPServer (use VirtualMCPServer for multi-upstream).
ConditionReasonExternalAuthConfigMultiUpstream = "MultiUpstreamNotSupported"

// ConditionReasonExternalAuthConfigValid indicates the referenced
// MCPExternalAuthConfig passed every check the MCPServer controller applies
// to it. Mirrors ConditionReasonMCPRemoteProxyExternalAuthConfigValid.
ConditionReasonExternalAuthConfigValid = "ExternalAuthConfigValid"

// ConditionReasonWebhookConfigInvalid indicates the referenced webhook config is invalid or missing
ConditionReasonWebhookConfigInvalid = "WebhookConfigInvalid"

Expand Down Expand Up @@ -666,7 +671,8 @@ type OutboundNetworkPermissions struct {
// CABundleSource defines a source for CA certificate bundles.
type CABundleSource struct {
// ConfigMapRef references a ConfigMap containing the CA certificate bundle.
// If Key is not specified, it defaults to "ca.crt".
// The ConfigMap key is required by the API. If omitted in a stored object, it
// defaults to "ca.crt" for backwards compatibility.
// +optional
ConfigMapRef *corev1.ConfigMapKeySelector `json:"configMapRef,omitempty"`
}
Expand Down
10 changes: 10 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.

16 changes: 16 additions & 0 deletions cmd/thv-operator/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,19 @@ func isStorageVersionMigratorEnabled() (bool, error) {
return enabled, nil
}

// setupEmbeddedAuthCABundleFieldIndex sets up the shared CA ConfigMap reference index.
func setupEmbeddedAuthCABundleFieldIndex(mgr ctrl.Manager) error {
if err := mgr.GetFieldIndexer().IndexField(
context.Background(),
&mcpv1beta1.MCPExternalAuthConfig{},
controllers.EmbeddedAuthCABundleConfigMapIndex,
controllers.IndexEmbeddedAuthCABundleConfigMaps,
); err != nil {
return fmt.Errorf("index MCPExternalAuthConfig CA bundle references: %w", err)
}
return nil
}

// setupGroupRefFieldIndexes sets up field indexing for spec.groupRef on all resource types
// that can reference an MCPGroup. This enables efficient lookups by groupRef in controllers.
func setupGroupRefFieldIndexes(mgr ctrl.Manager) error {
Expand Down Expand Up @@ -277,6 +290,9 @@ func setupGroupRefFieldIndexes(mgr ctrl.Manager) error {
// imagePullSecretsDefaults are merged with per-CR imagePullSecrets when
// reconcilers construct workloads.
func setupServerControllers(mgr ctrl.Manager, imagePullSecretsDefaults imagepullsecrets.Defaults) error {
if err := setupEmbeddedAuthCABundleFieldIndex(mgr); err != nil {
return err
}
if err := setupGroupRefFieldIndexes(mgr); err != nil {
return err
}
Expand Down
Loading
Loading