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 }