k8s.io/kubernetes@v1.29.3/pkg/apis/networking/validation/validation_test.go (about) 1 /* 2 Copyright 2014 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 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 // Tweaks used below. 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 // Success cases are expected to pass validation. 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 // Error cases are not expected to pass validation. 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 // invalid use cases 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 // Test for wildcard host and wildcard TLS 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 // TestValidateEmptyIngressTLS verifies that an empty TLS configuration can be 1696 // specified, which ingress controllers may interpret to mean that TLS should be 1697 // used with a default certificate that the ingress controller furnishes. 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 }