k8s.io/apiserver@v0.31.1/pkg/admission/plugin/policy/validating/validator.go (about) 1 /* 2 Copyright 2022 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 validating 18 19 import ( 20 "context" 21 "errors" 22 "fmt" 23 "strings" 24 25 celtypes "github.com/google/cel-go/common/types" 26 27 v1 "k8s.io/api/admissionregistration/v1" 28 corev1 "k8s.io/api/core/v1" 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 "k8s.io/apimachinery/pkg/runtime" 31 "k8s.io/apimachinery/pkg/runtime/schema" 32 "k8s.io/apiserver/pkg/admission" 33 "k8s.io/apiserver/pkg/admission/plugin/cel" 34 "k8s.io/apiserver/pkg/admission/plugin/webhook/matchconditions" 35 celconfig "k8s.io/apiserver/pkg/apis/cel" 36 "k8s.io/apiserver/pkg/authorization/authorizer" 37 apiservercel "k8s.io/apiserver/pkg/cel" 38 "k8s.io/klog/v2" 39 ) 40 41 // validator implements the Validator interface 42 type validator struct { 43 celMatcher matchconditions.Matcher 44 validationFilter cel.Filter 45 auditAnnotationFilter cel.Filter 46 messageFilter cel.Filter 47 failPolicy *v1.FailurePolicyType 48 } 49 50 func NewValidator(validationFilter cel.Filter, celMatcher matchconditions.Matcher, auditAnnotationFilter, messageFilter cel.Filter, failPolicy *v1.FailurePolicyType) Validator { 51 return &validator{ 52 celMatcher: celMatcher, 53 validationFilter: validationFilter, 54 auditAnnotationFilter: auditAnnotationFilter, 55 messageFilter: messageFilter, 56 failPolicy: failPolicy, 57 } 58 } 59 60 func policyDecisionActionForError(f v1.FailurePolicyType) PolicyDecisionAction { 61 if f == v1.Ignore { 62 return ActionAdmit 63 } 64 return ActionDeny 65 } 66 67 func auditAnnotationEvaluationForError(f v1.FailurePolicyType) PolicyAuditAnnotationAction { 68 if f == v1.Ignore { 69 return AuditAnnotationActionExclude 70 } 71 return AuditAnnotationActionError 72 } 73 74 // Validate takes a list of Evaluation and a failure policy and converts them into actionable PolicyDecisions 75 // runtimeCELCostBudget was added for testing purpose only. Callers should always use const RuntimeCELCostBudget from k8s.io/apiserver/pkg/apis/cel/config.go as input. 76 77 func (v *validator) Validate(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *corev1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult { 78 var f v1.FailurePolicyType 79 if v.failPolicy == nil { 80 f = v1.Fail 81 } else { 82 f = *v.failPolicy 83 } 84 if v.celMatcher != nil { 85 matchResults := v.celMatcher.Match(ctx, versionedAttr, versionedParams, authz) 86 if matchResults.Error != nil { 87 return ValidateResult{ 88 Decisions: []PolicyDecision{ 89 { 90 Action: policyDecisionActionForError(f), 91 Evaluation: EvalError, 92 Message: matchResults.Error.Error(), 93 }, 94 }, 95 } 96 } 97 98 // if preconditions are not met, then do not return any validations 99 if !matchResults.Matches { 100 return ValidateResult{} 101 } 102 } 103 104 optionalVars := cel.OptionalVariableBindings{VersionedParams: versionedParams, Authorizer: authz} 105 expressionOptionalVars := cel.OptionalVariableBindings{VersionedParams: versionedParams} 106 admissionRequest := cel.CreateAdmissionRequest(versionedAttr.Attributes, metav1.GroupVersionResource(matchedResource), metav1.GroupVersionKind(versionedAttr.VersionedKind)) 107 // Decide which fields are exposed 108 ns := cel.CreateNamespaceObject(namespace) 109 evalResults, remainingBudget, err := v.validationFilter.ForInput(ctx, versionedAttr, admissionRequest, optionalVars, ns, runtimeCELCostBudget) 110 if err != nil { 111 return ValidateResult{ 112 Decisions: []PolicyDecision{ 113 { 114 Action: policyDecisionActionForError(f), 115 Evaluation: EvalError, 116 Message: err.Error(), 117 }, 118 }, 119 } 120 } 121 decisions := make([]PolicyDecision, len(evalResults)) 122 messageResults, _, err := v.messageFilter.ForInput(ctx, versionedAttr, admissionRequest, expressionOptionalVars, ns, remainingBudget) 123 for i, evalResult := range evalResults { 124 var decision = &decisions[i] 125 // TODO: move this to generics 126 validation, ok := evalResult.ExpressionAccessor.(*ValidationCondition) 127 if !ok { 128 klog.Error("Invalid type conversion to ValidationCondition") 129 decision.Action = policyDecisionActionForError(f) 130 decision.Evaluation = EvalError 131 decision.Message = "Invalid type sent to validator, expected ValidationCondition" 132 continue 133 } 134 135 var messageResult *cel.EvaluationResult 136 if len(messageResults) > i { 137 messageResult = &messageResults[i] 138 } 139 if evalResult.Error != nil { 140 decision.Action = policyDecisionActionForError(f) 141 decision.Evaluation = EvalError 142 decision.Message = evalResult.Error.Error() 143 } else if errors.Is(err, apiservercel.ErrInternal) || errors.Is(err, apiservercel.ErrOutOfBudget) { 144 decision.Action = policyDecisionActionForError(f) 145 decision.Evaluation = EvalError 146 decision.Message = fmt.Sprintf("failed messageExpression: %s", err) 147 } else if evalResult.EvalResult != celtypes.True { 148 decision.Action = ActionDeny 149 if validation.Reason == nil { 150 decision.Reason = metav1.StatusReasonInvalid 151 } else { 152 decision.Reason = *validation.Reason 153 } 154 // decide the failure message 155 var message string 156 // attempt to set message with messageExpression result 157 if messageResult != nil && messageResult.Error == nil && messageResult.EvalResult != nil { 158 // also fallback if the eval result is non-string (including null) or 159 // whitespaces. 160 if message, ok = messageResult.EvalResult.Value().(string); ok { 161 message = strings.TrimSpace(message) 162 // deny excessively long message from EvalResult 163 if len(message) > celconfig.MaxEvaluatedMessageExpressionSizeBytes { 164 klog.V(2).InfoS("excessively long message denied", "message", message) 165 message = "" 166 } 167 // deny message that contains newlines 168 if strings.ContainsAny(message, "\n") { 169 klog.V(2).InfoS("multi-line message denied", "message", message) 170 message = "" 171 } 172 } 173 } 174 if messageResult != nil && messageResult.Error != nil { 175 // log any error with messageExpression 176 klog.V(2).ErrorS(messageResult.Error, "error while evaluating messageExpression") 177 } 178 // fallback to set message to the custom message 179 if message == "" && len(validation.Message) > 0 { 180 message = strings.TrimSpace(validation.Message) 181 } 182 // fallback to use the expression to compose a message 183 if message == "" { 184 message = fmt.Sprintf("failed expression: %v", strings.TrimSpace(validation.Expression)) 185 } 186 decision.Message = message 187 } else { 188 decision.Action = ActionAdmit 189 decision.Evaluation = EvalAdmit 190 } 191 } 192 193 options := cel.OptionalVariableBindings{VersionedParams: versionedParams} 194 auditAnnotationEvalResults, _, err := v.auditAnnotationFilter.ForInput(ctx, versionedAttr, admissionRequest, options, namespace, runtimeCELCostBudget) 195 if err != nil { 196 return ValidateResult{ 197 Decisions: []PolicyDecision{ 198 { 199 Action: policyDecisionActionForError(f), 200 Evaluation: EvalError, 201 Message: err.Error(), 202 }, 203 }, 204 } 205 } 206 207 auditAnnotationResults := make([]PolicyAuditAnnotation, len(auditAnnotationEvalResults)) 208 for i, evalResult := range auditAnnotationEvalResults { 209 if evalResult.ExpressionAccessor == nil { 210 continue 211 } 212 var auditAnnotationResult = &auditAnnotationResults[i] 213 // TODO: move this to generics 214 validation, ok := evalResult.ExpressionAccessor.(*AuditAnnotationCondition) 215 if !ok { 216 klog.Error("Invalid type conversion to AuditAnnotationCondition") 217 auditAnnotationResult.Action = auditAnnotationEvaluationForError(f) 218 auditAnnotationResult.Error = fmt.Sprintf("Invalid type sent to validator, expected AuditAnnotationCondition but got %T", evalResult.ExpressionAccessor) 219 continue 220 } 221 auditAnnotationResult.Key = validation.Key 222 223 if evalResult.Error != nil { 224 auditAnnotationResult.Action = auditAnnotationEvaluationForError(f) 225 auditAnnotationResult.Error = evalResult.Error.Error() 226 } else { 227 switch evalResult.EvalResult.Type() { 228 case celtypes.StringType: 229 value := strings.TrimSpace(evalResult.EvalResult.Value().(string)) 230 if len(value) == 0 { 231 auditAnnotationResult.Action = AuditAnnotationActionExclude 232 } else { 233 auditAnnotationResult.Action = AuditAnnotationActionPublish 234 auditAnnotationResult.Value = value 235 } 236 case celtypes.NullType: 237 auditAnnotationResult.Action = AuditAnnotationActionExclude 238 default: 239 auditAnnotationResult.Action = AuditAnnotationActionError 240 auditAnnotationResult.Error = fmt.Sprintf("valueExpression '%v' resulted in unsupported return type: %v. "+ 241 "Return type must be either string or null.", validation.ValueExpression, evalResult.EvalResult.Type()) 242 } 243 } 244 } 245 return ValidateResult{Decisions: decisions, AuditAnnotations: auditAnnotationResults} 246 }