k8s.io/apiserver@v0.31.1/pkg/authorization/cel/matcher.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  	"context"
    21  	"fmt"
    22  	"time"
    23  
    24  	celgo "github.com/google/cel-go/cel"
    25  
    26  	authorizationv1 "k8s.io/api/authorization/v1"
    27  	utilerrors "k8s.io/apimachinery/pkg/util/errors"
    28  )
    29  
    30  type CELMatcher struct {
    31  	CompilationResults []CompilationResult
    32  
    33  	// These track if any expressions use fieldSelector and labelSelector,
    34  	// so construction of data passed to the CEL expression can be optimized if those fields are unused.
    35  	UsesLabelSelector bool
    36  	UsesFieldSelector bool
    37  
    38  	// These are optional fields which can be populated if metrics reporting is desired
    39  	Metrics        MatcherMetrics
    40  	AuthorizerType string
    41  	AuthorizerName string
    42  }
    43  
    44  // eval evaluates the given SubjectAccessReview against all cel matchCondition expression
    45  func (c *CELMatcher) Eval(ctx context.Context, r *authorizationv1.SubjectAccessReview) (bool, error) {
    46  	var evalErrors []error
    47  
    48  	metrics := c.Metrics
    49  	if metrics == nil {
    50  		metrics = NoopMatcherMetrics{}
    51  	}
    52  	start := time.Now()
    53  	defer func() {
    54  		metrics.RecordAuthorizationMatchConditionEvaluation(ctx, c.AuthorizerType, c.AuthorizerName, time.Since(start))
    55  		if len(evalErrors) > 0 {
    56  			metrics.RecordAuthorizationMatchConditionEvaluationFailure(ctx, c.AuthorizerType, c.AuthorizerName)
    57  		}
    58  	}()
    59  
    60  	va := map[string]interface{}{
    61  		"request": convertObjectToUnstructured(&r.Spec, c.UsesFieldSelector, c.UsesLabelSelector),
    62  	}
    63  	for _, compilationResult := range c.CompilationResults {
    64  		evalResult, _, err := compilationResult.Program.ContextEval(ctx, va)
    65  		if err != nil {
    66  			evalErrors = append(evalErrors, fmt.Errorf("cel evaluation error: expression '%v' resulted in error: %w", compilationResult.ExpressionAccessor.GetExpression(), err))
    67  			continue
    68  		}
    69  		if evalResult.Type() != celgo.BoolType {
    70  			evalErrors = append(evalErrors, fmt.Errorf("cel evaluation error: expression '%v' eval result type should be bool but got %W", compilationResult.ExpressionAccessor.GetExpression(), evalResult.Type()))
    71  			continue
    72  		}
    73  		match, ok := evalResult.Value().(bool)
    74  		if !ok {
    75  			evalErrors = append(evalErrors, fmt.Errorf("cel evaluation error: expression '%v' eval result value should be bool but got %W", compilationResult.ExpressionAccessor.GetExpression(), evalResult.Value()))
    76  			continue
    77  		}
    78  		// If at least one matchCondition successfully evaluates to FALSE,
    79  		// return early
    80  		if !match {
    81  			metrics.RecordAuthorizationMatchConditionExclusion(ctx, c.AuthorizerType, c.AuthorizerName)
    82  			return false, nil
    83  		}
    84  	}
    85  	// if there is any error, return
    86  	if len(evalErrors) > 0 {
    87  		return false, utilerrors.NewAggregate(evalErrors)
    88  	}
    89  	// return ALL matchConditions evaluate to TRUE successfully without error
    90  	return true, nil
    91  }