Skip to content

Commit f5ec890

Browse files
make Gateway controllers watch changes on Secrets referenced by spec.listeners.tls.certificateRef
Signed-off-by: Jintao Zhang <[email protected]>
1 parent 5016e68 commit f5ec890

File tree

3 files changed

+178
-0
lines changed

3 files changed

+178
-0
lines changed

controller/gateway/controller.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ import (
2121
"sigs.k8s.io/controller-runtime/pkg/controller"
2222
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
2323
"sigs.k8s.io/controller-runtime/pkg/handler"
24+
ctrllog "sigs.k8s.io/controller-runtime/pkg/log"
2425
"sigs.k8s.io/controller-runtime/pkg/predicate"
26+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
2527
"sigs.k8s.io/controller-runtime/pkg/source"
2628
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
2729
gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
@@ -137,9 +139,65 @@ func (r *Reconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager) err
137139
),
138140
)
139141
}
142+
143+
// Watch Secrets to requeue Gateways that reference them via listeners.tls.certificateRefs.
144+
builder.WatchesRawSource(
145+
source.Kind(
146+
mgr.GetCache(),
147+
&corev1.Secret{},
148+
handler.TypedEnqueueRequestsFromMapFunc(func(ctx context.Context, s *corev1.Secret) []reconcile.Request {
149+
// List Gateways and select those that reference the Secret in any listener certificateRef.
150+
var gwList gwtypes.GatewayList
151+
if err := r.List(ctx, &gwList); err != nil {
152+
ctrllog.FromContext(ctx).Error(err, "failed to list gateways for Secret watch", "secret", client.ObjectKeyFromObject(s))
153+
return nil
154+
}
155+
recs := make([]reconcile.Request, 0, len(gwList.Items))
156+
for i := range gwList.Items {
157+
gw := gwList.Items[i]
158+
// Optional pre-filter: only enqueue Gateways managed by this controller.
159+
if !r.gatewayHasMatchingGatewayClass(&gw) {
160+
continue
161+
}
162+
if secretReferencedByGateway(&gw, s.Namespace, s.Name) {
163+
recs = append(recs, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(&gw)})
164+
}
165+
}
166+
return recs
167+
}),
168+
),
169+
)
170+
140171
return builder.Complete(r)
141172
}
142173

174+
// secretReferencedByGateway returns true if any listener in the Gateway references the Secret
175+
// identified by secretNS/secretName in its TLS certificateRefs.
176+
func secretReferencedByGateway(gw *gwtypes.Gateway, secretNS, secretName string) bool {
177+
for _, l := range gw.Spec.Listeners {
178+
if l.TLS == nil {
179+
continue
180+
}
181+
for _, ref := range l.TLS.CertificateRefs {
182+
// Only accept core Secret references when Group/Kind is specified.
183+
if ref.Group != nil && string(*ref.Group) != corev1.GroupName {
184+
continue
185+
}
186+
if ref.Kind != nil && string(*ref.Kind) != "Secret" {
187+
continue
188+
}
189+
ns := gw.Namespace
190+
if ref.Namespace != nil {
191+
ns = string(*ref.Namespace)
192+
}
193+
if ns == secretNS && string(ref.Name) == secretName {
194+
return true
195+
}
196+
}
197+
}
198+
return false
199+
}
200+
143201
// Reconcile moves the current state of an object to the intended state.
144202
func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
145203
logger := log.GetLogger(ctx, "gateway", r.LoggingMode)
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package gateway
2+
3+
import (
4+
"testing"
5+
6+
corev1 "k8s.io/api/core/v1"
7+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
8+
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
9+
10+
gwtypes "github.com/kong/kong-operator/internal/types"
11+
)
12+
13+
func gwWithListenerCertRef(ns, name string, ref gatewayv1.SecretObjectReference) *gwtypes.Gateway {
14+
gw := &gwtypes.Gateway{
15+
ObjectMeta: metav1.ObjectMeta{
16+
Namespace: ns,
17+
Name: name,
18+
},
19+
Spec: gwtypes.GatewaySpec{
20+
Listeners: []gwtypes.Listener{
21+
{
22+
Name: "https",
23+
Port: 443,
24+
Protocol: gwtypes.HTTPSProtocolType,
25+
TLS: &gatewayv1.GatewayTLSConfig{
26+
Mode: gatewayTLSModePtr(gatewayv1.TLSModeTerminate),
27+
CertificateRefs: []gatewayv1.SecretObjectReference{ref},
28+
},
29+
},
30+
},
31+
},
32+
}
33+
return gw
34+
}
35+
36+
func gatewayTLSModePtr(m gatewayv1.TLSModeType) *gatewayv1.TLSModeType { return &m }
37+
38+
func Test_secretReferencedByGateway_SameNamespace_DefaultGroupKind(t *testing.T) {
39+
// No Group/Kind set => treated as core/v1 Secret
40+
ref := gatewayv1.SecretObjectReference{
41+
Name: "cert",
42+
// Namespace nil -> same namespace as Gateway
43+
}
44+
gw := gwWithListenerCertRef("ns1", "gw1", ref)
45+
46+
if !secretReferencedByGateway(gw, "ns1", "cert") {
47+
t.Fatalf("expected match for same-namespace Secret reference with default group/kind")
48+
}
49+
}
50+
51+
func Test_secretReferencedByGateway_CrossNamespace_ExplicitCoreSecret(t *testing.T) {
52+
// Explicit Group/Kind of core/Secret and explicit namespace
53+
ns := gatewayv1.Namespace("other")
54+
kind := gatewayv1.Kind("Secret")
55+
group := gatewayv1.Group(corev1.GroupName)
56+
ref := gatewayv1.SecretObjectReference{
57+
Group: &group,
58+
Kind: &kind,
59+
Name: "cert2",
60+
Namespace: &ns,
61+
}
62+
gw := gwWithListenerCertRef("ns1", "gw1", ref)
63+
64+
if !secretReferencedByGateway(gw, "other", "cert2") {
65+
t.Fatalf("expected match for cross-namespace explicit core/Secret reference")
66+
}
67+
}
68+
69+
func Test_secretReferencedByGateway_IgnoresNonSecretOrNonCore(t *testing.T) {
70+
// Non-core group => should be ignored
71+
ns := gatewayv1.Namespace("ns1")
72+
badGroup := gatewayv1.Group("example.com")
73+
kindSecret := gatewayv1.Kind("Secret")
74+
refNonCore := gatewayv1.SecretObjectReference{Group: &badGroup, Kind: &kindSecret, Name: "cert", Namespace: &ns}
75+
gw := gwWithListenerCertRef("ns1", "gw1", refNonCore)
76+
if secretReferencedByGateway(gw, "ns1", "cert") {
77+
t.Fatalf("did not expect match for non-core group")
78+
}
79+
80+
// Non-Secret kind => should be ignored
81+
groupCore := gatewayv1.Group(corev1.GroupName)
82+
badKind := gatewayv1.Kind("ConfigMap")
83+
refNonSecret := gatewayv1.SecretObjectReference{Group: &groupCore, Kind: &badKind, Name: "cert"}
84+
gw2 := gwWithListenerCertRef("ns1", "gw2", refNonSecret)
85+
if secretReferencedByGateway(gw2, "ns1", "cert") {
86+
t.Fatalf("did not expect match for non-Secret kind")
87+
}
88+
}
89+
90+
func Test_secretReferencedByGateway_NoTLSOrNoRefs(t *testing.T) {
91+
gw := &gwtypes.Gateway{Spec: gwtypes.GatewaySpec{}}
92+
if secretReferencedByGateway(gw, "ns", "name") {
93+
t.Fatalf("did not expect match when no listeners/tls refs present")
94+
}
95+
}

ingress-controller/internal/controllers/gateway/gateway_controller.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"sigs.k8s.io/controller-runtime/pkg/handler"
2828
"sigs.k8s.io/controller-runtime/pkg/predicate"
2929
"sigs.k8s.io/controller-runtime/pkg/reconcile"
30+
"sigs.k8s.io/controller-runtime/pkg/source"
3031
gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
3132

3233
"github.com/kong/kong-operator/ingress-controller/internal/annotations"
@@ -133,6 +134,30 @@ func (r *GatewayReconciler) SetupWithManager(mgr ctrl.Manager) error {
133134
)
134135
}
135136

137+
// Watch Secrets to immediately reconcile Gateways when referenced certificate Secrets change.
138+
blder.WatchesRawSource(
139+
source.Kind(
140+
mgr.GetCache(),
141+
&corev1.Secret{},
142+
handler.TypedEnqueueRequestsFromMapFunc(func(ctx context.Context, s *corev1.Secret) []reconcile.Request {
143+
referrers, err := r.ReferenceIndexers.ListReferrerObjectsByReferent(s)
144+
if err != nil {
145+
r.Log.Error(err, "Failed to list referrers for Secret", "secret", client.ObjectKeyFromObject(s))
146+
return nil
147+
}
148+
recs := make([]reconcile.Request, 0, len(referrers))
149+
for _, obj := range referrers {
150+
nn := k8stypes.NamespacedName{Namespace: obj.GetNamespace(), Name: obj.GetName()}
151+
if !r.GatewayNN.MatchesNN(nn) { // respect --gateway-to-reconcile if set
152+
continue
153+
}
154+
recs = append(recs, reconcile.Request{NamespacedName: nn})
155+
}
156+
return recs
157+
}),
158+
),
159+
)
160+
136161
if err := blder.Complete(r); err != nil {
137162
return err
138163
}

0 commit comments

Comments
 (0)