Skip to content

Commit bb88d58

Browse files
authored
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 <[email protected]> * Fix format. Signed-off-by: Huang Xin <[email protected]> * Fix format. Signed-off-by: Huang Xin <[email protected]> * Skip Insert if the set has the combination already Signed-off-by: Huang Xin <[email protected]> * Add more unit tests. Signed-off-by: Huang Xin <[email protected]> * Fix nits. Signed-off-by: Huang Xin <[email protected]> --------- Signed-off-by: Huang Xin <[email protected]>
1 parent 2897160 commit bb88d58

File tree

2 files changed

+130
-3
lines changed

2 files changed

+130
-3
lines changed

apis/v1beta1/validation/gateway.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package validation
1919
import (
2020
"fmt"
2121

22+
"k8s.io/apimachinery/pkg/util/sets"
2223
"k8s.io/apimachinery/pkg/util/validation/field"
2324

2425
gatewayv1b1 "sigs.k8s.io/gateway-api/apis/v1beta1"
@@ -70,6 +71,7 @@ func validateGatewayListeners(listeners []gatewayv1b1.Listener, path *field.Path
7071
errs = append(errs, validateListenerHostname(listeners, path)...)
7172
errs = append(errs, ValidateTLSCertificateRefs(listeners, path)...)
7273
errs = append(errs, ValidateListenerNames(listeners, path)...)
74+
errs = append(errs, validateHostnameProtocolPort(listeners, path)...)
7375
return errs
7476
}
7577

@@ -133,3 +135,25 @@ func ValidateListenerNames(listeners []gatewayv1b1.Listener, path *field.Path) f
133135
}
134136
return errs
135137
}
138+
139+
// validateHostnameProtocolPort validates that the combination of port, protocol, and hostname are
140+
// unique for each listener.
141+
func validateHostnameProtocolPort(listeners []gatewayv1b1.Listener, path *field.Path) field.ErrorList {
142+
var errs field.ErrorList
143+
hostnameProtocolPortSets := sets.Set[string]{}
144+
for i, listener := range listeners {
145+
hostname := new(gatewayv1b1.Hostname)
146+
if listener.Hostname != nil {
147+
hostname = listener.Hostname
148+
}
149+
protocol := listener.Protocol
150+
port := listener.Port
151+
hostnameProtocolPort := fmt.Sprintf("%s:%s:%d", *hostname, protocol, port)
152+
if hostnameProtocolPortSets.Has(hostnameProtocolPort) {
153+
errs = append(errs, field.Forbidden(path.Index(i), fmt.Sprintln("combination of port, protocol, and hostname must be unique for each listener")))
154+
} else {
155+
hostnameProtocolPortSets.Insert(hostnameProtocolPort)
156+
}
157+
}
158+
return errs
159+
}

apis/v1beta1/validation/gateway_test.go

Lines changed: 106 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,14 +136,117 @@ func TestValidateGateway(t *testing.T) {
136136
},
137137
"names are not unique within the Gateway": {
138138
mutate: func(gw *gatewayv1b1.Gateway) {
139+
hostnameFoo := gatewayv1b1.Hostname("foo.com")
140+
hostnameBar := gatewayv1b1.Hostname("bar.com")
139141
gw.Spec.Listeners[0].Name = "foo"
140-
gw.Spec.Listeners = append(gw.Spec.Listeners, gatewayv1b1.Listener{
141-
Name: "foo",
142-
},
142+
gw.Spec.Listeners[0].Hostname = &hostnameFoo
143+
gw.Spec.Listeners = append(gw.Spec.Listeners,
144+
gatewayv1b1.Listener{
145+
Name: "foo",
146+
Hostname: &hostnameBar,
147+
},
143148
)
144149
},
145150
expectErrsOnFields: []string{"spec.listeners[1].name"},
146151
},
152+
"combination of port, protocol, and hostname are not unique for each listener": {
153+
mutate: func(gw *gatewayv1b1.Gateway) {
154+
hostnameFoo := gatewayv1b1.Hostname("foo.com")
155+
gw.Spec.Listeners[0].Name = "foo"
156+
gw.Spec.Listeners[0].Hostname = &hostnameFoo
157+
gw.Spec.Listeners[0].Protocol = gatewayv1b1.HTTPProtocolType
158+
gw.Spec.Listeners[0].Port = 80
159+
gw.Spec.Listeners = append(gw.Spec.Listeners,
160+
gatewayv1b1.Listener{
161+
Name: "bar",
162+
Hostname: &hostnameFoo,
163+
Protocol: gatewayv1b1.HTTPProtocolType,
164+
Port: 80,
165+
},
166+
)
167+
},
168+
expectErrsOnFields: []string{"spec.listeners[1]"},
169+
},
170+
"combination of port and protocol are not unique for each listenr when hostnames not set": {
171+
mutate: func(gw *gatewayv1b1.Gateway) {
172+
gw.Spec.Listeners[0].Name = "foo"
173+
gw.Spec.Listeners[0].Protocol = gatewayv1b1.HTTPProtocolType
174+
gw.Spec.Listeners[0].Port = 80
175+
gw.Spec.Listeners = append(gw.Spec.Listeners,
176+
gatewayv1b1.Listener{
177+
Name: "bar",
178+
Protocol: gatewayv1b1.HTTPProtocolType,
179+
Port: 80,
180+
},
181+
)
182+
},
183+
expectErrsOnFields: []string{"spec.listeners[1]"},
184+
},
185+
"port is unique when protocol and hostname are the same": {
186+
mutate: func(gw *gatewayv1b1.Gateway) {
187+
hostnameFoo := gatewayv1b1.Hostname("foo.com")
188+
gw.Spec.Listeners[0].Name = "foo"
189+
gw.Spec.Listeners[0].Hostname = &hostnameFoo
190+
gw.Spec.Listeners[0].Protocol = gatewayv1b1.HTTPProtocolType
191+
gw.Spec.Listeners[0].Port = 80
192+
gw.Spec.Listeners = append(gw.Spec.Listeners,
193+
gatewayv1b1.Listener{
194+
Name: "bar",
195+
Hostname: &hostnameFoo,
196+
Protocol: gatewayv1b1.HTTPProtocolType,
197+
Port: 8080,
198+
},
199+
)
200+
},
201+
expectErrsOnFields: nil,
202+
},
203+
"hostname is unique when protocol and port are the same": {
204+
mutate: func(gw *gatewayv1b1.Gateway) {
205+
hostnameFoo := gatewayv1b1.Hostname("foo.com")
206+
hostnameBar := gatewayv1b1.Hostname("bar.com")
207+
gw.Spec.Listeners[0].Name = "foo"
208+
gw.Spec.Listeners[0].Hostname = &hostnameFoo
209+
gw.Spec.Listeners[0].Protocol = gatewayv1b1.HTTPProtocolType
210+
gw.Spec.Listeners[0].Port = 80
211+
gw.Spec.Listeners = append(gw.Spec.Listeners,
212+
gatewayv1b1.Listener{
213+
Name: "bar",
214+
Hostname: &hostnameBar,
215+
Protocol: gatewayv1b1.HTTPProtocolType,
216+
Port: 80,
217+
},
218+
)
219+
},
220+
expectErrsOnFields: nil,
221+
},
222+
"protocol is unique when port and hostname are the same": {
223+
mutate: func(gw *gatewayv1b1.Gateway) {
224+
hostnameFoo := gatewayv1b1.Hostname("foo.com")
225+
tlsConfigFoo := tlsConfig
226+
tlsModeFoo := gatewayv1b1.TLSModeType("Terminate")
227+
tlsConfigFoo.Mode = &tlsModeFoo
228+
tlsConfigFoo.CertificateRefs = []gatewayv1b1.SecretObjectReference{
229+
{
230+
Name: "FooCertificateRefs",
231+
},
232+
}
233+
gw.Spec.Listeners[0].Name = "foo"
234+
gw.Spec.Listeners[0].Hostname = &hostnameFoo
235+
gw.Spec.Listeners[0].Protocol = gatewayv1b1.HTTPSProtocolType
236+
gw.Spec.Listeners[0].Port = 8000
237+
gw.Spec.Listeners[0].TLS = &tlsConfigFoo
238+
gw.Spec.Listeners = append(gw.Spec.Listeners,
239+
gatewayv1b1.Listener{
240+
Name: "bar",
241+
Hostname: &hostnameFoo,
242+
Protocol: gatewayv1b1.TLSProtocolType,
243+
Port: 8000,
244+
TLS: &tlsConfigFoo,
245+
},
246+
)
247+
},
248+
expectErrsOnFields: nil,
249+
},
147250
}
148251

149252
for name, tc := range testCases {

0 commit comments

Comments
 (0)