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  }