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  }