1
16
17 package validation
18
19 import (
20 "fmt"
21 "strings"
22 "testing"
23
24 apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26 "k8s.io/apimachinery/pkg/util/intstr"
27 "k8s.io/apimachinery/pkg/util/validation/field"
28 api "k8s.io/kubernetes/pkg/apis/core"
29 "k8s.io/kubernetes/pkg/apis/networking"
30 utilpointer "k8s.io/utils/pointer"
31 )
32
33 func makeValidNetworkPolicy() *networking.NetworkPolicy {
34 return &networking.NetworkPolicy{
35 ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"},
36 Spec: networking.NetworkPolicySpec{
37 PodSelector: metav1.LabelSelector{
38 MatchLabels: map[string]string{"a": "b"},
39 },
40 },
41 }
42 }
43
44 type netpolTweak func(networkPolicy *networking.NetworkPolicy)
45
46 func makeNetworkPolicyCustom(tweaks ...netpolTweak) *networking.NetworkPolicy {
47 networkPolicy := makeValidNetworkPolicy()
48 for _, fn := range tweaks {
49 fn(networkPolicy)
50 }
51 return networkPolicy
52 }
53
54 func makePort(proto *api.Protocol, port intstr.IntOrString, endPort int32) networking.NetworkPolicyPort {
55 r := networking.NetworkPolicyPort{
56 Protocol: proto,
57 Port: nil,
58 }
59 if port != intstr.FromInt32(0) && port != intstr.FromString("") && port != intstr.FromString("0") {
60 r.Port = &port
61 }
62 if endPort != 0 {
63 r.EndPort = utilpointer.Int32(endPort)
64 }
65 return r
66 }
67
68 func TestValidateNetworkPolicy(t *testing.T) {
69 protocolTCP := api.ProtocolTCP
70 protocolUDP := api.ProtocolUDP
71 protocolICMP := api.Protocol("ICMP")
72 protocolSCTP := api.ProtocolSCTP
73
74
75 setIngressEmptyFirstElement := func(networkPolicy *networking.NetworkPolicy) {
76 networkPolicy.Spec.Ingress = []networking.NetworkPolicyIngressRule{{}}
77 }
78
79 setIngressFromEmptyFirstElement := func(networkPolicy *networking.NetworkPolicy) {
80 if networkPolicy.Spec.Ingress == nil {
81 setIngressEmptyFirstElement(networkPolicy)
82 }
83 networkPolicy.Spec.Ingress[0].From = []networking.NetworkPolicyPeer{{}}
84 }
85
86 setIngressFromIfEmpty := func(networkPolicy *networking.NetworkPolicy) {
87 if networkPolicy.Spec.Ingress == nil {
88 setIngressEmptyFirstElement(networkPolicy)
89 }
90 if networkPolicy.Spec.Ingress[0].From == nil {
91 setIngressFromEmptyFirstElement(networkPolicy)
92 }
93 }
94
95 setIngressEmptyPorts := func(networkPolicy *networking.NetworkPolicy) {
96 networkPolicy.Spec.Ingress = []networking.NetworkPolicyIngressRule{{
97 Ports: []networking.NetworkPolicyPort{{}},
98 }}
99 }
100
101 setIngressPorts := func(ports ...networking.NetworkPolicyPort) netpolTweak {
102 return func(np *networking.NetworkPolicy) {
103 if np.Spec.Ingress == nil {
104 setIngressEmptyFirstElement(np)
105 }
106 np.Spec.Ingress[0].Ports = make([]networking.NetworkPolicyPort, len(ports))
107 copy(np.Spec.Ingress[0].Ports, ports)
108 }
109 }
110
111 setIngressFromPodSelector := func(k, v string) func(*networking.NetworkPolicy) {
112 return func(networkPolicy *networking.NetworkPolicy) {
113 setIngressFromIfEmpty(networkPolicy)
114 networkPolicy.Spec.Ingress[0].From[0].PodSelector = &metav1.LabelSelector{
115 MatchLabels: map[string]string{k: v},
116 }
117 }
118 }
119
120 setIngressFromNamespaceSelector := func(networkPolicy *networking.NetworkPolicy) {
121 setIngressFromIfEmpty(networkPolicy)
122 networkPolicy.Spec.Ingress[0].From[0].NamespaceSelector = &metav1.LabelSelector{
123 MatchLabels: map[string]string{"c": "d"},
124 }
125 }
126
127 setIngressFromIPBlockIPV4 := func(networkPolicy *networking.NetworkPolicy) {
128 setIngressFromIfEmpty(networkPolicy)
129 networkPolicy.Spec.Ingress[0].From[0].IPBlock = &networking.IPBlock{
130 CIDR: "192.168.0.0/16",
131 Except: []string{"192.168.3.0/24", "192.168.4.0/24"},
132 }
133 }
134
135 setIngressFromIPBlockIPV6 := func(networkPolicy *networking.NetworkPolicy) {
136 setIngressFromIfEmpty(networkPolicy)
137 networkPolicy.Spec.Ingress[0].From[0].IPBlock = &networking.IPBlock{
138 CIDR: "fd00:192:168::/48",
139 Except: []string{"fd00:192:168:3::/64", "fd00:192:168:4::/64"},
140 }
141 }
142
143 setEgressEmptyFirstElement := func(networkPolicy *networking.NetworkPolicy) {
144 networkPolicy.Spec.Egress = []networking.NetworkPolicyEgressRule{{}}
145 }
146
147 setEgressToEmptyFirstElement := func(networkPolicy *networking.NetworkPolicy) {
148 if networkPolicy.Spec.Egress == nil {
149 setEgressEmptyFirstElement(networkPolicy)
150 }
151 networkPolicy.Spec.Egress[0].To = []networking.NetworkPolicyPeer{{}}
152 }
153
154 setEgressToIfEmpty := func(networkPolicy *networking.NetworkPolicy) {
155 if networkPolicy.Spec.Egress == nil {
156 setEgressEmptyFirstElement(networkPolicy)
157 }
158 if networkPolicy.Spec.Egress[0].To == nil {
159 setEgressToEmptyFirstElement(networkPolicy)
160 }
161 }
162
163 setEgressToNamespaceSelector := func(networkPolicy *networking.NetworkPolicy) {
164 setEgressToIfEmpty(networkPolicy)
165 networkPolicy.Spec.Egress[0].To[0].NamespaceSelector = &metav1.LabelSelector{
166 MatchLabels: map[string]string{"c": "d"},
167 }
168 }
169
170 setEgressToPodSelector := func(networkPolicy *networking.NetworkPolicy) {
171 setEgressToIfEmpty(networkPolicy)
172 networkPolicy.Spec.Egress[0].To[0].PodSelector = &metav1.LabelSelector{
173 MatchLabels: map[string]string{"c": "d"},
174 }
175 }
176
177 setEgressToIPBlockIPV4 := func(networkPolicy *networking.NetworkPolicy) {
178 setEgressToIfEmpty(networkPolicy)
179 networkPolicy.Spec.Egress[0].To[0].IPBlock = &networking.IPBlock{
180 CIDR: "192.168.0.0/16",
181 Except: []string{"192.168.3.0/24", "192.168.4.0/24"},
182 }
183 }
184
185 setEgressToIPBlockIPV6 := func(networkPolicy *networking.NetworkPolicy) {
186 setEgressToIfEmpty(networkPolicy)
187 networkPolicy.Spec.Egress[0].To[0].IPBlock = &networking.IPBlock{
188 CIDR: "fd00:192:168::/48",
189 Except: []string{"fd00:192:168:3::/64", "fd00:192:168:4::/64"},
190 }
191 }
192
193 setEgressPorts := func(ports ...networking.NetworkPolicyPort) netpolTweak {
194 return func(np *networking.NetworkPolicy) {
195 if np.Spec.Egress == nil {
196 setEgressEmptyFirstElement(np)
197 }
198 np.Spec.Egress[0].Ports = make([]networking.NetworkPolicyPort, len(ports))
199 copy(np.Spec.Egress[0].Ports, ports)
200 }
201 }
202
203 setPolicyTypesEgress := func(networkPolicy *networking.NetworkPolicy) {
204 networkPolicy.Spec.PolicyTypes = []networking.PolicyType{networking.PolicyTypeEgress}
205 }
206
207 setPolicyTypesIngressEgress := func(networkPolicy *networking.NetworkPolicy) {
208 networkPolicy.Spec.PolicyTypes = []networking.PolicyType{networking.PolicyTypeIngress, networking.PolicyTypeEgress}
209 }
210
211 successCases := []*networking.NetworkPolicy{
212 makeNetworkPolicyCustom(setIngressEmptyFirstElement),
213 makeNetworkPolicyCustom(setIngressFromEmptyFirstElement, setIngressEmptyPorts),
214 makeNetworkPolicyCustom(setIngressPorts(
215 makePort(nil, intstr.FromInt32(80), 0),
216 makePort(&protocolTCP, intstr.FromInt32(0), 0),
217 makePort(&protocolTCP, intstr.FromInt32(443), 0),
218 makePort(&protocolUDP, intstr.FromString("dns"), 0),
219 makePort(&protocolSCTP, intstr.FromInt32(7777), 0),
220 )),
221 makeNetworkPolicyCustom(setIngressFromPodSelector("c", "d")),
222 makeNetworkPolicyCustom(setIngressFromNamespaceSelector),
223 makeNetworkPolicyCustom(setIngressFromPodSelector("e", "f"), setIngressFromNamespaceSelector),
224 makeNetworkPolicyCustom(setEgressToNamespaceSelector, setIngressFromIPBlockIPV4),
225 makeNetworkPolicyCustom(setIngressFromIPBlockIPV4),
226 makeNetworkPolicyCustom(setEgressToIPBlockIPV4, setPolicyTypesEgress),
227 makeNetworkPolicyCustom(setEgressToIPBlockIPV4, setPolicyTypesIngressEgress),
228 makeNetworkPolicyCustom(setEgressPorts(
229 makePort(nil, intstr.FromInt32(80), 0),
230 makePort(&protocolTCP, intstr.FromInt32(0), 0),
231 makePort(&protocolTCP, intstr.FromInt32(443), 0),
232 makePort(&protocolUDP, intstr.FromString("dns"), 0),
233 makePort(&protocolSCTP, intstr.FromInt32(7777), 0),
234 )),
235 makeNetworkPolicyCustom(setEgressToNamespaceSelector, setIngressFromIPBlockIPV6),
236 makeNetworkPolicyCustom(setIngressFromIPBlockIPV6),
237 makeNetworkPolicyCustom(setEgressToIPBlockIPV6, setPolicyTypesEgress),
238 makeNetworkPolicyCustom(setEgressToIPBlockIPV6, setPolicyTypesIngressEgress),
239 makeNetworkPolicyCustom(setEgressPorts(makePort(nil, intstr.FromInt32(32000), 32768), makePort(&protocolUDP, intstr.FromString("dns"), 0))),
240 makeNetworkPolicyCustom(
241 setEgressToNamespaceSelector,
242 setEgressPorts(
243 makePort(nil, intstr.FromInt32(30000), 32768),
244 makePort(nil, intstr.FromInt32(32000), 32768),
245 ),
246 setIngressFromPodSelector("e", "f"),
247 setIngressPorts(makePort(&protocolTCP, intstr.FromInt32(32768), 32768))),
248 }
249
250
251
252 for k, v := range successCases {
253 if errs := ValidateNetworkPolicy(v, NetworkPolicyValidationOptions{AllowInvalidLabelValueInSelector: true}); len(errs) != 0 {
254 t.Errorf("Expected success for the success validation test number %d, got %v", k, errs)
255 }
256 }
257
258 invalidSelector := map[string]string{"NoUppercaseOrSpecialCharsLike=Equals": "b"}
259
260 errorCases := map[string]*networking.NetworkPolicy{
261 "namespaceSelector and ipBlock": makeNetworkPolicyCustom(setIngressFromNamespaceSelector, setIngressFromIPBlockIPV4),
262 "podSelector and ipBlock": makeNetworkPolicyCustom(setEgressToPodSelector, setEgressToIPBlockIPV4),
263 "missing from and to type": makeNetworkPolicyCustom(setIngressFromEmptyFirstElement, setEgressToEmptyFirstElement),
264 "invalid spec.podSelector": makeNetworkPolicyCustom(setIngressFromNamespaceSelector, func(networkPolicy *networking.NetworkPolicy) {
265 networkPolicy.Spec = networking.NetworkPolicySpec{
266 PodSelector: metav1.LabelSelector{
267 MatchLabels: invalidSelector,
268 },
269 }
270 }),
271 "invalid ingress.ports.protocol": makeNetworkPolicyCustom(setIngressPorts(makePort(&protocolICMP, intstr.FromInt32(80), 0))),
272 "invalid ingress.ports.port (int)": makeNetworkPolicyCustom(setIngressPorts(makePort(&protocolTCP, intstr.FromInt32(123456789), 0))),
273 "invalid ingress.ports.port (str)": makeNetworkPolicyCustom(
274 setIngressPorts(makePort(&protocolTCP, intstr.FromString("!@#$"), 0))),
275 "invalid ingress.from.podSelector": makeNetworkPolicyCustom(setIngressFromEmptyFirstElement, func(networkPolicy *networking.NetworkPolicy) {
276 networkPolicy.Spec.Ingress[0].From[0].PodSelector = &metav1.LabelSelector{
277 MatchLabels: invalidSelector,
278 }
279 }),
280 "invalid egress.to.podSelector": makeNetworkPolicyCustom(setEgressToEmptyFirstElement, func(networkPolicy *networking.NetworkPolicy) {
281 networkPolicy.Spec.Egress[0].To[0].PodSelector = &metav1.LabelSelector{
282 MatchLabels: invalidSelector,
283 }
284 }),
285 "invalid egress.ports.protocol": makeNetworkPolicyCustom(setEgressPorts(makePort(&protocolICMP, intstr.FromInt32(80), 0))),
286 "invalid egress.ports.port (int)": makeNetworkPolicyCustom(setEgressPorts(makePort(&protocolTCP, intstr.FromInt32(123456789), 0))),
287 "invalid egress.ports.port (str)": makeNetworkPolicyCustom(setEgressPorts(makePort(&protocolTCP, intstr.FromString("!@#$"), 0))),
288 "invalid ingress.from.namespaceSelector": makeNetworkPolicyCustom(setIngressFromEmptyFirstElement, func(networkPolicy *networking.NetworkPolicy) {
289 networkPolicy.Spec.Ingress[0].From[0].NamespaceSelector = &metav1.LabelSelector{
290 MatchLabels: invalidSelector,
291 }
292 }),
293 "missing cidr field": makeNetworkPolicyCustom(setIngressFromIPBlockIPV4, func(networkPolicy *networking.NetworkPolicy) {
294 networkPolicy.Spec.Ingress[0].From[0].IPBlock.CIDR = ""
295 }),
296 "invalid cidr format": makeNetworkPolicyCustom(setIngressFromIPBlockIPV4, func(networkPolicy *networking.NetworkPolicy) {
297 networkPolicy.Spec.Ingress[0].From[0].IPBlock.CIDR = "192.168.5.6"
298 }),
299 "invalid ipv6 cidr format": makeNetworkPolicyCustom(setIngressFromIPBlockIPV6, func(networkPolicy *networking.NetworkPolicy) {
300 networkPolicy.Spec.Ingress[0].From[0].IPBlock.CIDR = "fd00:192:168::"
301 }),
302 "except field is an empty string": makeNetworkPolicyCustom(setIngressFromIPBlockIPV4, func(networkPolicy *networking.NetworkPolicy) {
303 networkPolicy.Spec.Ingress[0].From[0].IPBlock.Except = []string{""}
304 }),
305 "except field is an space string": makeNetworkPolicyCustom(setIngressFromIPBlockIPV4, func(networkPolicy *networking.NetworkPolicy) {
306 networkPolicy.Spec.Ingress[0].From[0].IPBlock.Except = []string{" "}
307 }),
308 "except field is an invalid ip": makeNetworkPolicyCustom(setIngressFromIPBlockIPV4, func(networkPolicy *networking.NetworkPolicy) {
309 networkPolicy.Spec.Ingress[0].From[0].IPBlock.Except = []string{"300.300.300.300"}
310 }),
311 "except IP is outside of CIDR range": makeNetworkPolicyCustom(setIngressFromEmptyFirstElement, func(networkPolicy *networking.NetworkPolicy) {
312 networkPolicy.Spec.Ingress[0].From[0].IPBlock = &networking.IPBlock{
313 CIDR: "192.168.8.0/24",
314 Except: []string{"192.168.9.1/24"},
315 }
316 }),
317 "except IP is not strictly within CIDR range": makeNetworkPolicyCustom(setIngressFromEmptyFirstElement, func(networkPolicy *networking.NetworkPolicy) {
318 networkPolicy.Spec.Ingress[0].From[0].IPBlock = &networking.IPBlock{
319 CIDR: "192.168.0.0/24",
320 Except: []string{"192.168.0.0/24"},
321 }
322 }),
323 "except IPv6 is outside of CIDR range": makeNetworkPolicyCustom(setIngressFromEmptyFirstElement, func(networkPolicy *networking.NetworkPolicy) {
324 networkPolicy.Spec.Ingress[0].From[0].IPBlock = &networking.IPBlock{
325 CIDR: "fd00:192:168:1::/64",
326 Except: []string{"fd00:192:168:2::/64"},
327 }
328 }),
329 "invalid policyTypes": makeNetworkPolicyCustom(setEgressToIPBlockIPV4, func(networkPolicy *networking.NetworkPolicy) {
330 networkPolicy.Spec.PolicyTypes = []networking.PolicyType{"foo", "bar"}
331 }),
332 "too many policyTypes": makeNetworkPolicyCustom(setEgressToIPBlockIPV4, func(networkPolicy *networking.NetworkPolicy) {
333 networkPolicy.Spec.PolicyTypes = []networking.PolicyType{"foo", "bar", "baz"}
334 }),
335 "multiple ports defined, one port range is invalid": makeNetworkPolicyCustom(
336 setEgressToNamespaceSelector,
337 setEgressPorts(
338 makePort(&protocolUDP, intstr.FromInt32(35000), 32768),
339 makePort(nil, intstr.FromInt32(32000), 32768),
340 ),
341 ),
342 "endPort defined with named/string port": makeNetworkPolicyCustom(
343 setEgressToNamespaceSelector,
344 setEgressPorts(
345 makePort(&protocolUDP, intstr.FromString("dns"), 32768),
346 makePort(nil, intstr.FromInt32(32000), 32768),
347 ),
348 ),
349 "endPort defined without port defined": makeNetworkPolicyCustom(
350 setEgressToNamespaceSelector,
351 setEgressPorts(makePort(&protocolTCP, intstr.FromInt32(0), 32768)),
352 ),
353 "port is greater than endPort": makeNetworkPolicyCustom(
354 setEgressToNamespaceSelector,
355 setEgressPorts(makePort(&protocolSCTP, intstr.FromInt32(35000), 32768)),
356 ),
357 "multiple invalid port ranges defined": makeNetworkPolicyCustom(
358 setEgressToNamespaceSelector,
359 setEgressPorts(
360 makePort(&protocolUDP, intstr.FromInt32(35000), 32768),
361 makePort(&protocolTCP, intstr.FromInt32(0), 32768),
362 makePort(&protocolTCP, intstr.FromString("https"), 32768),
363 ),
364 ),
365 "invalid endport range defined": makeNetworkPolicyCustom(setEgressToNamespaceSelector, setEgressPorts(makePort(&protocolTCP, intstr.FromInt32(30000), 65537))),
366 }
367
368
369 for testName, networkPolicy := range errorCases {
370 if errs := ValidateNetworkPolicy(networkPolicy, NetworkPolicyValidationOptions{AllowInvalidLabelValueInSelector: true}); len(errs) == 0 {
371 t.Errorf("Expected failure for test: %s", testName)
372 }
373 }
374 }
375
376 func TestValidateNetworkPolicyUpdate(t *testing.T) {
377 type npUpdateTest struct {
378 old networking.NetworkPolicy
379 update networking.NetworkPolicy
380 }
381 successCases := map[string]npUpdateTest{
382 "no change": {
383 old: networking.NetworkPolicy{
384 ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"},
385 Spec: networking.NetworkPolicySpec{
386 PodSelector: metav1.LabelSelector{
387 MatchLabels: map[string]string{"a": "b"},
388 },
389 Ingress: []networking.NetworkPolicyIngressRule{},
390 },
391 },
392 update: networking.NetworkPolicy{
393 ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"},
394 Spec: networking.NetworkPolicySpec{
395 PodSelector: metav1.LabelSelector{
396 MatchLabels: map[string]string{"a": "b"},
397 },
398 Ingress: []networking.NetworkPolicyIngressRule{},
399 },
400 },
401 },
402 "change spec": {
403 old: networking.NetworkPolicy{
404 ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"},
405 Spec: networking.NetworkPolicySpec{
406 PodSelector: metav1.LabelSelector{},
407 Ingress: []networking.NetworkPolicyIngressRule{},
408 },
409 },
410 update: networking.NetworkPolicy{
411 ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"},
412 Spec: networking.NetworkPolicySpec{
413 PodSelector: metav1.LabelSelector{
414 MatchLabels: map[string]string{"a": "b"},
415 },
416 Ingress: []networking.NetworkPolicyIngressRule{},
417 },
418 },
419 },
420 }
421
422 for testName, successCase := range successCases {
423 successCase.old.ObjectMeta.ResourceVersion = "1"
424 successCase.update.ObjectMeta.ResourceVersion = "1"
425 if errs := ValidateNetworkPolicyUpdate(&successCase.update, &successCase.old, NetworkPolicyValidationOptions{false}); len(errs) != 0 {
426 t.Errorf("expected success (%s): %v", testName, errs)
427 }
428 }
429
430 errorCases := map[string]npUpdateTest{
431 "change name": {
432 old: networking.NetworkPolicy{
433 ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"},
434 Spec: networking.NetworkPolicySpec{
435 PodSelector: metav1.LabelSelector{},
436 Ingress: []networking.NetworkPolicyIngressRule{},
437 },
438 },
439 update: networking.NetworkPolicy{
440 ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "bar"},
441 Spec: networking.NetworkPolicySpec{
442 PodSelector: metav1.LabelSelector{},
443 Ingress: []networking.NetworkPolicyIngressRule{},
444 },
445 },
446 },
447 }
448
449 for testName, errorCase := range errorCases {
450 errorCase.old.ObjectMeta.ResourceVersion = "1"
451 errorCase.update.ObjectMeta.ResourceVersion = "1"
452 if errs := ValidateNetworkPolicyUpdate(&errorCase.update, &errorCase.old, NetworkPolicyValidationOptions{false}); len(errs) == 0 {
453 t.Errorf("expected failure: %s", testName)
454 }
455 }
456 }
457
458 func TestValidateIngress(t *testing.T) {
459 serviceBackend := &networking.IngressServiceBackend{
460 Name: "defaultbackend",
461 Port: networking.ServiceBackendPort{
462 Name: "",
463 Number: 80,
464 },
465 }
466 defaultBackend := networking.IngressBackend{
467 Service: serviceBackend,
468 }
469 pathTypePrefix := networking.PathTypePrefix
470 pathTypeImplementationSpecific := networking.PathTypeImplementationSpecific
471 pathTypeFoo := networking.PathType("foo")
472
473 baseIngress := networking.Ingress{
474 ObjectMeta: metav1.ObjectMeta{
475 Name: "foo",
476 Namespace: metav1.NamespaceDefault,
477 },
478 Spec: networking.IngressSpec{
479 DefaultBackend: &defaultBackend,
480 Rules: []networking.IngressRule{{
481 Host: "foo.bar.com",
482 IngressRuleValue: networking.IngressRuleValue{
483 HTTP: &networking.HTTPIngressRuleValue{
484 Paths: []networking.HTTPIngressPath{{
485 Path: "/foo",
486 PathType: &pathTypeImplementationSpecific,
487 Backend: defaultBackend,
488 }},
489 },
490 },
491 }},
492 },
493 Status: networking.IngressStatus{
494 LoadBalancer: networking.IngressLoadBalancerStatus{
495 Ingress: []networking.IngressLoadBalancerIngress{
496 {IP: "127.0.0.1"},
497 },
498 },
499 },
500 }
501
502 testCases := map[string]struct {
503 tweakIngress func(ing *networking.Ingress)
504 expectErrsOnFields []string
505 }{
506 "empty path (implementation specific)": {
507 tweakIngress: func(ing *networking.Ingress) {
508 ing.Spec.Rules[0].IngressRuleValue.HTTP.Paths[0].Path = ""
509 },
510 expectErrsOnFields: []string{},
511 },
512 "valid path": {
513 tweakIngress: func(ing *networking.Ingress) {
514 ing.Spec.Rules[0].IngressRuleValue.HTTP.Paths[0].Path = "/valid"
515 },
516 expectErrsOnFields: []string{},
517 },
518
519 "backend with no service": {
520 tweakIngress: func(ing *networking.Ingress) {
521 ing.Spec.DefaultBackend.Service.Name = ""
522 },
523 expectErrsOnFields: []string{
524 "spec.defaultBackend.service.name",
525 },
526 },
527 "invalid path type": {
528 tweakIngress: func(ing *networking.Ingress) {
529 ing.Spec.Rules[0].IngressRuleValue.HTTP.Paths[0].PathType = &pathTypeFoo
530 },
531 expectErrsOnFields: []string{
532 "spec.rules[0].http.paths[0].pathType",
533 },
534 },
535 "empty path (prefix)": {
536 tweakIngress: func(ing *networking.Ingress) {
537 ing.Spec.Rules[0].IngressRuleValue.HTTP.Paths[0].Path = ""
538 ing.Spec.Rules[0].IngressRuleValue.HTTP.Paths[0].PathType = &pathTypePrefix
539 },
540 expectErrsOnFields: []string{
541 "spec.rules[0].http.paths[0].path",
542 },
543 },
544 "no paths": {
545 tweakIngress: func(ing *networking.Ingress) {
546 ing.Spec.Rules[0].IngressRuleValue.HTTP.Paths = []networking.HTTPIngressPath{}
547 },
548 expectErrsOnFields: []string{
549 "spec.rules[0].http.paths",
550 },
551 },
552 "invalid host (foobar:80)": {
553 tweakIngress: func(ing *networking.Ingress) {
554 ing.Spec.Rules[0].Host = "foobar:80"
555 },
556 expectErrsOnFields: []string{
557 "spec.rules[0].host",
558 },
559 },
560 "invalid host (127.0.0.1)": {
561 tweakIngress: func(ing *networking.Ingress) {
562 ing.Spec.Rules[0].Host = "127.0.0.1"
563 },
564 expectErrsOnFields: []string{
565 "spec.rules[0].host",
566 },
567 },
568 "valid wildcard host": {
569 tweakIngress: func(ing *networking.Ingress) {
570 ing.Spec.Rules[0].Host = "*.bar.com"
571 },
572 expectErrsOnFields: []string{},
573 },
574 "invalid wildcard host (foo.*.bar.com)": {
575 tweakIngress: func(ing *networking.Ingress) {
576 ing.Spec.Rules[0].Host = "foo.*.bar.com"
577 },
578 expectErrsOnFields: []string{
579 "spec.rules[0].host",
580 },
581 },
582 "invalid wildcard host (*)": {
583 tweakIngress: func(ing *networking.Ingress) {
584 ing.Spec.Rules[0].Host = "*"
585 },
586 expectErrsOnFields: []string{
587 "spec.rules[0].host",
588 },
589 },
590 "path resource backend and service name are not allowed together": {
591 tweakIngress: func(ing *networking.Ingress) {
592 ing.Spec.Rules[0].IngressRuleValue = networking.IngressRuleValue{
593 HTTP: &networking.HTTPIngressRuleValue{
594 Paths: []networking.HTTPIngressPath{{
595 Path: "/foo",
596 PathType: &pathTypeImplementationSpecific,
597 Backend: networking.IngressBackend{
598 Service: serviceBackend,
599 Resource: &api.TypedLocalObjectReference{
600 APIGroup: utilpointer.String("example.com"),
601 Kind: "foo",
602 Name: "bar",
603 },
604 },
605 }},
606 },
607 }
608 },
609 expectErrsOnFields: []string{
610 "spec.rules[0].http.paths[0].backend",
611 },
612 },
613 "path resource backend and service port are not allowed together": {
614 tweakIngress: func(ing *networking.Ingress) {
615 ing.Spec.Rules[0].IngressRuleValue = networking.IngressRuleValue{
616 HTTP: &networking.HTTPIngressRuleValue{
617 Paths: []networking.HTTPIngressPath{{
618 Path: "/foo",
619 PathType: &pathTypeImplementationSpecific,
620 Backend: networking.IngressBackend{
621 Service: serviceBackend,
622 Resource: &api.TypedLocalObjectReference{
623 APIGroup: utilpointer.String("example.com"),
624 Kind: "foo",
625 Name: "bar",
626 },
627 },
628 }},
629 },
630 }
631 },
632 expectErrsOnFields: []string{
633 "spec.rules[0].http.paths[0].backend",
634 },
635 },
636 "spec.backend resource and service name are not allowed together": {
637 tweakIngress: func(ing *networking.Ingress) {
638 ing.Spec.DefaultBackend = &networking.IngressBackend{
639 Service: serviceBackend,
640 Resource: &api.TypedLocalObjectReference{
641 APIGroup: utilpointer.String("example.com"),
642 Kind: "foo",
643 Name: "bar",
644 },
645 }
646 },
647 expectErrsOnFields: []string{
648 "spec.defaultBackend",
649 },
650 },
651 "spec.backend resource and service port are not allowed together": {
652 tweakIngress: func(ing *networking.Ingress) {
653 ing.Spec.DefaultBackend = &networking.IngressBackend{
654 Service: serviceBackend,
655 Resource: &api.TypedLocalObjectReference{
656 APIGroup: utilpointer.String("example.com"),
657 Kind: "foo",
658 Name: "bar",
659 },
660 }
661 },
662 expectErrsOnFields: []string{
663 "spec.defaultBackend",
664 },
665 },
666 }
667
668 for name, testCase := range testCases {
669 t.Run(name, func(t *testing.T) {
670 ingress := baseIngress.DeepCopy()
671 testCase.tweakIngress(ingress)
672 errs := validateIngress(ingress, IngressValidationOptions{})
673 if len(testCase.expectErrsOnFields) != len(errs) {
674 t.Fatalf("Expected %d errors, got %d errors: %v", len(testCase.expectErrsOnFields), len(errs), errs)
675 }
676 for i, err := range errs {
677 if err.Field != testCase.expectErrsOnFields[i] {
678 t.Errorf("Expected error on field: %s, got: %s", testCase.expectErrsOnFields[i], err.Error())
679 }
680 }
681 })
682 }
683 }
684
685 func TestValidateIngressRuleValue(t *testing.T) {
686 serviceBackend := networking.IngressServiceBackend{
687 Name: "defaultbackend",
688 Port: networking.ServiceBackendPort{
689 Name: "",
690 Number: 80,
691 },
692 }
693 fldPath := field.NewPath("testing.http.paths[0].path")
694 testCases := map[string]struct {
695 pathType networking.PathType
696 path string
697 expectedErrs field.ErrorList
698 }{
699 "implementation specific: no leading slash": {
700 pathType: networking.PathTypeImplementationSpecific,
701 path: "foo",
702 expectedErrs: field.ErrorList{field.Invalid(fldPath, "foo", "must be an absolute path")},
703 },
704 "implementation specific: leading slash": {
705 pathType: networking.PathTypeImplementationSpecific,
706 path: "/foo",
707 expectedErrs: field.ErrorList{},
708 },
709 "implementation specific: many slashes": {
710 pathType: networking.PathTypeImplementationSpecific,
711 path: "/foo/bar/foo",
712 expectedErrs: field.ErrorList{},
713 },
714 "implementation specific: repeating slashes": {
715 pathType: networking.PathTypeImplementationSpecific,
716 path: "/foo//bar/foo",
717 expectedErrs: field.ErrorList{},
718 },
719 "prefix: no leading slash": {
720 pathType: networking.PathTypePrefix,
721 path: "foo",
722 expectedErrs: field.ErrorList{field.Invalid(fldPath, "foo", "must be an absolute path")},
723 },
724 "prefix: leading slash": {
725 pathType: networking.PathTypePrefix,
726 path: "/foo",
727 expectedErrs: field.ErrorList{},
728 },
729 "prefix: many slashes": {
730 pathType: networking.PathTypePrefix,
731 path: "/foo/bar/foo",
732 expectedErrs: field.ErrorList{},
733 },
734 "prefix: repeating slashes": {
735 pathType: networking.PathTypePrefix,
736 path: "/foo//bar/foo",
737 expectedErrs: field.ErrorList{field.Invalid(fldPath, "/foo//bar/foo", "must not contain '//'")},
738 },
739 "exact: no leading slash": {
740 pathType: networking.PathTypeExact,
741 path: "foo",
742 expectedErrs: field.ErrorList{field.Invalid(fldPath, "foo", "must be an absolute path")},
743 },
744 "exact: leading slash": {
745 pathType: networking.PathTypeExact,
746 path: "/foo",
747 expectedErrs: field.ErrorList{},
748 },
749 "exact: many slashes": {
750 pathType: networking.PathTypeExact,
751 path: "/foo/bar/foo",
752 expectedErrs: field.ErrorList{},
753 },
754 "exact: repeating slashes": {
755 pathType: networking.PathTypeExact,
756 path: "/foo//bar/foo",
757 expectedErrs: field.ErrorList{field.Invalid(fldPath, "/foo//bar/foo", "must not contain '//'")},
758 },
759 "prefix: with /./": {
760 pathType: networking.PathTypePrefix,
761 path: "/foo/./foo",
762 expectedErrs: field.ErrorList{field.Invalid(fldPath, "/foo/./foo", "must not contain '/./'")},
763 },
764 "exact: with /../": {
765 pathType: networking.PathTypeExact,
766 path: "/foo/../foo",
767 expectedErrs: field.ErrorList{field.Invalid(fldPath, "/foo/../foo", "must not contain '/../'")},
768 },
769 "prefix: with %2f": {
770 pathType: networking.PathTypePrefix,
771 path: "/foo/%2f/foo",
772 expectedErrs: field.ErrorList{field.Invalid(fldPath, "/foo/%2f/foo", "must not contain '%2f'")},
773 },
774 "exact: with %2F": {
775 pathType: networking.PathTypeExact,
776 path: "/foo/%2F/foo",
777 expectedErrs: field.ErrorList{field.Invalid(fldPath, "/foo/%2F/foo", "must not contain '%2F'")},
778 },
779 }
780
781 for name, testCase := range testCases {
782 t.Run(name, func(t *testing.T) {
783 irv := &networking.IngressRuleValue{
784 HTTP: &networking.HTTPIngressRuleValue{
785 Paths: []networking.HTTPIngressPath{{
786 Path: testCase.path,
787 PathType: &testCase.pathType,
788 Backend: networking.IngressBackend{
789 Service: &serviceBackend,
790 },
791 }},
792 },
793 }
794 errs := validateIngressRuleValue(irv, field.NewPath("testing"), IngressValidationOptions{})
795 if len(errs) != len(testCase.expectedErrs) {
796 t.Fatalf("Expected %d errors, got %d (%+v)", len(testCase.expectedErrs), len(errs), errs)
797 }
798
799 for i, err := range errs {
800 if err.Error() != testCase.expectedErrs[i].Error() {
801 t.Fatalf("Expected error: %v, got %v", testCase.expectedErrs[i], err)
802 }
803 }
804 })
805 }
806 }
807
808 func TestValidateIngressCreate(t *testing.T) {
809 implementationPathType := networking.PathTypeImplementationSpecific
810 exactPathType := networking.PathTypeExact
811 serviceBackend := &networking.IngressServiceBackend{
812 Name: "defaultbackend",
813 Port: networking.ServiceBackendPort{
814 Number: 80,
815 },
816 }
817 defaultBackend := networking.IngressBackend{
818 Service: serviceBackend,
819 }
820 resourceBackend := &api.TypedLocalObjectReference{
821 APIGroup: utilpointer.String("example.com"),
822 Kind: "foo",
823 Name: "bar",
824 }
825 baseIngress := networking.Ingress{
826 ObjectMeta: metav1.ObjectMeta{
827 Name: "test123",
828 Namespace: "test123",
829 ResourceVersion: "1234",
830 },
831 Spec: networking.IngressSpec{
832 DefaultBackend: &defaultBackend,
833 Rules: []networking.IngressRule{},
834 },
835 }
836
837 testCases := map[string]struct {
838 tweakIngress func(ingress *networking.Ingress)
839 expectedErrs field.ErrorList
840 }{
841 "class field set": {
842 tweakIngress: func(ingress *networking.Ingress) {
843 ingress.Spec.IngressClassName = utilpointer.String("bar")
844 },
845 expectedErrs: field.ErrorList{},
846 },
847 "class annotation set": {
848 tweakIngress: func(ingress *networking.Ingress) {
849 ingress.Annotations = map[string]string{annotationIngressClass: "foo"}
850 },
851 expectedErrs: field.ErrorList{},
852 },
853 "class field and annotation set with same value": {
854 tweakIngress: func(ingress *networking.Ingress) {
855 ingress.Spec.IngressClassName = utilpointer.String("foo")
856 ingress.Annotations = map[string]string{annotationIngressClass: "foo"}
857 },
858 expectedErrs: field.ErrorList{},
859 },
860 "class field and annotation set with different value": {
861 tweakIngress: func(ingress *networking.Ingress) {
862 ingress.Spec.IngressClassName = utilpointer.String("bar")
863 ingress.Annotations = map[string]string{annotationIngressClass: "foo"}
864 },
865 expectedErrs: field.ErrorList{field.Invalid(field.NewPath("annotations").Child(annotationIngressClass), "foo", "must match `ingressClassName` when both are specified")},
866 },
867 "valid regex path": {
868 tweakIngress: func(ingress *networking.Ingress) {
869 ingress.Spec.Rules = []networking.IngressRule{{
870 Host: "foo.bar.com",
871 IngressRuleValue: networking.IngressRuleValue{
872 HTTP: &networking.HTTPIngressRuleValue{
873 Paths: []networking.HTTPIngressPath{{
874 Path: "/([a-z0-9]*)",
875 PathType: &implementationPathType,
876 Backend: defaultBackend,
877 }},
878 },
879 },
880 }}
881 },
882 expectedErrs: field.ErrorList{},
883 },
884 "invalid regex path allowed (v1)": {
885 tweakIngress: func(ingress *networking.Ingress) {
886 ingress.Spec.Rules = []networking.IngressRule{{
887 Host: "foo.bar.com",
888 IngressRuleValue: networking.IngressRuleValue{
889 HTTP: &networking.HTTPIngressRuleValue{
890 Paths: []networking.HTTPIngressPath{{
891 Path: "/([a-z0-9]*)[",
892 PathType: &implementationPathType,
893 Backend: defaultBackend,
894 }},
895 },
896 },
897 }}
898 },
899 expectedErrs: field.ErrorList{},
900 },
901 "Spec.Backend.Resource field allowed on create": {
902 tweakIngress: func(ingress *networking.Ingress) {
903 ingress.Spec.DefaultBackend = &networking.IngressBackend{
904 Resource: resourceBackend}
905 },
906 expectedErrs: field.ErrorList{},
907 },
908 "Paths.Backend.Resource field allowed on create": {
909 tweakIngress: func(ingress *networking.Ingress) {
910 ingress.Spec.Rules = []networking.IngressRule{{
911 Host: "foo.bar.com",
912 IngressRuleValue: networking.IngressRuleValue{
913 HTTP: &networking.HTTPIngressRuleValue{
914 Paths: []networking.HTTPIngressPath{{
915 Path: "/([a-z0-9]*)",
916 PathType: &implementationPathType,
917 Backend: networking.IngressBackend{
918 Resource: resourceBackend},
919 }},
920 },
921 },
922 }}
923 },
924 expectedErrs: field.ErrorList{},
925 },
926 "valid secret": {
927 tweakIngress: func(ingress *networking.Ingress) {
928 ingress.Spec.TLS = []networking.IngressTLS{{SecretName: "valid"}}
929 },
930 },
931 "invalid secret": {
932 tweakIngress: func(ingress *networking.Ingress) {
933 ingress.Spec.TLS = []networking.IngressTLS{{SecretName: "invalid name"}}
934 },
935 expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec").Child("tls").Index(0).Child("secretName"), "invalid name", `a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')`)},
936 },
937 "valid rules with wildcard host": {
938 tweakIngress: func(ingress *networking.Ingress) {
939 ingress.Spec.TLS = []networking.IngressTLS{{Hosts: []string{"*.bar.com"}}}
940 ingress.Spec.Rules = []networking.IngressRule{{
941 Host: "*.foo.com",
942 IngressRuleValue: networking.IngressRuleValue{
943 HTTP: &networking.HTTPIngressRuleValue{
944 Paths: []networking.HTTPIngressPath{{
945 Path: "/foo",
946 PathType: &exactPathType,
947 Backend: defaultBackend,
948 }},
949 },
950 },
951 }}
952 },
953 },
954 "invalid rules with wildcard host": {
955 tweakIngress: func(ingress *networking.Ingress) {
956 ingress.Spec.TLS = []networking.IngressTLS{{Hosts: []string{"*.bar.com"}}}
957 ingress.Spec.Rules = []networking.IngressRule{{
958 Host: "*.foo.com",
959 IngressRuleValue: networking.IngressRuleValue{
960 HTTP: &networking.HTTPIngressRuleValue{
961 Paths: []networking.HTTPIngressPath{{
962 Path: "foo",
963 PathType: &exactPathType,
964 Backend: defaultBackend,
965 }},
966 },
967 },
968 }}
969 },
970 expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec").Child("rules").Index(0).Child("http").Child("paths").Index(0).Child("path"), "foo", `must be an absolute path`)},
971 },
972 }
973
974 for name, testCase := range testCases {
975 t.Run(name, func(t *testing.T) {
976 newIngress := baseIngress.DeepCopy()
977 testCase.tweakIngress(newIngress)
978 errs := ValidateIngressCreate(newIngress)
979 if len(errs) != len(testCase.expectedErrs) {
980 t.Fatalf("Expected %d errors, got %d (%+v)", len(testCase.expectedErrs), len(errs), errs)
981 }
982
983 for i, err := range errs {
984 if err.Error() != testCase.expectedErrs[i].Error() {
985 t.Fatalf("Expected error: %v, got %v", testCase.expectedErrs[i].Error(), err.Error())
986 }
987 }
988 })
989 }
990 }
991
992 func TestValidateIngressUpdate(t *testing.T) {
993 implementationPathType := networking.PathTypeImplementationSpecific
994 exactPathType := networking.PathTypeExact
995 serviceBackend := &networking.IngressServiceBackend{
996 Name: "defaultbackend",
997 Port: networking.ServiceBackendPort{
998 Number: 80,
999 },
1000 }
1001 defaultBackend := networking.IngressBackend{
1002 Service: serviceBackend,
1003 }
1004 resourceBackend := &api.TypedLocalObjectReference{
1005 APIGroup: utilpointer.String("example.com"),
1006 Kind: "foo",
1007 Name: "bar",
1008 }
1009 baseIngress := networking.Ingress{
1010 ObjectMeta: metav1.ObjectMeta{
1011 Name: "test123",
1012 Namespace: "test123",
1013 ResourceVersion: "1234",
1014 },
1015 Spec: networking.IngressSpec{
1016 DefaultBackend: &defaultBackend,
1017 },
1018 }
1019
1020 testCases := map[string]struct {
1021 tweakIngresses func(newIngress, oldIngress *networking.Ingress)
1022 expectedErrs field.ErrorList
1023 }{
1024 "class field set": {
1025 tweakIngresses: func(newIngress, oldIngress *networking.Ingress) {
1026 newIngress.Spec.IngressClassName = utilpointer.String("bar")
1027 },
1028 expectedErrs: field.ErrorList{},
1029 },
1030 "class annotation set": {
1031 tweakIngresses: func(newIngress, oldIngress *networking.Ingress) {
1032 newIngress.Annotations = map[string]string{annotationIngressClass: "foo"}
1033 },
1034 expectedErrs: field.ErrorList{},
1035 },
1036 "class field and annotation set": {
1037 tweakIngresses: func(newIngress, oldIngress *networking.Ingress) {
1038 newIngress.Spec.IngressClassName = utilpointer.String("bar")
1039 newIngress.Annotations = map[string]string{annotationIngressClass: "foo"}
1040 },
1041 expectedErrs: field.ErrorList{},
1042 },
1043 "valid regex path -> valid regex path": {
1044 tweakIngresses: func(newIngress, oldIngress *networking.Ingress) {
1045 oldIngress.Spec.Rules = []networking.IngressRule{{
1046 Host: "foo.bar.com",
1047 IngressRuleValue: networking.IngressRuleValue{
1048 HTTP: &networking.HTTPIngressRuleValue{
1049 Paths: []networking.HTTPIngressPath{{
1050 Path: "/([a-z0-9]*)",
1051 PathType: &implementationPathType,
1052 Backend: defaultBackend,
1053 }},
1054 },
1055 },
1056 }}
1057 newIngress.Spec.Rules = []networking.IngressRule{{
1058 Host: "foo.bar.com",
1059 IngressRuleValue: networking.IngressRuleValue{
1060 HTTP: &networking.HTTPIngressRuleValue{
1061 Paths: []networking.HTTPIngressPath{{
1062 Path: "/([a-z0-9%]*)",
1063 PathType: &implementationPathType,
1064 Backend: defaultBackend,
1065 }},
1066 },
1067 },
1068 }}
1069 },
1070 expectedErrs: field.ErrorList{},
1071 },
1072 "valid regex path -> invalid regex path": {
1073 tweakIngresses: func(newIngress, oldIngress *networking.Ingress) {
1074 oldIngress.Spec.Rules = []networking.IngressRule{{
1075 Host: "foo.bar.com",
1076 IngressRuleValue: networking.IngressRuleValue{
1077 HTTP: &networking.HTTPIngressRuleValue{
1078 Paths: []networking.HTTPIngressPath{{
1079 Path: "/([a-z0-9]*)",
1080 PathType: &implementationPathType,
1081 Backend: defaultBackend,
1082 }},
1083 },
1084 },
1085 }}
1086 newIngress.Spec.Rules = []networking.IngressRule{{
1087 Host: "foo.bar.com",
1088 IngressRuleValue: networking.IngressRuleValue{
1089 HTTP: &networking.HTTPIngressRuleValue{
1090 Paths: []networking.HTTPIngressPath{{
1091 Path: "/bar[",
1092 PathType: &implementationPathType,
1093 Backend: defaultBackend,
1094 }},
1095 },
1096 },
1097 }}
1098 },
1099 expectedErrs: field.ErrorList{},
1100 },
1101 "invalid regex path -> valid regex path": {
1102 tweakIngresses: func(newIngress, oldIngress *networking.Ingress) {
1103 oldIngress.Spec.Rules = []networking.IngressRule{{
1104 Host: "foo.bar.com",
1105 IngressRuleValue: networking.IngressRuleValue{
1106 HTTP: &networking.HTTPIngressRuleValue{
1107 Paths: []networking.HTTPIngressPath{{
1108 Path: "/bar[",
1109 PathType: &implementationPathType,
1110 Backend: defaultBackend,
1111 }},
1112 },
1113 },
1114 }}
1115 newIngress.Spec.Rules = []networking.IngressRule{{
1116 Host: "foo.bar.com",
1117 IngressRuleValue: networking.IngressRuleValue{
1118 HTTP: &networking.HTTPIngressRuleValue{
1119 Paths: []networking.HTTPIngressPath{{
1120 Path: "/([a-z0-9]*)",
1121 PathType: &implementationPathType,
1122 Backend: defaultBackend,
1123 }},
1124 },
1125 },
1126 }}
1127 },
1128 expectedErrs: field.ErrorList{},
1129 },
1130 "invalid regex path -> invalid regex path": {
1131 tweakIngresses: func(newIngress, oldIngress *networking.Ingress) {
1132 oldIngress.Spec.Rules = []networking.IngressRule{{
1133 Host: "foo.bar.com",
1134 IngressRuleValue: networking.IngressRuleValue{
1135 HTTP: &networking.HTTPIngressRuleValue{
1136 Paths: []networking.HTTPIngressPath{{
1137 Path: "/foo[",
1138 PathType: &implementationPathType,
1139 Backend: defaultBackend,
1140 }},
1141 },
1142 },
1143 }}
1144 newIngress.Spec.Rules = []networking.IngressRule{{
1145 Host: "foo.bar.com",
1146 IngressRuleValue: networking.IngressRuleValue{
1147 HTTP: &networking.HTTPIngressRuleValue{
1148 Paths: []networking.HTTPIngressPath{{
1149 Path: "/bar[",
1150 PathType: &implementationPathType,
1151 Backend: defaultBackend,
1152 }},
1153 },
1154 },
1155 }}
1156 },
1157 expectedErrs: field.ErrorList{},
1158 },
1159 "new Backend.Resource allowed on update": {
1160 tweakIngresses: func(newIngress, oldIngress *networking.Ingress) {
1161 oldIngress.Spec.DefaultBackend = &defaultBackend
1162 newIngress.Spec.DefaultBackend = &networking.IngressBackend{
1163 Resource: resourceBackend}
1164 },
1165 expectedErrs: field.ErrorList{},
1166 },
1167 "old DefaultBackend.Resource allowed on update": {
1168 tweakIngresses: func(newIngress, oldIngress *networking.Ingress) {
1169 oldIngress.Spec.DefaultBackend = &networking.IngressBackend{
1170 Resource: resourceBackend}
1171 newIngress.Spec.DefaultBackend = &networking.IngressBackend{
1172 Resource: resourceBackend}
1173 },
1174 expectedErrs: field.ErrorList{},
1175 },
1176 "changing spec.backend from resource -> no resource": {
1177 tweakIngresses: func(newIngress, oldIngress *networking.Ingress) {
1178 oldIngress.Spec.DefaultBackend = &networking.IngressBackend{
1179 Resource: resourceBackend}
1180 newIngress.Spec.DefaultBackend = &defaultBackend
1181 },
1182 expectedErrs: field.ErrorList{},
1183 },
1184 "changing path backend from resource -> no resource": {
1185 tweakIngresses: func(newIngress, oldIngress *networking.Ingress) {
1186 oldIngress.Spec.Rules = []networking.IngressRule{{
1187 Host: "foo.bar.com",
1188 IngressRuleValue: networking.IngressRuleValue{
1189 HTTP: &networking.HTTPIngressRuleValue{
1190 Paths: []networking.HTTPIngressPath{{
1191 Path: "/foo[",
1192 PathType: &implementationPathType,
1193 Backend: networking.IngressBackend{
1194 Resource: resourceBackend},
1195 }},
1196 },
1197 },
1198 }}
1199 newIngress.Spec.Rules = []networking.IngressRule{{
1200 Host: "foo.bar.com",
1201 IngressRuleValue: networking.IngressRuleValue{
1202 HTTP: &networking.HTTPIngressRuleValue{
1203 Paths: []networking.HTTPIngressPath{{
1204 Path: "/bar[",
1205 PathType: &implementationPathType,
1206 Backend: defaultBackend,
1207 }},
1208 },
1209 },
1210 }}
1211 },
1212 expectedErrs: field.ErrorList{},
1213 },
1214 "changing path backend from resource -> resource": {
1215 tweakIngresses: func(newIngress, oldIngress *networking.Ingress) {
1216 oldIngress.Spec.Rules = []networking.IngressRule{{
1217 Host: "foo.bar.com",
1218 IngressRuleValue: networking.IngressRuleValue{
1219 HTTP: &networking.HTTPIngressRuleValue{
1220 Paths: []networking.HTTPIngressPath{{
1221 Path: "/foo[",
1222 PathType: &implementationPathType,
1223 Backend: networking.IngressBackend{
1224 Resource: resourceBackend},
1225 }},
1226 },
1227 },
1228 }}
1229 newIngress.Spec.Rules = []networking.IngressRule{{
1230 Host: "foo.bar.com",
1231 IngressRuleValue: networking.IngressRuleValue{
1232 HTTP: &networking.HTTPIngressRuleValue{
1233 Paths: []networking.HTTPIngressPath{{
1234 Path: "/bar[",
1235 PathType: &implementationPathType,
1236 Backend: networking.IngressBackend{
1237 Resource: resourceBackend},
1238 }},
1239 },
1240 },
1241 }}
1242 },
1243 expectedErrs: field.ErrorList{},
1244 },
1245 "changing path backend from non-resource -> non-resource": {
1246 tweakIngresses: func(newIngress, oldIngress *networking.Ingress) {
1247 oldIngress.Spec.Rules = []networking.IngressRule{{
1248 Host: "foo.bar.com",
1249 IngressRuleValue: networking.IngressRuleValue{
1250 HTTP: &networking.HTTPIngressRuleValue{
1251 Paths: []networking.HTTPIngressPath{{
1252 Path: "/foo[",
1253 PathType: &implementationPathType,
1254 Backend: defaultBackend,
1255 }},
1256 },
1257 },
1258 }}
1259 newIngress.Spec.Rules = []networking.IngressRule{{
1260 Host: "foo.bar.com",
1261 IngressRuleValue: networking.IngressRuleValue{
1262 HTTP: &networking.HTTPIngressRuleValue{
1263 Paths: []networking.HTTPIngressPath{{
1264 Path: "/bar[",
1265 PathType: &implementationPathType,
1266 Backend: defaultBackend,
1267 }},
1268 },
1269 },
1270 }}
1271 },
1272 expectedErrs: field.ErrorList{},
1273 },
1274 "changing path backend from non-resource -> resource": {
1275 tweakIngresses: func(newIngress, oldIngress *networking.Ingress) {
1276 oldIngress.Spec.Rules = []networking.IngressRule{{
1277 Host: "foo.bar.com",
1278 IngressRuleValue: networking.IngressRuleValue{
1279 HTTP: &networking.HTTPIngressRuleValue{
1280 Paths: []networking.HTTPIngressPath{{
1281 Path: "/foo[",
1282 PathType: &implementationPathType,
1283 Backend: defaultBackend,
1284 }},
1285 },
1286 },
1287 }}
1288 newIngress.Spec.Rules = []networking.IngressRule{{
1289 Host: "foo.bar.com",
1290 IngressRuleValue: networking.IngressRuleValue{
1291 HTTP: &networking.HTTPIngressRuleValue{
1292 Paths: []networking.HTTPIngressPath{{
1293 Path: "/bar[",
1294 PathType: &implementationPathType,
1295 Backend: networking.IngressBackend{
1296 Resource: resourceBackend},
1297 }},
1298 },
1299 },
1300 }}
1301 },
1302 expectedErrs: field.ErrorList{},
1303 },
1304 "change valid secret -> invalid secret": {
1305 tweakIngresses: func(newIngress, oldIngress *networking.Ingress) {
1306 oldIngress.Spec.TLS = []networking.IngressTLS{{SecretName: "valid"}}
1307 newIngress.Spec.TLS = []networking.IngressTLS{{SecretName: "invalid name"}}
1308 },
1309 expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec").Child("tls").Index(0).Child("secretName"), "invalid name", `a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')`)},
1310 },
1311 "change invalid secret -> invalid secret": {
1312 tweakIngresses: func(newIngress, oldIngress *networking.Ingress) {
1313 oldIngress.Spec.TLS = []networking.IngressTLS{{SecretName: "invalid name 1"}}
1314 newIngress.Spec.TLS = []networking.IngressTLS{{SecretName: "invalid name 2"}}
1315 },
1316 },
1317 "change valid rules with wildcard host -> invalid rules": {
1318 tweakIngresses: func(newIngress, oldIngress *networking.Ingress) {
1319 oldIngress.Spec.TLS = []networking.IngressTLS{{Hosts: []string{"*.bar.com"}}}
1320 oldIngress.Spec.Rules = []networking.IngressRule{{
1321 Host: "*.foo.com",
1322 IngressRuleValue: networking.IngressRuleValue{
1323 HTTP: &networking.HTTPIngressRuleValue{
1324 Paths: []networking.HTTPIngressPath{{
1325 Path: "/foo",
1326 PathType: &exactPathType,
1327 Backend: defaultBackend,
1328 }},
1329 },
1330 },
1331 }}
1332 newIngress.Spec.TLS = []networking.IngressTLS{{Hosts: []string{"*.bar.com"}}}
1333 newIngress.Spec.Rules = []networking.IngressRule{{
1334 Host: "*.foo.com",
1335 IngressRuleValue: networking.IngressRuleValue{
1336 HTTP: &networking.HTTPIngressRuleValue{
1337 Paths: []networking.HTTPIngressPath{{
1338 Path: "foo",
1339 PathType: &exactPathType,
1340 Backend: defaultBackend,
1341 }},
1342 },
1343 },
1344 }}
1345 },
1346 expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec").Child("rules").Index(0).Child("http").Child("paths").Index(0).Child("path"), "foo", `must be an absolute path`)},
1347 },
1348 "change invalid rules with wildcard host -> invalid rules": {
1349 tweakIngresses: func(newIngress, oldIngress *networking.Ingress) {
1350 oldIngress.Spec.TLS = []networking.IngressTLS{{Hosts: []string{"*.bar.com"}}}
1351 oldIngress.Spec.Rules = []networking.IngressRule{{
1352 Host: "*.foo.com",
1353 IngressRuleValue: networking.IngressRuleValue{
1354 HTTP: &networking.HTTPIngressRuleValue{
1355 Paths: []networking.HTTPIngressPath{{
1356 Path: "foo",
1357 PathType: &exactPathType,
1358 Backend: defaultBackend,
1359 }},
1360 },
1361 },
1362 }}
1363 newIngress.Spec.TLS = []networking.IngressTLS{{Hosts: []string{"*.bar.com"}}}
1364 newIngress.Spec.Rules = []networking.IngressRule{{
1365 Host: "*.foo.com",
1366 IngressRuleValue: networking.IngressRuleValue{
1367 HTTP: &networking.HTTPIngressRuleValue{
1368 Paths: []networking.HTTPIngressPath{{
1369 Path: "bar",
1370 PathType: &exactPathType,
1371 Backend: defaultBackend,
1372 }},
1373 },
1374 },
1375 }}
1376 },
1377 },
1378 }
1379
1380 for name, testCase := range testCases {
1381 t.Run(name, func(t *testing.T) {
1382 newIngress := baseIngress.DeepCopy()
1383 oldIngress := baseIngress.DeepCopy()
1384 testCase.tweakIngresses(newIngress, oldIngress)
1385
1386 errs := ValidateIngressUpdate(newIngress, oldIngress)
1387
1388 if len(errs) != len(testCase.expectedErrs) {
1389 t.Fatalf("Expected %d errors, got %d (%+v)", len(testCase.expectedErrs), len(errs), errs)
1390 }
1391
1392 for i, err := range errs {
1393 if err.Error() != testCase.expectedErrs[i].Error() {
1394 t.Fatalf("Expected error: %v, got %v", testCase.expectedErrs[i].Error(), err.Error())
1395 }
1396 }
1397 })
1398 }
1399 }
1400
1401 type netIngressTweak func(ingressClass *networking.IngressClass)
1402
1403 func makeValidIngressClass(name, controller string, tweaks ...netIngressTweak) *networking.IngressClass {
1404 ingressClass := &networking.IngressClass{
1405 ObjectMeta: metav1.ObjectMeta{
1406 Name: name,
1407 },
1408 Spec: networking.IngressClassSpec{
1409 Controller: controller,
1410 },
1411 }
1412
1413 for _, fn := range tweaks {
1414 fn(ingressClass)
1415 }
1416 return ingressClass
1417 }
1418
1419 func makeIngressClassParams(apiGroup *string, kind, name string, scope, namespace *string) *networking.IngressClassParametersReference {
1420 return &networking.IngressClassParametersReference{
1421 APIGroup: apiGroup,
1422 Kind: kind,
1423 Name: name,
1424 Scope: scope,
1425 Namespace: namespace,
1426 }
1427 }
1428
1429 func TestValidateIngressClass(t *testing.T) {
1430 setParams := func(params *networking.IngressClassParametersReference) netIngressTweak {
1431 return func(ingressClass *networking.IngressClass) {
1432 ingressClass.Spec.Parameters = params
1433 }
1434 }
1435
1436 testCases := map[string]struct {
1437 ingressClass *networking.IngressClass
1438 expectedErrs field.ErrorList
1439 }{
1440 "valid name, valid controller": {
1441 ingressClass: makeValidIngressClass("test123", "foo.co/bar"),
1442 expectedErrs: field.ErrorList{},
1443 },
1444 "invalid name, valid controller": {
1445 ingressClass: makeValidIngressClass("test*123", "foo.co/bar"),
1446 expectedErrs: field.ErrorList{field.Invalid(field.NewPath("metadata.name"), "test*123", "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')")},
1447 },
1448 "valid name, empty controller": {
1449 ingressClass: makeValidIngressClass("test123", ""),
1450 expectedErrs: field.ErrorList{field.Required(field.NewPath("spec.controller"), "")},
1451 },
1452 "valid name, controller max length": {
1453 ingressClass: makeValidIngressClass("test123", "foo.co/"+strings.Repeat("a", 243)),
1454 expectedErrs: field.ErrorList{},
1455 },
1456 "valid name, controller too long": {
1457 ingressClass: makeValidIngressClass("test123", "foo.co/"+strings.Repeat("a", 244)),
1458 expectedErrs: field.ErrorList{field.TooLong(field.NewPath("spec.controller"), "", 250)},
1459 },
1460 "valid name, valid controller, valid params": {
1461 ingressClass: makeValidIngressClass("test123", "foo.co/bar",
1462 setParams(makeIngressClassParams(utilpointer.String("example.com"), "foo", "bar", utilpointer.String("Cluster"), nil)),
1463 ),
1464 expectedErrs: field.ErrorList{},
1465 },
1466 "valid name, valid controller, invalid params (no kind)": {
1467 ingressClass: makeValidIngressClass("test123", "foo.co/bar",
1468 setParams(makeIngressClassParams(utilpointer.String("example.com"), "", "bar", utilpointer.String("Cluster"), nil)),
1469 ),
1470 expectedErrs: field.ErrorList{field.Required(field.NewPath("spec.parameters.kind"), "kind is required")},
1471 },
1472 "valid name, valid controller, invalid params (no name)": {
1473 ingressClass: makeValidIngressClass("test123", "foo.co/bar",
1474 setParams(makeIngressClassParams(utilpointer.String("example.com"), "foo", "", utilpointer.String("Cluster"), nil)),
1475 ),
1476 expectedErrs: field.ErrorList{field.Required(field.NewPath("spec.parameters.name"), "name is required")},
1477 },
1478 "valid name, valid controller, invalid params (bad kind)": {
1479 ingressClass: makeValidIngressClass("test123", "foo.co/bar",
1480 setParams(makeIngressClassParams(nil, "foo/", "bar", utilpointer.String("Cluster"), nil)),
1481 ),
1482 expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec.parameters.kind"), "foo/", "may not contain '/'")},
1483 },
1484 "valid name, valid controller, invalid params (bad scope)": {
1485 ingressClass: makeValidIngressClass("test123", "foo.co/bar",
1486 setParams(makeIngressClassParams(nil, "foo", "bar", utilpointer.String("bad-scope"), nil)),
1487 ),
1488 expectedErrs: field.ErrorList{field.NotSupported(field.NewPath("spec.parameters.scope"),
1489 "bad-scope", []string{"Cluster", "Namespace"})},
1490 },
1491 "valid name, valid controller, valid Namespace scope": {
1492 ingressClass: makeValidIngressClass("test123", "foo.co/bar",
1493 setParams(makeIngressClassParams(nil, "foo", "bar", utilpointer.String("Namespace"), utilpointer.String("foo-ns"))),
1494 ),
1495 expectedErrs: field.ErrorList{},
1496 },
1497 "valid name, valid controller, valid scope, invalid namespace": {
1498 ingressClass: makeValidIngressClass("test123", "foo.co/bar",
1499 setParams(makeIngressClassParams(nil, "foo", "bar", utilpointer.String("Namespace"), utilpointer.String("foo_ns"))),
1500 ),
1501 expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec.parameters.namespace"), "foo_ns",
1502 "a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-',"+
1503 " and must start and end with an alphanumeric character (e.g. 'my-name', or '123-abc', "+
1504 "regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?')")},
1505 },
1506 "valid name, valid controller, valid Cluster scope": {
1507 ingressClass: makeValidIngressClass("test123", "foo.co/bar",
1508 setParams(makeIngressClassParams(nil, "foo", "bar", utilpointer.String("Cluster"), nil)),
1509 ),
1510 expectedErrs: field.ErrorList{},
1511 },
1512 "valid name, valid controller, invalid scope": {
1513 ingressClass: makeValidIngressClass("test123", "foo.co/bar",
1514 setParams(makeIngressClassParams(nil, "foo", "bar", nil, utilpointer.String("foo_ns"))),
1515 ),
1516 expectedErrs: field.ErrorList{
1517 field.Required(field.NewPath("spec.parameters.scope"), ""),
1518 },
1519 },
1520 "namespace not set when scope is Namespace": {
1521 ingressClass: makeValidIngressClass("test123", "foo.co/bar",
1522 setParams(makeIngressClassParams(nil, "foo", "bar", utilpointer.String("Namespace"), nil)),
1523 ),
1524 expectedErrs: field.ErrorList{field.Required(field.NewPath("spec.parameters.namespace"),
1525 "`parameters.scope` is set to 'Namespace'")},
1526 },
1527 "namespace is forbidden when scope is Cluster": {
1528 ingressClass: makeValidIngressClass("test123", "foo.co/bar",
1529 setParams(makeIngressClassParams(nil, "foo", "bar", utilpointer.String("Cluster"), utilpointer.String("foo-ns"))),
1530 ),
1531 expectedErrs: field.ErrorList{field.Forbidden(field.NewPath("spec.parameters.namespace"),
1532 "`parameters.scope` is set to 'Cluster'")},
1533 },
1534 "empty namespace is forbidden when scope is Cluster": {
1535 ingressClass: makeValidIngressClass("test123", "foo.co/bar",
1536 setParams(makeIngressClassParams(nil, "foo", "bar", utilpointer.String("Cluster"), utilpointer.String(""))),
1537 ),
1538 expectedErrs: field.ErrorList{field.Forbidden(field.NewPath("spec.parameters.namespace"),
1539 "`parameters.scope` is set to 'Cluster'")},
1540 },
1541 }
1542
1543 for name, testCase := range testCases {
1544 t.Run(name, func(t *testing.T) {
1545 errs := ValidateIngressClass(testCase.ingressClass)
1546
1547 if len(errs) != len(testCase.expectedErrs) {
1548 t.Fatalf("Expected %d errors, got %d (%+v)", len(testCase.expectedErrs), len(errs), errs)
1549 }
1550
1551 for i, err := range errs {
1552 if err.Error() != testCase.expectedErrs[i].Error() {
1553 t.Fatalf("Expected error: %v, got %v", testCase.expectedErrs[i].Error(), err.Error())
1554 }
1555 }
1556 })
1557 }
1558 }
1559
1560 func TestValidateIngressClassUpdate(t *testing.T) {
1561 setResourceVersion := func(version string) netIngressTweak {
1562 return func(ingressClass *networking.IngressClass) {
1563 ingressClass.ObjectMeta.ResourceVersion = version
1564 }
1565 }
1566
1567 setParams := func(params *networking.IngressClassParametersReference) netIngressTweak {
1568 return func(ingressClass *networking.IngressClass) {
1569 ingressClass.Spec.Parameters = params
1570 }
1571 }
1572
1573 testCases := map[string]struct {
1574 newIngressClass *networking.IngressClass
1575 oldIngressClass *networking.IngressClass
1576 expectedErrs field.ErrorList
1577 }{
1578 "name change": {
1579 newIngressClass: makeValidIngressClass("test123", "foo.co/bar", setResourceVersion("2")),
1580 oldIngressClass: makeValidIngressClass("test123", "foo.co/different"),
1581 expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec").Child("controller"), "foo.co/bar", apimachineryvalidation.FieldImmutableErrorMsg)},
1582 },
1583 "parameters change": {
1584 newIngressClass: makeValidIngressClass("test123", "foo.co/bar",
1585 setResourceVersion("2"),
1586 setParams(
1587 makeIngressClassParams(utilpointer.String("v1"), "ConfigMap", "foo", utilpointer.String("Namespace"), utilpointer.String("bar")),
1588 ),
1589 ),
1590 oldIngressClass: makeValidIngressClass("test123", "foo.co/bar"),
1591 expectedErrs: field.ErrorList{},
1592 },
1593 }
1594
1595 for name, testCase := range testCases {
1596 t.Run(name, func(t *testing.T) {
1597 errs := ValidateIngressClassUpdate(testCase.newIngressClass, testCase.oldIngressClass)
1598
1599 if len(errs) != len(testCase.expectedErrs) {
1600 t.Fatalf("Expected %d errors, got %d (%+v)", len(testCase.expectedErrs), len(errs), errs)
1601 }
1602
1603 for i, err := range errs {
1604 if err.Error() != testCase.expectedErrs[i].Error() {
1605 t.Fatalf("Expected error: %v, got %v", testCase.expectedErrs[i].Error(), err.Error())
1606 }
1607 }
1608 })
1609 }
1610 }
1611
1612 func TestValidateIngressTLS(t *testing.T) {
1613 pathTypeImplementationSpecific := networking.PathTypeImplementationSpecific
1614 serviceBackend := &networking.IngressServiceBackend{
1615 Name: "defaultbackend",
1616 Port: networking.ServiceBackendPort{
1617 Number: 80,
1618 },
1619 }
1620 defaultBackend := networking.IngressBackend{
1621 Service: serviceBackend,
1622 }
1623 newValid := func() networking.Ingress {
1624 return networking.Ingress{
1625 ObjectMeta: metav1.ObjectMeta{
1626 Name: "foo",
1627 Namespace: metav1.NamespaceDefault,
1628 },
1629 Spec: networking.IngressSpec{
1630 DefaultBackend: &defaultBackend,
1631 Rules: []networking.IngressRule{{
1632 Host: "foo.bar.com",
1633 IngressRuleValue: networking.IngressRuleValue{
1634 HTTP: &networking.HTTPIngressRuleValue{
1635 Paths: []networking.HTTPIngressPath{{
1636 Path: "/foo",
1637 PathType: &pathTypeImplementationSpecific,
1638 Backend: defaultBackend,
1639 }},
1640 },
1641 },
1642 }},
1643 },
1644 Status: networking.IngressStatus{
1645 LoadBalancer: networking.IngressLoadBalancerStatus{
1646 Ingress: []networking.IngressLoadBalancerIngress{
1647 {IP: "127.0.0.1"},
1648 },
1649 },
1650 },
1651 }
1652 }
1653
1654 errorCases := map[string]networking.Ingress{}
1655
1656 wildcardHost := "foo.*.bar.com"
1657 badWildcardTLS := newValid()
1658 badWildcardTLS.Spec.Rules[0].Host = "*.foo.bar.com"
1659 badWildcardTLS.Spec.TLS = []networking.IngressTLS{{
1660 Hosts: []string{wildcardHost},
1661 }}
1662 badWildcardTLSErr := fmt.Sprintf("spec.tls[0].hosts[0]: Invalid value: '%v'", wildcardHost)
1663 errorCases[badWildcardTLSErr] = badWildcardTLS
1664
1665 for k, v := range errorCases {
1666 errs := validateIngress(&v, IngressValidationOptions{})
1667 if len(errs) == 0 {
1668 t.Errorf("expected failure for %q", k)
1669 } else {
1670 s := strings.Split(k, ":")
1671 err := errs[0]
1672 if err.Field != s[0] || !strings.Contains(err.Error(), s[1]) {
1673 t.Errorf("unexpected error: %q, expected: %q", err, k)
1674 }
1675 }
1676 }
1677
1678
1679 validCases := map[string]networking.Ingress{}
1680 wildHost := "*.bar.com"
1681 goodWildcardTLS := newValid()
1682 goodWildcardTLS.Spec.Rules[0].Host = "*.bar.com"
1683 goodWildcardTLS.Spec.TLS = []networking.IngressTLS{{
1684 Hosts: []string{wildHost},
1685 }}
1686 validCases[fmt.Sprintf("spec.tls[0].hosts: Valid value: '%v'", wildHost)] = goodWildcardTLS
1687 for k, v := range validCases {
1688 errs := validateIngress(&v, IngressValidationOptions{})
1689 if len(errs) != 0 {
1690 t.Errorf("expected success for %q", k)
1691 }
1692 }
1693 }
1694
1695
1696
1697
1698 func TestValidateEmptyIngressTLS(t *testing.T) {
1699 pathTypeImplementationSpecific := networking.PathTypeImplementationSpecific
1700 serviceBackend := &networking.IngressServiceBackend{
1701 Name: "defaultbackend",
1702 Port: networking.ServiceBackendPort{
1703 Number: 443,
1704 },
1705 }
1706 defaultBackend := networking.IngressBackend{
1707 Service: serviceBackend,
1708 }
1709 newValid := func() networking.Ingress {
1710 return networking.Ingress{
1711 ObjectMeta: metav1.ObjectMeta{
1712 Name: "foo",
1713 Namespace: metav1.NamespaceDefault,
1714 },
1715 Spec: networking.IngressSpec{
1716 Rules: []networking.IngressRule{{
1717 Host: "foo.bar.com",
1718 IngressRuleValue: networking.IngressRuleValue{
1719 HTTP: &networking.HTTPIngressRuleValue{
1720 Paths: []networking.HTTPIngressPath{{
1721 PathType: &pathTypeImplementationSpecific,
1722 Backend: defaultBackend,
1723 }},
1724 },
1725 },
1726 }},
1727 },
1728 }
1729 }
1730
1731 validCases := map[string]networking.Ingress{}
1732 goodEmptyTLS := newValid()
1733 goodEmptyTLS.Spec.TLS = []networking.IngressTLS{
1734 {},
1735 }
1736 validCases[fmt.Sprintf("spec.tls[0]: Valid value: %v", goodEmptyTLS.Spec.TLS[0])] = goodEmptyTLS
1737 goodEmptyHosts := newValid()
1738 goodEmptyHosts.Spec.TLS = []networking.IngressTLS{{
1739 Hosts: []string{},
1740 }}
1741 validCases[fmt.Sprintf("spec.tls[0]: Valid value: %v", goodEmptyHosts.Spec.TLS[0])] = goodEmptyHosts
1742 for k, v := range validCases {
1743 errs := validateIngress(&v, IngressValidationOptions{})
1744 if len(errs) != 0 {
1745 t.Errorf("expected success for %q", k)
1746 }
1747 }
1748 }
1749
1750 func TestValidateIngressStatusUpdate(t *testing.T) {
1751 serviceBackend := &networking.IngressServiceBackend{
1752 Name: "defaultbackend",
1753 Port: networking.ServiceBackendPort{
1754 Number: 80,
1755 },
1756 }
1757 defaultBackend := networking.IngressBackend{
1758 Service: serviceBackend,
1759 }
1760
1761 newValid := func() networking.Ingress {
1762 return networking.Ingress{
1763 ObjectMeta: metav1.ObjectMeta{
1764 Name: "foo",
1765 Namespace: metav1.NamespaceDefault,
1766 ResourceVersion: "9",
1767 },
1768 Spec: networking.IngressSpec{
1769 DefaultBackend: &defaultBackend,
1770 Rules: []networking.IngressRule{{
1771 Host: "foo.bar.com",
1772 IngressRuleValue: networking.IngressRuleValue{
1773 HTTP: &networking.HTTPIngressRuleValue{
1774 Paths: []networking.HTTPIngressPath{{
1775 Path: "/foo",
1776 Backend: defaultBackend,
1777 }},
1778 },
1779 },
1780 }},
1781 },
1782 Status: networking.IngressStatus{
1783 LoadBalancer: networking.IngressLoadBalancerStatus{
1784 Ingress: []networking.IngressLoadBalancerIngress{
1785 {IP: "127.0.0.1", Hostname: "foo.bar.com"},
1786 },
1787 },
1788 },
1789 }
1790 }
1791 oldValue := newValid()
1792 newValue := newValid()
1793 newValue.Status = networking.IngressStatus{
1794 LoadBalancer: networking.IngressLoadBalancerStatus{
1795 Ingress: []networking.IngressLoadBalancerIngress{
1796 {IP: "127.0.0.2", Hostname: "foo.com"},
1797 },
1798 },
1799 }
1800 invalidIP := newValid()
1801 invalidIP.Status = networking.IngressStatus{
1802 LoadBalancer: networking.IngressLoadBalancerStatus{
1803 Ingress: []networking.IngressLoadBalancerIngress{
1804 {IP: "abcd", Hostname: "foo.com"},
1805 },
1806 },
1807 }
1808 invalidHostname := newValid()
1809 invalidHostname.Status = networking.IngressStatus{
1810 LoadBalancer: networking.IngressLoadBalancerStatus{
1811 Ingress: []networking.IngressLoadBalancerIngress{
1812 {IP: "127.0.0.1", Hostname: "127.0.0.1"},
1813 },
1814 },
1815 }
1816
1817 errs := ValidateIngressStatusUpdate(&newValue, &oldValue)
1818 if len(errs) != 0 {
1819 t.Errorf("Unexpected error %v", errs)
1820 }
1821
1822 errorCases := map[string]networking.Ingress{
1823 "status.loadBalancer.ingress[0].ip: Invalid value": invalidIP,
1824 "status.loadBalancer.ingress[0].hostname: Invalid value": invalidHostname,
1825 }
1826 for k, v := range errorCases {
1827 errs := ValidateIngressStatusUpdate(&v, &oldValue)
1828 if len(errs) == 0 {
1829 t.Errorf("expected failure for %s", k)
1830 } else {
1831 s := strings.Split(k, ":")
1832 err := errs[0]
1833 if err.Field != s[0] || !strings.Contains(err.Error(), s[1]) {
1834 t.Errorf("unexpected error: %q, expected: %q", err, k)
1835 }
1836 }
1837 }
1838 }
1839
1840 func TestValidateIPAddress(t *testing.T) {
1841 testCases := map[string]struct {
1842 expectedErrors int
1843 ipAddress *networking.IPAddress
1844 }{
1845 "empty-ipaddress-bad-name": {
1846 expectedErrors: 1,
1847 ipAddress: &networking.IPAddress{
1848 ObjectMeta: metav1.ObjectMeta{
1849 Name: "test-name",
1850 },
1851 Spec: networking.IPAddressSpec{
1852 ParentRef: &networking.ParentReference{
1853 Group: "",
1854 Resource: "services",
1855 Name: "foo",
1856 Namespace: "bar",
1857 },
1858 },
1859 },
1860 },
1861 "empty-ipaddress-bad-name-no-parent-reference": {
1862 expectedErrors: 2,
1863 ipAddress: &networking.IPAddress{
1864 ObjectMeta: metav1.ObjectMeta{
1865 Name: "test-name",
1866 },
1867 },
1868 },
1869
1870 "good-ipaddress": {
1871 expectedErrors: 0,
1872 ipAddress: &networking.IPAddress{
1873 ObjectMeta: metav1.ObjectMeta{
1874 Name: "192.168.1.1",
1875 },
1876 Spec: networking.IPAddressSpec{
1877 ParentRef: &networking.ParentReference{
1878 Group: "",
1879 Resource: "services",
1880 Name: "foo",
1881 Namespace: "bar",
1882 },
1883 },
1884 },
1885 },
1886 "good-ipaddress-gateway": {
1887 expectedErrors: 0,
1888 ipAddress: &networking.IPAddress{
1889 ObjectMeta: metav1.ObjectMeta{
1890 Name: "192.168.1.1",
1891 },
1892 Spec: networking.IPAddressSpec{
1893 ParentRef: &networking.ParentReference{
1894 Group: "gateway.networking.k8s.io",
1895 Resource: "gateway",
1896 Name: "foo",
1897 Namespace: "bar",
1898 },
1899 },
1900 },
1901 },
1902 "good-ipv6address": {
1903 expectedErrors: 0,
1904 ipAddress: &networking.IPAddress{
1905 ObjectMeta: metav1.ObjectMeta{
1906 Name: "2001:4860:4860::8888",
1907 },
1908 Spec: networking.IPAddressSpec{
1909 ParentRef: &networking.ParentReference{
1910 Group: "",
1911 Resource: "services",
1912 Name: "foo",
1913 Namespace: "bar",
1914 },
1915 },
1916 },
1917 },
1918 "non-canonica-ipv6address": {
1919 expectedErrors: 1,
1920 ipAddress: &networking.IPAddress{
1921 ObjectMeta: metav1.ObjectMeta{
1922 Name: "2001:4860:4860:0::8888",
1923 },
1924 Spec: networking.IPAddressSpec{
1925 ParentRef: &networking.ParentReference{
1926 Group: "",
1927 Resource: "services",
1928 Name: "foo",
1929 Namespace: "bar",
1930 },
1931 },
1932 },
1933 },
1934 "missing-ipaddress-reference": {
1935 expectedErrors: 1,
1936 ipAddress: &networking.IPAddress{
1937 ObjectMeta: metav1.ObjectMeta{
1938 Name: "192.168.1.1",
1939 },
1940 },
1941 },
1942 "wrong-ipaddress-reference": {
1943 expectedErrors: 1,
1944 ipAddress: &networking.IPAddress{
1945 ObjectMeta: metav1.ObjectMeta{
1946 Name: "192.168.1.1",
1947 },
1948 Spec: networking.IPAddressSpec{
1949 ParentRef: &networking.ParentReference{
1950 Group: "custom.resource.com",
1951 Resource: "services",
1952 Name: "foo$%&",
1953 Namespace: "",
1954 },
1955 },
1956 },
1957 },
1958 "wrong-ipaddress-reference-multiple-errors": {
1959 expectedErrors: 4,
1960 ipAddress: &networking.IPAddress{
1961 ObjectMeta: metav1.ObjectMeta{
1962 Name: "192.168.1.1",
1963 },
1964 Spec: networking.IPAddressSpec{
1965 ParentRef: &networking.ParentReference{
1966 Group: ".cust@m.resource.com",
1967 Resource: "",
1968 Name: "",
1969 Namespace: "bar$$$$$%@",
1970 },
1971 },
1972 },
1973 },
1974 }
1975
1976 for name, testCase := range testCases {
1977 t.Run(name, func(t *testing.T) {
1978 errs := ValidateIPAddress(testCase.ipAddress)
1979 if len(errs) != testCase.expectedErrors {
1980 t.Errorf("Expected %d errors, got %d errors: %v", testCase.expectedErrors, len(errs), errs)
1981 }
1982 })
1983 }
1984 }
1985
1986 func TestValidateIPAddressUpdate(t *testing.T) {
1987 old := &networking.IPAddress{
1988 ObjectMeta: metav1.ObjectMeta{
1989 Name: "192.168.1.1",
1990 ResourceVersion: "1",
1991 },
1992 Spec: networking.IPAddressSpec{
1993 ParentRef: &networking.ParentReference{
1994 Group: "custom.resource.com",
1995 Resource: "services",
1996 Name: "foo",
1997 Namespace: "bar",
1998 },
1999 },
2000 }
2001
2002 testCases := []struct {
2003 name string
2004 new func(svc *networking.IPAddress) *networking.IPAddress
2005 expectErr bool
2006 }{{
2007 name: "Successful update, no changes",
2008 new: func(old *networking.IPAddress) *networking.IPAddress {
2009 out := old.DeepCopy()
2010 return out
2011 },
2012 expectErr: false,
2013 },
2014
2015 {
2016 name: "Failed update, update spec.ParentRef",
2017 new: func(svc *networking.IPAddress) *networking.IPAddress {
2018 out := svc.DeepCopy()
2019 out.Spec.ParentRef = &networking.ParentReference{
2020 Group: "custom.resource.com",
2021 Resource: "Gateway",
2022 Name: "foo",
2023 Namespace: "bar",
2024 }
2025
2026 return out
2027 }, expectErr: true,
2028 }, {
2029 name: "Failed update, delete spec.ParentRef",
2030 new: func(svc *networking.IPAddress) *networking.IPAddress {
2031 out := svc.DeepCopy()
2032 out.Spec.ParentRef = nil
2033 return out
2034 }, expectErr: true,
2035 },
2036 }
2037 for _, testCase := range testCases {
2038 t.Run(testCase.name, func(t *testing.T) {
2039 err := ValidateIPAddressUpdate(testCase.new(old), old)
2040 if !testCase.expectErr && err != nil {
2041 t.Errorf("ValidateIPAddressUpdate must be successful for test '%s', got %v", testCase.name, err)
2042 }
2043 if testCase.expectErr && err == nil {
2044 t.Errorf("ValidateIPAddressUpdate must return error for test: %s, but got nil", testCase.name)
2045 }
2046 })
2047 }
2048 }
2049
2050 func TestValidateServiceCIDR(t *testing.T) {
2051
2052 testCases := map[string]struct {
2053 expectedErrors int
2054 ipRange *networking.ServiceCIDR
2055 }{
2056 "empty-iprange": {
2057 expectedErrors: 1,
2058 ipRange: &networking.ServiceCIDR{
2059 ObjectMeta: metav1.ObjectMeta{
2060 Name: "test-name",
2061 },
2062 },
2063 },
2064 "three-ipranges": {
2065 expectedErrors: 1,
2066 ipRange: &networking.ServiceCIDR{
2067 ObjectMeta: metav1.ObjectMeta{
2068 Name: "test-name",
2069 },
2070 Spec: networking.ServiceCIDRSpec{
2071 CIDRs: []string{"192.168.0.0/24", "fd00::/64", "10.0.0.0/16"},
2072 },
2073 },
2074 },
2075 "good-iprange-ipv4": {
2076 expectedErrors: 0,
2077 ipRange: &networking.ServiceCIDR{
2078 ObjectMeta: metav1.ObjectMeta{
2079 Name: "test-name",
2080 },
2081 Spec: networking.ServiceCIDRSpec{
2082 CIDRs: []string{"192.168.0.0/24"},
2083 },
2084 },
2085 },
2086 "good-iprange-ipv6": {
2087 expectedErrors: 0,
2088 ipRange: &networking.ServiceCIDR{
2089 ObjectMeta: metav1.ObjectMeta{
2090 Name: "test-name",
2091 },
2092 Spec: networking.ServiceCIDRSpec{
2093 CIDRs: []string{"fd00:1234::/64"},
2094 },
2095 },
2096 },
2097 "good-iprange-ipv4-ipv6": {
2098 expectedErrors: 0,
2099 ipRange: &networking.ServiceCIDR{
2100 ObjectMeta: metav1.ObjectMeta{
2101 Name: "test-name",
2102 },
2103 Spec: networking.ServiceCIDRSpec{
2104 CIDRs: []string{"192.168.0.0/24", "fd00:1234::/64"},
2105 },
2106 },
2107 },
2108 "not-iprange-ipv4": {
2109 expectedErrors: 1,
2110 ipRange: &networking.ServiceCIDR{
2111 ObjectMeta: metav1.ObjectMeta{
2112 Name: "test-name",
2113 },
2114 Spec: networking.ServiceCIDRSpec{
2115 CIDRs: []string{"asdasdasd"},
2116 },
2117 },
2118 },
2119 "iponly-iprange-ipv4": {
2120 expectedErrors: 1,
2121 ipRange: &networking.ServiceCIDR{
2122 ObjectMeta: metav1.ObjectMeta{
2123 Name: "test-name",
2124 },
2125 Spec: networking.ServiceCIDRSpec{
2126 CIDRs: []string{"192.168.0.1"},
2127 },
2128 },
2129 },
2130 "badip-iprange-ipv4": {
2131 expectedErrors: 1,
2132 ipRange: &networking.ServiceCIDR{
2133 ObjectMeta: metav1.ObjectMeta{
2134 Name: "test-name",
2135 },
2136 Spec: networking.ServiceCIDRSpec{
2137 CIDRs: []string{"192.168.0.1/24"},
2138 },
2139 },
2140 },
2141 "badip-iprange-ipv6": {
2142 expectedErrors: 1,
2143 ipRange: &networking.ServiceCIDR{
2144 ObjectMeta: metav1.ObjectMeta{
2145 Name: "test-name",
2146 },
2147 Spec: networking.ServiceCIDRSpec{
2148 CIDRs: []string{"fd00:1234::2/64"},
2149 },
2150 },
2151 },
2152 "badip-iprange-caps-ipv6": {
2153 expectedErrors: 2,
2154 ipRange: &networking.ServiceCIDR{
2155 ObjectMeta: metav1.ObjectMeta{
2156 Name: "test-name",
2157 },
2158 Spec: networking.ServiceCIDRSpec{
2159 CIDRs: []string{"FD00:1234::2/64"},
2160 },
2161 },
2162 },
2163 "good-iprange-ipv4-bad-ipv6": {
2164 expectedErrors: 1,
2165 ipRange: &networking.ServiceCIDR{
2166 ObjectMeta: metav1.ObjectMeta{
2167 Name: "test-name",
2168 },
2169 Spec: networking.ServiceCIDRSpec{
2170 CIDRs: []string{"192.168.0.0/24", "FD00:1234::/64"},
2171 },
2172 },
2173 },
2174 "good-iprange-ipv6-bad-ipv4": {
2175 expectedErrors: 1,
2176 ipRange: &networking.ServiceCIDR{
2177 ObjectMeta: metav1.ObjectMeta{
2178 Name: "test-name",
2179 },
2180 Spec: networking.ServiceCIDRSpec{
2181 CIDRs: []string{"192.168.007.0/24", "fd00:1234::/64"},
2182 },
2183 },
2184 },
2185 }
2186
2187 for name, testCase := range testCases {
2188 t.Run(name, func(t *testing.T) {
2189 errs := ValidateServiceCIDR(testCase.ipRange)
2190 if len(errs) != testCase.expectedErrors {
2191 t.Errorf("Expected %d errors, got %d errors: %v", testCase.expectedErrors, len(errs), errs)
2192 }
2193 })
2194 }
2195 }
2196
2197 func TestValidateServiceCIDRUpdate(t *testing.T) {
2198 oldServiceCIDR := &networking.ServiceCIDR{
2199 ObjectMeta: metav1.ObjectMeta{
2200 Name: "mysvc",
2201 ResourceVersion: "1",
2202 },
2203 Spec: networking.ServiceCIDRSpec{
2204 CIDRs: []string{"192.168.0.0/24", "fd00:1234::/64"},
2205 },
2206 }
2207
2208 testCases := []struct {
2209 name string
2210 svc func(svc *networking.ServiceCIDR) *networking.ServiceCIDR
2211 expectErr bool
2212 }{
2213 {
2214 name: "Successful update, no changes",
2215 svc: func(svc *networking.ServiceCIDR) *networking.ServiceCIDR {
2216 out := svc.DeepCopy()
2217 return out
2218 },
2219 expectErr: false,
2220 },
2221
2222 {
2223 name: "Failed update, update spec.CIDRs single stack",
2224 svc: func(svc *networking.ServiceCIDR) *networking.ServiceCIDR {
2225 out := svc.DeepCopy()
2226 out.Spec.CIDRs = []string{"10.0.0.0/16"}
2227 return out
2228 }, expectErr: true,
2229 },
2230 {
2231 name: "Failed update, update spec.CIDRs dual stack",
2232 svc: func(svc *networking.ServiceCIDR) *networking.ServiceCIDR {
2233 out := svc.DeepCopy()
2234 out.Spec.CIDRs = []string{"10.0.0.0/24", "fd00:1234::/64"}
2235 return out
2236 }, expectErr: true,
2237 },
2238 }
2239 for _, testCase := range testCases {
2240 t.Run(testCase.name, func(t *testing.T) {
2241 err := ValidateServiceCIDRUpdate(testCase.svc(oldServiceCIDR), oldServiceCIDR)
2242 if !testCase.expectErr && err != nil {
2243 t.Errorf("ValidateServiceCIDRUpdate must be successful for test '%s', got %v", testCase.name, err)
2244 }
2245 if testCase.expectErr && err == nil {
2246 t.Errorf("ValidateServiceCIDRUpdate must return error for test: %s, but got nil", testCase.name)
2247 }
2248 })
2249 }
2250 }
2251
View as plain text