k8s.io/apiserver@v0.31.1/pkg/admission/plugin/webhook/matchconditions/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 matchconditions
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"fmt"
    23  	"time"
    24  
    25  	"github.com/google/cel-go/cel"
    26  	celtypes "github.com/google/cel-go/common/types"
    27  
    28  	v1 "k8s.io/api/admissionregistration/v1"
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	"k8s.io/apimachinery/pkg/runtime"
    31  	utilerrors "k8s.io/apimachinery/pkg/util/errors"
    32  	"k8s.io/apiserver/pkg/admission"
    33  	admissionmetrics "k8s.io/apiserver/pkg/admission/metrics"
    34  	celplugin "k8s.io/apiserver/pkg/admission/plugin/cel"
    35  	celconfig "k8s.io/apiserver/pkg/apis/cel"
    36  	"k8s.io/apiserver/pkg/authorization/authorizer"
    37  	"k8s.io/klog/v2"
    38  )
    39  
    40  var _ celplugin.ExpressionAccessor = &MatchCondition{}
    41  
    42  // MatchCondition contains the inputs needed to compile, evaluate and match a cel expression
    43  type MatchCondition v1.MatchCondition
    44  
    45  func (v *MatchCondition) GetExpression() string {
    46  	return v.Expression
    47  }
    48  
    49  func (v *MatchCondition) ReturnTypes() []*cel.Type {
    50  	return []*cel.Type{cel.BoolType}
    51  }
    52  
    53  var _ Matcher = &matcher{}
    54  
    55  // matcher evaluates compiled cel expressions and determines if they match the given request or not
    56  type matcher struct {
    57  	filter      celplugin.Filter
    58  	failPolicy  v1.FailurePolicyType
    59  	matcherType string
    60  	matcherKind string
    61  	objectName  string
    62  }
    63  
    64  func NewMatcher(filter celplugin.Filter, failPolicy *v1.FailurePolicyType, matcherKind, matcherType, objectName string) Matcher {
    65  	var f v1.FailurePolicyType
    66  	if failPolicy == nil {
    67  		f = v1.Fail
    68  	} else {
    69  		f = *failPolicy
    70  	}
    71  	return &matcher{
    72  		filter:      filter,
    73  		failPolicy:  f,
    74  		matcherKind: matcherKind,
    75  		matcherType: matcherType,
    76  		objectName:  objectName,
    77  	}
    78  }
    79  
    80  func (m *matcher) Match(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, authz authorizer.Authorizer) MatchResult {
    81  	t := time.Now()
    82  	evalResults, _, err := m.filter.ForInput(ctx, versionedAttr, celplugin.CreateAdmissionRequest(versionedAttr.Attributes, metav1.GroupVersionResource(versionedAttr.GetResource()), metav1.GroupVersionKind(versionedAttr.VersionedKind)), celplugin.OptionalVariableBindings{
    83  		VersionedParams: versionedParams,
    84  		Authorizer:      authz,
    85  	}, nil, celconfig.RuntimeCELCostBudgetMatchConditions)
    86  
    87  	if err != nil {
    88  		admissionmetrics.Metrics.ObserveMatchConditionEvaluationTime(ctx, time.Since(t), m.objectName, m.matcherKind, m.matcherType, string(versionedAttr.GetOperation()))
    89  		// filter returning error is unexpected and not an evaluation error so not incrementing metric here
    90  		if m.failPolicy == v1.Fail {
    91  			return MatchResult{
    92  				Error: err,
    93  			}
    94  		} else if m.failPolicy == v1.Ignore {
    95  			return MatchResult{
    96  				Matches: false,
    97  			}
    98  		}
    99  		//TODO: add default so that if in future we add different failure types it doesn't fall through
   100  	}
   101  
   102  	errorList := []error{}
   103  	for _, evalResult := range evalResults {
   104  		matchCondition, ok := evalResult.ExpressionAccessor.(*MatchCondition)
   105  		if !ok {
   106  			// This shouldnt happen, but if it does treat same as eval error
   107  			klog.Error("Invalid type conversion to MatchCondition")
   108  			errorList = append(errorList, errors.New(fmt.Sprintf("internal error converting ExpressionAccessor to MatchCondition")))
   109  			continue
   110  		}
   111  		if evalResult.Error != nil {
   112  			errorList = append(errorList, evalResult.Error)
   113  			admissionmetrics.Metrics.ObserveMatchConditionEvalError(ctx, m.objectName, m.matcherKind, m.matcherType, string(versionedAttr.GetOperation()))
   114  		}
   115  		if evalResult.EvalResult == celtypes.False {
   116  			admissionmetrics.Metrics.ObserveMatchConditionEvaluationTime(ctx, time.Since(t), m.objectName, m.matcherKind, m.matcherType, string(versionedAttr.GetOperation()))
   117  			// If any condition false, skip calling webhook always
   118  			return MatchResult{
   119  				Matches:             false,
   120  				FailedConditionName: matchCondition.Name,
   121  			}
   122  		}
   123  	}
   124  	if len(errorList) > 0 {
   125  		admissionmetrics.Metrics.ObserveMatchConditionEvaluationTime(ctx, time.Since(t), m.objectName, m.matcherKind, m.matcherType, string(versionedAttr.GetOperation()))
   126  		// If mix of true and eval errors then resort to fail policy
   127  		if m.failPolicy == v1.Fail {
   128  			// mix of true and errors with fail policy fail should fail request without calling webhook
   129  			err = utilerrors.NewAggregate(errorList)
   130  			return MatchResult{
   131  				Error: err,
   132  			}
   133  		} else if m.failPolicy == v1.Ignore {
   134  			// if fail policy ignore then skip call to webhook
   135  			return MatchResult{
   136  				Matches: false,
   137  			}
   138  		}
   139  	}
   140  	// if no results eval to false, return matches true with list of any errors encountered
   141  	return MatchResult{
   142  		Matches: true,
   143  	}
   144  }