Skip to content

Commit 660a608

Browse files
yroblataskbot
authored andcommitted
Merge branch 'main' into issue-4729
2 parents a93fe45 + c49ec9e commit 660a608

29 files changed

Lines changed: 475 additions & 145 deletions

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,12 +127,16 @@ type InlineOIDCSharedConfig struct {
127127
// +optional
128128
JWKSAuthTokenPath string `json:"jwksAuthTokenPath,omitempty"`
129129

130-
// JWKSAllowPrivateIP allows JWKS/OIDC endpoints on private IP addresses
130+
// JWKSAllowPrivateIP allows JWKS/OIDC endpoints on private IP addresses.
131+
// Note: at runtime, if either JWKSAllowPrivateIP or ProtectedResourceAllowPrivateIP
132+
// is true, private IPs are allowed for all OIDC HTTP requests (JWKS, discovery, introspection).
131133
// +kubebuilder:default=false
132134
// +optional
133135
JWKSAllowPrivateIP bool `json:"jwksAllowPrivateIP"`
134136

135-
// ProtectedResourceAllowPrivateIP allows protected resource endpoint on private IP addresses
137+
// ProtectedResourceAllowPrivateIP allows protected resource endpoint on private IP addresses.
138+
// Note: at runtime, if either ProtectedResourceAllowPrivateIP or JWKSAllowPrivateIP
139+
// is true, private IPs are allowed for all OIDC HTTP requests (JWKS, discovery, introspection).
136140
// +kubebuilder:default=false
137141
// +optional
138142
ProtectedResourceAllowPrivateIP bool `json:"protectedResourceAllowPrivateIP"`

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

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -815,14 +815,18 @@ type InlineOIDCConfig struct {
815815
// +optional
816816
JWKSAuthTokenPath string `json:"jwksAuthTokenPath,omitempty"`
817817

818-
// JWKSAllowPrivateIP allows JWKS/OIDC endpoints on private IP addresses
819-
// Use with caution - only enable for trusted internal IDPs
818+
// JWKSAllowPrivateIP allows JWKS/OIDC endpoints on private IP addresses.
819+
// Use with caution - only enable for trusted internal IDPs.
820+
// Note: at runtime, if either JWKSAllowPrivateIP or ProtectedResourceAllowPrivateIP
821+
// is true, private IPs are allowed for all OIDC HTTP requests (JWKS, discovery, introspection).
820822
// +kubebuilder:default=false
821823
// +optional
822824
JWKSAllowPrivateIP bool `json:"jwksAllowPrivateIP"`
823825

824-
// ProtectedResourceAllowPrivateIP allows protected resource endpoint on private IP addresses
825-
// Use with caution - only enable for trusted internal IDPs or testing
826+
// ProtectedResourceAllowPrivateIP allows protected resource endpoint on private IP addresses.
827+
// Use with caution - only enable for trusted internal IDPs or testing.
828+
// Note: at runtime, if either ProtectedResourceAllowPrivateIP or JWKSAllowPrivateIP
829+
// is true, private IPs are allowed for all OIDC HTTP requests (JWKS, discovery, introspection).
826830
// +kubebuilder:default=false
827831
// +optional
828832
ProtectedResourceAllowPrivateIP bool `json:"protectedResourceAllowPrivateIP"`

cmd/thv-operator/pkg/oidc/resolver.go

Lines changed: 43 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -25,22 +25,24 @@ const (
2525
defaultK8sTokenPath = "/var/run/secrets/kubernetes.io/serviceaccount/token" //nolint:gosec
2626
defaultK8sIssuer = "https://kubernetes.default.svc"
2727
defaultK8sAudience = "toolhive"
28+
configMapBoolTrue = "true"
2829
)
2930

3031
// OIDCConfig represents the resolved OIDC configuration values
3132
type OIDCConfig struct { //nolint:revive // Keeping OIDCConfig name for backward compatibility
32-
Issuer string
33-
Audience string
34-
JWKSURL string
35-
IntrospectionURL string
36-
ClientID string
37-
ClientSecret string // #nosec G117 -- not a hardcoded credential, populated at runtime from config
38-
ThvCABundlePath string
39-
JWKSAuthTokenPath string
40-
ResourceURL string
41-
JWKSAllowPrivateIP bool
42-
InsecureAllowHTTP bool
43-
Scopes []string
33+
Issuer string
34+
Audience string
35+
JWKSURL string
36+
IntrospectionURL string
37+
ClientID string
38+
ClientSecret string // #nosec G117 -- not a hardcoded credential, populated at runtime from config
39+
ThvCABundlePath string
40+
JWKSAuthTokenPath string
41+
ResourceURL string
42+
JWKSAllowPrivateIP bool
43+
ProtectedResourceAllowPrivateIP bool
44+
InsecureAllowHTTP bool
45+
Scopes []string
4446
}
4547

4648
// OIDCConfigurable is an interface for resources that have OIDC configuration
@@ -198,17 +200,18 @@ func (*resolver) resolveFromInlineSharedConfig(
198200
}
199201

200202
return &OIDCConfig{
201-
Issuer: config.Issuer,
202-
Audience: ref.Audience,
203-
JWKSURL: config.JWKSURL,
204-
IntrospectionURL: config.IntrospectionURL,
205-
ClientID: config.ClientID,
206-
ThvCABundlePath: computeCABundlePath(config.CABundleRef),
207-
JWKSAuthTokenPath: config.JWKSAuthTokenPath,
208-
ResourceURL: resourceURL,
209-
JWKSAllowPrivateIP: config.JWKSAllowPrivateIP,
210-
InsecureAllowHTTP: config.InsecureAllowHTTP,
211-
Scopes: ref.Scopes,
203+
Issuer: config.Issuer,
204+
Audience: ref.Audience,
205+
JWKSURL: config.JWKSURL,
206+
IntrospectionURL: config.IntrospectionURL,
207+
ClientID: config.ClientID,
208+
ThvCABundlePath: computeCABundlePath(config.CABundleRef),
209+
JWKSAuthTokenPath: config.JWKSAuthTokenPath,
210+
ResourceURL: resourceURL,
211+
JWKSAllowPrivateIP: config.JWKSAllowPrivateIP,
212+
ProtectedResourceAllowPrivateIP: config.ProtectedResourceAllowPrivateIP,
213+
InsecureAllowHTTP: config.InsecureAllowHTTP,
214+
Scopes: ref.Scopes,
212215
}, nil
213216
}
214217

@@ -311,10 +314,13 @@ func (r *resolver) resolveConfigMapConfig(
311314
config.JWKSAuthTokenPath = getMapValue(configMap.Data, "jwksAuthTokenPath")
312315

313316
// Handle boolean values
314-
if v, exists := configMap.Data["jwksAllowPrivateIP"]; exists && v == "true" {
317+
if v, exists := configMap.Data["jwksAllowPrivateIP"]; exists && v == configMapBoolTrue {
315318
config.JWKSAllowPrivateIP = true
316319
}
317-
if v, exists := configMap.Data["insecureAllowHTTP"]; exists && v == "true" {
320+
if v, exists := configMap.Data["protectedResourceAllowPrivateIP"]; exists && v == configMapBoolTrue {
321+
config.ProtectedResourceAllowPrivateIP = true
322+
}
323+
if v, exists := configMap.Data["insecureAllowHTTP"]; exists && v == configMapBoolTrue {
318324
config.InsecureAllowHTTP = true
319325
}
320326

@@ -344,17 +350,18 @@ func (*resolver) resolveInlineConfig(
344350
}
345351

346352
return &OIDCConfig{
347-
Issuer: config.Issuer,
348-
Audience: config.Audience,
349-
JWKSURL: config.JWKSURL,
350-
IntrospectionURL: config.IntrospectionURL,
351-
ClientID: config.ClientID,
352-
ThvCABundlePath: computeCABundlePath(config.CABundleRef),
353-
JWKSAuthTokenPath: config.JWKSAuthTokenPath,
354-
ResourceURL: resourceURL,
355-
JWKSAllowPrivateIP: config.JWKSAllowPrivateIP,
356-
InsecureAllowHTTP: config.InsecureAllowHTTP,
357-
Scopes: config.Scopes,
353+
Issuer: config.Issuer,
354+
Audience: config.Audience,
355+
JWKSURL: config.JWKSURL,
356+
IntrospectionURL: config.IntrospectionURL,
357+
ClientID: config.ClientID,
358+
ThvCABundlePath: computeCABundlePath(config.CABundleRef),
359+
JWKSAuthTokenPath: config.JWKSAuthTokenPath,
360+
ResourceURL: resourceURL,
361+
JWKSAllowPrivateIP: config.JWKSAllowPrivateIP,
362+
ProtectedResourceAllowPrivateIP: config.ProtectedResourceAllowPrivateIP,
363+
InsecureAllowHTTP: config.InsecureAllowHTTP,
364+
Scopes: config.Scopes,
358365
}, nil
359366
}
360367

cmd/thv-operator/pkg/oidc/resolver_configref_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,31 @@ func TestResolveFromConfigRef_InlineType(t *testing.T) {
154154
Scopes: []string{"openid", "email"},
155155
},
156156
},
157+
{
158+
name: "protectedResourceAllowPrivateIP propagated from shared inline config",
159+
ref: &mcpv1alpha1.MCPOIDCConfigReference{
160+
Name: "i", Audience: "inline-aud",
161+
},
162+
oidcCfg: &mcpv1alpha1.MCPOIDCConfig{
163+
Spec: mcpv1alpha1.MCPOIDCConfigSpec{
164+
Type: mcpv1alpha1.MCPOIDCConfigTypeInline,
165+
Inline: &mcpv1alpha1.InlineOIDCSharedConfig{
166+
Issuer: "https://accounts.google.com",
167+
ClientID: "gid",
168+
ProtectedResourceAllowPrivateIP: true,
169+
JWKSAllowPrivateIP: false,
170+
},
171+
},
172+
},
173+
expected: &OIDCConfig{
174+
Issuer: "https://accounts.google.com",
175+
Audience: "inline-aud",
176+
ClientID: "gid",
177+
ResourceURL: "http://srv.default.svc.cluster.local:8080",
178+
ProtectedResourceAllowPrivateIP: true,
179+
JWKSAllowPrivateIP: false,
180+
},
181+
},
157182
{
158183
name: "nil inline config returns nil",
159184
ref: &mcpv1alpha1.MCPOIDCConfigReference{

cmd/thv-operator/pkg/oidc/resolver_test.go

Lines changed: 99 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -168,28 +168,30 @@ func TestResolve_ConfigMapType(t *testing.T) {
168168
Namespace: "test-ns",
169169
},
170170
Data: map[string]string{
171-
"issuer": "https://auth.example.com",
172-
"audience": "test-audience",
173-
"jwksUrl": "https://auth.example.com/.well-known/jwks.json",
174-
"introspectionUrl": "https://auth.example.com/introspect",
175-
"clientId": "test-client",
176-
"clientSecret": "test-secret",
177-
"thvCABundlePath": "/etc/ssl/ca.pem",
178-
"jwksAuthTokenPath": "/etc/auth/token",
179-
"jwksAllowPrivateIP": "true",
171+
"issuer": "https://auth.example.com",
172+
"audience": "test-audience",
173+
"jwksUrl": "https://auth.example.com/.well-known/jwks.json",
174+
"introspectionUrl": "https://auth.example.com/introspect",
175+
"clientId": "test-client",
176+
"clientSecret": "test-secret",
177+
"thvCABundlePath": "/etc/ssl/ca.pem",
178+
"jwksAuthTokenPath": "/etc/auth/token",
179+
"jwksAllowPrivateIP": "true",
180+
"protectedResourceAllowPrivateIP": "true",
180181
},
181182
},
182183
expected: &OIDCConfig{
183-
Issuer: "https://auth.example.com",
184-
Audience: "test-audience",
185-
JWKSURL: "https://auth.example.com/.well-known/jwks.json",
186-
IntrospectionURL: "https://auth.example.com/introspect",
187-
ClientID: "test-client",
188-
ClientSecret: "test-secret",
189-
ThvCABundlePath: "/etc/ssl/ca.pem",
190-
JWKSAuthTokenPath: "/etc/auth/token",
191-
ResourceURL: "http://test-server.test-ns.svc.cluster.local:8080",
192-
JWKSAllowPrivateIP: true,
184+
Issuer: "https://auth.example.com",
185+
Audience: "test-audience",
186+
JWKSURL: "https://auth.example.com/.well-known/jwks.json",
187+
IntrospectionURL: "https://auth.example.com/introspect",
188+
ClientID: "test-client",
189+
ClientSecret: "test-secret",
190+
ThvCABundlePath: "/etc/ssl/ca.pem",
191+
JWKSAuthTokenPath: "/etc/auth/token",
192+
ResourceURL: "http://test-server.test-ns.svc.cluster.local:8080",
193+
JWKSAllowPrivateIP: true,
194+
ProtectedResourceAllowPrivateIP: true,
193195
},
194196
},
195197
{
@@ -227,6 +229,43 @@ func TestResolve_ConfigMapType(t *testing.T) {
227229
JWKSAllowPrivateIP: false,
228230
},
229231
},
232+
{
233+
name: "configmap with jwksAllowPrivateIP independent of protectedResourceAllowPrivateIP",
234+
mcpServer: &mcpv1alpha1.MCPServer{
235+
ObjectMeta: metav1.ObjectMeta{
236+
Name: "independent-server",
237+
Namespace: "test-ns",
238+
},
239+
Spec: mcpv1alpha1.MCPServerSpec{
240+
ProxyPort: 8080,
241+
OIDCConfig: &mcpv1alpha1.OIDCConfigRef{
242+
Type: mcpv1alpha1.OIDCConfigTypeConfigMap,
243+
ConfigMap: &mcpv1alpha1.ConfigMapOIDCRef{
244+
Name: "independent-config",
245+
},
246+
},
247+
},
248+
},
249+
configMap: &corev1.ConfigMap{
250+
ObjectMeta: metav1.ObjectMeta{
251+
Name: "independent-config",
252+
Namespace: "test-ns",
253+
},
254+
Data: map[string]string{
255+
"issuer": "https://auth.example.com",
256+
"audience": "test-audience",
257+
"jwksAllowPrivateIP": "true",
258+
// protectedResourceAllowPrivateIP intentionally absent
259+
},
260+
},
261+
expected: &OIDCConfig{
262+
Issuer: "https://auth.example.com",
263+
Audience: "test-audience",
264+
ResourceURL: "http://independent-server.test-ns.svc.cluster.local:8080",
265+
JWKSAllowPrivateIP: true,
266+
ProtectedResourceAllowPrivateIP: false,
267+
},
268+
},
230269
{
231270
name: "configmap with insecureAllowHTTP enabled",
232271
mcpServer: &mcpv1alpha1.MCPServer{
@@ -513,22 +552,24 @@ func TestResolve_InlineType(t *testing.T) {
513552
LocalObjectReference: corev1.LocalObjectReference{Name: "inline-ca"},
514553
},
515554
},
516-
JWKSAuthTokenPath: "/etc/auth/inline-token",
517-
JWKSAllowPrivateIP: true,
555+
JWKSAuthTokenPath: "/etc/auth/inline-token",
556+
JWKSAllowPrivateIP: true,
557+
ProtectedResourceAllowPrivateIP: true,
518558
},
519559
},
520560
},
521561
},
522562
expected: &OIDCConfig{
523-
Issuer: "https://inline.example.com",
524-
Audience: "inline-audience",
525-
JWKSURL: "https://inline.example.com/.well-known/jwks.json",
526-
IntrospectionURL: "https://inline.example.com/introspect",
527-
ClientID: "inline-client",
528-
ThvCABundlePath: "/config/certs/inline-ca/ca.crt",
529-
JWKSAuthTokenPath: "/etc/auth/inline-token",
530-
ResourceURL: "http://test-server.test-ns.svc.cluster.local:8080",
531-
JWKSAllowPrivateIP: true,
563+
Issuer: "https://inline.example.com",
564+
Audience: "inline-audience",
565+
JWKSURL: "https://inline.example.com/.well-known/jwks.json",
566+
IntrospectionURL: "https://inline.example.com/introspect",
567+
ClientID: "inline-client",
568+
ThvCABundlePath: "/config/certs/inline-ca/ca.crt",
569+
JWKSAuthTokenPath: "/etc/auth/inline-token",
570+
ResourceURL: "http://test-server.test-ns.svc.cluster.local:8080",
571+
JWKSAllowPrivateIP: true,
572+
ProtectedResourceAllowPrivateIP: true,
532573
},
533574
},
534575
{
@@ -586,6 +627,34 @@ func TestResolve_InlineType(t *testing.T) {
586627
InsecureAllowHTTP: true,
587628
},
588629
},
630+
{
631+
name: "inline with protectedResourceAllowPrivateIP independent of jwksAllowPrivateIP",
632+
mcpServer: &mcpv1alpha1.MCPServer{
633+
ObjectMeta: metav1.ObjectMeta{
634+
Name: "protected-resource-server",
635+
Namespace: "test-ns",
636+
},
637+
Spec: mcpv1alpha1.MCPServerSpec{
638+
ProxyPort: 8080,
639+
OIDCConfig: &mcpv1alpha1.OIDCConfigRef{
640+
Type: mcpv1alpha1.OIDCConfigTypeInline,
641+
Inline: &mcpv1alpha1.InlineOIDCConfig{
642+
Issuer: "https://auth.example.com",
643+
Audience: "test-audience",
644+
ProtectedResourceAllowPrivateIP: true,
645+
JWKSAllowPrivateIP: false,
646+
},
647+
},
648+
},
649+
},
650+
expected: &OIDCConfig{
651+
Issuer: "https://auth.example.com",
652+
Audience: "test-audience",
653+
ResourceURL: "http://protected-resource-server.test-ns.svc.cluster.local:8080",
654+
ProtectedResourceAllowPrivateIP: true,
655+
JWKSAllowPrivateIP: false,
656+
},
657+
},
589658
{
590659
name: "inline with scopes",
591660
mcpServer: &mcpv1alpha1.MCPServer{

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,8 @@ func mapResolvedOIDCToVmcpConfig(
276276
Resource: resolved.ResourceURL,
277277
JWKSURL: resolved.JWKSURL,
278278
IntrospectionURL: resolved.IntrospectionURL,
279-
ProtectedResourceAllowPrivateIP: resolved.JWKSAllowPrivateIP,
279+
ProtectedResourceAllowPrivateIP: resolved.ProtectedResourceAllowPrivateIP,
280+
JwksAllowPrivateIP: resolved.JWKSAllowPrivateIP,
280281
InsecureAllowHTTP: resolved.InsecureAllowHTTP,
281282
Scopes: resolved.Scopes,
282283
}
@@ -319,7 +320,10 @@ func mapResolvedOIDCToVmcpConfigFromRef(
319320
ClientID: resolved.ClientID,
320321
Audience: resolved.Audience,
321322
Resource: resolved.ResourceURL,
322-
ProtectedResourceAllowPrivateIP: resolved.JWKSAllowPrivateIP,
323+
JWKSURL: resolved.JWKSURL,
324+
IntrospectionURL: resolved.IntrospectionURL,
325+
ProtectedResourceAllowPrivateIP: resolved.ProtectedResourceAllowPrivateIP,
326+
JwksAllowPrivateIP: resolved.JWKSAllowPrivateIP,
323327
InsecureAllowHTTP: resolved.InsecureAllowHTTP,
324328
Scopes: resolved.Scopes,
325329
}

0 commit comments

Comments
 (0)