k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/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 "k8s.io/utils/ptr" 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 ipb.CIDR == "" { 220 allErrs = append(allErrs, field.Required(fldPath.Child("cidr"), "")) 221 return allErrs 222 } 223 allErrs = append(allErrs, validation.IsValidCIDR(fldPath.Child("cidr"), ipb.CIDR)...) 224 _, cidrIPNet, err := netutils.ParseCIDRSloppy(ipb.CIDR) 225 if err != nil { 226 // Implies validation would have failed so we already added errors for it. 227 return allErrs 228 } 229 230 for i, exceptCIDRStr := range ipb.Except { 231 exceptPath := fldPath.Child("except").Index(i) 232 allErrs = append(allErrs, validation.IsValidCIDR(exceptPath, exceptCIDRStr)...) 233 _, exceptCIDR, err := netutils.ParseCIDRSloppy(exceptCIDRStr) 234 if err != nil { 235 // Implies validation would have failed so we already added errors for it. 236 continue 237 } 238 239 cidrMaskLen, _ := cidrIPNet.Mask.Size() 240 exceptMaskLen, _ := exceptCIDR.Mask.Size() 241 if !cidrIPNet.Contains(exceptCIDR.IP) || cidrMaskLen >= exceptMaskLen { 242 allErrs = append(allErrs, field.Invalid(exceptPath, exceptCIDRStr, "must be a strict subset of `cidr`")) 243 } 244 } 245 return allErrs 246 } 247 248 // ValidateIngressName validates that the given name can be used as an Ingress 249 // name. 250 var ValidateIngressName = apimachineryvalidation.NameIsDNSSubdomain 251 252 // IngressValidationOptions cover beta to GA transitions for HTTP PathType 253 type IngressValidationOptions struct { 254 // AllowInvalidSecretName indicates whether spec.tls[*].secretName values that are not valid Secret names should be allowed 255 AllowInvalidSecretName bool 256 257 // AllowInvalidWildcardHostRule indicates whether invalid rule values are allowed in rules with wildcard hostnames 258 AllowInvalidWildcardHostRule bool 259 } 260 261 // ValidateIngress validates Ingresses on create and update. 262 func validateIngress(ingress *networking.Ingress, opts IngressValidationOptions) field.ErrorList { 263 allErrs := apivalidation.ValidateObjectMeta(&ingress.ObjectMeta, true, ValidateIngressName, field.NewPath("metadata")) 264 allErrs = append(allErrs, ValidateIngressSpec(&ingress.Spec, field.NewPath("spec"), opts)...) 265 return allErrs 266 } 267 268 // ValidateIngressCreate validates Ingresses on create. 269 func ValidateIngressCreate(ingress *networking.Ingress) field.ErrorList { 270 allErrs := field.ErrorList{} 271 opts := IngressValidationOptions{ 272 AllowInvalidSecretName: false, 273 AllowInvalidWildcardHostRule: false, 274 } 275 allErrs = append(allErrs, validateIngress(ingress, opts)...) 276 annotationVal, annotationIsSet := ingress.Annotations[annotationIngressClass] 277 if annotationIsSet && ingress.Spec.IngressClassName != nil && annotationVal != *ingress.Spec.IngressClassName { 278 annotationPath := field.NewPath("annotations").Child(annotationIngressClass) 279 allErrs = append(allErrs, field.Invalid(annotationPath, annotationVal, "must match `ingressClassName` when both are specified")) 280 } 281 return allErrs 282 } 283 284 // ValidateIngressUpdate validates ingresses on update. 285 func ValidateIngressUpdate(ingress, oldIngress *networking.Ingress) field.ErrorList { 286 allErrs := apivalidation.ValidateObjectMetaUpdate(&ingress.ObjectMeta, &oldIngress.ObjectMeta, field.NewPath("metadata")) 287 opts := IngressValidationOptions{ 288 AllowInvalidSecretName: allowInvalidSecretName(oldIngress), 289 AllowInvalidWildcardHostRule: allowInvalidWildcardHostRule(oldIngress), 290 } 291 292 allErrs = append(allErrs, validateIngress(ingress, opts)...) 293 return allErrs 294 } 295 296 func validateIngressTLS(spec *networking.IngressSpec, fldPath *field.Path, opts IngressValidationOptions) field.ErrorList { 297 allErrs := field.ErrorList{} 298 // TODO: Perform a more thorough validation of spec.TLS.Hosts that takes 299 // the wildcard spec from RFC 6125 into account. 300 for tlsIndex, itls := range spec.TLS { 301 for i, host := range itls.Hosts { 302 if strings.Contains(host, "*") { 303 for _, msg := range validation.IsWildcardDNS1123Subdomain(host) { 304 allErrs = append(allErrs, field.Invalid(fldPath.Index(tlsIndex).Child("hosts").Index(i), host, msg)) 305 } 306 continue 307 } 308 for _, msg := range validation.IsDNS1123Subdomain(host) { 309 allErrs = append(allErrs, field.Invalid(fldPath.Index(tlsIndex).Child("hosts").Index(i), host, msg)) 310 } 311 } 312 313 if !opts.AllowInvalidSecretName { 314 for _, msg := range validateTLSSecretName(itls.SecretName) { 315 allErrs = append(allErrs, field.Invalid(fldPath.Index(tlsIndex).Child("secretName"), itls.SecretName, msg)) 316 } 317 } 318 } 319 320 return allErrs 321 } 322 323 // ValidateIngressSpec tests if required fields in the IngressSpec are set. 324 func ValidateIngressSpec(spec *networking.IngressSpec, fldPath *field.Path, opts IngressValidationOptions) field.ErrorList { 325 allErrs := field.ErrorList{} 326 if len(spec.Rules) == 0 && spec.DefaultBackend == nil { 327 errMsg := fmt.Sprintf("either `%s` or `rules` must be specified", "defaultBackend") 328 allErrs = append(allErrs, field.Invalid(fldPath, spec.Rules, errMsg)) 329 } 330 if spec.DefaultBackend != nil { 331 allErrs = append(allErrs, validateIngressBackend(spec.DefaultBackend, fldPath.Child("defaultBackend"), opts)...) 332 } 333 if len(spec.Rules) > 0 { 334 allErrs = append(allErrs, validateIngressRules(spec.Rules, fldPath.Child("rules"), opts)...) 335 } 336 if len(spec.TLS) > 0 { 337 allErrs = append(allErrs, validateIngressTLS(spec, fldPath.Child("tls"), opts)...) 338 } 339 if spec.IngressClassName != nil { 340 for _, msg := range ValidateIngressClassName(*spec.IngressClassName, false) { 341 allErrs = append(allErrs, field.Invalid(fldPath.Child("ingressClassName"), *spec.IngressClassName, msg)) 342 } 343 } 344 return allErrs 345 } 346 347 // ValidateIngressStatusUpdate tests if required fields in the Ingress are set when updating status. 348 func ValidateIngressStatusUpdate(ingress, oldIngress *networking.Ingress) field.ErrorList { 349 allErrs := apivalidation.ValidateObjectMetaUpdate(&ingress.ObjectMeta, &oldIngress.ObjectMeta, field.NewPath("metadata")) 350 allErrs = append(allErrs, ValidateIngressLoadBalancerStatus(&ingress.Status.LoadBalancer, field.NewPath("status", "loadBalancer"))...) 351 return allErrs 352 } 353 354 // ValidateLIngressoadBalancerStatus validates required fields on an IngressLoadBalancerStatus 355 func ValidateIngressLoadBalancerStatus(status *networking.IngressLoadBalancerStatus, fldPath *field.Path) field.ErrorList { 356 allErrs := field.ErrorList{} 357 for i, ingress := range status.Ingress { 358 idxPath := fldPath.Child("ingress").Index(i) 359 if len(ingress.IP) > 0 { 360 allErrs = append(allErrs, validation.IsValidIP(idxPath.Child("ip"), ingress.IP)...) 361 } 362 if len(ingress.Hostname) > 0 { 363 for _, msg := range validation.IsDNS1123Subdomain(ingress.Hostname) { 364 allErrs = append(allErrs, field.Invalid(idxPath.Child("hostname"), ingress.Hostname, msg)) 365 } 366 if isIP := (netutils.ParseIPSloppy(ingress.Hostname) != nil); isIP { 367 allErrs = append(allErrs, field.Invalid(idxPath.Child("hostname"), ingress.Hostname, "must be a DNS name, not an IP address")) 368 } 369 } 370 } 371 return allErrs 372 } 373 374 func validateIngressRules(ingressRules []networking.IngressRule, fldPath *field.Path, opts IngressValidationOptions) field.ErrorList { 375 allErrs := field.ErrorList{} 376 if len(ingressRules) == 0 { 377 return append(allErrs, field.Required(fldPath, "")) 378 } 379 for i, ih := range ingressRules { 380 wildcardHost := false 381 if len(ih.Host) > 0 { 382 if isIP := (netutils.ParseIPSloppy(ih.Host) != nil); isIP { 383 allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("host"), ih.Host, "must be a DNS name, not an IP address")) 384 } 385 // TODO: Ports and ips are allowed in the host part of a url 386 // according to RFC 3986, consider allowing them. 387 if strings.Contains(ih.Host, "*") { 388 for _, msg := range validation.IsWildcardDNS1123Subdomain(ih.Host) { 389 allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("host"), ih.Host, msg)) 390 } 391 wildcardHost = true 392 } else { 393 for _, msg := range validation.IsDNS1123Subdomain(ih.Host) { 394 allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("host"), ih.Host, msg)) 395 } 396 } 397 } 398 399 if !wildcardHost || !opts.AllowInvalidWildcardHostRule { 400 allErrs = append(allErrs, validateIngressRuleValue(&ih.IngressRuleValue, fldPath.Index(i), opts)...) 401 } 402 } 403 return allErrs 404 } 405 406 func validateIngressRuleValue(ingressRule *networking.IngressRuleValue, fldPath *field.Path, opts IngressValidationOptions) field.ErrorList { 407 allErrs := field.ErrorList{} 408 if ingressRule.HTTP != nil { 409 allErrs = append(allErrs, validateHTTPIngressRuleValue(ingressRule.HTTP, fldPath.Child("http"), opts)...) 410 } 411 return allErrs 412 } 413 414 func validateHTTPIngressRuleValue(httpIngressRuleValue *networking.HTTPIngressRuleValue, fldPath *field.Path, opts IngressValidationOptions) field.ErrorList { 415 allErrs := field.ErrorList{} 416 if len(httpIngressRuleValue.Paths) == 0 { 417 allErrs = append(allErrs, field.Required(fldPath.Child("paths"), "")) 418 } 419 for i, path := range httpIngressRuleValue.Paths { 420 allErrs = append(allErrs, validateHTTPIngressPath(&path, fldPath.Child("paths").Index(i), opts)...) 421 } 422 return allErrs 423 } 424 425 func validateHTTPIngressPath(path *networking.HTTPIngressPath, fldPath *field.Path, opts IngressValidationOptions) field.ErrorList { 426 allErrs := field.ErrorList{} 427 428 if path.PathType == nil { 429 return append(allErrs, field.Required(fldPath.Child("pathType"), "pathType must be specified")) 430 } 431 432 switch *path.PathType { 433 case networking.PathTypeExact, networking.PathTypePrefix: 434 if !strings.HasPrefix(path.Path, "/") { 435 allErrs = append(allErrs, field.Invalid(fldPath.Child("path"), path.Path, "must be an absolute path")) 436 } 437 if len(path.Path) > 0 { 438 for _, invalidSeq := range invalidPathSequences { 439 if strings.Contains(path.Path, invalidSeq) { 440 allErrs = append(allErrs, field.Invalid(fldPath.Child("path"), path.Path, fmt.Sprintf("must not contain '%s'", invalidSeq))) 441 } 442 } 443 444 for _, invalidSuff := range invalidPathSuffixes { 445 if strings.HasSuffix(path.Path, invalidSuff) { 446 allErrs = append(allErrs, field.Invalid(fldPath.Child("path"), path.Path, fmt.Sprintf("cannot end with '%s'", invalidSuff))) 447 } 448 } 449 } 450 case networking.PathTypeImplementationSpecific: 451 if len(path.Path) > 0 { 452 if !strings.HasPrefix(path.Path, "/") { 453 allErrs = append(allErrs, field.Invalid(fldPath.Child("path"), path.Path, "must be an absolute path")) 454 } 455 } 456 default: 457 allErrs = append(allErrs, field.NotSupported(fldPath.Child("pathType"), *path.PathType, supportedPathTypes.List())) 458 } 459 allErrs = append(allErrs, validateIngressBackend(&path.Backend, fldPath.Child("backend"), opts)...) 460 return allErrs 461 } 462 463 // validateIngressBackend tests if a given backend is valid. 464 func validateIngressBackend(backend *networking.IngressBackend, fldPath *field.Path, opts IngressValidationOptions) field.ErrorList { 465 allErrs := field.ErrorList{} 466 467 hasResourceBackend := backend.Resource != nil 468 hasServiceBackend := backend.Service != nil 469 470 switch { 471 case hasResourceBackend && hasServiceBackend: 472 return append(allErrs, field.Invalid(fldPath, "", "cannot set both resource and service backends")) 473 case hasResourceBackend: 474 allErrs = append(allErrs, validateIngressTypedLocalObjectReference(backend.Resource, fldPath.Child("resource"))...) 475 case hasServiceBackend: 476 477 if len(backend.Service.Name) == 0 { 478 allErrs = append(allErrs, field.Required(fldPath.Child("service", "name"), "")) 479 } else { 480 for _, msg := range apivalidation.ValidateServiceName(backend.Service.Name, false) { 481 allErrs = append(allErrs, field.Invalid(fldPath.Child("service", "name"), backend.Service.Name, msg)) 482 } 483 } 484 485 hasPortName := len(backend.Service.Port.Name) > 0 486 hasPortNumber := backend.Service.Port.Number != 0 487 if hasPortName && hasPortNumber { 488 allErrs = append(allErrs, field.Invalid(fldPath, "", "cannot set both port name & port number")) 489 } else if hasPortName { 490 for _, msg := range validation.IsValidPortName(backend.Service.Port.Name) { 491 allErrs = append(allErrs, field.Invalid(fldPath.Child("service", "port", "name"), backend.Service.Port.Name, msg)) 492 } 493 } else if hasPortNumber { 494 for _, msg := range validation.IsValidPortNum(int(backend.Service.Port.Number)) { 495 allErrs = append(allErrs, field.Invalid(fldPath.Child("service", "port", "number"), backend.Service.Port.Number, msg)) 496 } 497 } else { 498 allErrs = append(allErrs, field.Required(fldPath, "port name or number is required")) 499 } 500 default: 501 allErrs = append(allErrs, field.Invalid(fldPath, "", "resource or service backend is required")) 502 } 503 return allErrs 504 } 505 506 // ValidateIngressClassName validates that the given name can be used as an 507 // IngressClass name. 508 var ValidateIngressClassName = apimachineryvalidation.NameIsDNSSubdomain 509 510 // ValidateIngressClass ensures that IngressClass resources are valid. 511 func ValidateIngressClass(ingressClass *networking.IngressClass) field.ErrorList { 512 allErrs := apivalidation.ValidateObjectMeta(&ingressClass.ObjectMeta, false, ValidateIngressClassName, field.NewPath("metadata")) 513 allErrs = append(allErrs, validateIngressClassSpec(&ingressClass.Spec, field.NewPath("spec"))...) 514 return allErrs 515 } 516 517 // ValidateIngressClassUpdate ensures that IngressClass updates are valid. 518 func ValidateIngressClassUpdate(newIngressClass, oldIngressClass *networking.IngressClass) field.ErrorList { 519 allErrs := apivalidation.ValidateObjectMetaUpdate(&newIngressClass.ObjectMeta, &oldIngressClass.ObjectMeta, field.NewPath("metadata")) 520 allErrs = append(allErrs, validateIngressClassSpecUpdate(&newIngressClass.Spec, &oldIngressClass.Spec, field.NewPath("spec"))...) 521 allErrs = append(allErrs, ValidateIngressClass(newIngressClass)...) 522 return allErrs 523 } 524 525 // validateIngressClassSpec ensures that IngressClassSpec fields are valid. 526 func validateIngressClassSpec(spec *networking.IngressClassSpec, fldPath *field.Path) field.ErrorList { 527 allErrs := field.ErrorList{} 528 if len(spec.Controller) > maxLenIngressClassController { 529 allErrs = append(allErrs, field.TooLong(fldPath.Child("controller"), spec.Controller, maxLenIngressClassController)) 530 } 531 allErrs = append(allErrs, validation.IsDomainPrefixedPath(fldPath.Child("controller"), spec.Controller)...) 532 allErrs = append(allErrs, validateIngressClassParametersReference(spec.Parameters, fldPath.Child("parameters"))...) 533 return allErrs 534 } 535 536 // validateIngressClassSpecUpdate ensures that IngressClassSpec updates are 537 // valid. 538 func validateIngressClassSpecUpdate(newSpec, oldSpec *networking.IngressClassSpec, fldPath *field.Path) field.ErrorList { 539 return apivalidation.ValidateImmutableField(newSpec.Controller, oldSpec.Controller, fldPath.Child("controller")) 540 } 541 542 // validateIngressTypedLocalObjectReference ensures that Parameters fields are valid. 543 func validateIngressTypedLocalObjectReference(params *api.TypedLocalObjectReference, fldPath *field.Path) field.ErrorList { 544 allErrs := field.ErrorList{} 545 546 if params == nil { 547 return allErrs 548 } 549 550 if params.APIGroup != nil { 551 for _, msg := range validation.IsDNS1123Subdomain(*params.APIGroup) { 552 allErrs = append(allErrs, field.Invalid(fldPath.Child("apiGroup"), *params.APIGroup, msg)) 553 } 554 } 555 556 if params.Kind == "" { 557 allErrs = append(allErrs, field.Required(fldPath.Child("kind"), "kind is required")) 558 } else { 559 for _, msg := range pathvalidation.IsValidPathSegmentName(params.Kind) { 560 allErrs = append(allErrs, field.Invalid(fldPath.Child("kind"), params.Kind, msg)) 561 } 562 } 563 564 if params.Name == "" { 565 allErrs = append(allErrs, field.Required(fldPath.Child("name"), "name is required")) 566 } else { 567 for _, msg := range pathvalidation.IsValidPathSegmentName(params.Name) { 568 allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), params.Name, msg)) 569 } 570 } 571 572 return allErrs 573 } 574 575 // validateIngressClassParametersReference ensures that Parameters fields are valid. 576 // Parameters was previously of type `TypedLocalObjectReference` and used 577 // `validateIngressTypedLocalObjectReference()`. This function extends validation 578 // for additional fields introduced for namespace-scoped references. 579 func validateIngressClassParametersReference(params *networking.IngressClassParametersReference, fldPath *field.Path) field.ErrorList { 580 allErrs := field.ErrorList{} 581 582 if params == nil { 583 return allErrs 584 } 585 586 allErrs = append(allErrs, validateIngressTypedLocalObjectReference(&api.TypedLocalObjectReference{ 587 APIGroup: params.APIGroup, 588 Kind: params.Kind, 589 Name: params.Name, 590 }, fldPath)...) 591 592 if params.Scope == nil { 593 allErrs = append(allErrs, field.Required(fldPath.Child("scope"), "")) 594 return allErrs 595 } 596 597 scope := ptr.Deref(params.Scope, "") 598 599 if !supportedIngressClassParametersReferenceScopes.Has(scope) { 600 allErrs = append(allErrs, field.NotSupported(fldPath.Child("scope"), scope, 601 supportedIngressClassParametersReferenceScopes.List())) 602 } else { 603 if scope == networking.IngressClassParametersReferenceScopeNamespace { 604 if params.Namespace == nil { 605 allErrs = append(allErrs, field.Required(fldPath.Child("namespace"), "`parameters.scope` is set to 'Namespace'")) 606 } else { 607 for _, msg := range apivalidation.ValidateNamespaceName(*params.Namespace, false) { 608 allErrs = append(allErrs, field.Invalid(fldPath.Child("namespace"), *params.Namespace, msg)) 609 } 610 } 611 } 612 613 if scope == networking.IngressClassParametersReferenceScopeCluster && params.Namespace != nil { 614 allErrs = append(allErrs, field.Forbidden(fldPath.Child("namespace"), "`parameters.scope` is set to 'Cluster'")) 615 } 616 } 617 618 return allErrs 619 } 620 621 func allowInvalidSecretName(oldIngress *networking.Ingress) bool { 622 if oldIngress != nil { 623 for _, tls := range oldIngress.Spec.TLS { 624 if len(validateTLSSecretName(tls.SecretName)) > 0 { 625 // backwards compatibility with existing persisted object 626 return true 627 } 628 } 629 } 630 return false 631 } 632 633 func validateTLSSecretName(name string) []string { 634 if len(name) == 0 { 635 return nil 636 } 637 return apivalidation.ValidateSecretName(name, false) 638 } 639 640 func allowInvalidWildcardHostRule(oldIngress *networking.Ingress) bool { 641 if oldIngress != nil { 642 for _, rule := range oldIngress.Spec.Rules { 643 if strings.Contains(rule.Host, "*") && len(validateIngressRuleValue(&rule.IngressRuleValue, nil, IngressValidationOptions{})) > 0 { 644 // backwards compatibility with existing invalid data 645 return true 646 } 647 } 648 } 649 return false 650 } 651 652 // ValidateIPAddressName validates that the name is the decimal representation of an IP address. 653 // IPAddress does not support generating names, prefix is not considered. 654 func ValidateIPAddressName(name string, prefix bool) []string { 655 var errs []string 656 ip, err := netip.ParseAddr(name) 657 if err != nil { 658 errs = append(errs, err.Error()) 659 } else if ip.String() != name { 660 errs = append(errs, "must be a canonical format IP address") 661 662 } 663 return errs 664 } 665 666 func ValidateIPAddress(ipAddress *networking.IPAddress) field.ErrorList { 667 allErrs := apivalidation.ValidateObjectMeta(&ipAddress.ObjectMeta, false, ValidateIPAddressName, field.NewPath("metadata")) 668 errs := validateIPAddressParentReference(ipAddress.Spec.ParentRef, field.NewPath("spec")) 669 allErrs = append(allErrs, errs...) 670 return allErrs 671 672 } 673 674 // validateIPAddressParentReference ensures that the IPAddress ParenteReference exists and is valid. 675 func validateIPAddressParentReference(params *networking.ParentReference, fldPath *field.Path) field.ErrorList { 676 allErrs := field.ErrorList{} 677 678 if params == nil { 679 allErrs = append(allErrs, field.Required(fldPath.Child("parentRef"), "")) 680 return allErrs 681 } 682 683 fldPath = fldPath.Child("parentRef") 684 // group is required but the Core group used by Services is the empty value, so it can not be enforced 685 if params.Group != "" { 686 for _, msg := range validation.IsDNS1123Subdomain(params.Group) { 687 allErrs = append(allErrs, field.Invalid(fldPath.Child("group"), params.Group, msg)) 688 } 689 } 690 691 // resource is required 692 if params.Resource == "" { 693 allErrs = append(allErrs, field.Required(fldPath.Child("resource"), "")) 694 } else { 695 for _, msg := range pathvalidation.IsValidPathSegmentName(params.Resource) { 696 allErrs = append(allErrs, field.Invalid(fldPath.Child("resource"), params.Resource, msg)) 697 } 698 } 699 700 // name is required 701 if params.Name == "" { 702 allErrs = append(allErrs, field.Required(fldPath.Child("name"), "")) 703 } else { 704 for _, msg := range pathvalidation.IsValidPathSegmentName(params.Name) { 705 allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), params.Name, msg)) 706 } 707 } 708 709 // namespace is optional 710 if params.Namespace != "" { 711 for _, msg := range pathvalidation.IsValidPathSegmentName(params.Namespace) { 712 allErrs = append(allErrs, field.Invalid(fldPath.Child("namespace"), params.Namespace, msg)) 713 } 714 } 715 return allErrs 716 } 717 718 // ValidateIPAddressUpdate tests if an update to an IPAddress is valid. 719 func ValidateIPAddressUpdate(update, old *networking.IPAddress) field.ErrorList { 720 var allErrs field.ErrorList 721 allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&update.ObjectMeta, &old.ObjectMeta, field.NewPath("metadata"))...) 722 allErrs = append(allErrs, apivalidation.ValidateImmutableField(update.Spec.ParentRef, old.Spec.ParentRef, field.NewPath("spec").Child("parentRef"))...) 723 return allErrs 724 } 725 726 var ValidateServiceCIDRName = apimachineryvalidation.NameIsDNSSubdomain 727 728 func ValidateServiceCIDR(cidrConfig *networking.ServiceCIDR) field.ErrorList { 729 allErrs := apivalidation.ValidateObjectMeta(&cidrConfig.ObjectMeta, false, ValidateServiceCIDRName, field.NewPath("metadata")) 730 fieldPath := field.NewPath("spec", "cidrs") 731 732 if len(cidrConfig.Spec.CIDRs) == 0 { 733 allErrs = append(allErrs, field.Required(fieldPath, "at least one CIDR required")) 734 return allErrs 735 } 736 737 if len(cidrConfig.Spec.CIDRs) > 2 { 738 allErrs = append(allErrs, field.Invalid(fieldPath, cidrConfig.Spec, "may only hold up to 2 values")) 739 return allErrs 740 } 741 // validate cidrs are dual stack, one of each IP family 742 if len(cidrConfig.Spec.CIDRs) == 2 { 743 isDual, err := netutils.IsDualStackCIDRStrings(cidrConfig.Spec.CIDRs) 744 if err != nil || !isDual { 745 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")) 746 return allErrs 747 } 748 } 749 750 for i, cidr := range cidrConfig.Spec.CIDRs { 751 allErrs = append(allErrs, validateCIDR(cidr, fieldPath.Index(i))...) 752 } 753 754 return allErrs 755 } 756 757 func validateCIDR(cidr string, fldPath *field.Path) field.ErrorList { 758 allErrs := field.ErrorList{} 759 prefix, err := netip.ParsePrefix(cidr) 760 if err != nil { 761 allErrs = append(allErrs, field.Invalid(fldPath, cidr, err.Error())) 762 } else { 763 if prefix.Addr() != prefix.Masked().Addr() { 764 allErrs = append(allErrs, field.Invalid(fldPath, cidr, "wrong CIDR format, IP doesn't match network IP address")) 765 } 766 if prefix.String() != cidr { 767 allErrs = append(allErrs, field.Invalid(fldPath, cidr, "CIDR not in RFC 5952 canonical format")) 768 } 769 } 770 return allErrs 771 } 772 773 // ValidateServiceCIDRUpdate tests if an update to a ServiceCIDR is valid. 774 func ValidateServiceCIDRUpdate(update, old *networking.ServiceCIDR) field.ErrorList { 775 var allErrs field.ErrorList 776 allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&update.ObjectMeta, &old.ObjectMeta, field.NewPath("metadata"))...) 777 allErrs = append(allErrs, apivalidation.ValidateImmutableField(update.Spec.CIDRs, old.Spec.CIDRs, field.NewPath("spec").Child("cidrs"))...) 778 779 return allErrs 780 } 781 782 // ValidateServiceCIDRStatusUpdate tests if if an update to a ServiceCIDR Status is valid. 783 func ValidateServiceCIDRStatusUpdate(update, old *networking.ServiceCIDR) field.ErrorList { 784 allErrs := apivalidation.ValidateObjectMetaUpdate(&update.ObjectMeta, &old.ObjectMeta, field.NewPath("metadata")) 785 return allErrs 786 }