fix: resolve FrontendTLS CA cert refs in Gateway namespace, not ListenerSet#14232
Conversation
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
This PR fixes FrontendTLS client-certificate validation lookups when a ListenerSet is in a different namespace than its parent Gateway, ensuring CA certificate references are resolved using the Gateway’s namespace rather than the ListenerSet’s.
Changes:
- Thread
gatewayNamespacethrough listener/filter-chain translation so TLS client validation uses the Gateway namespace. - Refactor
applyClientCertificateValidationto accept explicit parent GVK/namespace instead of deriving fromlistener.Parent. - Add an integration-style translator test fixture (input/output YAML) and a Go test covering the cross-namespace ListenerSet scenario.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| pkg/kgateway/translator/listener/gateway_listener_translator.go | Passes Gateway namespace through translation and updates client-cert validation parent context. |
| pkg/kgateway/translator/gateway/testutils/inputs/frontendtlsconfig/listenerset-cross-ns.yaml | Adds a repro input for cross-namespace ListenerSet + Gateway FrontendTLS CA ref. |
| pkg/kgateway/translator/gateway/testutils/outputs/frontendtlsconfig/listenerset-cross-ns.yaml | Adds expected translated output verifying correct CA resolution/validation behavior. |
| pkg/kgateway/translator/gateway/gateway_translator_test.go | Adds a test case that executes the new input/output fixture. |
| // Check if ListenerPolicy has clientCertificateValidation override | ||
| if listenerPolCertVal := getCertValidationFromAttached(listener); listenerPolCertVal != nil { | ||
| // Apply ListenerPolicy override, which takes precedence over Gateway-level config | ||
| generated, caErr := applyClientCertificateValidation(kctx, ctx, queries, listener, listenerPolCertVal, tlsConfig) | ||
| listenerParentGVK := listener.Parent.GetObjectKind().GroupVersionKind() | ||
| if listenerParentGVK.Empty() { | ||
| switch listener.Parent.(type) { | ||
| case *gwv1.Gateway: | ||
| listenerParentGVK = wellknown.GatewayGVK | ||
| case *gwv1.ListenerSet: | ||
| listenerParentGVK = wellknown.XListenerSetGVK | ||
| } | ||
| } | ||
| generated, caErr := applyClientCertificateValidation(kctx, ctx, queries, listenerPolCertVal, tlsConfig, listenerParentGVK, listener.Parent.GetNamespace()) | ||
| if !generated { |
| listenerParentGVK := listener.Parent.GetObjectKind().GroupVersionKind() | ||
| if listenerParentGVK.Empty() { | ||
| switch listener.Parent.(type) { | ||
| case *gwv1.Gateway: | ||
| listenerParentGVK = wellknown.GatewayGVK | ||
| case *gwv1.ListenerSet: | ||
| listenerParentGVK = wellknown.XListenerSetGVK | ||
| } | ||
| } | ||
| generated, caErr := applyClientCertificateValidation(kctx, ctx, queries, listenerPolCertVal, tlsConfig, listenerParentGVK, listener.Parent.GetNamespace()) |
fedaba5 to
5908794
Compare
5908794 to
1810efb
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.
Comments suppressed due to low confidence (2)
pkg/kgateway/translator/listener/gateway_listener_translator.go:1
- Adding
gatewayNamespaceas an additional positional parameter to multiple translator functions increases signature churn and makes call sites easier to get wrong over time. Consider encapsulating these shared translation inputs (e.g., gateway identity/namespace, frontend TLS config) into a small struct/context object passed through translation, which tends to be more stable and self-documenting than repeatedly extending function signatures.
package listener
pkg/kgateway/translator/listener/gateway_listener_translator.go:1
- Adding
gatewayNamespaceas an additional positional parameter to multiple translator functions increases signature churn and makes call sites easier to get wrong over time. Consider encapsulating these shared translation inputs (e.g., gateway identity/namespace, frontend TLS config) into a small struct/context object passed through translation, which tends to be more stable and self-documenting than repeatedly extending function signatures.
package listener
| // For AllowInsecureFallback mode, if CA cert fetching fails, skip validation rather than failing the listener | ||
| // This allows the listener to work without client certs even if the CA cert ConfigMap is missing | ||
| generated, caErr = applyClientCertificateValidation(kctx, ctx, queries, listener, resolvedValidation, tlsConfig) | ||
| generated, caErr = applyClientCertificateValidation(kctx, ctx, queries, resolvedValidation, tlsConfig, wellknown.GatewayGVK, gatewayNamespace) |
There was a problem hiding this comment.
This is pre-existing behavior, not introduced by this PR. The original code already shared caErr between the two paths. Both results are joined into the final return at the end of translateTLSConfig via errors.Join(certErr, caErr). This PR does not change the error handling pattern.
| listenerParentGVK := listener.Parent.GetObjectKind().GroupVersionKind() | ||
| if listenerParentGVK.Empty() { | ||
| switch listener.Parent.(type) { | ||
| case *gwv1.Gateway: | ||
| listenerParentGVK = wellknown.GatewayGVK | ||
| case *gwv1.ListenerSet: | ||
| listenerParentGVK = wellknown.XListenerSetGVK | ||
| default: | ||
| return nil, fmt.Errorf("unsupported parent type for ListenerPolicy clientCertificateValidation: %T", listener.Parent) | ||
| } | ||
| } | ||
| generated, caErr = applyClientCertificateValidation(kctx, ctx, queries, listenerPolCertVal, tlsConfig, listenerParentGVK, listener.Parent.GetNamespace()) |
There was a problem hiding this comment.
This is pre-existing behavior, not introduced by this PR. The original code already shared caErr between the two paths. Both results are joined into the final return via errors.Join(certErr, caErr).
| var matchedTcpListeners []ir.TcpIR | ||
| for _, tfc := range ml.TcpFilterChains { | ||
| if tcpListener := tfc.translateTcpFilterChain(kctx, ctx, queries, ml.name, reporter, ml.gateway.FrontendTLSConfig); tcpListener != nil { | ||
| if tcpListener := tfc.translateTcpFilterChain(kctx, ctx, queries, ml.name, reporter, ml.gateway.FrontendTLSConfig, ml.gateway.Namespace); tcpListener != nil { |
There was a problem hiding this comment.
The change in translateTcpFilterChain is purely mechanical -- adding a gatewayNamespace parameter and threading it through to translateTLSConfig. No logic changed. The HTTPS test confirms the fix works for the reported bug.
| } | ||
|
|
||
| tlsConfig, err := translateTLSConfig(kctx, ctx, tc.parents.listener, tc.tls, queries, resolvedValidation) | ||
| tlsConfig, err := translateTLSConfig(kctx, ctx, tc.parents.listener, tc.tls, queries, resolvedValidation, gatewayNamespace) |
There was a problem hiding this comment.
Same mechanical change as line 383 -- just threading gatewayNamespace through.
| } | ||
|
|
||
| tlsConfig, err := translateTLSConfig(kctx, ctx, tc.parents.listener, tc.tls, queries, resolvedValidation) | ||
| tlsConfig, err := translateTLSConfig(kctx, ctx, tc.parents.listener, tc.tls, queries, resolvedValidation, gatewayNamespace) |
There was a problem hiding this comment.
Same mechanical change as line 383 -- just threading gatewayNamespace through.
puertomontt
left a comment
There was a problem hiding this comment.
what about reference grant
The code before derived parentGVK and parentNamespace from listener.Parent. For ListenerSet listeners, that checked ReferenceGrants against the ListenerSet, wrong referent, wrong namespace. After: for Gateway FrontendTLSConfig, we pass wellknown.GatewayGVK + the Gateway's namespace, so ReferenceGrants are checked against the Gateway, the actual owner of the frontend TLS config. The existing frontendtlsconfig/basic.yaml test covers cross-namespace ConfigMap + ReferenceGrant, but only with Gateway-owned listeners. I'll add a variant of our ListenerSet test where the ConfigMap is in a separate namespace and a ReferenceGrant allows the Gateway to access it, confirming that path works correctly with a ListenerSet. |
…nerSet (kgateway-dev#14230) Signed-off-by: ayushi-work <[email protected]>
1810efb to
aebd1d2
Compare
|
Hi @puertomontt The PR was removed from the merge queue due to a CI runner disk space issue |
…enerSet-namespace
|
@puertomontt After this branch last synced with main, PR #13978 added |
Signed-off-by: ayushi-work <[email protected]>
0f46f45 to
4db14da
Compare
Head branch was pushed to by a user without write access
Signed-off-by: ayushi-work <[email protected]>
7137b43 to
5a01362
Compare
|
Hi! Is there something else that needs to be done? |
Description
Fixes a bug where
frontendTLS client certificate validation fails for listeners contributed byListenerSetresources, because the control plane looks for the CA certificateConfigMap/Secretin the wrong namespace (the ListenerSet's namespace instead of the Gateway's).Fixes #14230
Problem
When a
Gatewayspecifiesspec.tls.frontendwithclientCertificateValidationpointing to aConfigMapin the same namespace, and aListenerSetin a different namespace binds to that Gateway, theListenerSet's listeners never become programmed. The status reports that the referencedConfigMapcan't be found in the ListenerSet's namespace.Root Cause
In
applyClientCertificateValidation, both theparentGVKand the namespace used to resolve CA certificate references were derived fromlistener.Parent:For listeners from a
ListenerSet,listener.Parentis the*gwv1.ListenerSet(set atpkg/krtcollections/policy.go:567), so the namespace resolves to the ListenerSet's namespace. But the CA cert refs come from the Gateway'sFrontendTLSConfig-- a Gateway-level configuration that should always be resolved relative to the Gateway's namespace.The same function is called for two distinct cases with different ownership:
translateTLSConfig)FrontendTLSConfig(viaresolveFrontendTLSConfig)ListenerPolicyattachment (viagetCertValidationFromAttached)listener.Parent.GetNamespace()(was already correct)Fix
Make
applyClientCertificateValidationaccept explicitparentGVKandparentNamespaceparameters instead of deriving them fromlistener.Parent. At each call site, the caller provides the values that match the config's actual owner:wellknown.GatewayGVK+gatewayNamespace(the Gateway's namespace)listener.Parent+listener.Parent.GetNamespace()(preserving existing behavior)The Gateway namespace is threaded from
MergedListener.TranslateListener(whereml.gateway.Namespaceis available) throughtranslateHttpsFilterChain/translateTcpFilterChain/translateTLSConfig.Change Type
/kind fix
Changelog
Test Plan
listenerset-cross-ns.yamlthat reproduces the exact bug scenario (Gateway innamespace-onewith frontend TLS, ListenerSet innamespace-two)Programmedwith CA cert validation applied from the Gateway's namespace