...

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

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

     1  /*
     2  Copyright 2023 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package validation
    18  
    19  import (
    20  	"fmt"
    21  	"strings"
    22  
    23  	apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
    24  	"k8s.io/apimachinery/pkg/util/validation"
    25  	"k8s.io/apimachinery/pkg/util/validation/field"
    26  )
    27  
    28  // ValidateSignerName checks that signerName is syntactically valid.
    29  //
    30  // ensure signerName is of the form domain.com/something and up to 571 characters.
    31  // This length and format is specified to accommodate signerNames like:
    32  // <fqdn>/<resource-namespace>.<resource-name>.
    33  // The max length of a FQDN is 253 characters (DNS1123Subdomain max length)
    34  // The max length of a namespace name is 63 characters (DNS1123Label max length)
    35  // The max length of a resource name is 253 characters (DNS1123Subdomain max length)
    36  // We then add an additional 2 characters to account for the one '.' and one '/'.
    37  func ValidateSignerName(fldPath *field.Path, signerName string) field.ErrorList {
    38  	var el field.ErrorList
    39  	if len(signerName) == 0 {
    40  		el = append(el, field.Required(fldPath, ""))
    41  		return el
    42  	}
    43  
    44  	segments := strings.Split(signerName, "/")
    45  	// validate that there is one '/' in the signerName.
    46  	// we do this after validating the domain segment to provide more info to the user.
    47  	if len(segments) != 2 {
    48  		el = append(el, field.Invalid(fldPath, signerName, "must be a fully qualified domain and path of the form 'example.com/signer-name'"))
    49  		// return early here as we should not continue attempting to validate a missing or malformed path segment
    50  		// (i.e. one containing multiple or zero `/`)
    51  		return el
    52  	}
    53  
    54  	// validate that segments[0] is less than 253 characters altogether
    55  	maxDomainSegmentLength := validation.DNS1123SubdomainMaxLength
    56  	if len(segments[0]) > maxDomainSegmentLength {
    57  		el = append(el, field.TooLong(fldPath, segments[0], maxDomainSegmentLength))
    58  	}
    59  	// validate that segments[0] consists of valid DNS1123 labels separated by '.'
    60  	domainLabels := strings.Split(segments[0], ".")
    61  	for _, lbl := range domainLabels {
    62  		// use IsDNS1123Label as we want to ensure the max length of any single label in the domain
    63  		// is 63 characters
    64  		if errs := validation.IsDNS1123Label(lbl); len(errs) > 0 {
    65  			for _, err := range errs {
    66  				el = append(el, field.Invalid(fldPath, segments[0], fmt.Sprintf("validating label %q: %s", lbl, err)))
    67  			}
    68  			// if we encounter any errors whilst parsing the domain segment, break from
    69  			// validation as any further error messages will be duplicates, and non-distinguishable
    70  			// from each other, confusing users.
    71  			break
    72  		}
    73  	}
    74  
    75  	// validate that there is at least one '.' in segments[0]
    76  	if len(domainLabels) < 2 {
    77  		el = append(el, field.Invalid(fldPath, segments[0], "should be a domain with at least two segments separated by dots"))
    78  	}
    79  
    80  	// validate that segments[1] consists of valid DNS1123 subdomains separated by '.'.
    81  	pathLabels := strings.Split(segments[1], ".")
    82  	for _, lbl := range pathLabels {
    83  		// use IsDNS1123Subdomain because it enforces a length restriction of 253 characters
    84  		// which is required in order to fit a full resource name into a single 'label'
    85  		if errs := validation.IsDNS1123Subdomain(lbl); len(errs) > 0 {
    86  			for _, err := range errs {
    87  				el = append(el, field.Invalid(fldPath, segments[1], fmt.Sprintf("validating label %q: %s", lbl, err)))
    88  			}
    89  			// if we encounter any errors whilst parsing the path segment, break from
    90  			// validation as any further error messages will be duplicates, and non-distinguishable
    91  			// from each other, confusing users.
    92  			break
    93  		}
    94  	}
    95  
    96  	// ensure that segments[1] can accommodate a dns label + dns subdomain + '.'
    97  	maxPathSegmentLength := validation.DNS1123SubdomainMaxLength + validation.DNS1123LabelMaxLength + 1
    98  	maxSignerNameLength := maxDomainSegmentLength + maxPathSegmentLength + 1
    99  	if len(signerName) > maxSignerNameLength {
   100  		el = append(el, field.TooLong(fldPath, signerName, maxSignerNameLength))
   101  	}
   102  
   103  	return el
   104  }
   105  
   106  // ValidateClusterTrustBundleName checks that a ClusterTrustBundle name conforms
   107  // to the rules documented on the type.
   108  func ValidateClusterTrustBundleName(signerName string) func(name string, prefix bool) []string {
   109  	return func(name string, isPrefix bool) []string {
   110  		if signerName == "" {
   111  			if strings.Contains(name, ":") {
   112  				return []string{"ClusterTrustBundle without signer name must not have \":\" in its name"}
   113  			}
   114  			return apimachineryvalidation.NameIsDNSSubdomain(name, isPrefix)
   115  		}
   116  
   117  		requiredPrefix := strings.ReplaceAll(signerName, "/", ":") + ":"
   118  		if !strings.HasPrefix(name, requiredPrefix) {
   119  			return []string{fmt.Sprintf("ClusterTrustBundle for signerName %s must be named with prefix %s", signerName, requiredPrefix)}
   120  		}
   121  		return apimachineryvalidation.NameIsDNSSubdomain(strings.TrimPrefix(name, requiredPrefix), isPrefix)
   122  	}
   123  }
   124  
   125  func extractSignerNameFromClusterTrustBundleName(name string) (string, bool) {
   126  	if splitPoint := strings.LastIndex(name, ":"); splitPoint != -1 {
   127  		// This looks like it refers to a signerName trustbundle.
   128  		return strings.ReplaceAll(name[:splitPoint], ":", "/"), true
   129  	} else {
   130  		return "", false
   131  	}
   132  }
   133  

View as plain text