k8s.io/kubernetes@v1.29.3/pkg/apis/networking/validation/validation.go (about) 1 /* 2 Copyright 2017 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 "net/netip" 22 "strings" 23 24 apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation" 25 pathvalidation "k8s.io/apimachinery/pkg/api/validation/path" 26 unversionedvalidation "k8s.io/apimachinery/pkg/apis/meta/v1/validation" 27 "k8s.io/apimachinery/pkg/util/intstr" 28 "k8s.io/apimachinery/pkg/util/sets" 29 "k8s.io/apimachinery/pkg/util/validation" 30 "k8s.io/apimachinery/pkg/util/validation/field" 31 api "k8s.io/kubernetes/pkg/apis/core" 32 apivalidation "k8s.io/kubernetes/pkg/apis/core/validation" 33 "k8s.io/kubernetes/pkg/apis/networking" 34 netutils "k8s.io/utils/net" 35 utilpointer "k8s.io/utils/pointer" 36 ) 37 38 const ( 39 annotationIngressClass = "kubernetes.io/ingress.class" 40 maxLenIngressClassController = 250 41 ) 42 43 var ( 44 supportedPathTypes = sets.NewString( 45 string(networking.PathTypeExact), 46 string(networking.PathTypePrefix), 47 string(networking.PathTypeImplementationSpecific), 48 ) 49 invalidPathSequences = []string{"//", "/./", "/../", "%2f", "%2F"} 50 invalidPathSuffixes = []string{"/..", "/."} 51 52 supportedIngressClassParametersReferenceScopes = sets.NewString( 53 networking.IngressClassParametersReferenceScopeNamespace, 54 networking.IngressClassParametersReferenceScopeCluster, 55 ) 56 ) 57 58 type NetworkPolicyValidationOptions struct { 59 AllowInvalidLabelValueInSelector bool 60 } 61 62 // ValidateNetworkPolicyName can be used to check whether the given networkpolicy 63 // name is valid. 64 func ValidateNetworkPolicyName(name string, prefix bool) []string { 65 return apimachineryvalidation.NameIsDNSSubdomain(name, prefix) 66 } 67 68 // ValidateNetworkPolicyPort validates a NetworkPolicyPort 69 func ValidateNetworkPolicyPort(port *networking.NetworkPolicyPort, portPath *field.Path) field.ErrorList { 70 allErrs := field.ErrorList{} 71 if port.Protocol != nil && *port.Protocol != api.ProtocolTCP && *port.Protocol != api.ProtocolUDP && *port.Protocol != api.ProtocolSCTP { 72 allErrs = append(allErrs, field.NotSupported(portPath.Child("protocol"), *port.Protocol, []string{string(api.ProtocolTCP), string(api.ProtocolUDP), string(api.ProtocolSCTP)})) 73 } 74 if port.Port != nil { 75 if port.Port.Type == intstr.Int { 76 for _, msg := range validation.IsValidPortNum(int(port.Port.IntVal)) { 77 allErrs = append(allErrs, field.Invalid(portPath.Child("port"), port.Port.IntVal, msg)) 78 } 79 if port.EndPort != nil { 80 if *port.EndPort < port.Port.IntVal { 81 allErrs = append(allErrs, field.Invalid(portPath.Child("endPort"), port.Port.IntVal, "must be greater than or equal to `port`")) 82 } 83 for _, msg := range validation.IsValidPortNum(int(*port.EndPort)) { 84 allErrs = append(allErrs, field.Invalid(portPath.Child("endPort"), *port.EndPort, msg)) 85 } 86 } 87 } else { 88 if port.EndPort != nil { 89 allErrs = append(allErrs, field.Invalid(portPath.Child("endPort"), *port.EndPort, "may not be specified when `port` is non-numeric")) 90 } 91 for _, msg := range validation.IsValidPortName(port.Port.StrVal) { 92 allErrs = append(allErrs, field.Invalid(portPath.Child("port"), port.Port.StrVal, msg)) 93 } 94 } 95 } else { 96 if port.EndPort != nil { 97 allErrs = append(allErrs, field.Invalid(portPath.Child("endPort"), *port.EndPort, "may not be specified when `port` is not specified")) 98 } 99 } 100 101 return allErrs 102 } 103 104 // ValidateNetworkPolicyPeer validates a NetworkPolicyPeer 105 func ValidateNetworkPolicyPeer(peer *networking.NetworkPolicyPeer, opts NetworkPolicyValidationOptions, peerPath *field.Path) field.ErrorList { 106 allErrs := field.ErrorList{} 107 numPeers := 0 108 labelSelectorValidationOpts := unversionedvalidation.LabelSelectorValidationOptions{ 109 AllowInvalidLabelValueInSelector: opts.AllowInvalidLabelValueInSelector, 110 } 111 112 if peer.PodSelector != nil { 113 numPeers++ 114 allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(peer.PodSelector, labelSelectorValidationOpts, peerPath.Child("podSelector"))...) 115 } 116 if peer.NamespaceSelector != nil { 117 numPeers++ 118 allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(peer.NamespaceSelector, labelSelectorValidationOpts, peerPath.Child("namespaceSelector"))...) 119 } 120 if peer.IPBlock != nil { 121 numPeers++ 122 allErrs = append(allErrs, ValidateIPBlock(peer.IPBlock, peerPath.Child("ipBlock"))...) 123 } 124 125 if numPeers == 0 { 126 allErrs = append(allErrs, field.Required(peerPath, "must specify a peer")) 127 } else if numPeers > 1 && peer.IPBlock != nil { 128 allErrs = append(allErrs, field.Forbidden(peerPath, "may not specify both ipBlock and another peer")) 129 } 130 131 return allErrs 132 } 133 134 // ValidateNetworkPolicySpec tests if required fields in the networkpolicy spec are set. 135 func ValidateNetworkPolicySpec(spec *networking.NetworkPolicySpec, opts NetworkPolicyValidationOptions, fldPath *field.Path) field.ErrorList { 136 allErrs := field.ErrorList{} 137 labelSelectorValidationOpts := unversionedvalidation.LabelSelectorValidationOptions{ 138 AllowInvalidLabelValueInSelector: opts.AllowInvalidLabelValueInSelector, 139 } 140 allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector( 141 &spec.PodSelector, 142 labelSelectorValidationOpts, 143 fldPath.Child("podSelector"), 144 )...) 145 146 // Validate ingress rules. 147 for i, ingress := range spec.Ingress { 148 ingressPath := fldPath.Child("ingress").Index(i) 149 for i, port := range ingress.Ports { 150 portPath := ingressPath.Child("ports").Index(i) 151 allErrs = append(allErrs, ValidateNetworkPolicyPort(&port, portPath)...) 152 } 153 for i, from := range ingress.From { 154 fromPath := ingressPath.Child("from").Index(i) 155 allErrs = append(allErrs, ValidateNetworkPolicyPeer(&from, opts, fromPath)...) 156 } 157 } 158 // Validate egress rules 159 for i, egress := range spec.Egress { 160 egressPath := fldPath.Child("egress").Index(i) 161 for i, port := range egress.Ports { 162 portPath := egressPath.Child("ports").Index(i) 163 allErrs = append(allErrs, ValidateNetworkPolicyPort(&port, portPath)...) 164 } 165 for i, to := range egress.To { 166 toPath := egressPath.Child("to").Index(i) 167 allErrs = append(allErrs, ValidateNetworkPolicyPeer(&to, opts, toPath)...) 168 } 169 } 170 // Validate PolicyTypes 171 allowed := sets.NewString(string(networking.PolicyTypeIngress), string(networking.PolicyTypeEgress)) 172 if len(spec.PolicyTypes) > len(allowed) { 173 allErrs = append(allErrs, field.Invalid(fldPath.Child("policyTypes"), &spec.PolicyTypes, "may not specify more than two policyTypes")) 174 return allErrs 175 } 176 for i, pType := range spec.PolicyTypes { 177 policyPath := fldPath.Child("policyTypes").Index(i) 178 if !allowed.Has(string(pType)) { 179 allErrs = append(allErrs, field.NotSupported(policyPath, pType, []string{string(networking.PolicyTypeIngress), string(networking.PolicyTypeEgress)})) 180 } 181 } 182 return allErrs 183 } 184 185 // ValidateNetworkPolicy validates a networkpolicy. 186 func ValidateNetworkPolicy(np *networking.NetworkPolicy, opts NetworkPolicyValidationOptions) field.ErrorList { 187 allErrs := apivalidation.ValidateObjectMeta(&np.ObjectMeta, true, ValidateNetworkPolicyName, field.NewPath("metadata")) 188 allErrs = append(allErrs, ValidateNetworkPolicySpec(&np.Spec, opts, field.NewPath("spec"))...) 189 return allErrs 190 } 191 192 // ValidationOptionsForNetworking generates NetworkPolicyValidationOptions for Networking 193 func ValidationOptionsForNetworking(new, old *networking.NetworkPolicy) NetworkPolicyValidationOptions { 194 opts := NetworkPolicyValidationOptions{ 195 AllowInvalidLabelValueInSelector: false, 196 } 197 if old != nil { 198 labelSelectorValidationOpts := unversionedvalidation.LabelSelectorValidationOptions{ 199 AllowInvalidLabelValueInSelector: opts.AllowInvalidLabelValueInSelector, 200 } 201 if len(unversionedvalidation.ValidateLabelSelector(&old.Spec.PodSelector, labelSelectorValidationOpts, nil)) > 0 { 202 opts.AllowInvalidLabelValueInSelector = true 203 } 204 } 205 return opts 206 } 207 208 // ValidateNetworkPolicyUpdate tests if an update to a NetworkPolicy is valid. 209 func ValidateNetworkPolicyUpdate(update, old *networking.NetworkPolicy, opts NetworkPolicyValidationOptions) field.ErrorList { 210 allErrs := field.ErrorList{} 211 allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&update.ObjectMeta, &old.ObjectMeta, field.NewPath("metadata"))...) 212 allErrs = append(allErrs, ValidateNetworkPolicySpec(&update.Spec, opts, field.NewPath("spec"))...) 213 return allErrs 214 } 215 216 // ValidateIPBlock validates a cidr and the except fields of an IpBlock NetworkPolicyPeer 217 func ValidateIPBlock(ipb *networking.IPBlock, fldPath *field.Path) field.ErrorList { 218 allErrs := field.ErrorList{} 219 if len(ipb.CIDR) == 0 || ipb.CIDR == "" { 220 allErrs = append(allErrs, field.Required(fldPath.Child("cidr"), "")) 221 return allErrs 222 } 223 cidrIPNet, err := apivalidation.ValidateCIDR(ipb.CIDR) 224 if err != nil { 225 allErrs = append(allErrs, field.Invalid(fldPath.Child("cidr"), ipb.CIDR, "not a valid CIDR")) 226 return allErrs 227 } 228 exceptCIDR := ipb.Except 229 for i, exceptIP := range exceptCIDR { 230 exceptPath := fldPath.Child("except").Index(i) 231 exceptCIDR, err := apivalidation.ValidateCIDR(exceptIP) 232 if err != nil { 233 allErrs = append(allErrs, field.Invalid(exceptPath, exceptIP, "not a valid CIDR")) 234 return allErrs 235 } 236 cidrMaskLen, _ := cidrIPNet.Mask.Size() 237 exceptMaskLen, _ := exceptCIDR.Mask.Size() 238 if !cidrIPNet.Contains(exceptCIDR.IP) || cidrMaskLen >= exceptMaskLen { 239 allErrs = append(allErrs, field.Invalid(exceptPath, exceptIP, "must be a strict subset of `cidr`")) 240 } 241 } 242 return allErrs 243 } 244 245 // ValidateIngressName validates that the given name can be used as an Ingress 246 // name. 247 var ValidateIngressName = apimachineryvalidation.NameIsDNSSubdomain 248 249 // IngressValidationOptions cover beta to GA transitions for HTTP PathType 250 type IngressValidationOptions struct { 251 // AllowInvalidSecretName indicates whether spec.tls[*].secretName values that are not valid Secret names should be allowed 252 AllowInvalidSecretName bool 253 254 // AllowInvalidWildcardHostRule indicates whether invalid rule values are allowed in rules with wildcard hostnames 255 AllowInvalidWildcardHostRule bool 256 } 257 258 // ValidateIngress validates Ingresses on create and update. 259 func validateIngress(ingress *networking.Ingress, opts IngressValidationOptions) field.ErrorList { 260 allErrs := apivalidation.ValidateObjectMeta(&ingress.ObjectMeta, true, ValidateIngressName, field.NewPath("metadata")) 261 allErrs = append(allErrs, ValidateIngressSpec(&ingress.Spec, field.NewPath("spec"), opts)...) 262 return allErrs 263 } 264 265 // ValidateIngressCreate validates Ingresses on create. 266 func ValidateIngressCreate(ingress *networking.Ingress) field.ErrorList { 267 allErrs := field.ErrorList{} 268 opts := IngressValidationOptions{ 269 AllowInvalidSecretName: false, 270 AllowInvalidWildcardHostRule: false, 271 } 272 allErrs = append(allErrs, validateIngress(ingress, opts)...) 273 annotationVal, annotationIsSet := ingress.Annotations[annotationIngressClass] 274 if annotationIsSet && ingress.Spec.IngressClassName != nil && annotationVal != *ingress.Spec.IngressClassName { 275 annotationPath := field.NewPath("annotations").Child(annotationIngressClass) 276 allErrs = append(allErrs, field.Invalid(annotationPath, annotationVal, "must match `ingressClassName` when both are specified")) 277 } 278 return allErrs 279 } 280 281 // ValidateIngressUpdate validates ingresses on update. 282 func ValidateIngressUpdate(ingress, oldIngress *networking.Ingress) field.ErrorList { 283 allErrs := apivalidation.ValidateObjectMetaUpdate(&ingress.ObjectMeta, &oldIngress.ObjectMeta, field.NewPath("metadata")) 284 opts := IngressValidationOptions{ 285 AllowInvalidSecretName: allowInvalidSecretName(oldIngress), 286 AllowInvalidWildcardHostRule: allowInvalidWildcardHostRule(oldIngress), 287 } 288 289 allErrs = append(allErrs, validateIngress(ingress, opts)...) 290 return allErrs 291 } 292 293 func validateIngressTLS(spec *networking.IngressSpec, fldPath *field.Path, opts IngressValidationOptions) field.ErrorList { 294 allErrs := field.ErrorList{} 295 // TODO: Perform a more thorough validation of spec.TLS.Hosts that takes 296 // the wildcard spec from RFC 6125 into account. 297 for tlsIndex, itls := range spec.TLS { 298 for i, host := range itls.Hosts { 299 if strings.Contains(host, "*") { 300 for _, msg := range validation.IsWildcardDNS1123Subdomain(host) { 301 allErrs = append(allErrs, field.Invalid(fldPath.Index(tlsIndex).Child("hosts").Index(i), host, msg)) 302 } 303 continue 304 } 305 for _, msg := range validation.IsDNS1123Subdomain(host) { 306 allErrs = append(allErrs, field.Invalid(fldPath.Index(tlsIndex).Child("hosts").Index(i), host, msg)) 307 } 308 } 309 310 if !opts.AllowInvalidSecretName { 311 for _, msg := range validateTLSSecretName(itls.SecretName) { 312 allErrs = append(allErrs, field.Invalid(fldPath.Index(tlsIndex).Child("secretName"), itls.SecretName, msg)) 313 } 314 } 315 } 316 317 return allErrs 318 } 319 320 // ValidateIngressSpec tests if required fields in the IngressSpec are set. 321 func ValidateIngressSpec(spec *networking.IngressSpec, fldPath *field.Path, opts IngressValidationOptions) field.ErrorList { 322 allErrs := field.ErrorList{} 323 if len(spec.Rules) == 0 && spec.DefaultBackend == nil { 324 errMsg := fmt.Sprintf("either `%s` or `rules` must be specified", "defaultBackend") 325 allErrs = append(allErrs, field.Invalid(fldPath, spec.Rules, errMsg)) 326 } 327 if spec.DefaultBackend != nil { 328 allErrs = append(allErrs, validateIngressBackend(spec.DefaultBackend, fldPath.Child("defaultBackend"), opts)...) 329 } 330 if len(spec.Rules) > 0 { 331 allErrs = append(allErrs, validateIngressRules(spec.Rules, fldPath.Child("rules"), opts)...) 332 } 333 if len(spec.TLS) > 0 { 334 allErrs = append(allErrs, validateIngressTLS(spec, fldPath.Child("tls"), opts)...) 335 } 336 if spec.IngressClassName != nil { 337 for _, msg := range ValidateIngressClassName(*spec.IngressClassName, false) { 338 allErrs = append(allErrs, field.Invalid(fldPath.Child("ingressClassName"), *spec.IngressClassName, msg)) 339 } 340 } 341 return allErrs 342 } 343 344 // ValidateIngressStatusUpdate tests if required fields in the Ingress are set when updating status. 345 func ValidateIngressStatusUpdate(ingress, oldIngress *networking.Ingress) field.ErrorList { 346 allErrs := apivalidation.ValidateObjectMetaUpdate(&ingress.ObjectMeta, &oldIngress.ObjectMeta, field.NewPath("metadata")) 347 allErrs = append(allErrs, ValidateIngressLoadBalancerStatus(&ingress.Status.LoadBalancer, field.NewPath("status", "loadBalancer"))...) 348 return allErrs 349 } 350 351 // ValidateLIngressoadBalancerStatus validates required fields on an IngressLoadBalancerStatus 352 func ValidateIngressLoadBalancerStatus(status *networking.IngressLoadBalancerStatus, fldPath *field.Path) field.ErrorList { 353 allErrs := field.ErrorList{} 354 for i, ingress := range status.Ingress { 355 idxPath := fldPath.Child("ingress").Index(i) 356 if len(ingress.IP) > 0 { 357 if isIP := (netutils.ParseIPSloppy(ingress.IP) != nil); !isIP { 358 allErrs = append(allErrs, field.Invalid(idxPath.Child("ip"), ingress.IP, "must be a valid IP address")) 359 } 360 } 361 if len(ingress.Hostname) > 0 { 362 for _, msg := range validation.IsDNS1123Subdomain(ingress.Hostname) { 363 allErrs = append(allErrs, field.Invalid(idxPath.Child("hostname"), ingress.Hostname, msg)) 364 } 365 if isIP := (netutils.ParseIPSloppy(ingress.Hostname) != nil); isIP { 366 allErrs = append(allErrs, field.Invalid(idxPath.Child("hostname"), ingress.Hostname, "must be a DNS name, not an IP address")) 367 } 368 } 369 } 370 return allErrs 371 } 372 373 func validateIngressRules(ingressRules []networking.IngressRule, fldPath *field.Path, opts IngressValidationOptions) field.ErrorList { 374 allErrs := field.ErrorList{} 375 if len(ingressRules) == 0 { 376 return append(allErrs, field.Required(fldPath, "")) 377 } 378 for i, ih := range ingressRules { 379 wildcardHost := false 380 if len(ih.Host) > 0 { 381 if isIP := (netutils.ParseIPSloppy(ih.Host) != nil); isIP { 382 allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("host"), ih.Host, "must be a DNS name, not an IP address")) 383 } 384 // TODO: Ports and ips are allowed in the host part of a url 385 // according to RFC 3986, consider allowing them. 386 if strings.Contains(ih.Host, "*") { 387 for _, msg := range validation.IsWildcardDNS1123Subdomain(ih.Host) { 388 allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("host"), ih.Host, msg)) 389 } 390 wildcardHost = true 391 } else { 392 for _, msg := range validation.IsDNS1123Subdomain(ih.Host) { 393 allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("host"), ih.Host, msg)) 394 } 395 } 396 } 397 398 if !wildcardHost || !opts.AllowInvalidWildcardHostRule { 399 allErrs = append(allErrs, validateIngressRuleValue(&ih.IngressRuleValue, fldPath.Index(i), opts)...) 400 } 401 } 402 return allErrs 403 } 404 405 func validateIngressRuleValue(ingressRule *networking.IngressRuleValue, fldPath *field.Path, opts IngressValidationOptions) field.ErrorList { 406 allErrs := field.ErrorList{} 407 if ingressRule.HTTP != nil { 408 allErrs = append(allErrs, validateHTTPIngressRuleValue(ingressRule.HTTP, fldPath.Child("http"), opts)...) 409 } 410 return allErrs 411 } 412 413 func validateHTTPIngressRuleValue(httpIngressRuleValue *networking.HTTPIngressRuleValue, fldPath *field.Path, opts IngressValidationOptions) field.ErrorList { 414 allErrs := field.ErrorList{} 415 if len(httpIngressRuleValue.Paths) == 0 { 416 allErrs = append(allErrs, field.Required(fldPath.Child("paths"), "")) 417 } 418 for i, path := range httpIngressRuleValue.Paths { 419 allErrs = append(allErrs, validateHTTPIngressPath(&path, fldPath.Child("paths").Index(i), opts)...) 420 } 421 return allErrs 422 } 423 424 func validateHTTPIngressPath(path *networking.HTTPIngressPath, fldPath *field.Path, opts IngressValidationOptions) field.ErrorList { 425 allErrs := field.ErrorList{} 426 427 if path.PathType == nil { 428 return append(allErrs, field.Required(fldPath.Child("pathType"), "pathType must be specified")) 429 } 430 431 switch *path.PathType { 432 case networking.PathTypeExact, networking.PathTypePrefix: 433 if !strings.HasPrefix(path.Path, "/") { 434 allErrs = append(allErrs, field.Invalid(fldPath.Child("path"), path.Path, "must be an absolute path")) 435 } 436 if len(path.Path) > 0 { 437 for _, invalidSeq := range invalidPathSequences { 438 if strings.Contains(path.Path, invalidSeq) { 439 allErrs = append(allErrs, field.Invalid(fldPath.Child("path"), path.Path, fmt.Sprintf("must not contain '%s'", invalidSeq))) 440 } 441 } 442 443 for _, invalidSuff := range invalidPathSuffixes { 444 if strings.HasSuffix(path.Path, invalidSuff) { 445 allErrs = append(allErrs, field.Invalid(fldPath.Child("path"), path.Path, fmt.Sprintf("cannot end with '%s'", invalidSuff))) 446 } 447 } 448 } 449 case networking.PathTypeImplementationSpecific: 450 if len(path.Path) > 0 { 451 if !strings.HasPrefix(path.Path, "/") { 452 allErrs = append(allErrs, field.Invalid(fldPath.Child("path"), path.Path, "must be an absolute path")) 453 } 454 } 455 default: 456 allErrs = append(allErrs, field.NotSupported(fldPath.Child("pathType"), *path.PathType, supportedPathTypes.List())) 457 } 458 allErrs = append(allErrs, validateIngressBackend(&path.Backend, fldPath.Child("backend"), opts)...) 459 return allErrs 460 } 461 462 // validateIngressBackend tests if a given backend is valid. 463 func validateIngressBackend(backend *networking.IngressBackend, fldPath *field.Path, opts IngressValidationOptions) field.ErrorList { 464 allErrs := field.ErrorList{} 465 466 hasResourceBackend := backend.Resource != nil 467 hasServiceBackend := backend.Service != nil 468 469 switch { 470 case hasResourceBackend && hasServiceBackend: 471 return append(allErrs, field.Invalid(fldPath, "", "cannot set both resource and service backends")) 472 case hasResourceBackend: 473 allErrs = append(allErrs, validateIngressTypedLocalObjectReference(backend.Resource, fldPath.Child("resource"))...) 474 case hasServiceBackend: 475 476 if len(backend.Service.Name) == 0 { 477 allErrs = append(allErrs, field.Required(fldPath.Child("service", "name"), "")) 478 } else { 479 for _, msg := range apivalidation.ValidateServiceName(backend.Service.Name, false) { 480 allErrs = append(allErrs, field.Invalid(fldPath.Child("service", "name"), backend.Service.Name, msg)) 481 } 482 } 483 484 hasPortName := len(backend.Service.Port.Name) > 0 485 hasPortNumber := backend.Service.Port.Number != 0 486 if hasPortName && hasPortNumber { 487 allErrs = append(allErrs, field.Invalid(fldPath, "", "cannot set both port name & port number")) 488 } else if hasPortName { 489 for _, msg := range validation.IsValidPortName(backend.Service.Port.Name) { 490 allErrs = append(allErrs, field.Invalid(fldPath.Child("service", "port", "name"), backend.Service.Port.Name, msg)) 491 } 492 } else if hasPortNumber { 493 for _, msg := range validation.IsValidPortNum(int(backend.Service.Port.Number)) { 494 allErrs = append(allErrs, field.Invalid(fldPath.Child("service", "port", "number"), backend.Service.Port.Number, msg)) 495 } 496 } else { 497 allErrs = append(allErrs, field.Required(fldPath, "port name or number is required")) 498 } 499 default: 500 allErrs = append(allErrs, field.Invalid(fldPath, "", "resource or service backend is required")) 501 } 502 return allErrs 503 } 504 505 // ValidateIngressClassName validates that the given name can be used as an 506 // IngressClass name. 507 var ValidateIngressClassName = apimachineryvalidation.NameIsDNSSubdomain 508 509 // ValidateIngressClass ensures that IngressClass resources are valid. 510 func ValidateIngressClass(ingressClass *networking.IngressClass) field.ErrorList { 511 allErrs := apivalidation.ValidateObjectMeta(&ingressClass.ObjectMeta, false, ValidateIngressClassName, field.NewPath("metadata")) 512 allErrs = append(allErrs, validateIngressClassSpec(&ingressClass.Spec, field.NewPath("spec"))...) 513 return allErrs 514 } 515 516 // ValidateIngressClassUpdate ensures that IngressClass updates are valid. 517 func ValidateIngressClassUpdate(newIngressClass, oldIngressClass *networking.IngressClass) field.ErrorList { 518 allErrs := apivalidation.ValidateObjectMetaUpdate(&newIngressClass.ObjectMeta, &oldIngressClass.ObjectMeta, field.NewPath("metadata")) 519 allErrs = append(allErrs, validateIngressClassSpecUpdate(&newIngressClass.Spec, &oldIngressClass.Spec, field.NewPath("spec"))...) 520 allErrs = append(allErrs, ValidateIngressClass(newIngressClass)...) 521 return allErrs 522 } 523 524 // validateIngressClassSpec ensures that IngressClassSpec fields are valid. 525 func validateIngressClassSpec(spec *networking.IngressClassSpec, fldPath *field.Path) field.ErrorList { 526 allErrs := field.ErrorList{} 527 if len(spec.Controller) > maxLenIngressClassController { 528 allErrs = append(allErrs, field.TooLong(fldPath.Child("controller"), spec.Controller, maxLenIngressClassController)) 529 } 530 allErrs = append(allErrs, validation.IsDomainPrefixedPath(fldPath.Child("controller"), spec.Controller)...) 531 allErrs = append(allErrs, validateIngressClassParametersReference(spec.Parameters, fldPath.Child("parameters"))...) 532 return allErrs 533 } 534 535 // validateIngressClassSpecUpdate ensures that IngressClassSpec updates are 536 // valid. 537 func validateIngressClassSpecUpdate(newSpec, oldSpec *networking.IngressClassSpec, fldPath *field.Path) field.ErrorList { 538 return apivalidation.ValidateImmutableField(newSpec.Controller, oldSpec.Controller, fldPath.Child("controller")) 539 } 540 541 // validateIngressTypedLocalObjectReference ensures that Parameters fields are valid. 542 func validateIngressTypedLocalObjectReference(params *api.TypedLocalObjectReference, fldPath *field.Path) field.ErrorList { 543 allErrs := field.ErrorList{} 544 545 if params == nil { 546 return allErrs 547 } 548 549 if params.APIGroup != nil { 550 for _, msg := range validation.IsDNS1123Subdomain(*params.APIGroup) { 551 allErrs = append(allErrs, field.Invalid(fldPath.Child("apiGroup"), *params.APIGroup, msg)) 552 } 553 } 554 555 if params.Kind == "" { 556 allErrs = append(allErrs, field.Required(fldPath.Child("kind"), "kind is required")) 557 } else { 558 for _, msg := range pathvalidation.IsValidPathSegmentName(params.Kind) { 559 allErrs = append(allErrs, field.Invalid(fldPath.Child("kind"), params.Kind, msg)) 560 } 561 } 562 563 if params.Name == "" { 564 allErrs = append(allErrs, field.Required(fldPath.Child("name"), "name is required")) 565 } else { 566 for _, msg := range pathvalidation.IsValidPathSegmentName(params.Name) { 567 allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), params.Name, msg)) 568 } 569 } 570 571 return allErrs 572 } 573 574 // validateIngressClassParametersReference ensures that Parameters fields are valid. 575 // Parameters was previously of type `TypedLocalObjectReference` and used 576 // `validateIngressTypedLocalObjectReference()`. This function extends validation 577 // for additional fields introduced for namespace-scoped references. 578 func validateIngressClassParametersReference(params *networking.IngressClassParametersReference, fldPath *field.Path) field.ErrorList { 579 allErrs := field.ErrorList{} 580 581 if params == nil { 582 return allErrs 583 } 584 585 allErrs = append(allErrs, validateIngressTypedLocalObjectReference(&api.TypedLocalObjectReference{ 586 APIGroup: params.APIGroup, 587 Kind: params.Kind, 588 Name: params.Name, 589 }, fldPath)...) 590 591 if params.Scope == nil { 592 allErrs = append(allErrs, field.Required(fldPath.Child("scope"), "")) 593 return allErrs 594 } 595 596 scope := utilpointer.StringDeref(params.Scope, "") 597 598 if !supportedIngressClassParametersReferenceScopes.Has(scope) { 599 allErrs = append(allErrs, field.NotSupported(fldPath.Child("scope"), scope, 600 supportedIngressClassParametersReferenceScopes.List())) 601 } else { 602 if scope == networking.IngressClassParametersReferenceScopeNamespace { 603 if params.Namespace == nil { 604 allErrs = append(allErrs, field.Required(fldPath.Child("namespace"), "`parameters.scope` is set to 'Namespace'")) 605 } else { 606 for _, msg := range apivalidation.ValidateNamespaceName(*params.Namespace, false) { 607 allErrs = append(allErrs, field.Invalid(fldPath.Child("namespace"), *params.Namespace, msg)) 608 } 609 } 610 } 611 612 if scope == networking.IngressClassParametersReferenceScopeCluster && params.Namespace != nil { 613 allErrs = append(allErrs, field.Forbidden(fldPath.Child("namespace"), "`parameters.scope` is set to 'Cluster'")) 614 } 615 } 616 617 return allErrs 618 } 619 620 func allowInvalidSecretName(oldIngress *networking.Ingress) bool { 621 if oldIngress != nil { 622 for _, tls := range oldIngress.Spec.TLS { 623 if len(validateTLSSecretName(tls.SecretName)) > 0 { 624 // backwards compatibility with existing persisted object 625 return true 626 } 627 } 628 } 629 return false 630 } 631 632 func validateTLSSecretName(name string) []string { 633 if len(name) == 0 { 634 return nil 635 } 636 return apivalidation.ValidateSecretName(name, false) 637 } 638 639 func allowInvalidWildcardHostRule(oldIngress *networking.Ingress) bool { 640 if oldIngress != nil { 641 for _, rule := range oldIngress.Spec.Rules { 642 if strings.Contains(rule.Host, "*") && len(validateIngressRuleValue(&rule.IngressRuleValue, nil, IngressValidationOptions{})) > 0 { 643 // backwards compatibility with existing invalid data 644 return true 645 } 646 } 647 } 648 return false 649 } 650 651 // ValidateIPAddressName validates that the name is the decimal representation of an IP address. 652 // IPAddress does not support generating names, prefix is not considered. 653 func ValidateIPAddressName(name string, prefix bool) []string { 654 var errs []string 655 ip, err := netip.ParseAddr(name) 656 if err != nil { 657 errs = append(errs, err.Error()) 658 } else if ip.String() != name { 659 errs = append(errs, "must be a canonical format IP address") 660 661 } 662 return errs 663 } 664 665 func ValidateIPAddress(ipAddress *networking.IPAddress) field.ErrorList { 666 allErrs := apivalidation.ValidateObjectMeta(&ipAddress.ObjectMeta, false, ValidateIPAddressName, field.NewPath("metadata")) 667 errs := validateIPAddressParentReference(ipAddress.Spec.ParentRef, field.NewPath("spec")) 668 allErrs = append(allErrs, errs...) 669 return allErrs 670 671 } 672 673 // validateIPAddressParentReference ensures that the IPAddress ParenteReference exists and is valid. 674 func validateIPAddressParentReference(params *networking.ParentReference, fldPath *field.Path) field.ErrorList { 675 allErrs := field.ErrorList{} 676 677 if params == nil { 678 allErrs = append(allErrs, field.Required(fldPath.Child("parentRef"), "")) 679 return allErrs 680 } 681 682 fldPath = fldPath.Child("parentRef") 683 // group is required but the Core group used by Services is the empty value, so it can not be enforced 684 if params.Group != "" { 685 for _, msg := range validation.IsDNS1123Subdomain(params.Group) { 686 allErrs = append(allErrs, field.Invalid(fldPath.Child("group"), params.Group, msg)) 687 } 688 } 689 690 // resource is required 691 if params.Resource == "" { 692 allErrs = append(allErrs, field.Required(fldPath.Child("resource"), "")) 693 } else { 694 for _, msg := range pathvalidation.IsValidPathSegmentName(params.Resource) { 695 allErrs = append(allErrs, field.Invalid(fldPath.Child("resource"), params.Resource, msg)) 696 } 697 } 698 699 // name is required 700 if params.Name == "" { 701 allErrs = append(allErrs, field.Required(fldPath.Child("name"), "")) 702 } else { 703 for _, msg := range pathvalidation.IsValidPathSegmentName(params.Name) { 704 allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), params.Name, msg)) 705 } 706 } 707 708 // namespace is optional 709 if params.Namespace != "" { 710 for _, msg := range pathvalidation.IsValidPathSegmentName(params.Namespace) { 711 allErrs = append(allErrs, field.Invalid(fldPath.Child("namespace"), params.Namespace, msg)) 712 } 713 } 714 return allErrs 715 } 716 717 // ValidateIPAddressUpdate tests if an update to an IPAddress is valid. 718 func ValidateIPAddressUpdate(update, old *networking.IPAddress) field.ErrorList { 719 var allErrs field.ErrorList 720 allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&update.ObjectMeta, &old.ObjectMeta, field.NewPath("metadata"))...) 721 allErrs = append(allErrs, apivalidation.ValidateImmutableField(update.Spec.ParentRef, old.Spec.ParentRef, field.NewPath("spec").Child("parentRef"))...) 722 return allErrs 723 } 724 725 var ValidateServiceCIDRName = apimachineryvalidation.NameIsDNSSubdomain 726 727 func ValidateServiceCIDR(cidrConfig *networking.ServiceCIDR) field.ErrorList { 728 allErrs := apivalidation.ValidateObjectMeta(&cidrConfig.ObjectMeta, false, ValidateServiceCIDRName, field.NewPath("metadata")) 729 fieldPath := field.NewPath("spec", "cidrs") 730 731 if len(cidrConfig.Spec.CIDRs) == 0 { 732 allErrs = append(allErrs, field.Required(fieldPath, "at least one CIDR required")) 733 return allErrs 734 } 735 736 if len(cidrConfig.Spec.CIDRs) > 2 { 737 allErrs = append(allErrs, field.Invalid(fieldPath, cidrConfig.Spec, "may only hold up to 2 values")) 738 return allErrs 739 } 740 // validate cidrs are dual stack, one of each IP family 741 if len(cidrConfig.Spec.CIDRs) == 2 { 742 isDual, err := netutils.IsDualStackCIDRStrings(cidrConfig.Spec.CIDRs) 743 if err != nil || !isDual { 744 allErrs = append(allErrs, field.Invalid(fieldPath, cidrConfig.Spec, "may specify no more than one IP for each IP family, i.e 192.168.0.0/24 and 2001:db8::/64")) 745 return allErrs 746 } 747 } 748 749 for i, cidr := range cidrConfig.Spec.CIDRs { 750 allErrs = append(allErrs, validateCIDR(cidr, fieldPath.Index(i))...) 751 } 752 753 return allErrs 754 } 755 756 func validateCIDR(cidr string, fldPath *field.Path) field.ErrorList { 757 allErrs := field.ErrorList{} 758 prefix, err := netip.ParsePrefix(cidr) 759 if err != nil { 760 allErrs = append(allErrs, field.Invalid(fldPath, cidr, err.Error())) 761 } else { 762 if prefix.Addr() != prefix.Masked().Addr() { 763 allErrs = append(allErrs, field.Invalid(fldPath, cidr, "wrong CIDR format, IP doesn't match network IP address")) 764 } 765 if prefix.String() != cidr { 766 allErrs = append(allErrs, field.Invalid(fldPath, cidr, "CIDR not in RFC 5952 canonical format")) 767 } 768 } 769 return allErrs 770 } 771 772 // ValidateServiceCIDRUpdate tests if an update to a ServiceCIDR is valid. 773 func ValidateServiceCIDRUpdate(update, old *networking.ServiceCIDR) field.ErrorList { 774 var allErrs field.ErrorList 775 allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&update.ObjectMeta, &old.ObjectMeta, field.NewPath("metadata"))...) 776 allErrs = append(allErrs, apivalidation.ValidateImmutableField(update.Spec.CIDRs, old.Spec.CIDRs, field.NewPath("spec").Child("cidrs"))...) 777 778 return allErrs 779 } 780 781 // ValidateServiceCIDRStatusUpdate tests if if an update to a ServiceCIDR Status is valid. 782 func ValidateServiceCIDRStatusUpdate(update, old *networking.ServiceCIDR) field.ErrorList { 783 allErrs := apivalidation.ValidateObjectMetaUpdate(&update.ObjectMeta, &old.ObjectMeta, field.NewPath("metadata")) 784 return allErrs 785 }