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  }