Skip to content

Commit 0d61669

Browse files
committed
BUG/MINOR: fix stale crt-list entry after gateway deletion
When a gateway is deleted, gateway.reset() replaces g.Listeners with a new empty map before the certificate builder runs. secretsPerVirtualListener then calls GetListenerForKey for each entry in previousReferencedSecrets; since g.Listeners is empty it returns nil and silently drops the deleted gateway's secrets from the previous virtual-listener mapping. handleUpdatedCrtList therefore computes an empty removedSecretRefsForListener and skips the crt-list update. The PEM is still deleted (handleDeReferencedSecretsStorage fires on the raw secret diff), leaving a .list file that references a non-existent PEM and causing HAProxy to fail on the next reload. Fix by replacing the indirect reconstruction of the previous mapping with previousSecretsPerVirtualListener, which reads directly from GateTree.PreviousVirtualListeners. The *Listener values stored there are held by the VirtualListener slice and survive gateway.reset() because reset only reassigns the map pointer on the Gateway, not the Listener objects themselves.
1 parent 8b993cd commit 0d61669

1 file changed

Lines changed: 44 additions & 3 deletions

File tree

k8s/gate/tree/certificate_builder.go

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
objtypes "github.com/haproxytech/haproxy-unified-gateway/k8s/gate/object-types"
2424
"github.com/haproxytech/haproxy-unified-gateway/k8s/gate/store"
2525
"github.com/haproxytech/haproxy-unified-gateway/k8s/gate/utils"
26+
utilsk8s "github.com/haproxytech/haproxy-unified-gateway/k8s/gate/utils-k8s"
2627
"sigs.k8s.io/controller-runtime/pkg/client"
2728
)
2829

@@ -88,7 +89,7 @@ func (b *CertificateBuilderImpl) computeCertificateDiffs() {
8889
b.handleUpdatedSecretsStorage(previousSecretsReferenced, currentSecretsReferenced)
8990

9091
// crt-list
91-
b.handleCrtList(previousSecretsReferenced, currentSecretsReferenced)
92+
b.handleCrtList(currentSecretsReferenced)
9293
}
9394

9495
func (b *CertificateBuilderImpl) ensureCertificatesStorage() {
@@ -210,8 +211,8 @@ func (b *CertificateBuilderImpl) handleUpdatedSecretsStorage(previousRefSecrets,
210211
// Namespace: "example", Name: "offload"}] ===> SecretKey
211212
// map[client.ObjectKey{Namespace: "example", Name: hug-gateway_https"}]struct{}{} ====> Set of GatewayListenerKey referencing this Secret
212213
// }
213-
func (b *CertificateBuilderImpl) handleCrtList(previousRefSecrets, newRefSecrets map[client.ObjectKey]map[client.ObjectKey]struct{}) {
214-
previousSecretsByGatewayListener := b.secretsPerVirtualListener(previousRefSecrets)
214+
func (b *CertificateBuilderImpl) handleCrtList(newRefSecrets map[client.ObjectKey]map[client.ObjectKey]struct{}) {
215+
previousSecretsByGatewayListener := b.previousSecretsPerVirtualListener()
215216
newSecretsByGatewayListener := b.secretsPerVirtualListener(newRefSecrets)
216217

217218
b.handleNewReferencedCrtList(previousSecretsByGatewayListener, newSecretsByGatewayListener)
@@ -251,6 +252,46 @@ func (b *CertificateBuilderImpl) secretsPerVirtualListener(mapSecret2Listeners m
251252
return secretsPerVirtualListener
252253
}
253254

255+
// previousSecretsPerVirtualListener rebuilds the previous virtual-listener→secret mapping
256+
// directly from GateTree.PreviousVirtualListeners instead of re-deriving it from the
257+
// previousReferencedSecrets index via GetListenerForKey.
258+
//
259+
// The indirect path breaks when a gateway is deleted: gateway.reset() clears g.Listeners
260+
// before the certificate builder runs, so GetListenerForKey returns nil and the deleted
261+
// gateway's secrets are silently dropped from the previous mapping. That causes
262+
// handleUpdatedCrtList to see no removed secrets and skip the crt-list update, leaving a
263+
// stale entry in the .list file that points to the already-deleted PEM.
264+
//
265+
// PreviousVirtualListeners holds *Listener values from the previous cycle. Those Listener
266+
// objects are referenced by the slice in each VirtualListener and survive gateway.reset()
267+
// because reset only replaces g.Listeners (the map on the Gateway), not the Listener
268+
// objects themselves.
269+
func (b *CertificateBuilderImpl) previousSecretsPerVirtualListener() map[string]map[client.ObjectKey]struct{} {
270+
result := make(map[string]map[client.ObjectKey]struct{})
271+
for vlName, vl := range b.GateTree.PreviousVirtualListeners {
272+
for _, listener := range vl.Listeners {
273+
if listener.K8sResource.TLS == nil {
274+
continue
275+
}
276+
for _, certRef := range listener.K8sResource.TLS.CertificateRefs {
277+
if !utilsk8s.IsSecretGroupKindSupported(certRef) {
278+
continue
279+
}
280+
ns := listener.Owner.Namespace
281+
if certRef.Namespace != nil {
282+
ns = string(*certRef.Namespace)
283+
}
284+
secretKey := client.ObjectKey{Namespace: ns, Name: string(certRef.Name)}
285+
if _, ok := result[vlName]; !ok {
286+
result[vlName] = make(map[client.ObjectKey]struct{})
287+
}
288+
result[vlName][secretKey] = struct{}{}
289+
}
290+
}
291+
}
292+
return result
293+
}
294+
254295
// handleNewReferencedCrtList creates crt-list entries for newly referenced virtual listeners.
255296
// It compares the previous and new sets of secrets per virtual listener to identify which
256297
// virtual listeners have been newly referenced (present in new but not in previous).

0 commit comments

Comments
 (0)