Skip to content

Commit eb7703a

Browse files
authored
feat: support gateway proxy webhook (#277)
Signed-off-by: Ashing Zheng <[email protected]>
1 parent 9ae2c80 commit eb7703a

File tree

4 files changed

+692
-51
lines changed

4 files changed

+692
-51
lines changed

internal/webhook/v1/gatewayproxy_webhook.go

Lines changed: 137 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ package v1
1818
import (
1919
"context"
2020
"fmt"
21+
"sort"
22+
"strings"
2123

2224
"k8s.io/apimachinery/pkg/runtime"
2325
"k8s.io/apimachinery/pkg/types"
@@ -63,7 +65,12 @@ func (v *GatewayProxyCustomValidator) ValidateCreate(ctx context.Context, obj ru
6365
}
6466
gatewayProxyLog.Info("Validation for GatewayProxy upon creation", "name", gp.GetName(), "namespace", gp.GetNamespace())
6567

66-
return v.collectWarnings(ctx, gp), nil
68+
warnings := v.collectWarnings(ctx, gp)
69+
if err := v.validateGatewayProxyConflict(ctx, gp); err != nil {
70+
return nil, err
71+
}
72+
73+
return warnings, nil
6774
}
6875

6976
func (v *GatewayProxyCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) {
@@ -73,7 +80,12 @@ func (v *GatewayProxyCustomValidator) ValidateUpdate(ctx context.Context, oldObj
7380
}
7481
gatewayProxyLog.Info("Validation for GatewayProxy upon update", "name", gp.GetName(), "namespace", gp.GetNamespace())
7582

76-
return v.collectWarnings(ctx, gp), nil
83+
warnings := v.collectWarnings(ctx, gp)
84+
if err := v.validateGatewayProxyConflict(ctx, gp); err != nil {
85+
return nil, err
86+
}
87+
88+
return warnings, nil
7789
}
7890

7991
func (v *GatewayProxyCustomValidator) ValidateDelete(context.Context, runtime.Object) (admission.Warnings, error) {
@@ -111,3 +123,126 @@ func (v *GatewayProxyCustomValidator) collectWarnings(ctx context.Context, gp *v
111123

112124
return warnings
113125
}
126+
127+
func (v *GatewayProxyCustomValidator) validateGatewayProxyConflict(ctx context.Context, gp *v1alpha1.GatewayProxy) error {
128+
current := buildGatewayProxyConfig(gp)
129+
if !current.readyForConflict() {
130+
return nil
131+
}
132+
133+
var list v1alpha1.GatewayProxyList
134+
if err := v.Client.List(ctx, &list); err != nil {
135+
gatewayProxyLog.Error(err, "failed to list GatewayProxy objects for conflict detection")
136+
return fmt.Errorf("failed to list existing GatewayProxy resources: %w", err)
137+
}
138+
139+
for _, other := range list.Items {
140+
if other.GetNamespace() == gp.GetNamespace() && other.GetName() == gp.GetName() {
141+
// skip self
142+
continue
143+
}
144+
otherConfig := buildGatewayProxyConfig(&other)
145+
if !otherConfig.readyForConflict() {
146+
continue
147+
}
148+
if !current.sharesAdminKeyWith(otherConfig) {
149+
continue
150+
}
151+
if current.serviceKey != "" && current.serviceKey == otherConfig.serviceKey {
152+
return fmt.Errorf("gateway proxy configuration conflict: GatewayProxy %s/%s and %s/%s both target %s while sharing %s",
153+
gp.GetNamespace(), gp.GetName(),
154+
other.GetNamespace(), other.GetName(),
155+
current.serviceDescription,
156+
current.adminKeyDetail(),
157+
)
158+
}
159+
if len(current.endpoints) > 0 && len(otherConfig.endpoints) > 0 {
160+
if overlap := current.endpointOverlap(otherConfig); len(overlap) > 0 {
161+
return fmt.Errorf("gateway proxy configuration conflict: GatewayProxy %s/%s and %s/%s both target control plane endpoints [%s] while sharing %s",
162+
gp.GetNamespace(), gp.GetName(),
163+
other.GetNamespace(), other.GetName(),
164+
strings.Join(overlap, ", "),
165+
current.adminKeyDetail(),
166+
)
167+
}
168+
}
169+
}
170+
171+
return nil
172+
}
173+
174+
type gatewayProxyConfig struct {
175+
inlineAdminKey string
176+
secretKey string
177+
serviceKey string
178+
serviceDescription string
179+
endpoints map[string]struct{}
180+
}
181+
182+
func buildGatewayProxyConfig(gp *v1alpha1.GatewayProxy) gatewayProxyConfig {
183+
var cfg gatewayProxyConfig
184+
185+
if gp == nil || gp.Spec.Provider == nil || gp.Spec.Provider.Type != v1alpha1.ProviderTypeControlPlane || gp.Spec.Provider.ControlPlane == nil {
186+
return cfg
187+
}
188+
189+
cp := gp.Spec.Provider.ControlPlane
190+
191+
if cp.Auth.AdminKey != nil {
192+
if value := strings.TrimSpace(cp.Auth.AdminKey.Value); value != "" {
193+
cfg.inlineAdminKey = value
194+
} else if cp.Auth.AdminKey.ValueFrom != nil && cp.Auth.AdminKey.ValueFrom.SecretKeyRef != nil {
195+
ref := cp.Auth.AdminKey.ValueFrom.SecretKeyRef
196+
cfg.secretKey = fmt.Sprintf("%s/%s:%s", gp.GetNamespace(), ref.Name, ref.Key)
197+
}
198+
}
199+
200+
if cp.Service != nil && cp.Service.Name != "" {
201+
cfg.serviceKey = fmt.Sprintf("service:%s/%s:%d", gp.GetNamespace(), cp.Service.Name, cp.Service.Port)
202+
cfg.serviceDescription = fmt.Sprintf("Service %s/%s port %d", gp.GetNamespace(), cp.Service.Name, cp.Service.Port)
203+
}
204+
205+
if len(cp.Endpoints) > 0 {
206+
cfg.endpoints = make(map[string]struct{}, len(cp.Endpoints))
207+
for _, endpoint := range cp.Endpoints {
208+
cfg.endpoints[endpoint] = struct{}{}
209+
}
210+
}
211+
212+
return cfg
213+
}
214+
215+
func (c gatewayProxyConfig) adminKeyDetail() string {
216+
if c.secretKey != "" {
217+
return fmt.Sprintf("AdminKey secret %s", c.secretKey)
218+
}
219+
return "the same inline AdminKey value"
220+
}
221+
222+
func (c gatewayProxyConfig) sharesAdminKeyWith(other gatewayProxyConfig) bool {
223+
if c.inlineAdminKey != "" && other.inlineAdminKey != "" {
224+
return c.inlineAdminKey == other.inlineAdminKey
225+
}
226+
if c.secretKey != "" && other.secretKey != "" {
227+
return c.secretKey == other.secretKey
228+
}
229+
return false
230+
}
231+
232+
func (c gatewayProxyConfig) readyForConflict() bool {
233+
if c.inlineAdminKey == "" && c.secretKey == "" {
234+
return false
235+
}
236+
return c.serviceKey != "" || len(c.endpoints) > 0
237+
}
238+
239+
func (c gatewayProxyConfig) endpointOverlap(other gatewayProxyConfig) []string {
240+
var overlap []string
241+
for endpoint := range c.endpoints {
242+
if _, ok := other.endpoints[endpoint]; ok {
243+
overlap = append(overlap, endpoint)
244+
}
245+
}
246+
sort.Strings(overlap)
247+
return overlap
248+
}

0 commit comments

Comments
 (0)