Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 137 additions & 2 deletions internal/webhook/v1/gatewayproxy_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ package v1
import (
"context"
"fmt"
"sort"
"strings"

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

return v.collectWarnings(ctx, gp), nil
warnings := v.collectWarnings(ctx, gp)
if err := v.validateGatewayProxyConflict(ctx, gp); err != nil {
return nil, err
}

return warnings, nil
}

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

return v.collectWarnings(ctx, gp), nil
warnings := v.collectWarnings(ctx, gp)
if err := v.validateGatewayProxyConflict(ctx, gp); err != nil {
return nil, err
}

return warnings, nil
}

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

return warnings
}

func (v *GatewayProxyCustomValidator) validateGatewayProxyConflict(ctx context.Context, gp *v1alpha1.GatewayProxy) error {
current := buildGatewayProxyConfig(gp)
if !current.readyForConflict() {
return nil
}

var list v1alpha1.GatewayProxyList
if err := v.Client.List(ctx, &list); err != nil {
gatewayProxyLog.Error(err, "failed to list GatewayProxy objects for conflict detection")
return fmt.Errorf("failed to list existing GatewayProxy resources: %w", err)
}

for _, other := range list.Items {
if other.GetNamespace() == gp.GetNamespace() && other.GetName() == gp.GetName() {
// skip self
continue
}
otherConfig := buildGatewayProxyConfig(&other)
if !otherConfig.readyForConflict() {
continue
}
if !current.sharesAdminKeyWith(otherConfig) {
continue
}
if current.serviceKey != "" && current.serviceKey == otherConfig.serviceKey {
return fmt.Errorf("gateway proxy configuration conflict: GatewayProxy %s/%s and %s/%s both target %s while sharing %s",
gp.GetNamespace(), gp.GetName(),
other.GetNamespace(), other.GetName(),
current.serviceDescription,
current.adminKeyDetail(),
)
}
if len(current.endpoints) > 0 && len(otherConfig.endpoints) > 0 {
if overlap := current.endpointOverlap(otherConfig); len(overlap) > 0 {
return fmt.Errorf("gateway proxy configuration conflict: GatewayProxy %s/%s and %s/%s both target control plane endpoints [%s] while sharing %s",
gp.GetNamespace(), gp.GetName(),
other.GetNamespace(), other.GetName(),
strings.Join(overlap, ", "),
current.adminKeyDetail(),
)
}
}
}

return nil
}

type gatewayProxyConfig struct {
inlineAdminKey string
secretKey string
serviceKey string
serviceDescription string
endpoints map[string]struct{}
}

func buildGatewayProxyConfig(gp *v1alpha1.GatewayProxy) gatewayProxyConfig {
var cfg gatewayProxyConfig

if gp == nil || gp.Spec.Provider == nil || gp.Spec.Provider.Type != v1alpha1.ProviderTypeControlPlane || gp.Spec.Provider.ControlPlane == nil {
return cfg
}

cp := gp.Spec.Provider.ControlPlane

if cp.Auth.AdminKey != nil {
if value := strings.TrimSpace(cp.Auth.AdminKey.Value); value != "" {
cfg.inlineAdminKey = value
} else if cp.Auth.AdminKey.ValueFrom != nil && cp.Auth.AdminKey.ValueFrom.SecretKeyRef != nil {
ref := cp.Auth.AdminKey.ValueFrom.SecretKeyRef
cfg.secretKey = fmt.Sprintf("%s/%s:%s", gp.GetNamespace(), ref.Name, ref.Key)
}
}

if cp.Service != nil && cp.Service.Name != "" {
cfg.serviceKey = fmt.Sprintf("service:%s/%s:%d", gp.GetNamespace(), cp.Service.Name, cp.Service.Port)
cfg.serviceDescription = fmt.Sprintf("Service %s/%s port %d", gp.GetNamespace(), cp.Service.Name, cp.Service.Port)
}

if len(cp.Endpoints) > 0 {
cfg.endpoints = make(map[string]struct{}, len(cp.Endpoints))
for _, endpoint := range cp.Endpoints {
cfg.endpoints[endpoint] = struct{}{}
}
}

return cfg
}

func (c gatewayProxyConfig) adminKeyDetail() string {
if c.secretKey != "" {
return fmt.Sprintf("AdminKey secret %s", c.secretKey)
}
return "the same inline AdminKey value"
}

func (c gatewayProxyConfig) sharesAdminKeyWith(other gatewayProxyConfig) bool {
if c.inlineAdminKey != "" && other.inlineAdminKey != "" {
return c.inlineAdminKey == other.inlineAdminKey
}
if c.secretKey != "" && other.secretKey != "" {
return c.secretKey == other.secretKey
}
return false
}

func (c gatewayProxyConfig) readyForConflict() bool {
if c.inlineAdminKey == "" && c.secretKey == "" {
return false
}
return c.serviceKey != "" || len(c.endpoints) > 0
}

func (c gatewayProxyConfig) endpointOverlap(other gatewayProxyConfig) []string {
var overlap []string
for endpoint := range c.endpoints {
if _, ok := other.endpoints[endpoint]; ok {
overlap = append(overlap, endpoint)
}
}
sort.Strings(overlap)
return overlap
}
Loading
Loading