From a3e2111eaf2b6df16e1f3fb069d544eda0fe115b Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Tue, 3 Nov 2026 12:58:15 -0600 Subject: [PATCH 1/2] fix: Don't report a non-entitled wildcard as Pending A gateway whose only dropped listener is a wildcard hostname the project is not entitled to reported Programmed=False with reason Pending, which means the certificate is still being issued and will clear on its own. It never does, so the HTTPProxy stayed Pending indefinitely and kept HTTPProxyStuckUnreconciled firing as if the platform were stuck. Report ListenersNotValid instead, matching the listener's own status. --- internal/controller/gateway_controller.go | 9 ++++++--- internal/controller/gateway_controller_test.go | 15 +++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/internal/controller/gateway_controller.go b/internal/controller/gateway_controller.go index c95c033d..595506e4 100644 --- a/internal/controller/gateway_controller.go +++ b/internal/controller/gateway_controller.go @@ -955,8 +955,8 @@ func (r *GatewayReconciler) getDesiredDownstreamGateway( } // listenerDropReport names the spec listeners that never reached the downstream -// gateway, and records whether an unusable certificate held back every one of -// them. +// gateway, and records whether a certificate still on its way held back every +// one of them. type listenerDropReport struct { names []gatewayv1.SectionName allCertWithheld bool @@ -1030,7 +1030,10 @@ func summarizeDroppedListeners( report.names = append(report.names, l.Name) - if status, gated := listenerCertHealth[l.Name]; !gated || status.healthy { + // A wildcard the project is not entitled to never gets a certificate, + // so it does not clear on its own like a certificate still being issued. + if status, gated := listenerCertHealth[l.Name]; !gated || status.healthy || + status.issuanceReason == certificateServiceReasonWildcardNotEntitled { report.allCertWithheld = false } } diff --git a/internal/controller/gateway_controller_test.go b/internal/controller/gateway_controller_test.go index f1e5913b..fc9fc59c 100644 --- a/internal/controller/gateway_controller_test.go +++ b/internal/controller/gateway_controller_test.go @@ -2161,6 +2161,21 @@ func TestReconcileGatewayStatus_DroppedListenerIsNotProgrammed(t *testing.T) { expectStatus: metav1.ConditionFalse, expectReason: string(gatewayv1.GatewayReasonPending), }, + { + name: "listener withheld by a wildcard the project is not entitled to", + listeners: []gatewayv1.Listener{customListener("custom-https", "*.claimed.example.com")}, + claimedHostnames: []string{"*.claimed.example.com"}, + certHealth: map[gatewayv1.SectionName]listenerCertStatus{ + "custom-https": { + reason: gatewayv1.ListenerReasonInvalidCertificateRef, + message: "wildcard hostnames are not enabled", + issuanceBlocked: true, + issuanceReason: certificateServiceReasonWildcardNotEntitled, + }, + }, + expectStatus: metav1.ConditionFalse, + expectReason: string(gatewayv1.GatewayReasonListenersNotValid), + }, { name: "certificate and hostname drops report the hostname", listeners: []gatewayv1.Listener{ From 2351e84693044acdc7c8b31f041a7c8397c3687d Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Tue, 3 Nov 2026 13:03:20 -0600 Subject: [PATCH 2/2] fix: Summarize dropped listeners after the certificate service The entitlement block is only recorded on the listener's certificate status by the certificate service, so the summary taken before it still saw a listener waiting on issuance. Add a reconcile-level test. --- internal/controller/gateway_controller.go | 23 +++++++++---------- .../controller/wildcard_entitlement_test.go | 23 +++++++++++++++++++ 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/internal/controller/gateway_controller.go b/internal/controller/gateway_controller.go index 595506e4..1afe2956 100644 --- a/internal/controller/gateway_controller.go +++ b/internal/controller/gateway_controller.go @@ -329,16 +329,6 @@ func (r *GatewayReconciler) ensureDownstreamGateway( listenerCertHealth, ) - // A listener the user asked for that never reaches the downstream gateway - // makes the gateway dishonest about being programmed, whatever held it back. - // Derived from the listener set actually built, so a new reason to withhold - // one is covered without being enumerated here. - droppedListeners := summarizeDroppedListeners( - upstreamGateway, - desiredDownstreamGateway, - listenerCertHealth, - ) - if err := writeDownstreamGateway(ctx, downstreamStrategy, upstreamGateway, downstreamGateway, desiredDownstreamGateway.Spec, awaitsHostnameClaim(hostnameRefusals)); err != nil { result.Err = err @@ -388,6 +378,17 @@ func (r *GatewayReconciler) ensureDownstreamGateway( clearCertificateServiceFailing(upstreamClusterName, upstreamGateway) } + // A listener the user asked for that never reaches the downstream gateway + // makes the gateway dishonest about being programmed, whatever held it back. + // Derived from the listener set actually built, so a new reason to withhold + // one is covered without being enumerated here. Summarized after the + // certificate service so an entitlement block is known. + droppedListeners := summarizeDroppedListeners( + upstreamGateway, + desiredDownstreamGateway, + listenerCertHealth, + ) + certResult := r.ensureListenerCertificates( ctx, upstreamGateway, @@ -1030,8 +1031,6 @@ func summarizeDroppedListeners( report.names = append(report.names, l.Name) - // A wildcard the project is not entitled to never gets a certificate, - // so it does not clear on its own like a certificate still being issued. if status, gated := listenerCertHealth[l.Name]; !gated || status.healthy || status.issuanceReason == certificateServiceReasonWildcardNotEntitled { report.allCertWithheld = false diff --git a/internal/controller/wildcard_entitlement_test.go b/internal/controller/wildcard_entitlement_test.go index 771c6133..d8d887ca 100644 --- a/internal/controller/wildcard_entitlement_test.go +++ b/internal/controller/wildcard_entitlement_test.go @@ -492,3 +492,26 @@ func seriesMatching(t *testing.T, vec prometheus.Collector, want prometheus.Labe } return count } + +func TestWildcardNotEntitledIsNotReportedPending(t *testing.T) { + f := newEntitlementFixture(t, "*.shop.example.com", false, fakeWildcardEntitlements{entitled: false}) + + _, created, _ := f.h.reconcile() + require.NotNil(t, created, "downstream gateway") + downstream := &gatewayv1.Gateway{} + require.NoError(t, f.h.downstream.Get(f.h.ctx, client.ObjectKeyFromObject(created), downstream)) + apimeta.SetStatusCondition(&downstream.Status.Conditions, metav1.Condition{ + Type: string(gatewayv1.GatewayConditionProgrammed), + Status: metav1.ConditionTrue, + Reason: string(gatewayv1.GatewayReasonProgrammed), + }) + require.NoError(t, f.h.downstream.Status().Update(f.h.ctx, downstream)) + + upstream, _, _ := f.h.reconcile() + + programmed := apimeta.FindStatusCondition(upstream.Status.Conditions, string(gatewayv1.GatewayConditionProgrammed)) + require.NotNil(t, programmed, "upstream Programmed condition") + assert.Equal(t, metav1.ConditionFalse, programmed.Status) + assert.Equal(t, string(gatewayv1.GatewayReasonListenersNotValid), programmed.Reason, + "a wildcard the project is not entitled to never gets a certificate, so it is not Pending") +}