k8s.io/apiserver@v0.31.1/pkg/authorization/cel/compile.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 cel 18 19 import ( 20 "fmt" 21 22 "github.com/google/cel-go/cel" 23 celast "github.com/google/cel-go/common/ast" 24 "github.com/google/cel-go/common/operators" 25 "github.com/google/cel-go/common/types/ref" 26 27 authorizationv1 "k8s.io/api/authorization/v1" 28 "k8s.io/apimachinery/pkg/util/version" 29 apiservercel "k8s.io/apiserver/pkg/cel" 30 "k8s.io/apiserver/pkg/cel/environment" 31 genericfeatures "k8s.io/apiserver/pkg/features" 32 utilfeature "k8s.io/apiserver/pkg/util/feature" 33 ) 34 35 const ( 36 subjectAccessReviewRequestVarName = "request" 37 38 fieldSelectorVarName = "fieldSelector" 39 labelSelectorVarName = "labelSelector" 40 ) 41 42 // CompilationResult represents a compiled authorization cel expression. 43 type CompilationResult struct { 44 Program cel.Program 45 ExpressionAccessor ExpressionAccessor 46 47 // These track if a given expression uses fieldSelector and labelSelector, 48 // so construction of data passed to the CEL expression can be optimized if those fields are unused. 49 UsesFieldSelector bool 50 UsesLabelSelector bool 51 } 52 53 // EvaluationResult contains the minimal required fields and metadata of a cel evaluation 54 type EvaluationResult struct { 55 EvalResult ref.Val 56 ExpressionAccessor ExpressionAccessor 57 } 58 59 // Compiler is an interface for compiling CEL expressions with the desired environment mode. 60 type Compiler interface { 61 CompileCELExpression(expressionAccessor ExpressionAccessor) (CompilationResult, error) 62 } 63 64 type compiler struct { 65 envSet *environment.EnvSet 66 } 67 68 // NewCompiler returns a new Compiler. 69 func NewCompiler(env *environment.EnvSet) Compiler { 70 return &compiler{ 71 envSet: mustBuildEnv(env), 72 } 73 } 74 75 func (c compiler) CompileCELExpression(expressionAccessor ExpressionAccessor) (CompilationResult, error) { 76 resultError := func(errorString string, errType apiservercel.ErrorType) (CompilationResult, error) { 77 err := &apiservercel.Error{ 78 Type: errType, 79 Detail: errorString, 80 } 81 return CompilationResult{ 82 ExpressionAccessor: expressionAccessor, 83 }, err 84 } 85 env, err := c.envSet.Env(environment.StoredExpressions) 86 if err != nil { 87 return resultError(fmt.Sprintf("unexpected error loading CEL environment: %v", err), apiservercel.ErrorTypeInternal) 88 } 89 ast, issues := env.Compile(expressionAccessor.GetExpression()) 90 if issues != nil { 91 return resultError("compilation failed: "+issues.String(), apiservercel.ErrorTypeInvalid) 92 } 93 found := false 94 returnTypes := expressionAccessor.ReturnTypes() 95 for _, returnType := range returnTypes { 96 if ast.OutputType() == returnType { 97 found = true 98 break 99 } 100 } 101 if !found { 102 var reason string 103 if len(returnTypes) == 1 { 104 reason = fmt.Sprintf("must evaluate to %v but got %v", returnTypes[0].String(), ast.OutputType()) 105 } else { 106 reason = fmt.Sprintf("must evaluate to one of %v", returnTypes) 107 } 108 109 return resultError(reason, apiservercel.ErrorTypeInvalid) 110 } 111 checkedExpr, err := cel.AstToCheckedExpr(ast) 112 if err != nil { 113 // should be impossible since env.Compile returned no issues 114 return resultError("unexpected compilation error: "+err.Error(), apiservercel.ErrorTypeInternal) 115 } 116 celAST, err := celast.ToAST(checkedExpr) 117 if err != nil { 118 // should be impossible since env.Compile returned no issues 119 return resultError("unexpected compilation error: "+err.Error(), apiservercel.ErrorTypeInternal) 120 } 121 122 var usesFieldSelector, usesLabelSelector bool 123 celast.PreOrderVisit(celast.NavigateAST(celAST), celast.NewExprVisitor(func(e celast.Expr) { 124 // we already know we use both, no need to inspect more 125 if usesFieldSelector && usesLabelSelector { 126 return 127 } 128 129 var fieldName string 130 switch e.Kind() { 131 case celast.SelectKind: 132 // simple select (.fieldSelector / .labelSelector) 133 fieldName = e.AsSelect().FieldName() 134 case celast.CallKind: 135 // optional select (.?fieldSelector / .?labelSelector) 136 if e.AsCall().FunctionName() != operators.OptSelect { 137 return 138 } 139 args := e.AsCall().Args() 140 // args[0] is the receiver (what comes before the `.?`), args[1] is the field name being optionally selected (what comes after the `.?`) 141 if len(args) != 2 || args[1].Kind() != celast.LiteralKind || args[1].AsLiteral().Type() != cel.StringType { 142 return 143 } 144 fieldName, _ = args[1].AsLiteral().Value().(string) 145 } 146 147 switch fieldName { 148 case fieldSelectorVarName: 149 usesFieldSelector = true 150 case labelSelectorVarName: 151 usesLabelSelector = true 152 } 153 })) 154 155 prog, err := env.Program(ast) 156 if err != nil { 157 return resultError("program instantiation failed: "+err.Error(), apiservercel.ErrorTypeInternal) 158 } 159 return CompilationResult{ 160 Program: prog, 161 ExpressionAccessor: expressionAccessor, 162 UsesFieldSelector: usesFieldSelector, 163 UsesLabelSelector: usesLabelSelector, 164 }, nil 165 } 166 167 func mustBuildEnv(baseEnv *environment.EnvSet) *environment.EnvSet { 168 field := func(name string, declType *apiservercel.DeclType, required bool) *apiservercel.DeclField { 169 return apiservercel.NewDeclField(name, declType, required, nil, nil) 170 } 171 fields := func(fields ...*apiservercel.DeclField) map[string]*apiservercel.DeclField { 172 result := make(map[string]*apiservercel.DeclField, len(fields)) 173 for _, f := range fields { 174 result[f.Name] = f 175 } 176 return result 177 } 178 subjectAccessReviewSpecRequestType := buildRequestType(field, fields) 179 extended, err := baseEnv.Extend( 180 environment.VersionedOptions{ 181 // we record this as 1.0 since it was available in the 182 // first version that supported this feature 183 IntroducedVersion: version.MajorMinor(1, 0), 184 EnvOptions: []cel.EnvOption{ 185 cel.Variable(subjectAccessReviewRequestVarName, subjectAccessReviewSpecRequestType.CelType()), 186 }, 187 DeclTypes: []*apiservercel.DeclType{ 188 subjectAccessReviewSpecRequestType, 189 }, 190 }, 191 ) 192 if err != nil { 193 panic(fmt.Sprintf("environment misconfigured: %v", err)) 194 } 195 196 return extended 197 } 198 199 // buildRequestType generates a DeclType for SubjectAccessReviewSpec. 200 // if attributes are added here, also add to convertObjectToUnstructured. 201 func buildRequestType(field func(name string, declType *apiservercel.DeclType, required bool) *apiservercel.DeclField, fields func(fields ...*apiservercel.DeclField) map[string]*apiservercel.DeclField) *apiservercel.DeclType { 202 resourceAttributesType := buildResourceAttributesType(field, fields) 203 nonResourceAttributesType := buildNonResourceAttributesType(field, fields) 204 return apiservercel.NewObjectType("kubernetes.SubjectAccessReviewSpec", fields( 205 field("resourceAttributes", resourceAttributesType, false), 206 field("nonResourceAttributes", nonResourceAttributesType, false), 207 field("user", apiservercel.StringType, false), 208 field("groups", apiservercel.NewListType(apiservercel.StringType, -1), false), 209 field("extra", apiservercel.NewMapType(apiservercel.StringType, apiservercel.NewListType(apiservercel.StringType, -1), -1), false), 210 field("uid", apiservercel.StringType, false), 211 )) 212 } 213 214 // buildResourceAttributesType generates a DeclType for ResourceAttributes. 215 // if attributes are added here, also add to convertObjectToUnstructured. 216 func buildResourceAttributesType(field func(name string, declType *apiservercel.DeclType, required bool) *apiservercel.DeclField, fields func(fields ...*apiservercel.DeclField) map[string]*apiservercel.DeclField) *apiservercel.DeclType { 217 resourceAttributesFields := []*apiservercel.DeclField{ 218 field("namespace", apiservercel.StringType, false), 219 field("verb", apiservercel.StringType, false), 220 field("group", apiservercel.StringType, false), 221 field("version", apiservercel.StringType, false), 222 field("resource", apiservercel.StringType, false), 223 field("subresource", apiservercel.StringType, false), 224 field("name", apiservercel.StringType, false), 225 } 226 if utilfeature.DefaultFeatureGate.Enabled(genericfeatures.AuthorizeWithSelectors) { 227 resourceAttributesFields = append(resourceAttributesFields, field("fieldSelector", buildFieldSelectorType(field, fields), false)) 228 resourceAttributesFields = append(resourceAttributesFields, field("labelSelector", buildLabelSelectorType(field, fields), false)) 229 } 230 return apiservercel.NewObjectType("kubernetes.ResourceAttributes", fields(resourceAttributesFields...)) 231 } 232 233 func buildFieldSelectorType(field func(name string, declType *apiservercel.DeclType, required bool) *apiservercel.DeclField, fields func(fields ...*apiservercel.DeclField) map[string]*apiservercel.DeclField) *apiservercel.DeclType { 234 return apiservercel.NewObjectType("kubernetes.FieldSelectorAttributes", fields( 235 field("rawSelector", apiservercel.StringType, false), 236 field("requirements", apiservercel.NewListType(buildSelectorRequirementType(field, fields), -1), false), 237 )) 238 } 239 240 func buildLabelSelectorType(field func(name string, declType *apiservercel.DeclType, required bool) *apiservercel.DeclField, fields func(fields ...*apiservercel.DeclField) map[string]*apiservercel.DeclField) *apiservercel.DeclType { 241 return apiservercel.NewObjectType("kubernetes.LabelSelectorAttributes", fields( 242 field("rawSelector", apiservercel.StringType, false), 243 field("requirements", apiservercel.NewListType(buildSelectorRequirementType(field, fields), -1), false), 244 )) 245 } 246 247 func buildSelectorRequirementType(field func(name string, declType *apiservercel.DeclType, required bool) *apiservercel.DeclField, fields func(fields ...*apiservercel.DeclField) map[string]*apiservercel.DeclField) *apiservercel.DeclType { 248 return apiservercel.NewObjectType("kubernetes.SelectorRequirement", fields( 249 field("key", apiservercel.StringType, false), 250 field("operator", apiservercel.StringType, false), 251 field("values", apiservercel.NewListType(apiservercel.StringType, -1), false), 252 )) 253 } 254 255 // buildNonResourceAttributesType generates a DeclType for NonResourceAttributes. 256 // if attributes are added here, also add to convertObjectToUnstructured. 257 func buildNonResourceAttributesType(field func(name string, declType *apiservercel.DeclType, required bool) *apiservercel.DeclField, fields func(fields ...*apiservercel.DeclField) map[string]*apiservercel.DeclField) *apiservercel.DeclType { 258 return apiservercel.NewObjectType("kubernetes.NonResourceAttributes", fields( 259 field("path", apiservercel.StringType, false), 260 field("verb", apiservercel.StringType, false), 261 )) 262 } 263 264 func convertObjectToUnstructured(obj *authorizationv1.SubjectAccessReviewSpec, includeFieldSelector, includeLabelSelector bool) map[string]interface{} { 265 // Construct version containing every SubjectAccessReview user and string attribute field, even omitempty ones, for evaluation by CEL 266 extra := obj.Extra 267 if extra == nil { 268 extra = map[string]authorizationv1.ExtraValue{} 269 } 270 ret := map[string]interface{}{ 271 "user": obj.User, 272 "groups": obj.Groups, 273 "uid": string(obj.UID), 274 "extra": extra, 275 } 276 if obj.ResourceAttributes != nil { 277 resourceAttributes := map[string]interface{}{ 278 "namespace": obj.ResourceAttributes.Namespace, 279 "verb": obj.ResourceAttributes.Verb, 280 "group": obj.ResourceAttributes.Group, 281 "version": obj.ResourceAttributes.Version, 282 "resource": obj.ResourceAttributes.Resource, 283 "subresource": obj.ResourceAttributes.Subresource, 284 "name": obj.ResourceAttributes.Name, 285 } 286 287 if includeFieldSelector && obj.ResourceAttributes.FieldSelector != nil { 288 if len(obj.ResourceAttributes.FieldSelector.Requirements) > 0 { 289 requirements := make([]map[string]interface{}, 0, len(obj.ResourceAttributes.FieldSelector.Requirements)) 290 for _, r := range obj.ResourceAttributes.FieldSelector.Requirements { 291 requirements = append(requirements, map[string]interface{}{ 292 "key": r.Key, 293 "operator": r.Operator, 294 "values": r.Values, 295 }) 296 } 297 resourceAttributes[fieldSelectorVarName] = map[string]interface{}{"requirements": requirements} 298 } 299 if len(obj.ResourceAttributes.FieldSelector.RawSelector) > 0 { 300 resourceAttributes[fieldSelectorVarName] = map[string]interface{}{"rawSelector": obj.ResourceAttributes.FieldSelector.RawSelector} 301 } 302 } 303 304 if includeLabelSelector && obj.ResourceAttributes.LabelSelector != nil { 305 if len(obj.ResourceAttributes.LabelSelector.Requirements) > 0 { 306 requirements := make([]map[string]interface{}, 0, len(obj.ResourceAttributes.LabelSelector.Requirements)) 307 for _, r := range obj.ResourceAttributes.LabelSelector.Requirements { 308 requirements = append(requirements, map[string]interface{}{ 309 "key": r.Key, 310 "operator": r.Operator, 311 "values": r.Values, 312 }) 313 } 314 resourceAttributes[labelSelectorVarName] = map[string]interface{}{"requirements": requirements} 315 } 316 if len(obj.ResourceAttributes.LabelSelector.RawSelector) > 0 { 317 resourceAttributes[labelSelectorVarName] = map[string]interface{}{"rawSelector": obj.ResourceAttributes.LabelSelector.RawSelector} 318 } 319 } 320 321 ret["resourceAttributes"] = resourceAttributes 322 } 323 if obj.NonResourceAttributes != nil { 324 ret["nonResourceAttributes"] = map[string]string{ 325 "verb": obj.NonResourceAttributes.Verb, 326 "path": obj.NonResourceAttributes.Path, 327 } 328 } 329 return ret 330 }