From bb88d580ec93112f739b9cc78420f2fc79a2308c Mon Sep 17 00:00:00 2001 From: Xin Huang Date: Fri, 7 Apr 2023 02:33:49 +0800 Subject: [PATCH] Webhook: validate the combination of port, protocol, and hostname are unique for each listener. (#1457) * Add validateHostnameProtocolPort to validate that the combination of port, protocol, and name are unique for each listener. Signed-off-by: Huang Xin * Fix format. Signed-off-by: Huang Xin * Fix format. Signed-off-by: Huang Xin * Skip Insert if the set has the combination already Signed-off-by: Huang Xin * Add more unit tests. Signed-off-by: Huang Xin * Fix nits. Signed-off-by: Huang Xin --------- Signed-off-by: Huang Xin --- apis/v1beta1/validation/gateway.go | 24 ++++++ apis/v1beta1/validation/gateway_test.go | 109 +++++++++++++++++++++++- 2 files changed, 130 insertions(+), 3 deletions(-) diff --git a/apis/v1beta1/validation/gateway.go b/apis/v1beta1/validation/gateway.go index 871b10c8ca..d12b9b3621 100644 --- a/apis/v1beta1/validation/gateway.go +++ b/apis/v1beta1/validation/gateway.go @@ -19,6 +19,7 @@ package validation import ( "fmt" + "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/validation/field" gatewayv1b1 "sigs.k8s.io/gateway-api/apis/v1beta1" @@ -70,6 +71,7 @@ func validateGatewayListeners(listeners []gatewayv1b1.Listener, path *field.Path errs = append(errs, validateListenerHostname(listeners, path)...) errs = append(errs, ValidateTLSCertificateRefs(listeners, path)...) errs = append(errs, ValidateListenerNames(listeners, path)...) + errs = append(errs, validateHostnameProtocolPort(listeners, path)...) return errs } @@ -133,3 +135,25 @@ func ValidateListenerNames(listeners []gatewayv1b1.Listener, path *field.Path) f } return errs } + +// validateHostnameProtocolPort validates that the combination of port, protocol, and hostname are +// unique for each listener. +func validateHostnameProtocolPort(listeners []gatewayv1b1.Listener, path *field.Path) field.ErrorList { + var errs field.ErrorList + hostnameProtocolPortSets := sets.Set[string]{} + for i, listener := range listeners { + hostname := new(gatewayv1b1.Hostname) + if listener.Hostname != nil { + hostname = listener.Hostname + } + protocol := listener.Protocol + port := listener.Port + hostnameProtocolPort := fmt.Sprintf("%s:%s:%d", *hostname, protocol, port) + if hostnameProtocolPortSets.Has(hostnameProtocolPort) { + errs = append(errs, field.Forbidden(path.Index(i), fmt.Sprintln("combination of port, protocol, and hostname must be unique for each listener"))) + } else { + hostnameProtocolPortSets.Insert(hostnameProtocolPort) + } + } + return errs +} diff --git a/apis/v1beta1/validation/gateway_test.go b/apis/v1beta1/validation/gateway_test.go index eaaafbca44..746e6a16d6 100644 --- a/apis/v1beta1/validation/gateway_test.go +++ b/apis/v1beta1/validation/gateway_test.go @@ -136,14 +136,117 @@ func TestValidateGateway(t *testing.T) { }, "names are not unique within the Gateway": { mutate: func(gw *gatewayv1b1.Gateway) { + hostnameFoo := gatewayv1b1.Hostname("foo.com") + hostnameBar := gatewayv1b1.Hostname("bar.com") gw.Spec.Listeners[0].Name = "foo" - gw.Spec.Listeners = append(gw.Spec.Listeners, gatewayv1b1.Listener{ - Name: "foo", - }, + gw.Spec.Listeners[0].Hostname = &hostnameFoo + gw.Spec.Listeners = append(gw.Spec.Listeners, + gatewayv1b1.Listener{ + Name: "foo", + Hostname: &hostnameBar, + }, ) }, expectErrsOnFields: []string{"spec.listeners[1].name"}, }, + "combination of port, protocol, and hostname are not unique for each listener": { + mutate: func(gw *gatewayv1b1.Gateway) { + hostnameFoo := gatewayv1b1.Hostname("foo.com") + gw.Spec.Listeners[0].Name = "foo" + gw.Spec.Listeners[0].Hostname = &hostnameFoo + gw.Spec.Listeners[0].Protocol = gatewayv1b1.HTTPProtocolType + gw.Spec.Listeners[0].Port = 80 + gw.Spec.Listeners = append(gw.Spec.Listeners, + gatewayv1b1.Listener{ + Name: "bar", + Hostname: &hostnameFoo, + Protocol: gatewayv1b1.HTTPProtocolType, + Port: 80, + }, + ) + }, + expectErrsOnFields: []string{"spec.listeners[1]"}, + }, + "combination of port and protocol are not unique for each listenr when hostnames not set": { + mutate: func(gw *gatewayv1b1.Gateway) { + gw.Spec.Listeners[0].Name = "foo" + gw.Spec.Listeners[0].Protocol = gatewayv1b1.HTTPProtocolType + gw.Spec.Listeners[0].Port = 80 + gw.Spec.Listeners = append(gw.Spec.Listeners, + gatewayv1b1.Listener{ + Name: "bar", + Protocol: gatewayv1b1.HTTPProtocolType, + Port: 80, + }, + ) + }, + expectErrsOnFields: []string{"spec.listeners[1]"}, + }, + "port is unique when protocol and hostname are the same": { + mutate: func(gw *gatewayv1b1.Gateway) { + hostnameFoo := gatewayv1b1.Hostname("foo.com") + gw.Spec.Listeners[0].Name = "foo" + gw.Spec.Listeners[0].Hostname = &hostnameFoo + gw.Spec.Listeners[0].Protocol = gatewayv1b1.HTTPProtocolType + gw.Spec.Listeners[0].Port = 80 + gw.Spec.Listeners = append(gw.Spec.Listeners, + gatewayv1b1.Listener{ + Name: "bar", + Hostname: &hostnameFoo, + Protocol: gatewayv1b1.HTTPProtocolType, + Port: 8080, + }, + ) + }, + expectErrsOnFields: nil, + }, + "hostname is unique when protocol and port are the same": { + mutate: func(gw *gatewayv1b1.Gateway) { + hostnameFoo := gatewayv1b1.Hostname("foo.com") + hostnameBar := gatewayv1b1.Hostname("bar.com") + gw.Spec.Listeners[0].Name = "foo" + gw.Spec.Listeners[0].Hostname = &hostnameFoo + gw.Spec.Listeners[0].Protocol = gatewayv1b1.HTTPProtocolType + gw.Spec.Listeners[0].Port = 80 + gw.Spec.Listeners = append(gw.Spec.Listeners, + gatewayv1b1.Listener{ + Name: "bar", + Hostname: &hostnameBar, + Protocol: gatewayv1b1.HTTPProtocolType, + Port: 80, + }, + ) + }, + expectErrsOnFields: nil, + }, + "protocol is unique when port and hostname are the same": { + mutate: func(gw *gatewayv1b1.Gateway) { + hostnameFoo := gatewayv1b1.Hostname("foo.com") + tlsConfigFoo := tlsConfig + tlsModeFoo := gatewayv1b1.TLSModeType("Terminate") + tlsConfigFoo.Mode = &tlsModeFoo + tlsConfigFoo.CertificateRefs = []gatewayv1b1.SecretObjectReference{ + { + Name: "FooCertificateRefs", + }, + } + gw.Spec.Listeners[0].Name = "foo" + gw.Spec.Listeners[0].Hostname = &hostnameFoo + gw.Spec.Listeners[0].Protocol = gatewayv1b1.HTTPSProtocolType + gw.Spec.Listeners[0].Port = 8000 + gw.Spec.Listeners[0].TLS = &tlsConfigFoo + gw.Spec.Listeners = append(gw.Spec.Listeners, + gatewayv1b1.Listener{ + Name: "bar", + Hostname: &hostnameFoo, + Protocol: gatewayv1b1.TLSProtocolType, + Port: 8000, + TLS: &tlsConfigFoo, + }, + ) + }, + expectErrsOnFields: nil, + }, } for name, tc := range testCases {