...

Source file src/k8s.io/kubernetes/pkg/apis/networking/validation/validation.go

Documentation: k8s.io/kubernetes/pkg/apis/networking/validation

     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  }
   787  

View as plain text