...

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

Documentation: k8s.io/apiextensions-apiserver/pkg/apiserver/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  	"reflect"
    21  
    22  	"k8s.io/apiserver/pkg/cel/common"
    23  	celopenapi "k8s.io/apiserver/pkg/cel/openapi"
    24  	"k8s.io/kube-openapi/pkg/validation/spec"
    25  	"k8s.io/kube-openapi/pkg/validation/strfmt"
    26  	"k8s.io/kube-openapi/pkg/validation/validate"
    27  )
    28  
    29  // schemaArgs are the arguments to constructor for OpenAPI schema validator,
    30  // NewSchemaValidator
    31  type schemaArgs struct {
    32  	schema       *spec.Schema
    33  	root         interface{}
    34  	path         string
    35  	knownFormats strfmt.Registry
    36  	options      []validate.Option
    37  }
    38  
    39  // RatchetingSchemaValidator wraps kube-openapis SchemaValidator to provide a
    40  // ValidateUpdate function which allows ratcheting
    41  type RatchetingSchemaValidator struct {
    42  	schemaArgs
    43  }
    44  
    45  func NewRatchetingSchemaValidator(schema *spec.Schema, rootSchema interface{}, root string, formats strfmt.Registry, options ...validate.Option) *RatchetingSchemaValidator {
    46  	return &RatchetingSchemaValidator{
    47  		schemaArgs: schemaArgs{
    48  			schema:       schema,
    49  			root:         rootSchema,
    50  			path:         root,
    51  			knownFormats: formats,
    52  			options:      options,
    53  		},
    54  	}
    55  }
    56  
    57  func (r *RatchetingSchemaValidator) Validate(new interface{}, options ...ValidationOption) *validate.Result {
    58  	sv := validate.NewSchemaValidator(r.schema, r.root, r.path, r.knownFormats, r.options...)
    59  	return sv.Validate(new)
    60  }
    61  
    62  func (r *RatchetingSchemaValidator) ValidateUpdate(new, old interface{}, options ...ValidationOption) *validate.Result {
    63  	opts := NewValidationOptions(options...)
    64  
    65  	if !opts.Ratcheting {
    66  		sv := validate.NewSchemaValidator(r.schema, r.root, r.path, r.knownFormats, r.options...)
    67  		return sv.Validate(new)
    68  	}
    69  
    70  	correlation := opts.CorrelatedObject
    71  	if correlation == nil {
    72  		correlation = common.NewCorrelatedObject(new, old, &celopenapi.Schema{Schema: r.schema})
    73  	}
    74  
    75  	return newRatchetingValueValidator(
    76  		correlation,
    77  		r.schemaArgs,
    78  	).Validate(new)
    79  }
    80  
    81  // ratchetingValueValidator represents an invocation of SchemaValidator.ValidateUpdate
    82  // for specific arguments for `old` and `new`
    83  //
    84  // It follows the openapi SchemaValidator down its traversal of the new value
    85  // by injecting validate.Option into each recursive invocation.
    86  //
    87  // A ratchetingValueValidator will be constructed and added to the tree for
    88  // each explored sub-index and sub-property during validation.
    89  //
    90  // It's main job is to keep the old/new values correlated as the traversal
    91  // continues, and postprocess errors according to our ratcheting policy.
    92  //
    93  // ratchetingValueValidator is not thread safe.
    94  type ratchetingValueValidator struct {
    95  	// schemaArgs provides the arguments to use in the temporary SchemaValidator
    96  	// that is created during a call to Validate.
    97  	schemaArgs
    98  	correlation *common.CorrelatedObject
    99  }
   100  
   101  func newRatchetingValueValidator(correlation *common.CorrelatedObject, args schemaArgs) *ratchetingValueValidator {
   102  	return &ratchetingValueValidator{
   103  		schemaArgs:  args,
   104  		correlation: correlation,
   105  	}
   106  }
   107  
   108  // getValidateOption provides a kube-openapi validate.Option for SchemaValidator
   109  // that injects a ratchetingValueValidator to be used for all subkeys and subindices
   110  func (r *ratchetingValueValidator) getValidateOption() validate.Option {
   111  	return func(svo *validate.SchemaValidatorOptions) {
   112  		svo.NewValidatorForField = r.SubPropertyValidator
   113  		svo.NewValidatorForIndex = r.SubIndexValidator
   114  	}
   115  }
   116  
   117  // Validate validates the update from r.oldValue to r.value
   118  //
   119  // During evaluation, a temporary tree of ratchetingValueValidator is built for all
   120  // traversed field paths. It is necessary to build the tree to take advantage of
   121  // DeepEqual checks performed by lower levels of the object during validation without
   122  // greatly modifying `kube-openapi`'s implementation.
   123  //
   124  // The tree, and all cache storage/scratch space for the validation of a single
   125  // call to `Validate` is thrown away at the end of the top-level call
   126  // to `Validate`.
   127  //
   128  // `Validate` will create a node in the tree to for each of the explored children.
   129  // The node's main purpose is to store a lazily computed DeepEqual check between
   130  // the oldValue and the currently passed value. If the check is performed, it
   131  // will be stored in the node to be re-used by a parent node during a DeepEqual
   132  // comparison, if necessary.
   133  //
   134  // This call has a side-effect of populating it's `children` variable with
   135  // the explored nodes of the object tree.
   136  func (r *ratchetingValueValidator) Validate(new interface{}) *validate.Result {
   137  	opts := append([]validate.Option{
   138  		r.getValidateOption(),
   139  	}, r.options...)
   140  
   141  	s := validate.NewSchemaValidator(r.schema, r.root, r.path, r.knownFormats, opts...)
   142  
   143  	res := s.Validate(r.correlation.Value)
   144  
   145  	if res.IsValid() {
   146  		return res
   147  	}
   148  
   149  	// Current ratcheting rule is to ratchet errors if DeepEqual(old, new) is true.
   150  	if r.correlation.CachedDeepEqual() {
   151  		newRes := &validate.Result{}
   152  		newRes.MergeAsWarnings(res)
   153  		return newRes
   154  	}
   155  
   156  	return res
   157  }
   158  
   159  // SubPropertyValidator overrides the standard validator constructor for sub-properties by
   160  // returning our special ratcheting variant.
   161  //
   162  // If we can correlate an old value, we return a ratcheting validator to
   163  // use for the child.
   164  //
   165  // If the old value cannot be correlated, then default validation is used.
   166  func (r *ratchetingValueValidator) SubPropertyValidator(field string, schema *spec.Schema, rootSchema interface{}, root string, formats strfmt.Registry, options ...validate.Option) validate.ValueValidator {
   167  	childNode := r.correlation.Key(field)
   168  	if childNode == nil || (r.path == "" && isTypeMetaField(field)) {
   169  		// Defer to default validation if we cannot correlate the old value
   170  		// or if we are validating the root object and the field is a metadata
   171  		// field.
   172  		//
   173  		// We cannot ratchet changes to the APIVersion field since they aren't visible.
   174  		// (both old and new are converted to the same type)
   175  		return validate.NewSchemaValidator(schema, rootSchema, root, formats, options...)
   176  	}
   177  
   178  	return newRatchetingValueValidator(childNode, schemaArgs{
   179  		schema:       schema,
   180  		root:         rootSchema,
   181  		path:         root,
   182  		knownFormats: formats,
   183  		options:      options,
   184  	})
   185  }
   186  
   187  // SubIndexValidator overrides the standard validator constructor for sub-indicies by
   188  // returning our special ratcheting variant.
   189  //
   190  // If we can correlate an old value, we return a ratcheting validator to
   191  // use for the child.
   192  //
   193  // If the old value cannot be correlated, then default validation is used.
   194  func (r *ratchetingValueValidator) SubIndexValidator(index int, schema *spec.Schema, rootSchema interface{}, root string, formats strfmt.Registry, options ...validate.Option) validate.ValueValidator {
   195  	childNode := r.correlation.Index(index)
   196  	if childNode == nil {
   197  		return validate.NewSchemaValidator(schema, rootSchema, root, formats, options...)
   198  	}
   199  
   200  	return newRatchetingValueValidator(childNode, schemaArgs{
   201  		schema:       schema,
   202  		root:         rootSchema,
   203  		path:         root,
   204  		knownFormats: formats,
   205  		options:      options,
   206  	})
   207  }
   208  
   209  var _ validate.ValueValidator = (&ratchetingValueValidator{})
   210  
   211  func (r ratchetingValueValidator) SetPath(path string) {
   212  	// Do nothing
   213  	// Unused by kube-openapi
   214  }
   215  
   216  func (r ratchetingValueValidator) Applies(source interface{}, valueKind reflect.Kind) bool {
   217  	return true
   218  }
   219  
   220  func isTypeMetaField(path string) bool {
   221  	return path == "kind" || path == "apiVersion"
   222  }
   223  

View as plain text