k8s.io/apiserver@v0.31.1/pkg/authentication/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 24 "k8s.io/apimachinery/pkg/util/version" 25 apiservercel "k8s.io/apiserver/pkg/cel" 26 "k8s.io/apiserver/pkg/cel/environment" 27 ) 28 29 const ( 30 claimsVarName = "claims" 31 userVarName = "user" 32 ) 33 34 // compiler implements the Compiler interface. 35 type compiler struct { 36 // varEnvs is a map of CEL environments, keyed by the name of the CEL variable. 37 // The CEL variable is available to the expression. 38 // We have 2 environments, one for claims and one for user. 39 varEnvs map[string]*environment.EnvSet 40 } 41 42 // NewCompiler returns a new Compiler. 43 func NewCompiler(env *environment.EnvSet) Compiler { 44 return &compiler{ 45 varEnvs: mustBuildEnvs(env), 46 } 47 } 48 49 // CompileClaimsExpression compiles the given expressionAccessor into a CEL program that can be evaluated. 50 // The claims CEL variable is available to the expression. 51 func (c compiler) CompileClaimsExpression(expressionAccessor ExpressionAccessor) (CompilationResult, error) { 52 return c.compile(expressionAccessor, claimsVarName) 53 } 54 55 // CompileUserExpression compiles the given expressionAccessor into a CEL program that can be evaluated. 56 // The user CEL variable is available to the expression. 57 func (c compiler) CompileUserExpression(expressionAccessor ExpressionAccessor) (CompilationResult, error) { 58 return c.compile(expressionAccessor, userVarName) 59 } 60 61 func (c compiler) compile(expressionAccessor ExpressionAccessor, envVarName string) (CompilationResult, error) { 62 resultError := func(errorString string, errType apiservercel.ErrorType) (CompilationResult, error) { 63 return CompilationResult{}, &apiservercel.Error{ 64 Type: errType, 65 Detail: errorString, 66 } 67 } 68 69 env, err := c.varEnvs[envVarName].Env(environment.StoredExpressions) 70 if err != nil { 71 return resultError(fmt.Sprintf("unexpected error loading CEL environment: %v", err), apiservercel.ErrorTypeInternal) 72 } 73 74 ast, issues := env.Compile(expressionAccessor.GetExpression()) 75 if issues != nil { 76 return resultError("compilation failed: "+issues.String(), apiservercel.ErrorTypeInvalid) 77 } 78 79 found := false 80 returnTypes := expressionAccessor.ReturnTypes() 81 for _, returnType := range returnTypes { 82 if ast.OutputType() == returnType || cel.AnyType == returnType { 83 found = true 84 break 85 } 86 } 87 if !found { 88 var reason string 89 if len(returnTypes) == 1 { 90 reason = fmt.Sprintf("must evaluate to %v", returnTypes[0].String()) 91 } else { 92 reason = fmt.Sprintf("must evaluate to one of %v", returnTypes) 93 } 94 95 return resultError(reason, apiservercel.ErrorTypeInvalid) 96 } 97 98 if _, err = cel.AstToCheckedExpr(ast); err != nil { 99 // should be impossible since env.Compile returned no issues 100 return resultError("unexpected compilation error: "+err.Error(), apiservercel.ErrorTypeInternal) 101 } 102 prog, err := env.Program(ast) 103 if err != nil { 104 return resultError("program instantiation failed: "+err.Error(), apiservercel.ErrorTypeInternal) 105 } 106 107 return CompilationResult{ 108 Program: prog, 109 AST: ast, 110 ExpressionAccessor: expressionAccessor, 111 }, nil 112 } 113 114 func buildUserType() *apiservercel.DeclType { 115 field := func(name string, declType *apiservercel.DeclType, required bool) *apiservercel.DeclField { 116 return apiservercel.NewDeclField(name, declType, required, nil, nil) 117 } 118 fields := func(fields ...*apiservercel.DeclField) map[string]*apiservercel.DeclField { 119 result := make(map[string]*apiservercel.DeclField, len(fields)) 120 for _, f := range fields { 121 result[f.Name] = f 122 } 123 return result 124 } 125 126 return apiservercel.NewObjectType("kubernetes.UserInfo", fields( 127 field("username", apiservercel.StringType, false), 128 field("uid", apiservercel.StringType, false), 129 field("groups", apiservercel.NewListType(apiservercel.StringType, -1), false), 130 field("extra", apiservercel.NewMapType(apiservercel.StringType, apiservercel.NewListType(apiservercel.StringType, -1), -1), false), 131 )) 132 } 133 134 func mustBuildEnvs(baseEnv *environment.EnvSet) map[string]*environment.EnvSet { 135 buildEnvSet := func(envOpts []cel.EnvOption, declTypes []*apiservercel.DeclType) *environment.EnvSet { 136 env, err := baseEnv.Extend(environment.VersionedOptions{ 137 IntroducedVersion: version.MajorMinor(1, 0), 138 EnvOptions: envOpts, 139 DeclTypes: declTypes, 140 }) 141 if err != nil { 142 panic(fmt.Sprintf("environment misconfigured: %v", err)) 143 } 144 return env 145 } 146 147 userType := buildUserType() 148 claimsType := apiservercel.NewMapType(apiservercel.StringType, apiservercel.AnyType, -1) 149 150 envs := make(map[string]*environment.EnvSet, 2) // build two environments, one for claims and one for user 151 envs[claimsVarName] = buildEnvSet([]cel.EnvOption{cel.Variable(claimsVarName, claimsType.CelType())}, []*apiservercel.DeclType{claimsType}) 152 envs[userVarName] = buildEnvSet([]cel.EnvOption{cel.Variable(userVarName, userType.CelType())}, []*apiservercel.DeclType{userType}) 153 154 return envs 155 }