...

Source file src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting/validation.go

Documentation: k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting

     1  /*
     2  Copyright 2019 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 defaulting
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"reflect"
    23  
    24  	structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
    25  	"k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel"
    26  	schemaobjectmeta "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/objectmeta"
    27  	"k8s.io/apiextensions-apiserver/pkg/apiserver/schema/pruning"
    28  	apiservervalidation "k8s.io/apiextensions-apiserver/pkg/apiserver/validation"
    29  	apiextensionsfeatures "k8s.io/apiextensions-apiserver/pkg/features"
    30  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    31  	"k8s.io/apimachinery/pkg/runtime"
    32  	"k8s.io/apimachinery/pkg/util/validation/field"
    33  	celconfig "k8s.io/apiserver/pkg/apis/cel"
    34  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    35  )
    36  
    37  // ValidateDefaults checks that default values validate and are properly pruned.
    38  // context is passed for supporting context cancellation during cel validation
    39  func ValidateDefaults(ctx context.Context, pth *field.Path, s *structuralschema.Structural, isResourceRoot, requirePrunedDefaults bool) (field.ErrorList, error) {
    40  	f := NewRootObjectFunc().WithTypeMeta(metav1.TypeMeta{APIVersion: "validation/v1", Kind: "Validation"})
    41  
    42  	if isResourceRoot {
    43  		if s == nil {
    44  			s = &structuralschema.Structural{}
    45  		}
    46  		if !s.XEmbeddedResource {
    47  			clone := *s
    48  			clone.XEmbeddedResource = true
    49  			s = &clone
    50  		}
    51  	}
    52  
    53  	allErr, error, _ := validate(ctx, pth, s, s, f, false, requirePrunedDefaults, celconfig.RuntimeCELCostBudget)
    54  	return allErr, error
    55  }
    56  
    57  // validate is the recursive step func for the validation. insideMeta is true if s specifies
    58  // TypeMeta or ObjectMeta. The SurroundingObjectFunc f is used to validate defaults of
    59  // TypeMeta or ObjectMeta fields.
    60  // context is passed for supporting context cancellation during cel validation
    61  func validate(ctx context.Context, pth *field.Path, s *structuralschema.Structural, rootSchema *structuralschema.Structural, f SurroundingObjectFunc, insideMeta, requirePrunedDefaults bool, costBudget int64) (allErrs field.ErrorList, error error, remainingCost int64) {
    62  	remainingCost = costBudget
    63  	if s == nil {
    64  		return nil, nil, remainingCost
    65  	}
    66  
    67  	if s.XEmbeddedResource {
    68  		insideMeta = false
    69  		f = NewRootObjectFunc().WithTypeMeta(metav1.TypeMeta{APIVersion: "validation/v1", Kind: "Validation"})
    70  		rootSchema = s
    71  	}
    72  
    73  	isResourceRoot := s == rootSchema
    74  
    75  	if s.Default.Object != nil {
    76  		validator := apiservervalidation.NewSchemaValidatorFromOpenAPI(s.ToKubeOpenAPI())
    77  
    78  		if insideMeta {
    79  			obj, _, err := f(runtime.DeepCopyJSONValue(s.Default.Object))
    80  			if err != nil {
    81  				// this should never happen. f(s.Default.Object) only gives an error if f is the
    82  				// root object func, but the default value is not a map. But then we wouldn't be
    83  				// in this case.
    84  				return nil, fmt.Errorf("failed to validate default value inside metadata: %v", err), remainingCost
    85  			}
    86  
    87  			// check ObjectMeta/TypeMeta and everything else
    88  			if err := schemaobjectmeta.Coerce(nil, obj, rootSchema, true, false); err != nil {
    89  				allErrs = append(allErrs, field.Invalid(pth.Child("default"), s.Default.Object, fmt.Sprintf("must result in valid metadata: %v", err)))
    90  			} else if errs := schemaobjectmeta.Validate(nil, obj, rootSchema, true); len(errs) > 0 {
    91  				allErrs = append(allErrs, field.Invalid(pth.Child("default"), s.Default.Object, fmt.Sprintf("must result in valid metadata: %v", errs.ToAggregate())))
    92  			} else if errs := apiservervalidation.ValidateCustomResource(pth.Child("default"), s.Default.Object, validator); len(errs) > 0 {
    93  				allErrs = append(allErrs, errs...)
    94  			} else if celValidator := cel.NewValidator(s, isResourceRoot, celconfig.PerCallLimit); celValidator != nil {
    95  				celErrs, rmCost := celValidator.Validate(ctx, pth.Child("default"), s, s.Default.Object, s.Default.Object, remainingCost)
    96  				allErrs = append(allErrs, celErrs...)
    97  
    98  				if len(celErrs) == 0 && utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CRDValidationRatcheting) {
    99  					// If ratcheting is enabled some CEL rules may use optionalOldSelf
   100  					// For such rules the above validation is not sufficient for
   101  					// determining if the default value is a valid value to introduce
   102  					// via create or uncorrelated update.
   103  					//
   104  					// Validate an update from nil to the default value to ensure
   105  					// that the default value pass
   106  					celErrs, rmCostWithoutOldObject := celValidator.Validate(ctx, pth.Child("default"), s, s.Default.Object, nil, remainingCost)
   107  					allErrs = append(allErrs, celErrs...)
   108  
   109  					// capture the cost of both types of runs and take whichever
   110  					// leaves less remaining cost
   111  					if rmCostWithoutOldObject < rmCost {
   112  						rmCost = rmCostWithoutOldObject
   113  					}
   114  				}
   115  
   116  				remainingCost = rmCost
   117  				if remainingCost < 0 {
   118  					return allErrs, nil, remainingCost
   119  				}
   120  			}
   121  		} else {
   122  			// check whether default is pruned
   123  			if requirePrunedDefaults {
   124  				pruned := runtime.DeepCopyJSONValue(s.Default.Object)
   125  				pruning.Prune(pruned, s, s.XEmbeddedResource)
   126  				if !reflect.DeepEqual(pruned, s.Default.Object) {
   127  					allErrs = append(allErrs, field.Invalid(pth.Child("default"), s.Default.Object, "must not have unknown fields"))
   128  				}
   129  			}
   130  
   131  			// check ObjectMeta/TypeMeta and everything else
   132  			if err := schemaobjectmeta.Coerce(pth.Child("default"), s.Default.Object, s, s.XEmbeddedResource, false); err != nil {
   133  				allErrs = append(allErrs, err)
   134  			} else if errs := schemaobjectmeta.Validate(pth.Child("default"), s.Default.Object, s, s.XEmbeddedResource); len(errs) > 0 {
   135  				allErrs = append(allErrs, errs...)
   136  			} else if errs := apiservervalidation.ValidateCustomResource(pth.Child("default"), s.Default.Object, validator); len(errs) > 0 {
   137  				allErrs = append(allErrs, errs...)
   138  			} else if celValidator := cel.NewValidator(s, isResourceRoot, celconfig.PerCallLimit); celValidator != nil {
   139  				celErrs, rmCost := celValidator.Validate(ctx, pth.Child("default"), s, s.Default.Object, s.Default.Object, remainingCost)
   140  				allErrs = append(allErrs, celErrs...)
   141  
   142  				if len(celErrs) == 0 && utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CRDValidationRatcheting) {
   143  					// If ratcheting is enabled some CEL rules may use optionalOldSelf
   144  					// For such rules the above validation is not sufficient for
   145  					// determining if the default value is a valid value to introduce
   146  					// via create or uncorrelated update.
   147  					//
   148  					// Validate an update from nil to the default value to ensure
   149  					// that the default value pass
   150  					celErrs, rmCostWithoutOldObject := celValidator.Validate(ctx, pth.Child("default"), s, s.Default.Object, nil, remainingCost)
   151  					allErrs = append(allErrs, celErrs...)
   152  
   153  					// capture the cost of both types of runs and take whichever
   154  					// leaves less remaining cost
   155  					if rmCostWithoutOldObject < rmCost {
   156  						rmCost = rmCostWithoutOldObject
   157  					}
   158  				}
   159  
   160  				remainingCost = rmCost
   161  				if remainingCost < 0 {
   162  					return allErrs, nil, remainingCost
   163  				}
   164  			}
   165  		}
   166  	}
   167  
   168  	// do not follow additionalProperties because defaults are forbidden there
   169  
   170  	if s.Items != nil {
   171  		errs, err, rCost := validate(ctx, pth.Child("items"), s.Items, rootSchema, f.Index(), insideMeta, requirePrunedDefaults, remainingCost)
   172  		remainingCost = rCost
   173  		allErrs = append(allErrs, errs...)
   174  		if err != nil {
   175  			return nil, err, remainingCost
   176  		}
   177  		if remainingCost < 0 {
   178  			return allErrs, nil, remainingCost
   179  		}
   180  	}
   181  
   182  	for k, subSchema := range s.Properties {
   183  		subInsideMeta := insideMeta
   184  		if s.XEmbeddedResource && (k == "metadata" || k == "apiVersion" || k == "kind") {
   185  			subInsideMeta = true
   186  		}
   187  		errs, err, rCost := validate(ctx, pth.Child("properties").Key(k), &subSchema, rootSchema, f.Child(k), subInsideMeta, requirePrunedDefaults, remainingCost)
   188  		remainingCost = rCost
   189  		allErrs = append(allErrs, errs...)
   190  		if err != nil {
   191  			return nil, err, remainingCost
   192  		}
   193  		if remainingCost < 0 {
   194  			return allErrs, nil, remainingCost
   195  		}
   196  	}
   197  
   198  	return allErrs, nil, remainingCost
   199  }
   200  

View as plain text