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 }