k8s.io/apiserver@v0.31.1/pkg/admission/plugin/cel/compile.go (about)

     1  /*
     2  Copyright 2022 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 cel
    18  
    19  import (
    20  	"fmt"
    21  
    22  	"github.com/google/cel-go/cel"
    23  
    24  	"k8s.io/apimachinery/pkg/util/version"
    25  	celconfig "k8s.io/apiserver/pkg/apis/cel"
    26  	apiservercel "k8s.io/apiserver/pkg/cel"
    27  	"k8s.io/apiserver/pkg/cel/environment"
    28  	"k8s.io/apiserver/pkg/cel/library"
    29  )
    30  
    31  const (
    32  	ObjectVarName                    = "object"
    33  	OldObjectVarName                 = "oldObject"
    34  	ParamsVarName                    = "params"
    35  	RequestVarName                   = "request"
    36  	NamespaceVarName                 = "namespaceObject"
    37  	AuthorizerVarName                = "authorizer"
    38  	RequestResourceAuthorizerVarName = "authorizer.requestResource"
    39  	VariableVarName                  = "variables"
    40  )
    41  
    42  // BuildRequestType generates a DeclType for AdmissionRequest. This may be replaced with a utility that
    43  // converts the native type definition to apiservercel.DeclType once such a utility becomes available.
    44  // The 'uid' field is omitted since it is not needed for in-process admission review.
    45  // The 'object' and 'oldObject' fields are omitted since they are exposed as root level CEL variables.
    46  func BuildRequestType() *apiservercel.DeclType {
    47  	field := func(name string, declType *apiservercel.DeclType, required bool) *apiservercel.DeclField {
    48  		return apiservercel.NewDeclField(name, declType, required, nil, nil)
    49  	}
    50  	fields := func(fields ...*apiservercel.DeclField) map[string]*apiservercel.DeclField {
    51  		result := make(map[string]*apiservercel.DeclField, len(fields))
    52  		for _, f := range fields {
    53  			result[f.Name] = f
    54  		}
    55  		return result
    56  	}
    57  	gvkType := apiservercel.NewObjectType("kubernetes.GroupVersionKind", fields(
    58  		field("group", apiservercel.StringType, true),
    59  		field("version", apiservercel.StringType, true),
    60  		field("kind", apiservercel.StringType, true),
    61  	))
    62  	gvrType := apiservercel.NewObjectType("kubernetes.GroupVersionResource", fields(
    63  		field("group", apiservercel.StringType, true),
    64  		field("version", apiservercel.StringType, true),
    65  		field("resource", apiservercel.StringType, true),
    66  	))
    67  	userInfoType := apiservercel.NewObjectType("kubernetes.UserInfo", fields(
    68  		field("username", apiservercel.StringType, false),
    69  		field("uid", apiservercel.StringType, false),
    70  		field("groups", apiservercel.NewListType(apiservercel.StringType, -1), false),
    71  		field("extra", apiservercel.NewMapType(apiservercel.StringType, apiservercel.NewListType(apiservercel.StringType, -1), -1), false),
    72  	))
    73  	return apiservercel.NewObjectType("kubernetes.AdmissionRequest", fields(
    74  		field("kind", gvkType, true),
    75  		field("resource", gvrType, true),
    76  		field("subResource", apiservercel.StringType, false),
    77  		field("requestKind", gvkType, true),
    78  		field("requestResource", gvrType, true),
    79  		field("requestSubResource", apiservercel.StringType, false),
    80  		field("name", apiservercel.StringType, true),
    81  		field("namespace", apiservercel.StringType, false),
    82  		field("operation", apiservercel.StringType, true),
    83  		field("userInfo", userInfoType, true),
    84  		field("dryRun", apiservercel.BoolType, false),
    85  		field("options", apiservercel.DynType, false),
    86  	))
    87  }
    88  
    89  // BuildNamespaceType generates a DeclType for Namespace.
    90  // Certain nested fields in Namespace (e.g. managedFields, ownerReferences etc.) are omitted in the generated DeclType
    91  // by design.
    92  func BuildNamespaceType() *apiservercel.DeclType {
    93  	field := func(name string, declType *apiservercel.DeclType, required bool) *apiservercel.DeclField {
    94  		return apiservercel.NewDeclField(name, declType, required, nil, nil)
    95  	}
    96  	fields := func(fields ...*apiservercel.DeclField) map[string]*apiservercel.DeclField {
    97  		result := make(map[string]*apiservercel.DeclField, len(fields))
    98  		for _, f := range fields {
    99  			result[f.Name] = f
   100  		}
   101  		return result
   102  	}
   103  
   104  	specType := apiservercel.NewObjectType("kubernetes.NamespaceSpec", fields(
   105  		field("finalizers", apiservercel.NewListType(apiservercel.StringType, -1), true),
   106  	))
   107  	conditionType := apiservercel.NewObjectType("kubernetes.NamespaceCondition", fields(
   108  		field("status", apiservercel.StringType, true),
   109  		field("type", apiservercel.StringType, true),
   110  		field("lastTransitionTime", apiservercel.TimestampType, true),
   111  		field("message", apiservercel.StringType, true),
   112  		field("reason", apiservercel.StringType, true),
   113  	))
   114  	statusType := apiservercel.NewObjectType("kubernetes.NamespaceStatus", fields(
   115  		field("conditions", apiservercel.NewListType(conditionType, -1), true),
   116  		field("phase", apiservercel.StringType, true),
   117  	))
   118  	metadataType := apiservercel.NewObjectType("kubernetes.NamespaceMetadata", fields(
   119  		field("name", apiservercel.StringType, true),
   120  		field("generateName", apiservercel.StringType, true),
   121  		field("namespace", apiservercel.StringType, true),
   122  		field("labels", apiservercel.NewMapType(apiservercel.StringType, apiservercel.StringType, -1), true),
   123  		field("annotations", apiservercel.NewMapType(apiservercel.StringType, apiservercel.StringType, -1), true),
   124  		field("UID", apiservercel.StringType, true),
   125  		field("creationTimestamp", apiservercel.TimestampType, true),
   126  		field("deletionGracePeriodSeconds", apiservercel.IntType, true),
   127  		field("deletionTimestamp", apiservercel.TimestampType, true),
   128  		field("generation", apiservercel.IntType, true),
   129  		field("resourceVersion", apiservercel.StringType, true),
   130  		field("finalizers", apiservercel.NewListType(apiservercel.StringType, -1), true),
   131  	))
   132  	return apiservercel.NewObjectType("kubernetes.Namespace", fields(
   133  		field("metadata", metadataType, true),
   134  		field("spec", specType, true),
   135  		field("status", statusType, true),
   136  	))
   137  }
   138  
   139  // CompilationResult represents a compiled validations expression.
   140  type CompilationResult struct {
   141  	Program            cel.Program
   142  	Error              *apiservercel.Error
   143  	ExpressionAccessor ExpressionAccessor
   144  	OutputType         *cel.Type
   145  }
   146  
   147  // Compiler provides a CEL expression compiler configured with the desired admission related CEL variables and
   148  // environment mode.
   149  type Compiler interface {
   150  	CompileCELExpression(expressionAccessor ExpressionAccessor, options OptionalVariableDeclarations, mode environment.Type) CompilationResult
   151  }
   152  
   153  type compiler struct {
   154  	varEnvs variableDeclEnvs
   155  }
   156  
   157  func NewCompiler(env *environment.EnvSet) Compiler {
   158  	return &compiler{varEnvs: mustBuildEnvs(env)}
   159  }
   160  
   161  type variableDeclEnvs map[OptionalVariableDeclarations]*environment.EnvSet
   162  
   163  // CompileCELExpression returns a compiled CEL expression.
   164  // perCallLimit was added for testing purpose only. Callers should always use const PerCallLimit from k8s.io/apiserver/pkg/apis/cel/config.go as input.
   165  func (c compiler) CompileCELExpression(expressionAccessor ExpressionAccessor, options OptionalVariableDeclarations, envType environment.Type) CompilationResult {
   166  	resultError := func(errorString string, errType apiservercel.ErrorType, cause error) CompilationResult {
   167  		return CompilationResult{
   168  			Error: &apiservercel.Error{
   169  				Type:   errType,
   170  				Detail: errorString,
   171  				Cause:  cause,
   172  			},
   173  			ExpressionAccessor: expressionAccessor,
   174  		}
   175  	}
   176  
   177  	env, err := c.varEnvs[options].Env(envType)
   178  	if err != nil {
   179  		return resultError(fmt.Sprintf("unexpected error loading CEL environment: %v", err), apiservercel.ErrorTypeInternal, nil)
   180  	}
   181  
   182  	ast, issues := env.Compile(expressionAccessor.GetExpression())
   183  	if issues != nil {
   184  		return resultError("compilation failed: "+issues.String(), apiservercel.ErrorTypeInvalid, apiservercel.NewCompilationError(issues))
   185  	}
   186  	found := false
   187  	returnTypes := expressionAccessor.ReturnTypes()
   188  	for _, returnType := range returnTypes {
   189  		if ast.OutputType() == returnType || cel.AnyType == returnType {
   190  			found = true
   191  			break
   192  		}
   193  	}
   194  	if !found {
   195  		var reason string
   196  		if len(returnTypes) == 1 {
   197  			reason = fmt.Sprintf("must evaluate to %v", returnTypes[0].String())
   198  		} else {
   199  			reason = fmt.Sprintf("must evaluate to one of %v", returnTypes)
   200  		}
   201  
   202  		return resultError(reason, apiservercel.ErrorTypeInvalid, nil)
   203  	}
   204  
   205  	_, err = cel.AstToCheckedExpr(ast)
   206  	if err != nil {
   207  		// should be impossible since env.Compile returned no issues
   208  		return resultError("unexpected compilation error: "+err.Error(), apiservercel.ErrorTypeInternal, nil)
   209  	}
   210  	prog, err := env.Program(ast,
   211  		cel.InterruptCheckFrequency(celconfig.CheckFrequency),
   212  	)
   213  	if err != nil {
   214  		return resultError("program instantiation failed: "+err.Error(), apiservercel.ErrorTypeInternal, nil)
   215  	}
   216  	return CompilationResult{
   217  		Program:            prog,
   218  		ExpressionAccessor: expressionAccessor,
   219  		OutputType:         ast.OutputType(),
   220  	}
   221  }
   222  
   223  func mustBuildEnvs(baseEnv *environment.EnvSet) variableDeclEnvs {
   224  	requestType := BuildRequestType()
   225  	namespaceType := BuildNamespaceType()
   226  	envs := make(variableDeclEnvs, 8) // since the number of variable combinations is small, pre-build a environment for each
   227  	for _, hasParams := range []bool{false, true} {
   228  		for _, hasAuthorizer := range []bool{false, true} {
   229  			for _, strictCost := range []bool{false, true} {
   230  				var envOpts []cel.EnvOption
   231  				if hasParams {
   232  					envOpts = append(envOpts, cel.Variable(ParamsVarName, cel.DynType))
   233  				}
   234  				if hasAuthorizer {
   235  					envOpts = append(envOpts,
   236  						cel.Variable(AuthorizerVarName, library.AuthorizerType),
   237  						cel.Variable(RequestResourceAuthorizerVarName, library.ResourceCheckType))
   238  				}
   239  				envOpts = append(envOpts,
   240  					cel.Variable(ObjectVarName, cel.DynType),
   241  					cel.Variable(OldObjectVarName, cel.DynType),
   242  					cel.Variable(NamespaceVarName, namespaceType.CelType()),
   243  					cel.Variable(RequestVarName, requestType.CelType()))
   244  
   245  				extended, err := baseEnv.Extend(
   246  					environment.VersionedOptions{
   247  						// Feature epoch was actually 1.26, but we artificially set it to 1.0 because these
   248  						// options should always be present.
   249  						IntroducedVersion: version.MajorMinor(1, 0),
   250  						EnvOptions:        envOpts,
   251  						DeclTypes: []*apiservercel.DeclType{
   252  							namespaceType,
   253  							requestType,
   254  						},
   255  					},
   256  				)
   257  				if err != nil {
   258  					panic(fmt.Sprintf("environment misconfigured: %v", err))
   259  				}
   260  				if strictCost {
   261  					extended, err = extended.Extend(environment.StrictCostOpt)
   262  					if err != nil {
   263  						panic(fmt.Sprintf("environment misconfigured: %v", err))
   264  					}
   265  				}
   266  				envs[OptionalVariableDeclarations{HasParams: hasParams, HasAuthorizer: hasAuthorizer, StrictCost: strictCost}] = extended
   267  			}
   268  		}
   269  	}
   270  	return envs
   271  }