k8s.io/apiserver@v0.31.1/pkg/admission/plugin/policy/validating/typechecking.go (about)

     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 validating
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"sort"
    23  	"strings"
    24  	"time"
    25  
    26  	"github.com/google/cel-go/cel"
    27  
    28  	"k8s.io/api/admissionregistration/v1"
    29  	"k8s.io/apimachinery/pkg/api/meta"
    30  	"k8s.io/apimachinery/pkg/runtime/schema"
    31  	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
    32  	"k8s.io/apimachinery/pkg/util/sets"
    33  	"k8s.io/apimachinery/pkg/util/validation/field"
    34  	"k8s.io/apimachinery/pkg/util/version"
    35  	plugincel "k8s.io/apiserver/pkg/admission/plugin/cel"
    36  	apiservercel "k8s.io/apiserver/pkg/cel"
    37  	"k8s.io/apiserver/pkg/cel/common"
    38  	"k8s.io/apiserver/pkg/cel/environment"
    39  	"k8s.io/apiserver/pkg/cel/library"
    40  	"k8s.io/apiserver/pkg/cel/openapi"
    41  	"k8s.io/apiserver/pkg/cel/openapi/resolver"
    42  	"k8s.io/apiserver/pkg/features"
    43  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    44  	"k8s.io/klog/v2"
    45  )
    46  
    47  const maxTypesToCheck = 10
    48  
    49  type TypeChecker struct {
    50  	SchemaResolver resolver.SchemaResolver
    51  	RestMapper     meta.RESTMapper
    52  }
    53  
    54  // TypeCheckingContext holds information about the policy being type-checked.
    55  // The struct is opaque to the caller.
    56  type TypeCheckingContext struct {
    57  	gvks          []schema.GroupVersionKind
    58  	declTypes     []*apiservercel.DeclType
    59  	paramGVK      schema.GroupVersionKind
    60  	paramDeclType *apiservercel.DeclType
    61  
    62  	variables []v1.Variable
    63  }
    64  
    65  type typeOverwrite struct {
    66  	object *apiservercel.DeclType
    67  	params *apiservercel.DeclType
    68  }
    69  
    70  // TypeCheckingResult holds the issues found during type checking, any returned
    71  // error, and the gvk that the type checking is performed against.
    72  type TypeCheckingResult struct {
    73  	// GVK is the associated GVK
    74  	GVK schema.GroupVersionKind
    75  	// Issues contain machine-readable information about the typechecking result.
    76  	Issues error
    77  	// Err is the possible error that was encounter during type checking.
    78  	Err error
    79  }
    80  
    81  // TypeCheckingResults is a collection of TypeCheckingResult
    82  type TypeCheckingResults []*TypeCheckingResult
    83  
    84  func (rs TypeCheckingResults) String() string {
    85  	var messages []string
    86  	for _, r := range rs {
    87  		message := r.String()
    88  		if message != "" {
    89  			messages = append(messages, message)
    90  		}
    91  	}
    92  	return strings.Join(messages, "\n")
    93  }
    94  
    95  // String converts the result to human-readable form as a string.
    96  func (r *TypeCheckingResult) String() string {
    97  	if r.Issues == nil && r.Err == nil {
    98  		return ""
    99  	}
   100  	if r.Err != nil {
   101  		return fmt.Sprintf("%v: type checking error: %v\n", r.GVK, r.Err)
   102  	}
   103  	return fmt.Sprintf("%v: %s\n", r.GVK, r.Issues)
   104  }
   105  
   106  // Check preforms the type check against the given policy, and format the result
   107  // as []ExpressionWarning that is ready to be set in policy.Status
   108  // The result is nil if type checking returns no warning.
   109  // The policy object is NOT mutated. The caller should update Status accordingly
   110  func (c *TypeChecker) Check(policy *v1.ValidatingAdmissionPolicy) []v1.ExpressionWarning {
   111  	ctx := c.CreateContext(policy)
   112  
   113  	// warnings to return, note that the capacity is optimistically set to zero
   114  	var warnings []v1.ExpressionWarning // intentionally not setting capacity
   115  
   116  	// check main validation expressions and their message expressions, located in spec.validations[*]
   117  	fieldRef := field.NewPath("spec", "validations")
   118  	for i, v := range policy.Spec.Validations {
   119  		results := c.CheckExpression(ctx, v.Expression)
   120  		if len(results) != 0 {
   121  			warnings = append(warnings, v1.ExpressionWarning{
   122  				FieldRef: fieldRef.Index(i).Child("expression").String(),
   123  				Warning:  results.String(),
   124  			})
   125  		}
   126  		// Note that MessageExpression is optional
   127  		if v.MessageExpression == "" {
   128  			continue
   129  		}
   130  		results = c.CheckExpression(ctx, v.MessageExpression)
   131  		if len(results) != 0 {
   132  			warnings = append(warnings, v1.ExpressionWarning{
   133  				FieldRef: fieldRef.Index(i).Child("messageExpression").String(),
   134  				Warning:  results.String(),
   135  			})
   136  		}
   137  	}
   138  
   139  	return warnings
   140  }
   141  
   142  // CreateContext resolves all types and their schemas from a policy definition and creates the context.
   143  func (c *TypeChecker) CreateContext(policy *v1.ValidatingAdmissionPolicy) *TypeCheckingContext {
   144  	ctx := new(TypeCheckingContext)
   145  	allGvks := c.typesToCheck(policy)
   146  	gvks := make([]schema.GroupVersionKind, 0, len(allGvks))
   147  	declTypes := make([]*apiservercel.DeclType, 0, len(allGvks))
   148  	for _, gvk := range allGvks {
   149  		declType, err := c.declType(gvk)
   150  		if err != nil {
   151  			// type checking errors MUST NOT alter the behavior of the policy
   152  			// even if an error occurs.
   153  			if !errors.Is(err, resolver.ErrSchemaNotFound) {
   154  				// Anything except ErrSchemaNotFound is an internal error
   155  				klog.V(2).ErrorS(err, "internal error: schema resolution failure", "gvk", gvk)
   156  			}
   157  			// skip for not found or internal error
   158  			continue
   159  		}
   160  		gvks = append(gvks, gvk)
   161  		declTypes = append(declTypes, declType)
   162  	}
   163  	ctx.gvks = gvks
   164  	ctx.declTypes = declTypes
   165  
   166  	paramsGVK := c.paramsGVK(policy) // maybe empty, correctly handled
   167  	paramsDeclType, err := c.declType(paramsGVK)
   168  	if err != nil {
   169  		if !errors.Is(err, resolver.ErrSchemaNotFound) {
   170  			klog.V(2).ErrorS(err, "internal error: cannot resolve schema for params", "gvk", paramsGVK)
   171  		}
   172  		paramsDeclType = nil
   173  	}
   174  	ctx.paramGVK = paramsGVK
   175  	ctx.paramDeclType = paramsDeclType
   176  	ctx.variables = policy.Spec.Variables
   177  	return ctx
   178  }
   179  
   180  func (c *TypeChecker) compiler(ctx *TypeCheckingContext, typeOverwrite typeOverwrite) (*plugincel.CompositedCompiler, error) {
   181  	envSet, err := buildEnvSet(
   182  		/* hasParams */ ctx.paramDeclType != nil,
   183  		/* hasAuthorizer */ true,
   184  		typeOverwrite)
   185  	if err != nil {
   186  		return nil, err
   187  	}
   188  	env, err := plugincel.NewCompositionEnv(plugincel.VariablesTypeName, envSet)
   189  	if err != nil {
   190  		return nil, err
   191  	}
   192  	compiler := &plugincel.CompositedCompiler{
   193  		Compiler:       &typeCheckingCompiler{typeOverwrite: typeOverwrite, compositionEnv: env},
   194  		CompositionEnv: env,
   195  	}
   196  	return compiler, nil
   197  }
   198  
   199  // CheckExpression type checks a single expression, given the context
   200  func (c *TypeChecker) CheckExpression(ctx *TypeCheckingContext, expression string) TypeCheckingResults {
   201  	var results TypeCheckingResults
   202  	for i, gvk := range ctx.gvks {
   203  		declType := ctx.declTypes[i]
   204  		compiler, err := c.compiler(ctx, typeOverwrite{
   205  			object: declType,
   206  			params: ctx.paramDeclType,
   207  		})
   208  		if err != nil {
   209  			utilruntime.HandleError(err)
   210  			continue
   211  		}
   212  		options := plugincel.OptionalVariableDeclarations{
   213  			HasParams:     ctx.paramDeclType != nil,
   214  			HasAuthorizer: true,
   215  			StrictCost:    utilfeature.DefaultFeatureGate.Enabled(features.StrictCostEnforcementForVAP),
   216  		}
   217  		compiler.CompileAndStoreVariables(convertv1beta1Variables(ctx.variables), options, environment.StoredExpressions)
   218  		result := compiler.CompileCELExpression(celExpression(expression), options, environment.StoredExpressions)
   219  		if err := result.Error; err != nil {
   220  			typeCheckingResult := &TypeCheckingResult{GVK: gvk}
   221  			if err.Type == apiservercel.ErrorTypeInvalid {
   222  				typeCheckingResult.Issues = err
   223  			} else {
   224  				typeCheckingResult.Err = err
   225  			}
   226  			results = append(results, typeCheckingResult)
   227  		}
   228  	}
   229  	return results
   230  }
   231  
   232  type celExpression string
   233  
   234  func (c celExpression) GetExpression() string {
   235  	return string(c)
   236  }
   237  
   238  func (c celExpression) ReturnTypes() []*cel.Type {
   239  	return []*cel.Type{cel.AnyType}
   240  }
   241  func generateUniqueTypeName(kind string) string {
   242  	return fmt.Sprintf("%s%d", kind, time.Now().Nanosecond())
   243  }
   244  
   245  func (c *TypeChecker) declType(gvk schema.GroupVersionKind) (*apiservercel.DeclType, error) {
   246  	if gvk.Empty() {
   247  		return nil, nil
   248  	}
   249  	s, err := c.SchemaResolver.ResolveSchema(gvk)
   250  	if err != nil {
   251  		return nil, err
   252  	}
   253  	return common.SchemaDeclType(&openapi.Schema{Schema: s}, true).MaybeAssignTypeName(generateUniqueTypeName(gvk.Kind)), nil
   254  }
   255  
   256  func (c *TypeChecker) paramsGVK(policy *v1.ValidatingAdmissionPolicy) schema.GroupVersionKind {
   257  	if policy.Spec.ParamKind == nil {
   258  		return schema.GroupVersionKind{}
   259  	}
   260  	gv, err := schema.ParseGroupVersion(policy.Spec.ParamKind.APIVersion)
   261  	if err != nil {
   262  		return schema.GroupVersionKind{}
   263  	}
   264  	return gv.WithKind(policy.Spec.ParamKind.Kind)
   265  }
   266  
   267  // typesToCheck extracts a list of GVKs that needs type checking from the policy
   268  // the result is sorted in the order of Group, Version, and Kind
   269  func (c *TypeChecker) typesToCheck(p *v1.ValidatingAdmissionPolicy) []schema.GroupVersionKind {
   270  	gvks := sets.New[schema.GroupVersionKind]()
   271  	if p.Spec.MatchConstraints == nil || len(p.Spec.MatchConstraints.ResourceRules) == 0 {
   272  		return nil
   273  	}
   274  	restMapperRefreshAttempted := false // at most once per policy, refresh RESTMapper and retry resolution.
   275  	for _, rule := range p.Spec.MatchConstraints.ResourceRules {
   276  		groups := extractGroups(&rule.Rule)
   277  		if len(groups) == 0 {
   278  			continue
   279  		}
   280  		versions := extractVersions(&rule.Rule)
   281  		if len(versions) == 0 {
   282  			continue
   283  		}
   284  		resources := extractResources(&rule.Rule)
   285  		if len(resources) == 0 {
   286  			continue
   287  		}
   288  		// sort GVRs so that the loop below provides
   289  		// consistent results.
   290  		sort.Strings(groups)
   291  		sort.Strings(versions)
   292  		sort.Strings(resources)
   293  		count := 0
   294  		for _, group := range groups {
   295  			for _, version := range versions {
   296  				for _, resource := range resources {
   297  					gvr := schema.GroupVersionResource{
   298  						Group:    group,
   299  						Version:  version,
   300  						Resource: resource,
   301  					}
   302  					resolved, err := c.RestMapper.KindsFor(gvr)
   303  					if err != nil {
   304  						if restMapperRefreshAttempted {
   305  							// RESTMapper refresh happens at most once per policy
   306  							continue
   307  						}
   308  						c.tryRefreshRESTMapper()
   309  						restMapperRefreshAttempted = true
   310  						resolved, err = c.RestMapper.KindsFor(gvr)
   311  						if err != nil {
   312  							continue
   313  						}
   314  					}
   315  					for _, r := range resolved {
   316  						if !r.Empty() {
   317  							gvks.Insert(r)
   318  							count++
   319  							// early return if maximum number of types are already
   320  							// collected
   321  							if count == maxTypesToCheck {
   322  								if gvks.Len() == 0 {
   323  									return nil
   324  								}
   325  								return sortGVKList(gvks.UnsortedList())
   326  							}
   327  						}
   328  					}
   329  				}
   330  			}
   331  		}
   332  	}
   333  	if gvks.Len() == 0 {
   334  		return nil
   335  	}
   336  	return sortGVKList(gvks.UnsortedList())
   337  }
   338  
   339  func extractGroups(rule *v1.Rule) []string {
   340  	groups := make([]string, 0, len(rule.APIGroups))
   341  	for _, group := range rule.APIGroups {
   342  		// give up if wildcard
   343  		if strings.ContainsAny(group, "*") {
   344  			return nil
   345  		}
   346  		groups = append(groups, group)
   347  	}
   348  	return groups
   349  }
   350  
   351  func extractVersions(rule *v1.Rule) []string {
   352  	versions := make([]string, 0, len(rule.APIVersions))
   353  	for _, version := range rule.APIVersions {
   354  		if strings.ContainsAny(version, "*") {
   355  			return nil
   356  		}
   357  		versions = append(versions, version)
   358  	}
   359  	return versions
   360  }
   361  
   362  func extractResources(rule *v1.Rule) []string {
   363  	resources := make([]string, 0, len(rule.Resources))
   364  	for _, resource := range rule.Resources {
   365  		// skip wildcard and subresources
   366  		if strings.ContainsAny(resource, "*/") {
   367  			continue
   368  		}
   369  		resources = append(resources, resource)
   370  	}
   371  	return resources
   372  }
   373  
   374  // sortGVKList sorts the list by Group, Version, and Kind
   375  // returns the list itself.
   376  func sortGVKList(list []schema.GroupVersionKind) []schema.GroupVersionKind {
   377  	sort.Slice(list, func(i, j int) bool {
   378  		if g := strings.Compare(list[i].Group, list[j].Group); g != 0 {
   379  			return g < 0
   380  		}
   381  		if v := strings.Compare(list[i].Version, list[j].Version); v != 0 {
   382  			return v < 0
   383  		}
   384  		return strings.Compare(list[i].Kind, list[j].Kind) < 0
   385  	})
   386  	return list
   387  }
   388  
   389  // tryRefreshRESTMapper refreshes the RESTMapper if it supports refreshing.
   390  func (c *TypeChecker) tryRefreshRESTMapper() {
   391  	if r, ok := c.RestMapper.(meta.ResettableRESTMapper); ok {
   392  		r.Reset()
   393  	}
   394  }
   395  
   396  func buildEnvSet(hasParams bool, hasAuthorizer bool, types typeOverwrite) (*environment.EnvSet, error) {
   397  	baseEnv := environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), utilfeature.DefaultFeatureGate.Enabled(features.StrictCostEnforcementForVAP))
   398  	requestType := plugincel.BuildRequestType()
   399  	namespaceType := plugincel.BuildNamespaceType()
   400  
   401  	var varOpts []cel.EnvOption
   402  	var declTypes []*apiservercel.DeclType
   403  
   404  	// namespace, hand-crafted type
   405  	declTypes = append(declTypes, namespaceType)
   406  	varOpts = append(varOpts, createVariableOpts(namespaceType, plugincel.NamespaceVarName)...)
   407  
   408  	// request, hand-crafted type
   409  	declTypes = append(declTypes, requestType)
   410  	varOpts = append(varOpts, createVariableOpts(requestType, plugincel.RequestVarName)...)
   411  
   412  	// object and oldObject, same type, type(s) resolved from constraints
   413  	declTypes = append(declTypes, types.object)
   414  	varOpts = append(varOpts, createVariableOpts(types.object, plugincel.ObjectVarName, plugincel.OldObjectVarName)...)
   415  
   416  	// params, defined by ParamKind
   417  	if hasParams && types.params != nil {
   418  		declTypes = append(declTypes, types.params)
   419  		varOpts = append(varOpts, createVariableOpts(types.params, plugincel.ParamsVarName)...)
   420  	}
   421  
   422  	// authorizer, implicitly available to all expressions of a policy
   423  	if hasAuthorizer {
   424  		// we only need its structure but not the variable itself
   425  		varOpts = append(varOpts, cel.Variable("authorizer", library.AuthorizerType))
   426  	}
   427  
   428  	return baseEnv.Extend(
   429  		environment.VersionedOptions{
   430  			// Feature epoch was actually 1.26, but we artificially set it to 1.0 because these
   431  			// options should always be present.
   432  			IntroducedVersion: version.MajorMinor(1, 0),
   433  			EnvOptions:        varOpts,
   434  			DeclTypes:         declTypes,
   435  		},
   436  	)
   437  }
   438  
   439  // createVariableOpts creates a slice of EnvOption
   440  // that can be used for creating a CEL env containing variables of declType.
   441  // declType can be nil, in which case the variables will be of DynType.
   442  func createVariableOpts(declType *apiservercel.DeclType, variables ...string) []cel.EnvOption {
   443  	opts := make([]cel.EnvOption, 0, len(variables))
   444  	t := cel.DynType
   445  	if declType != nil {
   446  		t = declType.CelType()
   447  	}
   448  	for _, v := range variables {
   449  		opts = append(opts, cel.Variable(v, t))
   450  	}
   451  	return opts
   452  }
   453  
   454  type typeCheckingCompiler struct {
   455  	compositionEnv *plugincel.CompositionEnv
   456  	typeOverwrite  typeOverwrite
   457  }
   458  
   459  // CompileCELExpression compiles the given expression.
   460  // The implementation is the same as that of staging/src/k8s.io/apiserver/pkg/admission/plugin/cel/compile.go
   461  // except that:
   462  // - object, oldObject, and params are typed instead of Dyn
   463  // - compiler does not enforce the output type
   464  // - the compiler does not initialize the program
   465  func (c *typeCheckingCompiler) CompileCELExpression(expressionAccessor plugincel.ExpressionAccessor, options plugincel.OptionalVariableDeclarations, mode environment.Type) plugincel.CompilationResult {
   466  	resultError := func(errorString string, errType apiservercel.ErrorType) plugincel.CompilationResult {
   467  		return plugincel.CompilationResult{
   468  			Error: &apiservercel.Error{
   469  				Type:   errType,
   470  				Detail: errorString,
   471  			},
   472  			ExpressionAccessor: expressionAccessor,
   473  		}
   474  	}
   475  	env, err := c.compositionEnv.Env(mode)
   476  	if err != nil {
   477  		return resultError(fmt.Sprintf("fail to build env: %v", err), apiservercel.ErrorTypeInternal)
   478  	}
   479  	ast, issues := env.Compile(expressionAccessor.GetExpression())
   480  	if issues != nil {
   481  		return resultError(issues.String(), apiservercel.ErrorTypeInvalid)
   482  	}
   483  	// type checker does not require the program, however the type must still be set.
   484  	return plugincel.CompilationResult{
   485  		OutputType: ast.OutputType(),
   486  	}
   487  }
   488  
   489  var _ plugincel.Compiler = (*typeCheckingCompiler)(nil)