Add mutation support into CompositedCompiler and reorganize for clarity
Kubernetes-commit: 081353bf8ad963d43c5da6714a24f62cfe0b8401
This commit is contained in:
parent
9ead80d1bb
commit
0e6467b270
|
@ -0,0 +1,190 @@
|
||||||
|
/*
|
||||||
|
Copyright 2024 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cel
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/google/cel-go/interpreter"
|
||||||
|
"math"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
admissionv1 "k8s.io/api/admission/v1"
|
||||||
|
v1 "k8s.io/api/core/v1"
|
||||||
|
"k8s.io/apiserver/pkg/admission"
|
||||||
|
"k8s.io/apiserver/pkg/cel"
|
||||||
|
"k8s.io/apiserver/pkg/cel/library"
|
||||||
|
)
|
||||||
|
|
||||||
|
// newActivation creates an activation for CEL admission plugins from the given request, admission chain and
|
||||||
|
// variable binding information.
|
||||||
|
func newActivation(compositionCtx CompositionContext, versionedAttr *admission.VersionedAttributes, request *admissionv1.AdmissionRequest, inputs OptionalVariableBindings, namespace *v1.Namespace) (*evaluationActivation, error) {
|
||||||
|
oldObjectVal, err := objectToResolveVal(versionedAttr.VersionedOldObject)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to prepare oldObject variable for evaluation: %w", err)
|
||||||
|
}
|
||||||
|
objectVal, err := objectToResolveVal(versionedAttr.VersionedObject)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to prepare object variable for evaluation: %w", err)
|
||||||
|
}
|
||||||
|
var paramsVal, authorizerVal, requestResourceAuthorizerVal any
|
||||||
|
if inputs.VersionedParams != nil {
|
||||||
|
paramsVal, err = objectToResolveVal(inputs.VersionedParams)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to prepare params variable for evaluation: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if inputs.Authorizer != nil {
|
||||||
|
authorizerVal = library.NewAuthorizerVal(versionedAttr.GetUserInfo(), inputs.Authorizer)
|
||||||
|
requestResourceAuthorizerVal = library.NewResourceAuthorizerVal(versionedAttr.GetUserInfo(), inputs.Authorizer, versionedAttr)
|
||||||
|
}
|
||||||
|
|
||||||
|
requestVal, err := convertObjectToUnstructured(request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to prepare request variable for evaluation: %w", err)
|
||||||
|
}
|
||||||
|
namespaceVal, err := objectToResolveVal(namespace)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to prepare namespace variable for evaluation: %w", err)
|
||||||
|
}
|
||||||
|
va := &evaluationActivation{
|
||||||
|
object: objectVal,
|
||||||
|
oldObject: oldObjectVal,
|
||||||
|
params: paramsVal,
|
||||||
|
request: requestVal.Object,
|
||||||
|
namespace: namespaceVal,
|
||||||
|
authorizer: authorizerVal,
|
||||||
|
requestResourceAuthorizer: requestResourceAuthorizerVal,
|
||||||
|
}
|
||||||
|
|
||||||
|
// composition is an optional feature that only applies for ValidatingAdmissionPolicy and MutatingAdmissionPolicy.
|
||||||
|
if compositionCtx != nil {
|
||||||
|
va.variables = compositionCtx.Variables(va)
|
||||||
|
}
|
||||||
|
return va, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type evaluationActivation struct {
|
||||||
|
object, oldObject, params, request, namespace, authorizer, requestResourceAuthorizer, variables interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResolveName returns a value from the activation by qualified name, or false if the name
|
||||||
|
// could not be found.
|
||||||
|
func (a *evaluationActivation) ResolveName(name string) (interface{}, bool) {
|
||||||
|
switch name {
|
||||||
|
case ObjectVarName:
|
||||||
|
return a.object, true
|
||||||
|
case OldObjectVarName:
|
||||||
|
return a.oldObject, true
|
||||||
|
case ParamsVarName:
|
||||||
|
return a.params, true // params may be null
|
||||||
|
case RequestVarName:
|
||||||
|
return a.request, true
|
||||||
|
case NamespaceVarName:
|
||||||
|
return a.namespace, true
|
||||||
|
case AuthorizerVarName:
|
||||||
|
return a.authorizer, a.authorizer != nil
|
||||||
|
case RequestResourceAuthorizerVarName:
|
||||||
|
return a.requestResourceAuthorizer, a.requestResourceAuthorizer != nil
|
||||||
|
case VariableVarName: // variables always present
|
||||||
|
return a.variables, true
|
||||||
|
default:
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parent returns the parent of the current activation, may be nil.
|
||||||
|
// If non-nil, the parent will be searched during resolve calls.
|
||||||
|
func (a *evaluationActivation) Parent() interpreter.Activation {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evaluate runs a compiled CEL admission plugin expression using the provided activation and CEL
|
||||||
|
// runtime cost budget.
|
||||||
|
func (a *evaluationActivation) Evaluate(ctx context.Context, compositionCtx CompositionContext, compilationResult CompilationResult, remainingBudget int64) (EvaluationResult, int64, error) {
|
||||||
|
var evaluation = EvaluationResult{}
|
||||||
|
if compilationResult.ExpressionAccessor == nil { // in case of placeholder
|
||||||
|
return evaluation, remainingBudget, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
evaluation.ExpressionAccessor = compilationResult.ExpressionAccessor
|
||||||
|
if compilationResult.Error != nil {
|
||||||
|
evaluation.Error = &cel.Error{
|
||||||
|
Type: cel.ErrorTypeInvalid,
|
||||||
|
Detail: fmt.Sprintf("compilation error: %v", compilationResult.Error),
|
||||||
|
Cause: compilationResult.Error,
|
||||||
|
}
|
||||||
|
return evaluation, remainingBudget, nil
|
||||||
|
}
|
||||||
|
if compilationResult.Program == nil {
|
||||||
|
evaluation.Error = &cel.Error{
|
||||||
|
Type: cel.ErrorTypeInternal,
|
||||||
|
Detail: "unexpected internal error compiling expression",
|
||||||
|
}
|
||||||
|
return evaluation, remainingBudget, nil
|
||||||
|
}
|
||||||
|
t1 := time.Now()
|
||||||
|
evalResult, evalDetails, err := compilationResult.Program.ContextEval(ctx, a)
|
||||||
|
// budget may be spent due to lazy evaluation of composited variables
|
||||||
|
if compositionCtx != nil {
|
||||||
|
compositionCost := compositionCtx.GetAndResetCost()
|
||||||
|
if compositionCost > remainingBudget {
|
||||||
|
return evaluation, -1, &cel.Error{
|
||||||
|
Type: cel.ErrorTypeInvalid,
|
||||||
|
Detail: "validation failed due to running out of cost budget, no further validation rules will be run",
|
||||||
|
Cause: cel.ErrOutOfBudget,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
remainingBudget -= compositionCost
|
||||||
|
}
|
||||||
|
elapsed := time.Since(t1)
|
||||||
|
evaluation.Elapsed = elapsed
|
||||||
|
if evalDetails == nil {
|
||||||
|
return evaluation, -1, &cel.Error{
|
||||||
|
Type: cel.ErrorTypeInternal,
|
||||||
|
Detail: fmt.Sprintf("runtime cost could not be calculated for expression: %v, no further expression will be run", compilationResult.ExpressionAccessor.GetExpression()),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rtCost := evalDetails.ActualCost()
|
||||||
|
if rtCost == nil {
|
||||||
|
return evaluation, -1, &cel.Error{
|
||||||
|
Type: cel.ErrorTypeInvalid,
|
||||||
|
Detail: fmt.Sprintf("runtime cost could not be calculated for expression: %v, no further expression will be run", compilationResult.ExpressionAccessor.GetExpression()),
|
||||||
|
Cause: cel.ErrOutOfBudget,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if *rtCost > math.MaxInt64 || int64(*rtCost) > remainingBudget {
|
||||||
|
return evaluation, -1, &cel.Error{
|
||||||
|
Type: cel.ErrorTypeInvalid,
|
||||||
|
Detail: "validation failed due to running out of cost budget, no further validation rules will be run",
|
||||||
|
Cause: cel.ErrOutOfBudget,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
remainingBudget -= int64(*rtCost)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
evaluation.Error = &cel.Error{
|
||||||
|
Type: cel.ErrorTypeInvalid,
|
||||||
|
Detail: fmt.Sprintf("expression '%v' resulted in error: %v", compilationResult.ExpressionAccessor.GetExpression(), err),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
evaluation.EvalResult = evalResult
|
||||||
|
}
|
||||||
|
return evaluation, remainingBudget, nil
|
||||||
|
}
|
|
@ -24,8 +24,10 @@ import (
|
||||||
"k8s.io/apimachinery/pkg/util/version"
|
"k8s.io/apimachinery/pkg/util/version"
|
||||||
celconfig "k8s.io/apiserver/pkg/apis/cel"
|
celconfig "k8s.io/apiserver/pkg/apis/cel"
|
||||||
apiservercel "k8s.io/apiserver/pkg/cel"
|
apiservercel "k8s.io/apiserver/pkg/cel"
|
||||||
|
"k8s.io/apiserver/pkg/cel/common"
|
||||||
"k8s.io/apiserver/pkg/cel/environment"
|
"k8s.io/apiserver/pkg/cel/environment"
|
||||||
"k8s.io/apiserver/pkg/cel/library"
|
"k8s.io/apiserver/pkg/cel/library"
|
||||||
|
"k8s.io/apiserver/pkg/cel/mutation"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -186,7 +188,7 @@ func (c compiler) CompileCELExpression(expressionAccessor ExpressionAccessor, op
|
||||||
found := false
|
found := false
|
||||||
returnTypes := expressionAccessor.ReturnTypes()
|
returnTypes := expressionAccessor.ReturnTypes()
|
||||||
for _, returnType := range returnTypes {
|
for _, returnType := range returnTypes {
|
||||||
if ast.OutputType() == returnType || cel.AnyType == returnType {
|
if ast.OutputType().IsExactType(returnType) || cel.AnyType.IsExactType(returnType) {
|
||||||
found = true
|
found = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -194,9 +196,9 @@ func (c compiler) CompileCELExpression(expressionAccessor ExpressionAccessor, op
|
||||||
if !found {
|
if !found {
|
||||||
var reason string
|
var reason string
|
||||||
if len(returnTypes) == 1 {
|
if len(returnTypes) == 1 {
|
||||||
reason = fmt.Sprintf("must evaluate to %v", returnTypes[0].String())
|
reason = fmt.Sprintf("must evaluate to %v but got %v", returnTypes[0].String(), ast.OutputType().String())
|
||||||
} else {
|
} else {
|
||||||
reason = fmt.Sprintf("must evaluate to one of %v", returnTypes)
|
reason = fmt.Sprintf("must evaluate to one of %v but got %v", returnTypes, ast.OutputType().String())
|
||||||
}
|
}
|
||||||
|
|
||||||
return resultError(reason, apiservercel.ErrorTypeInvalid, nil)
|
return resultError(reason, apiservercel.ErrorTypeInvalid, nil)
|
||||||
|
@ -226,46 +228,78 @@ func mustBuildEnvs(baseEnv *environment.EnvSet) variableDeclEnvs {
|
||||||
envs := make(variableDeclEnvs, 8) // since the number of variable combinations is small, pre-build a environment for each
|
envs := make(variableDeclEnvs, 8) // since the number of variable combinations is small, pre-build a environment for each
|
||||||
for _, hasParams := range []bool{false, true} {
|
for _, hasParams := range []bool{false, true} {
|
||||||
for _, hasAuthorizer := range []bool{false, true} {
|
for _, hasAuthorizer := range []bool{false, true} {
|
||||||
|
var err error
|
||||||
for _, strictCost := range []bool{false, true} {
|
for _, strictCost := range []bool{false, true} {
|
||||||
var envOpts []cel.EnvOption
|
decl := OptionalVariableDeclarations{HasParams: hasParams, HasAuthorizer: hasAuthorizer, StrictCost: strictCost}
|
||||||
if hasParams {
|
envs[decl], err = createEnvForOpts(baseEnv, namespaceType, requestType, decl)
|
||||||
envOpts = append(envOpts, cel.Variable(ParamsVarName, cel.DynType))
|
|
||||||
}
|
|
||||||
if hasAuthorizer {
|
|
||||||
envOpts = append(envOpts,
|
|
||||||
cel.Variable(AuthorizerVarName, library.AuthorizerType),
|
|
||||||
cel.Variable(RequestResourceAuthorizerVarName, library.ResourceCheckType))
|
|
||||||
}
|
|
||||||
envOpts = append(envOpts,
|
|
||||||
cel.Variable(ObjectVarName, cel.DynType),
|
|
||||||
cel.Variable(OldObjectVarName, cel.DynType),
|
|
||||||
cel.Variable(NamespaceVarName, namespaceType.CelType()),
|
|
||||||
cel.Variable(RequestVarName, requestType.CelType()))
|
|
||||||
|
|
||||||
extended, err := baseEnv.Extend(
|
|
||||||
environment.VersionedOptions{
|
|
||||||
// Feature epoch was actually 1.26, but we artificially set it to 1.0 because these
|
|
||||||
// options should always be present.
|
|
||||||
IntroducedVersion: version.MajorMinor(1, 0),
|
|
||||||
EnvOptions: envOpts,
|
|
||||||
DeclTypes: []*apiservercel.DeclType{
|
|
||||||
namespaceType,
|
|
||||||
requestType,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Sprintf("environment misconfigured: %v", err))
|
panic(err)
|
||||||
}
|
}
|
||||||
if strictCost {
|
}
|
||||||
extended, err = extended.Extend(environment.StrictCostOpt)
|
// We only need this ObjectTypes where strict cost is true
|
||||||
if err != nil {
|
decl := OptionalVariableDeclarations{HasParams: hasParams, HasAuthorizer: hasAuthorizer, StrictCost: true, HasPatchTypes: true}
|
||||||
panic(fmt.Sprintf("environment misconfigured: %v", err))
|
envs[decl], err = createEnvForOpts(baseEnv, namespaceType, requestType, decl)
|
||||||
}
|
if err != nil {
|
||||||
}
|
panic(err)
|
||||||
envs[OptionalVariableDeclarations{HasParams: hasParams, HasAuthorizer: hasAuthorizer, StrictCost: strictCost}] = extended
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return envs
|
return envs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func createEnvForOpts(baseEnv *environment.EnvSet, namespaceType *apiservercel.DeclType, requestType *apiservercel.DeclType, opts OptionalVariableDeclarations) (*environment.EnvSet, error) {
|
||||||
|
var envOpts []cel.EnvOption
|
||||||
|
envOpts = append(envOpts,
|
||||||
|
cel.Variable(ObjectVarName, cel.DynType),
|
||||||
|
cel.Variable(OldObjectVarName, cel.DynType),
|
||||||
|
cel.Variable(NamespaceVarName, namespaceType.CelType()),
|
||||||
|
cel.Variable(RequestVarName, requestType.CelType()))
|
||||||
|
if opts.HasParams {
|
||||||
|
envOpts = append(envOpts, cel.Variable(ParamsVarName, cel.DynType))
|
||||||
|
}
|
||||||
|
if opts.HasAuthorizer {
|
||||||
|
envOpts = append(envOpts,
|
||||||
|
cel.Variable(AuthorizerVarName, library.AuthorizerType),
|
||||||
|
cel.Variable(RequestResourceAuthorizerVarName, library.ResourceCheckType))
|
||||||
|
}
|
||||||
|
|
||||||
|
extended, err := baseEnv.Extend(
|
||||||
|
environment.VersionedOptions{
|
||||||
|
// Feature epoch was actually 1.26, but we artificially set it to 1.0 because these
|
||||||
|
// options should always be present.
|
||||||
|
IntroducedVersion: version.MajorMinor(1, 0),
|
||||||
|
EnvOptions: envOpts,
|
||||||
|
DeclTypes: []*apiservercel.DeclType{
|
||||||
|
namespaceType,
|
||||||
|
requestType,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("environment misconfigured: %w", err)
|
||||||
|
}
|
||||||
|
if opts.StrictCost {
|
||||||
|
extended, err = extended.Extend(environment.StrictCostOpt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("environment misconfigured: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.HasPatchTypes {
|
||||||
|
extended, err = extended.Extend(hasPatchTypes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("environment misconfigured: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return extended, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var hasPatchTypes = environment.VersionedOptions{
|
||||||
|
// Feature epoch was actually 1.32, but we artificially set it to 1.0 because these
|
||||||
|
// options should always be present.
|
||||||
|
IntroducedVersion: version.MajorMinor(1, 0),
|
||||||
|
EnvOptions: []cel.EnvOption{
|
||||||
|
common.ResolverEnvOption(&mutation.DynamicTypeResolver{}),
|
||||||
|
library.JSONPatch(), // for jsonPatch.escape() function
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
|
@ -36,15 +36,27 @@ import (
|
||||||
|
|
||||||
const VariablesTypeName = "kubernetes.variables"
|
const VariablesTypeName = "kubernetes.variables"
|
||||||
|
|
||||||
|
// CompositedCompiler compiles expressions with variable composition.
|
||||||
type CompositedCompiler struct {
|
type CompositedCompiler struct {
|
||||||
Compiler
|
Compiler
|
||||||
FilterCompiler
|
ConditionCompiler
|
||||||
|
MutatingCompiler
|
||||||
|
|
||||||
CompositionEnv *CompositionEnv
|
CompositionEnv *CompositionEnv
|
||||||
}
|
}
|
||||||
|
|
||||||
type CompositedFilter struct {
|
// CompositedConditionEvaluator provides evaluation of a condition expression with variable composition.
|
||||||
Filter
|
// The expressions must return a boolean.
|
||||||
|
type CompositedConditionEvaluator struct {
|
||||||
|
ConditionEvaluator
|
||||||
|
|
||||||
|
compositionEnv *CompositionEnv
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompositedEvaluator provides evaluation of a single expression with variable composition.
|
||||||
|
// The types that may returned by the expression is determined at compilation time.
|
||||||
|
type CompositedEvaluator struct {
|
||||||
|
MutatingEvaluator
|
||||||
|
|
||||||
compositionEnv *CompositionEnv
|
compositionEnv *CompositionEnv
|
||||||
}
|
}
|
||||||
|
@ -64,11 +76,13 @@ func NewCompositedCompilerFromTemplate(context *CompositionEnv) *CompositedCompi
|
||||||
CompiledVariables: map[string]CompilationResult{},
|
CompiledVariables: map[string]CompilationResult{},
|
||||||
}
|
}
|
||||||
compiler := NewCompiler(context.EnvSet)
|
compiler := NewCompiler(context.EnvSet)
|
||||||
filterCompiler := NewFilterCompiler(context.EnvSet)
|
conditionCompiler := &conditionCompiler{compiler}
|
||||||
|
mutation := &mutatingCompiler{compiler}
|
||||||
return &CompositedCompiler{
|
return &CompositedCompiler{
|
||||||
Compiler: compiler,
|
Compiler: compiler,
|
||||||
FilterCompiler: filterCompiler,
|
ConditionCompiler: conditionCompiler,
|
||||||
CompositionEnv: context,
|
MutatingCompiler: mutation,
|
||||||
|
CompositionEnv: context,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,11 +99,20 @@ func (c *CompositedCompiler) CompileAndStoreVariable(variable NamedExpressionAcc
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CompositedCompiler) Compile(expressions []ExpressionAccessor, optionalDecls OptionalVariableDeclarations, envType environment.Type) Filter {
|
func (c *CompositedCompiler) CompileCondition(expressions []ExpressionAccessor, optionalDecls OptionalVariableDeclarations, envType environment.Type) ConditionEvaluator {
|
||||||
filter := c.FilterCompiler.Compile(expressions, optionalDecls, envType)
|
condition := c.ConditionCompiler.CompileCondition(expressions, optionalDecls, envType)
|
||||||
return &CompositedFilter{
|
return &CompositedConditionEvaluator{
|
||||||
Filter: filter,
|
ConditionEvaluator: condition,
|
||||||
compositionEnv: c.CompositionEnv,
|
compositionEnv: c.CompositionEnv,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompileEvaluator compiles an mutatingEvaluator for the given expression, options and environment.
|
||||||
|
func (c *CompositedCompiler) CompileMutatingEvaluator(expression ExpressionAccessor, optionalDecls OptionalVariableDeclarations, envType environment.Type) MutatingEvaluator {
|
||||||
|
mutation := c.MutatingCompiler.CompileMutatingEvaluator(expression, optionalDecls, envType)
|
||||||
|
return &CompositedEvaluator{
|
||||||
|
MutatingEvaluator: mutation,
|
||||||
|
compositionEnv: c.CompositionEnv,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -160,9 +183,9 @@ func (c *compositionContext) Variables(activation any) ref.Val {
|
||||||
return lazyMap
|
return lazyMap
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *CompositedFilter) ForInput(ctx context.Context, versionedAttr *admission.VersionedAttributes, request *v1.AdmissionRequest, optionalVars OptionalVariableBindings, namespace *corev1.Namespace, runtimeCELCostBudget int64) ([]EvaluationResult, int64, error) {
|
func (f *CompositedConditionEvaluator) ForInput(ctx context.Context, versionedAttr *admission.VersionedAttributes, request *v1.AdmissionRequest, optionalVars OptionalVariableBindings, namespace *corev1.Namespace, runtimeCELCostBudget int64) ([]EvaluationResult, int64, error) {
|
||||||
ctx = f.compositionEnv.CreateContext(ctx)
|
ctx = f.compositionEnv.CreateContext(ctx)
|
||||||
return f.Filter.ForInput(ctx, versionedAttr, request, optionalVars, namespace, runtimeCELCostBudget)
|
return f.ConditionEvaluator.ForInput(ctx, versionedAttr, request, optionalVars, namespace, runtimeCELCostBudget)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *compositionContext) reportCost(cost int64) {
|
func (c *compositionContext) reportCost(cost int64) {
|
||||||
|
|
|
@ -223,8 +223,8 @@ func TestCompositedPolicies(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
compiler.CompileAndStoreVariables(tc.variables, OptionalVariableDeclarations{HasParams: false, HasAuthorizer: false, StrictCost: tc.strictCostEnforcement}, environment.NewExpressions)
|
compiler.CompileAndStoreVariables(tc.variables, OptionalVariableDeclarations{HasParams: false, HasAuthorizer: false, StrictCost: tc.strictCostEnforcement}, environment.NewExpressions)
|
||||||
validations := []ExpressionAccessor{&condition{Expression: tc.expression}}
|
validations := []ExpressionAccessor{&testCondition{Expression: tc.expression}}
|
||||||
f := compiler.Compile(validations, OptionalVariableDeclarations{HasParams: false, HasAuthorizer: false, StrictCost: tc.strictCostEnforcement}, environment.NewExpressions)
|
f := compiler.CompileCondition(validations, OptionalVariableDeclarations{HasParams: false, HasAuthorizer: false, StrictCost: tc.strictCostEnforcement}, environment.NewExpressions)
|
||||||
versionedAttr, err := admission.NewVersionedAttributes(tc.attributes, tc.attributes.GetKind(), newObjectInterfacesForTest())
|
versionedAttr, err := admission.NewVersionedAttributes(tc.attributes, tc.attributes.GetKind(), newObjectInterfacesForTest())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|
|
@ -0,0 +1,216 @@
|
||||||
|
/*
|
||||||
|
Copyright 2022 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cel
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
admissionv1 "k8s.io/api/admission/v1"
|
||||||
|
authenticationv1 "k8s.io/api/authentication/v1"
|
||||||
|
v1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apiserver/pkg/admission"
|
||||||
|
"k8s.io/apiserver/pkg/cel/environment"
|
||||||
|
)
|
||||||
|
|
||||||
|
// conditionCompiler implement the interface ConditionCompiler.
|
||||||
|
type conditionCompiler struct {
|
||||||
|
compiler Compiler
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConditionCompiler(env *environment.EnvSet) ConditionCompiler {
|
||||||
|
return &conditionCompiler{compiler: NewCompiler(env)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompileCondition compiles the cel expressions defined in the ExpressionAccessors into a ConditionEvaluator
|
||||||
|
func (c *conditionCompiler) CompileCondition(expressionAccessors []ExpressionAccessor, options OptionalVariableDeclarations, mode environment.Type) ConditionEvaluator {
|
||||||
|
compilationResults := make([]CompilationResult, len(expressionAccessors))
|
||||||
|
for i, expressionAccessor := range expressionAccessors {
|
||||||
|
if expressionAccessor == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
compilationResults[i] = c.compiler.CompileCELExpression(expressionAccessor, options, mode)
|
||||||
|
}
|
||||||
|
return NewCondition(compilationResults)
|
||||||
|
}
|
||||||
|
|
||||||
|
// condition implements the ConditionEvaluator interface
|
||||||
|
type condition struct {
|
||||||
|
compilationResults []CompilationResult
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCondition(compilationResults []CompilationResult) ConditionEvaluator {
|
||||||
|
return &condition{
|
||||||
|
compilationResults,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertObjectToUnstructured(obj interface{}) (*unstructured.Unstructured, error) {
|
||||||
|
if obj == nil || reflect.ValueOf(obj).IsNil() {
|
||||||
|
return &unstructured.Unstructured{Object: nil}, nil
|
||||||
|
}
|
||||||
|
ret, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &unstructured.Unstructured{Object: ret}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func objectToResolveVal(r runtime.Object) (interface{}, error) {
|
||||||
|
if r == nil || reflect.ValueOf(r).IsNil() {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
v, err := convertObjectToUnstructured(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return v.Object, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForInput evaluates the compiled CEL expressions converting them into CELEvaluations
|
||||||
|
// errors per evaluation are returned on the Evaluation object
|
||||||
|
// runtimeCELCostBudget was added for testing purpose only. Callers should always use const RuntimeCELCostBudget from k8s.io/apiserver/pkg/apis/cel/config.go as input.
|
||||||
|
func (c *condition) ForInput(ctx context.Context, versionedAttr *admission.VersionedAttributes, request *admissionv1.AdmissionRequest, inputs OptionalVariableBindings, namespace *v1.Namespace, runtimeCELCostBudget int64) ([]EvaluationResult, int64, error) {
|
||||||
|
// TODO: replace unstructured with ref.Val for CEL variables when native type support is available
|
||||||
|
evaluations := make([]EvaluationResult, len(c.compilationResults))
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// if this activation supports composition, we will need the compositionCtx. It may be nil.
|
||||||
|
compositionCtx, _ := ctx.(CompositionContext)
|
||||||
|
|
||||||
|
activation, err := newActivation(compositionCtx, versionedAttr, request, inputs, namespace)
|
||||||
|
if err != nil {
|
||||||
|
return nil, -1, err
|
||||||
|
}
|
||||||
|
|
||||||
|
remainingBudget := runtimeCELCostBudget
|
||||||
|
for i, compilationResult := range c.compilationResults {
|
||||||
|
evaluations[i], remainingBudget, err = activation.Evaluate(ctx, compositionCtx, compilationResult, remainingBudget)
|
||||||
|
if err != nil {
|
||||||
|
return nil, -1, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return evaluations, remainingBudget, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: to reuse https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/request/admissionreview.go#L154
|
||||||
|
func CreateAdmissionRequest(attr admission.Attributes, equivalentGVR metav1.GroupVersionResource, equivalentKind metav1.GroupVersionKind) *admissionv1.AdmissionRequest {
|
||||||
|
// Attempting to use same logic as webhook for constructing resource
|
||||||
|
// GVK, GVR, subresource
|
||||||
|
// Use the GVK, GVR that the matcher decided was equivalent to that of the request
|
||||||
|
// https://github.com/kubernetes/kubernetes/blob/90c362b3430bcbbf8f245fadbcd521dab39f1d7c/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/generic/webhook.go#L182-L210
|
||||||
|
gvk := equivalentKind
|
||||||
|
gvr := equivalentGVR
|
||||||
|
subresource := attr.GetSubresource()
|
||||||
|
|
||||||
|
requestGVK := attr.GetKind()
|
||||||
|
requestGVR := attr.GetResource()
|
||||||
|
requestSubResource := attr.GetSubresource()
|
||||||
|
|
||||||
|
aUserInfo := attr.GetUserInfo()
|
||||||
|
var userInfo authenticationv1.UserInfo
|
||||||
|
if aUserInfo != nil {
|
||||||
|
userInfo = authenticationv1.UserInfo{
|
||||||
|
Extra: make(map[string]authenticationv1.ExtraValue),
|
||||||
|
Groups: aUserInfo.GetGroups(),
|
||||||
|
UID: aUserInfo.GetUID(),
|
||||||
|
Username: aUserInfo.GetName(),
|
||||||
|
}
|
||||||
|
// Convert the extra information in the user object
|
||||||
|
for key, val := range aUserInfo.GetExtra() {
|
||||||
|
userInfo.Extra[key] = authenticationv1.ExtraValue(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dryRun := attr.IsDryRun()
|
||||||
|
|
||||||
|
return &admissionv1.AdmissionRequest{
|
||||||
|
Kind: metav1.GroupVersionKind{
|
||||||
|
Group: gvk.Group,
|
||||||
|
Kind: gvk.Kind,
|
||||||
|
Version: gvk.Version,
|
||||||
|
},
|
||||||
|
Resource: metav1.GroupVersionResource{
|
||||||
|
Group: gvr.Group,
|
||||||
|
Resource: gvr.Resource,
|
||||||
|
Version: gvr.Version,
|
||||||
|
},
|
||||||
|
SubResource: subresource,
|
||||||
|
RequestKind: &metav1.GroupVersionKind{
|
||||||
|
Group: requestGVK.Group,
|
||||||
|
Kind: requestGVK.Kind,
|
||||||
|
Version: requestGVK.Version,
|
||||||
|
},
|
||||||
|
RequestResource: &metav1.GroupVersionResource{
|
||||||
|
Group: requestGVR.Group,
|
||||||
|
Resource: requestGVR.Resource,
|
||||||
|
Version: requestGVR.Version,
|
||||||
|
},
|
||||||
|
RequestSubResource: requestSubResource,
|
||||||
|
Name: attr.GetName(),
|
||||||
|
Namespace: attr.GetNamespace(),
|
||||||
|
Operation: admissionv1.Operation(attr.GetOperation()),
|
||||||
|
UserInfo: userInfo,
|
||||||
|
// Leave Object and OldObject unset since we don't provide access to them via request
|
||||||
|
DryRun: &dryRun,
|
||||||
|
Options: runtime.RawExtension{
|
||||||
|
Object: attr.GetOperationOptions(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateNamespaceObject creates a Namespace object that is suitable for the CEL evaluation.
|
||||||
|
// If the namespace is nil, CreateNamespaceObject returns nil
|
||||||
|
func CreateNamespaceObject(namespace *v1.Namespace) *v1.Namespace {
|
||||||
|
if namespace == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &v1.Namespace{
|
||||||
|
Status: namespace.Status,
|
||||||
|
Spec: namespace.Spec,
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: namespace.Name,
|
||||||
|
GenerateName: namespace.GenerateName,
|
||||||
|
Namespace: namespace.Namespace,
|
||||||
|
UID: namespace.UID,
|
||||||
|
ResourceVersion: namespace.ResourceVersion,
|
||||||
|
Generation: namespace.Generation,
|
||||||
|
CreationTimestamp: namespace.CreationTimestamp,
|
||||||
|
DeletionTimestamp: namespace.DeletionTimestamp,
|
||||||
|
DeletionGracePeriodSeconds: namespace.DeletionGracePeriodSeconds,
|
||||||
|
Labels: namespace.Labels,
|
||||||
|
Annotations: namespace.Annotations,
|
||||||
|
Finalizers: namespace.Finalizers,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompilationErrors returns a list of all the errors from the compilation of the mutatingEvaluator
|
||||||
|
func (c *condition) CompilationErrors() []error {
|
||||||
|
compilationErrors := []error{}
|
||||||
|
for _, result := range c.compilationResults {
|
||||||
|
if result.Error != nil {
|
||||||
|
compilationErrors = append(compilationErrors, result.Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return compilationErrors
|
||||||
|
}
|
|
@ -28,8 +28,6 @@ import (
|
||||||
celtypes "github.com/google/cel-go/common/types"
|
celtypes "github.com/google/cel-go/common/types"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
pointer "k8s.io/utils/ptr"
|
|
||||||
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
@ -48,17 +46,18 @@ import (
|
||||||
genericfeatures "k8s.io/apiserver/pkg/features"
|
genericfeatures "k8s.io/apiserver/pkg/features"
|
||||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||||
|
pointer "k8s.io/utils/ptr"
|
||||||
)
|
)
|
||||||
|
|
||||||
type condition struct {
|
type testCondition struct {
|
||||||
Expression string
|
Expression string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *condition) GetExpression() string {
|
func (tc *testCondition) GetExpression() string {
|
||||||
return c.Expression
|
return tc.Expression
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *condition) ReturnTypes() []*celgo.Type {
|
func (tc *testCondition) ReturnTypes() []*celgo.Type {
|
||||||
return []*celgo.Type{celgo.BoolType}
|
return []*celgo.Type{celgo.BoolType}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,10 +70,10 @@ func TestCompile(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "invalid syntax",
|
name: "invalid syntax",
|
||||||
validation: []ExpressionAccessor{
|
validation: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "1 < 'asdf'",
|
Expression: "1 < 'asdf'",
|
||||||
},
|
},
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "1 < 2",
|
Expression: "1 < 2",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -85,13 +84,13 @@ func TestCompile(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "valid syntax",
|
name: "valid syntax",
|
||||||
validation: []ExpressionAccessor{
|
validation: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "1 < 2",
|
Expression: "1 < 2",
|
||||||
},
|
},
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "object.spec.string.matches('[0-9]+')",
|
Expression: "object.spec.string.matches('[0-9]+')",
|
||||||
},
|
},
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "request.kind.group == 'example.com' && request.kind.version == 'v1' && request.kind.kind == 'Fake'",
|
Expression: "request.kind.group == 'example.com' && request.kind.version == 'v1' && request.kind.kind == 'Fake'",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -100,13 +99,13 @@ func TestCompile(t *testing.T) {
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
c := filterCompiler{compiler: NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), true))}
|
c := conditionCompiler{compiler: NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), true))}
|
||||||
e := c.Compile(tc.validation, OptionalVariableDeclarations{HasParams: false, HasAuthorizer: false, StrictCost: true}, environment.NewExpressions)
|
e := c.CompileCondition(tc.validation, OptionalVariableDeclarations{HasParams: false, HasAuthorizer: false, StrictCost: true}, environment.NewExpressions)
|
||||||
if e == nil {
|
if e == nil {
|
||||||
t.Fatalf("unexpected nil validator")
|
t.Fatalf("unexpected nil validator")
|
||||||
}
|
}
|
||||||
validations := tc.validation
|
validations := tc.validation
|
||||||
CompilationResults := e.(*filter).compilationResults
|
CompilationResults := e.(*condition).compilationResults
|
||||||
require.Equal(t, len(validations), len(CompilationResults))
|
require.Equal(t, len(validations), len(CompilationResults))
|
||||||
|
|
||||||
meets := make([]bool, len(validations))
|
meets := make([]bool, len(validations))
|
||||||
|
@ -131,7 +130,7 @@ func TestCompile(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFilter(t *testing.T) {
|
func TestCondition(t *testing.T) {
|
||||||
simpleLabelSelector, err := labels.NewRequirement("apple", selection.Equals, []string{"banana"})
|
simpleLabelSelector, err := labels.NewRequirement("apple", selection.Equals, []string{"banana"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@ -205,7 +204,7 @@ func TestFilter(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "valid syntax for object",
|
name: "valid syntax for object",
|
||||||
validations: []ExpressionAccessor{
|
validations: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "has(object.subsets) && object.subsets.size() < 2",
|
Expression: "has(object.subsets) && object.subsets.size() < 2",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -220,7 +219,7 @@ func TestFilter(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "valid syntax for metadata",
|
name: "valid syntax for metadata",
|
||||||
validations: []ExpressionAccessor{
|
validations: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "object.metadata.name == 'endpoints1'",
|
Expression: "object.metadata.name == 'endpoints1'",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -235,10 +234,10 @@ func TestFilter(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "valid syntax for oldObject",
|
name: "valid syntax for oldObject",
|
||||||
validations: []ExpressionAccessor{
|
validations: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "oldObject == null",
|
Expression: "oldObject == null",
|
||||||
},
|
},
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "object != null",
|
Expression: "object != null",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -256,7 +255,7 @@ func TestFilter(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "valid syntax for request",
|
name: "valid syntax for request",
|
||||||
validations: []ExpressionAccessor{
|
validations: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "request.operation == 'CREATE'",
|
Expression: "request.operation == 'CREATE'",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -271,7 +270,7 @@ func TestFilter(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "valid syntax for configMap",
|
name: "valid syntax for configMap",
|
||||||
validations: []ExpressionAccessor{
|
validations: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "request.namespace != params.data.fakeString",
|
Expression: "request.namespace != params.data.fakeString",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -287,7 +286,7 @@ func TestFilter(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "test failure",
|
name: "test failure",
|
||||||
validations: []ExpressionAccessor{
|
validations: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "object.subsets.size() > 2",
|
Expression: "object.subsets.size() > 2",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -310,10 +309,10 @@ func TestFilter(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "test failure with multiple validations",
|
name: "test failure with multiple validations",
|
||||||
validations: []ExpressionAccessor{
|
validations: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "has(object.subsets)",
|
Expression: "has(object.subsets)",
|
||||||
},
|
},
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "object.subsets.size() > 2",
|
Expression: "object.subsets.size() > 2",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -332,10 +331,10 @@ func TestFilter(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "test failure policy with multiple failed validations",
|
name: "test failure policy with multiple failed validations",
|
||||||
validations: []ExpressionAccessor{
|
validations: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "oldObject != null",
|
Expression: "oldObject != null",
|
||||||
},
|
},
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "object.subsets.size() > 2",
|
Expression: "object.subsets.size() > 2",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -354,10 +353,10 @@ func TestFilter(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "test Object null in delete",
|
name: "test Object null in delete",
|
||||||
validations: []ExpressionAccessor{
|
validations: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "oldObject != null",
|
Expression: "oldObject != null",
|
||||||
},
|
},
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "object == null",
|
Expression: "object == null",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -376,7 +375,7 @@ func TestFilter(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "test runtime error",
|
name: "test runtime error",
|
||||||
validations: []ExpressionAccessor{
|
validations: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "oldObject.x == 100",
|
Expression: "oldObject.x == 100",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -392,7 +391,7 @@ func TestFilter(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "test against crd param",
|
name: "test against crd param",
|
||||||
validations: []ExpressionAccessor{
|
validations: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "object.subsets.size() < params.spec.testSize",
|
Expression: "object.subsets.size() < params.spec.testSize",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -408,10 +407,10 @@ func TestFilter(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "test compile failure",
|
name: "test compile failure",
|
||||||
validations: []ExpressionAccessor{
|
validations: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "fail to compile test",
|
Expression: "fail to compile test",
|
||||||
},
|
},
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "object.subsets.size() > params.spec.testSize",
|
Expression: "object.subsets.size() > params.spec.testSize",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -430,7 +429,7 @@ func TestFilter(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "test pod",
|
name: "test pod",
|
||||||
validations: []ExpressionAccessor{
|
validations: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "object.spec.nodeName == 'testnode'",
|
Expression: "object.spec.nodeName == 'testnode'",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -446,7 +445,7 @@ func TestFilter(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "test deny paramKind without paramRef",
|
name: "test deny paramKind without paramRef",
|
||||||
validations: []ExpressionAccessor{
|
validations: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "params != null",
|
Expression: "params != null",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -461,7 +460,7 @@ func TestFilter(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "test allow paramKind without paramRef",
|
name: "test allow paramKind without paramRef",
|
||||||
validations: []ExpressionAccessor{
|
validations: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "params == null",
|
Expression: "params == null",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -477,10 +476,10 @@ func TestFilter(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "test authorizer allow resource check",
|
name: "test authorizer allow resource check",
|
||||||
validations: []ExpressionAccessor{
|
validations: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "authorizer.group('').resource('endpoints').check('create').allowed()",
|
Expression: "authorizer.group('').resource('endpoints').check('create').allowed()",
|
||||||
},
|
},
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "authorizer.group('').resource('endpoints').check('create').errored()",
|
Expression: "authorizer.group('').resource('endpoints').check('create').errored()",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -503,7 +502,7 @@ func TestFilter(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "test authorizer error using fieldSelector with 1.30 compatibility",
|
name: "test authorizer error using fieldSelector with 1.30 compatibility",
|
||||||
validations: []ExpressionAccessor{
|
validations: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "authorizer.group('apps').resource('deployments').fieldSelector('foo=bar').labelSelector('apple=banana').subresource('status').namespace('test').name('backend').check('create').allowed()",
|
Expression: "authorizer.group('apps').resource('deployments').fieldSelector('foo=bar').labelSelector('apple=banana').subresource('status').namespace('test').name('backend').check('create').allowed()",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -535,7 +534,7 @@ func TestFilter(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "test authorizer allow resource check with all fields",
|
name: "test authorizer allow resource check with all fields",
|
||||||
validations: []ExpressionAccessor{
|
validations: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "authorizer.group('apps').resource('deployments').fieldSelector('foo=bar').labelSelector('apple=banana').subresource('status').namespace('test').name('backend').check('create').allowed()",
|
Expression: "authorizer.group('apps').resource('deployments').fieldSelector('foo=bar').labelSelector('apple=banana').subresource('status').namespace('test').name('backend').check('create').allowed()",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -567,7 +566,7 @@ func TestFilter(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "test authorizer allow resource check with parse failures",
|
name: "test authorizer allow resource check with parse failures",
|
||||||
validations: []ExpressionAccessor{
|
validations: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "authorizer.group('apps').resource('deployments').fieldSelector('foo badoperator bar').labelSelector('apple badoperator banana').subresource('status').namespace('test').name('backend').check('create').allowed()",
|
Expression: "authorizer.group('apps').resource('deployments').fieldSelector('foo badoperator bar').labelSelector('apple badoperator banana').subresource('status').namespace('test').name('backend').check('create').allowed()",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -595,7 +594,7 @@ func TestFilter(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "test authorizer allow resource check with all fields, without gate",
|
name: "test authorizer allow resource check with all fields, without gate",
|
||||||
validations: []ExpressionAccessor{
|
validations: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "authorizer.group('apps').resource('deployments').fieldSelector('foo=bar').labelSelector('apple=banana').subresource('status').namespace('test').name('backend').check('create').allowed()",
|
Expression: "authorizer.group('apps').resource('deployments').fieldSelector('foo=bar').labelSelector('apple=banana').subresource('status').namespace('test').name('backend').check('create').allowed()",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -621,7 +620,7 @@ func TestFilter(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "test authorizer not allowed resource check one incorrect field",
|
name: "test authorizer not allowed resource check one incorrect field",
|
||||||
validations: []ExpressionAccessor{
|
validations: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
|
|
||||||
Expression: "authorizer.group('apps').resource('deployments').subresource('status').namespace('test').name('backend').check('create').allowed()",
|
Expression: "authorizer.group('apps').resource('deployments').subresource('status').namespace('test').name('backend').check('create').allowed()",
|
||||||
},
|
},
|
||||||
|
@ -646,7 +645,7 @@ func TestFilter(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "test authorizer reason",
|
name: "test authorizer reason",
|
||||||
validations: []ExpressionAccessor{
|
validations: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "authorizer.group('').resource('endpoints').check('create').reason() == 'fake reason'",
|
Expression: "authorizer.group('').resource('endpoints').check('create').reason() == 'fake reason'",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -661,13 +660,13 @@ func TestFilter(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "test authorizer error",
|
name: "test authorizer error",
|
||||||
validations: []ExpressionAccessor{
|
validations: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "authorizer.group('').resource('endpoints').check('create').errored()",
|
Expression: "authorizer.group('').resource('endpoints').check('create').errored()",
|
||||||
},
|
},
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "authorizer.group('').resource('endpoints').check('create').error() == 'fake authz error'",
|
Expression: "authorizer.group('').resource('endpoints').check('create').error() == 'fake authz error'",
|
||||||
},
|
},
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "authorizer.group('').resource('endpoints').check('create').allowed()",
|
Expression: "authorizer.group('').resource('endpoints').check('create').allowed()",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -688,7 +687,7 @@ func TestFilter(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "test authorizer allow path check",
|
name: "test authorizer allow path check",
|
||||||
validations: []ExpressionAccessor{
|
validations: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "authorizer.path('/healthz').check('get').allowed()",
|
Expression: "authorizer.path('/healthz').check('get').allowed()",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -706,7 +705,7 @@ func TestFilter(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "test authorizer decision is denied path check",
|
name: "test authorizer decision is denied path check",
|
||||||
validations: []ExpressionAccessor{
|
validations: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "authorizer.path('/healthz').check('get').allowed() == false",
|
Expression: "authorizer.path('/healthz').check('get').allowed() == false",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -721,7 +720,7 @@ func TestFilter(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "test request resource authorizer allow check",
|
name: "test request resource authorizer allow check",
|
||||||
validations: []ExpressionAccessor{
|
validations: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "authorizer.requestResource.check('custom-verb').allowed()",
|
Expression: "authorizer.requestResource.check('custom-verb').allowed()",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -745,7 +744,7 @@ func TestFilter(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "test subresource request resource authorizer allow check",
|
name: "test subresource request resource authorizer allow check",
|
||||||
validations: []ExpressionAccessor{
|
validations: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "authorizer.requestResource.check('custom-verb').allowed()",
|
Expression: "authorizer.requestResource.check('custom-verb').allowed()",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -769,7 +768,7 @@ func TestFilter(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "test serviceAccount authorizer allow check",
|
name: "test serviceAccount authorizer allow check",
|
||||||
validations: []ExpressionAccessor{
|
validations: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "authorizer.serviceAccount('default', 'test-serviceaccount').group('').resource('endpoints').namespace('default').name('endpoints1').check('custom-verb').allowed()",
|
Expression: "authorizer.serviceAccount('default', 'test-serviceaccount').group('').resource('endpoints').namespace('default').name('endpoints1').check('custom-verb').allowed()",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -796,7 +795,7 @@ func TestFilter(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "test perCallLimit exceed",
|
name: "test perCallLimit exceed",
|
||||||
validations: []ExpressionAccessor{
|
validations: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "object.subsets.size() < params.spec.testSize",
|
Expression: "object.subsets.size() < params.spec.testSize",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -813,28 +812,28 @@ func TestFilter(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "test namespaceObject",
|
name: "test namespaceObject",
|
||||||
validations: []ExpressionAccessor{
|
validations: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "namespaceObject.metadata.name == 'test'",
|
Expression: "namespaceObject.metadata.name == 'test'",
|
||||||
},
|
},
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "'env' in namespaceObject.metadata.labels && namespaceObject.metadata.labels.env == 'test'",
|
Expression: "'env' in namespaceObject.metadata.labels && namespaceObject.metadata.labels.env == 'test'",
|
||||||
},
|
},
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "('fake' in namespaceObject.metadata.labels) && namespaceObject.metadata.labels.fake == 'test'",
|
Expression: "('fake' in namespaceObject.metadata.labels) && namespaceObject.metadata.labels.fake == 'test'",
|
||||||
},
|
},
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "namespaceObject.spec.finalizers[0] == 'kubernetes'",
|
Expression: "namespaceObject.spec.finalizers[0] == 'kubernetes'",
|
||||||
},
|
},
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "namespaceObject.status.phase == 'Active'",
|
Expression: "namespaceObject.status.phase == 'Active'",
|
||||||
},
|
},
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "size(namespaceObject.metadata.managedFields) == 1",
|
Expression: "size(namespaceObject.metadata.managedFields) == 1",
|
||||||
},
|
},
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "size(namespaceObject.metadata.ownerReferences) == 1",
|
Expression: "size(namespaceObject.metadata.ownerReferences) == 1",
|
||||||
},
|
},
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "'env' in namespaceObject.metadata.annotations",
|
Expression: "'env' in namespaceObject.metadata.annotations",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -891,14 +890,14 @@ func TestFilter(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
c := NewFilterCompiler(env)
|
c := NewConditionCompiler(env)
|
||||||
f := c.Compile(tc.validations, OptionalVariableDeclarations{HasParams: tc.hasParamKind, HasAuthorizer: tc.authorizer != nil, StrictCost: tc.strictCost}, environment.NewExpressions)
|
f := c.CompileCondition(tc.validations, OptionalVariableDeclarations{HasParams: tc.hasParamKind, HasAuthorizer: tc.authorizer != nil, StrictCost: tc.strictCost}, environment.NewExpressions)
|
||||||
if f == nil {
|
if f == nil {
|
||||||
t.Fatalf("unexpected nil validator")
|
t.Fatalf("unexpected nil validator")
|
||||||
}
|
}
|
||||||
|
|
||||||
validations := tc.validations
|
validations := tc.validations
|
||||||
CompilationResults := f.(*filter).compilationResults
|
CompilationResults := f.(*condition).compilationResults
|
||||||
require.Equal(t, len(validations), len(CompilationResults))
|
require.Equal(t, len(validations), len(CompilationResults))
|
||||||
|
|
||||||
versionedAttr, err := admission.NewVersionedAttributes(tc.attributes, tc.attributes.GetKind(), newObjectInterfacesForTest())
|
versionedAttr, err := admission.NewVersionedAttributes(tc.attributes, tc.attributes.GetKind(), newObjectInterfacesForTest())
|
||||||
|
@ -960,10 +959,10 @@ func TestRuntimeCELCostBudget(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "expression exceed RuntimeCELCostBudget at fist expression",
|
name: "expression exceed RuntimeCELCostBudget at fist expression",
|
||||||
validations: []ExpressionAccessor{
|
validations: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "has(object.subsets) && object.subsets.size() < 2",
|
Expression: "has(object.subsets) && object.subsets.size() < 2",
|
||||||
},
|
},
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "has(object.subsets)",
|
Expression: "has(object.subsets)",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -975,10 +974,10 @@ func TestRuntimeCELCostBudget(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "expression exceed RuntimeCELCostBudget at last expression",
|
name: "expression exceed RuntimeCELCostBudget at last expression",
|
||||||
validations: []ExpressionAccessor{
|
validations: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "has(object.subsets) && object.subsets.size() < 2",
|
Expression: "has(object.subsets) && object.subsets.size() < 2",
|
||||||
},
|
},
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "object.subsets.size() > 2",
|
Expression: "object.subsets.size() > 2",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -991,10 +990,10 @@ func TestRuntimeCELCostBudget(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "test RuntimeCELCostBudge is not exceed",
|
name: "test RuntimeCELCostBudge is not exceed",
|
||||||
validations: []ExpressionAccessor{
|
validations: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "oldObject != null",
|
Expression: "oldObject != null",
|
||||||
},
|
},
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "object.subsets.size() > 2",
|
Expression: "object.subsets.size() > 2",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1008,10 +1007,10 @@ func TestRuntimeCELCostBudget(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "test RuntimeCELCostBudge exactly covers",
|
name: "test RuntimeCELCostBudge exactly covers",
|
||||||
validations: []ExpressionAccessor{
|
validations: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "oldObject != null",
|
Expression: "oldObject != null",
|
||||||
},
|
},
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "object.subsets.size() > 2",
|
Expression: "object.subsets.size() > 2",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1025,13 +1024,13 @@ func TestRuntimeCELCostBudget(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "test RuntimeCELCostBudge exactly covers then constant",
|
name: "test RuntimeCELCostBudge exactly covers then constant",
|
||||||
validations: []ExpressionAccessor{
|
validations: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "oldObject != null",
|
Expression: "oldObject != null",
|
||||||
},
|
},
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "object.subsets.size() > 2",
|
Expression: "object.subsets.size() > 2",
|
||||||
},
|
},
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "true", // zero cost
|
Expression: "true", // zero cost
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1045,7 +1044,7 @@ func TestRuntimeCELCostBudget(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "Extended library cost: authz check",
|
name: "Extended library cost: authz check",
|
||||||
validations: []ExpressionAccessor{
|
validations: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "!authorizer.group('').resource('endpoints').check('create').allowed()",
|
Expression: "!authorizer.group('').resource('endpoints').check('create').allowed()",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1059,7 +1058,7 @@ func TestRuntimeCELCostBudget(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "Extended library cost: isSorted()",
|
name: "Extended library cost: isSorted()",
|
||||||
validations: []ExpressionAccessor{
|
validations: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "[1,2,3,4].isSorted()",
|
Expression: "[1,2,3,4].isSorted()",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1072,7 +1071,7 @@ func TestRuntimeCELCostBudget(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "Extended library cost: url",
|
name: "Extended library cost: url",
|
||||||
validations: []ExpressionAccessor{
|
validations: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "url('https:://kubernetes.io/').getHostname() == 'kubernetes.io'",
|
Expression: "url('https:://kubernetes.io/').getHostname() == 'kubernetes.io'",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1085,7 +1084,7 @@ func TestRuntimeCELCostBudget(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "Extended library cost: split",
|
name: "Extended library cost: split",
|
||||||
validations: []ExpressionAccessor{
|
validations: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "size('abc 123 def 123'.split(' ')) > 0",
|
Expression: "size('abc 123 def 123'.split(' ')) > 0",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1098,7 +1097,7 @@ func TestRuntimeCELCostBudget(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "Extended library cost: join",
|
name: "Extended library cost: join",
|
||||||
validations: []ExpressionAccessor{
|
validations: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "size(['aa', 'bb', 'cc', 'd', 'e', 'f', 'g', 'h', 'i', 'j'].join(' ')) > 0",
|
Expression: "size(['aa', 'bb', 'cc', 'd', 'e', 'f', 'g', 'h', 'i', 'j'].join(' ')) > 0",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1111,7 +1110,7 @@ func TestRuntimeCELCostBudget(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "Extended library cost: find",
|
name: "Extended library cost: find",
|
||||||
validations: []ExpressionAccessor{
|
validations: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "size('abc 123 def 123'.find('123')) > 0",
|
Expression: "size('abc 123 def 123'.find('123')) > 0",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1124,7 +1123,7 @@ func TestRuntimeCELCostBudget(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "Extended library cost: quantity",
|
name: "Extended library cost: quantity",
|
||||||
validations: []ExpressionAccessor{
|
validations: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "quantity(\"200M\") == quantity(\"0.2G\") && quantity(\"0.2G\") == quantity(\"200M\")",
|
Expression: "quantity(\"200M\") == quantity(\"0.2G\") && quantity(\"0.2G\") == quantity(\"200M\")",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1137,10 +1136,10 @@ func TestRuntimeCELCostBudget(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "With StrictCostEnforcementForVAP enabled: expression exceed RuntimeCELCostBudget at fist expression",
|
name: "With StrictCostEnforcementForVAP enabled: expression exceed RuntimeCELCostBudget at fist expression",
|
||||||
validations: []ExpressionAccessor{
|
validations: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "!authorizer.group('').resource('endpoints').check('create').allowed()",
|
Expression: "!authorizer.group('').resource('endpoints').check('create').allowed()",
|
||||||
},
|
},
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "has(object.subsets)",
|
Expression: "has(object.subsets)",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1154,10 +1153,10 @@ func TestRuntimeCELCostBudget(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "With StrictCostEnforcementForVAP enabled: expression exceed RuntimeCELCostBudget at last expression",
|
name: "With StrictCostEnforcementForVAP enabled: expression exceed RuntimeCELCostBudget at last expression",
|
||||||
validations: []ExpressionAccessor{
|
validations: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "!authorizer.group('').resource('endpoints').check('create').allowed()",
|
Expression: "!authorizer.group('').resource('endpoints').check('create').allowed()",
|
||||||
},
|
},
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "!authorizer.group('').resource('endpoints').check('create').allowed()",
|
Expression: "!authorizer.group('').resource('endpoints').check('create').allowed()",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1171,10 +1170,10 @@ func TestRuntimeCELCostBudget(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "With StrictCostEnforcementForVAP enabled: test RuntimeCELCostBudge is not exceed",
|
name: "With StrictCostEnforcementForVAP enabled: test RuntimeCELCostBudge is not exceed",
|
||||||
validations: []ExpressionAccessor{
|
validations: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "!authorizer.group('').resource('endpoints').check('create').allowed()",
|
Expression: "!authorizer.group('').resource('endpoints').check('create').allowed()",
|
||||||
},
|
},
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "!authorizer.group('').resource('endpoints').check('create').allowed()",
|
Expression: "!authorizer.group('').resource('endpoints').check('create').allowed()",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1190,10 +1189,10 @@ func TestRuntimeCELCostBudget(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "With StrictCostEnforcementForVAP enabled: test RuntimeCELCostBudge exactly covers",
|
name: "With StrictCostEnforcementForVAP enabled: test RuntimeCELCostBudge exactly covers",
|
||||||
validations: []ExpressionAccessor{
|
validations: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "!authorizer.group('').resource('endpoints').check('create').allowed()",
|
Expression: "!authorizer.group('').resource('endpoints').check('create').allowed()",
|
||||||
},
|
},
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "!authorizer.group('').resource('endpoints').check('create').allowed()",
|
Expression: "!authorizer.group('').resource('endpoints').check('create').allowed()",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1209,7 +1208,7 @@ func TestRuntimeCELCostBudget(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "With StrictCostEnforcementForVAP enabled: per call limit exceeds",
|
name: "With StrictCostEnforcementForVAP enabled: per call limit exceeds",
|
||||||
validations: []ExpressionAccessor{
|
validations: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "!authorizer.group('').resource('endpoints').check('create').allowed() && !authorizer.group('').resource('endpoints').check('create').allowed() && !authorizer.group('').resource('endpoints').check('create').allowed()",
|
Expression: "!authorizer.group('').resource('endpoints').check('create').allowed() && !authorizer.group('').resource('endpoints').check('create').allowed() && !authorizer.group('').resource('endpoints').check('create').allowed()",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1224,7 +1223,7 @@ func TestRuntimeCELCostBudget(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "With StrictCostEnforcementForVAP enabled: Extended library cost: isSorted()",
|
name: "With StrictCostEnforcementForVAP enabled: Extended library cost: isSorted()",
|
||||||
validations: []ExpressionAccessor{
|
validations: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "[1,2,3,4].isSorted()",
|
Expression: "[1,2,3,4].isSorted()",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1238,7 +1237,7 @@ func TestRuntimeCELCostBudget(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "With StrictCostEnforcementForVAP enabled: Extended library cost: url",
|
name: "With StrictCostEnforcementForVAP enabled: Extended library cost: url",
|
||||||
validations: []ExpressionAccessor{
|
validations: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "url('https:://kubernetes.io/').getHostname() == 'kubernetes.io'",
|
Expression: "url('https:://kubernetes.io/').getHostname() == 'kubernetes.io'",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1252,7 +1251,7 @@ func TestRuntimeCELCostBudget(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "With StrictCostEnforcementForVAP enabled: Extended library cost: split",
|
name: "With StrictCostEnforcementForVAP enabled: Extended library cost: split",
|
||||||
validations: []ExpressionAccessor{
|
validations: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "size('abc 123 def 123'.split(' ')) > 0",
|
Expression: "size('abc 123 def 123'.split(' ')) > 0",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1266,7 +1265,7 @@ func TestRuntimeCELCostBudget(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "With StrictCostEnforcementForVAP enabled: Extended library cost: join",
|
name: "With StrictCostEnforcementForVAP enabled: Extended library cost: join",
|
||||||
validations: []ExpressionAccessor{
|
validations: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "size(['aa', 'bb', 'cc', 'd', 'e', 'f', 'g', 'h', 'i', 'j'].join(' ')) > 0",
|
Expression: "size(['aa', 'bb', 'cc', 'd', 'e', 'f', 'g', 'h', 'i', 'j'].join(' ')) > 0",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1280,7 +1279,7 @@ func TestRuntimeCELCostBudget(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "With StrictCostEnforcementForVAP enabled: Extended library cost: find",
|
name: "With StrictCostEnforcementForVAP enabled: Extended library cost: find",
|
||||||
validations: []ExpressionAccessor{
|
validations: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "size('abc 123 def 123'.find('123')) > 0",
|
Expression: "size('abc 123 def 123'.find('123')) > 0",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1294,7 +1293,7 @@ func TestRuntimeCELCostBudget(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "With StrictCostEnforcementForVAP enabled: Extended library cost: quantity",
|
name: "With StrictCostEnforcementForVAP enabled: Extended library cost: quantity",
|
||||||
validations: []ExpressionAccessor{
|
validations: []ExpressionAccessor{
|
||||||
&condition{
|
&testCondition{
|
||||||
Expression: "quantity(\"200M\") == quantity(\"0.2G\") && quantity(\"0.2G\") == quantity(\"200M\")",
|
Expression: "quantity(\"200M\") == quantity(\"0.2G\") && quantity(\"0.2G\") == quantity(\"200M\")",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1309,13 +1308,13 @@ func TestRuntimeCELCostBudget(t *testing.T) {
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
c := filterCompiler{compiler: NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), tc.enableStrictCostEnforcement))}
|
c := conditionCompiler{compiler: NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), tc.enableStrictCostEnforcement))}
|
||||||
f := c.Compile(tc.validations, OptionalVariableDeclarations{HasParams: tc.hasParamKind, HasAuthorizer: true, StrictCost: tc.enableStrictCostEnforcement}, environment.NewExpressions)
|
f := c.CompileCondition(tc.validations, OptionalVariableDeclarations{HasParams: tc.hasParamKind, HasAuthorizer: true, StrictCost: tc.enableStrictCostEnforcement}, environment.NewExpressions)
|
||||||
if f == nil {
|
if f == nil {
|
||||||
t.Fatalf("unexpected nil validator")
|
t.Fatalf("unexpected nil validator")
|
||||||
}
|
}
|
||||||
validations := tc.validations
|
validations := tc.validations
|
||||||
CompilationResults := f.(*filter).compilationResults
|
CompilationResults := f.(*condition).compilationResults
|
||||||
require.Equal(t, len(validations), len(CompilationResults))
|
require.Equal(t, len(validations), len(CompilationResults))
|
||||||
|
|
||||||
versionedAttr, err := admission.NewVersionedAttributes(tc.attributes, tc.attributes.GetKind(), newObjectInterfacesForTest())
|
versionedAttr, err := admission.NewVersionedAttributes(tc.attributes, tc.attributes.GetKind(), newObjectInterfacesForTest())
|
||||||
|
@ -1476,7 +1475,7 @@ func TestCompilationErrors(t *testing.T) {
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
e := filter{
|
e := condition{
|
||||||
compilationResults: tc.results,
|
compilationResults: tc.results,
|
||||||
}
|
}
|
||||||
compilationErrors := e.CompilationErrors()
|
compilationErrors := e.CompilationErrors()
|
|
@ -1,361 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2022 The Kubernetes Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package cel
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"reflect"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/google/cel-go/interpreter"
|
|
||||||
|
|
||||||
admissionv1 "k8s.io/api/admission/v1"
|
|
||||||
authenticationv1 "k8s.io/api/authentication/v1"
|
|
||||||
v1 "k8s.io/api/core/v1"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
|
||||||
"k8s.io/apiserver/pkg/admission"
|
|
||||||
"k8s.io/apiserver/pkg/cel"
|
|
||||||
"k8s.io/apiserver/pkg/cel/environment"
|
|
||||||
"k8s.io/apiserver/pkg/cel/library"
|
|
||||||
)
|
|
||||||
|
|
||||||
// filterCompiler implement the interface FilterCompiler.
|
|
||||||
type filterCompiler struct {
|
|
||||||
compiler Compiler
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewFilterCompiler(env *environment.EnvSet) FilterCompiler {
|
|
||||||
return &filterCompiler{compiler: NewCompiler(env)}
|
|
||||||
}
|
|
||||||
|
|
||||||
type evaluationActivation struct {
|
|
||||||
object, oldObject, params, request, namespace, authorizer, requestResourceAuthorizer, variables interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResolveName returns a value from the activation by qualified name, or false if the name
|
|
||||||
// could not be found.
|
|
||||||
func (a *evaluationActivation) ResolveName(name string) (interface{}, bool) {
|
|
||||||
switch name {
|
|
||||||
case ObjectVarName:
|
|
||||||
return a.object, true
|
|
||||||
case OldObjectVarName:
|
|
||||||
return a.oldObject, true
|
|
||||||
case ParamsVarName:
|
|
||||||
return a.params, true // params may be null
|
|
||||||
case RequestVarName:
|
|
||||||
return a.request, true
|
|
||||||
case NamespaceVarName:
|
|
||||||
return a.namespace, true
|
|
||||||
case AuthorizerVarName:
|
|
||||||
return a.authorizer, a.authorizer != nil
|
|
||||||
case RequestResourceAuthorizerVarName:
|
|
||||||
return a.requestResourceAuthorizer, a.requestResourceAuthorizer != nil
|
|
||||||
case VariableVarName: // variables always present
|
|
||||||
return a.variables, true
|
|
||||||
default:
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parent returns the parent of the current activation, may be nil.
|
|
||||||
// If non-nil, the parent will be searched during resolve calls.
|
|
||||||
func (a *evaluationActivation) Parent() interpreter.Activation {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compile compiles the cel expressions defined in the ExpressionAccessors into a Filter
|
|
||||||
func (c *filterCompiler) Compile(expressionAccessors []ExpressionAccessor, options OptionalVariableDeclarations, mode environment.Type) Filter {
|
|
||||||
compilationResults := make([]CompilationResult, len(expressionAccessors))
|
|
||||||
for i, expressionAccessor := range expressionAccessors {
|
|
||||||
if expressionAccessor == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
compilationResults[i] = c.compiler.CompileCELExpression(expressionAccessor, options, mode)
|
|
||||||
}
|
|
||||||
return NewFilter(compilationResults)
|
|
||||||
}
|
|
||||||
|
|
||||||
// filter implements the Filter interface
|
|
||||||
type filter struct {
|
|
||||||
compilationResults []CompilationResult
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewFilter(compilationResults []CompilationResult) Filter {
|
|
||||||
return &filter{
|
|
||||||
compilationResults,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertObjectToUnstructured(obj interface{}) (*unstructured.Unstructured, error) {
|
|
||||||
if obj == nil || reflect.ValueOf(obj).IsNil() {
|
|
||||||
return &unstructured.Unstructured{Object: nil}, nil
|
|
||||||
}
|
|
||||||
ret, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &unstructured.Unstructured{Object: ret}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func objectToResolveVal(r runtime.Object) (interface{}, error) {
|
|
||||||
if r == nil || reflect.ValueOf(r).IsNil() {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
v, err := convertObjectToUnstructured(r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return v.Object, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ForInput evaluates the compiled CEL expressions converting them into CELEvaluations
|
|
||||||
// errors per evaluation are returned on the Evaluation object
|
|
||||||
// runtimeCELCostBudget was added for testing purpose only. Callers should always use const RuntimeCELCostBudget from k8s.io/apiserver/pkg/apis/cel/config.go as input.
|
|
||||||
func (f *filter) ForInput(ctx context.Context, versionedAttr *admission.VersionedAttributes, request *admissionv1.AdmissionRequest, inputs OptionalVariableBindings, namespace *v1.Namespace, runtimeCELCostBudget int64) ([]EvaluationResult, int64, error) {
|
|
||||||
// TODO: replace unstructured with ref.Val for CEL variables when native type support is available
|
|
||||||
evaluations := make([]EvaluationResult, len(f.compilationResults))
|
|
||||||
var err error
|
|
||||||
|
|
||||||
oldObjectVal, err := objectToResolveVal(versionedAttr.VersionedOldObject)
|
|
||||||
if err != nil {
|
|
||||||
return nil, -1, err
|
|
||||||
}
|
|
||||||
objectVal, err := objectToResolveVal(versionedAttr.VersionedObject)
|
|
||||||
if err != nil {
|
|
||||||
return nil, -1, err
|
|
||||||
}
|
|
||||||
var paramsVal, authorizerVal, requestResourceAuthorizerVal any
|
|
||||||
if inputs.VersionedParams != nil {
|
|
||||||
paramsVal, err = objectToResolveVal(inputs.VersionedParams)
|
|
||||||
if err != nil {
|
|
||||||
return nil, -1, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if inputs.Authorizer != nil {
|
|
||||||
authorizerVal = library.NewAuthorizerVal(versionedAttr.GetUserInfo(), inputs.Authorizer)
|
|
||||||
requestResourceAuthorizerVal = library.NewResourceAuthorizerVal(versionedAttr.GetUserInfo(), inputs.Authorizer, versionedAttr)
|
|
||||||
}
|
|
||||||
|
|
||||||
requestVal, err := convertObjectToUnstructured(request)
|
|
||||||
if err != nil {
|
|
||||||
return nil, -1, err
|
|
||||||
}
|
|
||||||
namespaceVal, err := objectToResolveVal(namespace)
|
|
||||||
if err != nil {
|
|
||||||
return nil, -1, err
|
|
||||||
}
|
|
||||||
va := &evaluationActivation{
|
|
||||||
object: objectVal,
|
|
||||||
oldObject: oldObjectVal,
|
|
||||||
params: paramsVal,
|
|
||||||
request: requestVal.Object,
|
|
||||||
namespace: namespaceVal,
|
|
||||||
authorizer: authorizerVal,
|
|
||||||
requestResourceAuthorizer: requestResourceAuthorizerVal,
|
|
||||||
}
|
|
||||||
|
|
||||||
// composition is an optional feature that only applies for ValidatingAdmissionPolicy.
|
|
||||||
// check if the context allows composition
|
|
||||||
var compositionCtx CompositionContext
|
|
||||||
var ok bool
|
|
||||||
if compositionCtx, ok = ctx.(CompositionContext); ok {
|
|
||||||
va.variables = compositionCtx.Variables(va)
|
|
||||||
}
|
|
||||||
|
|
||||||
remainingBudget := runtimeCELCostBudget
|
|
||||||
for i, compilationResult := range f.compilationResults {
|
|
||||||
var evaluation = &evaluations[i]
|
|
||||||
if compilationResult.ExpressionAccessor == nil { // in case of placeholder
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
evaluation.ExpressionAccessor = compilationResult.ExpressionAccessor
|
|
||||||
if compilationResult.Error != nil {
|
|
||||||
evaluation.Error = &cel.Error{
|
|
||||||
Type: cel.ErrorTypeInvalid,
|
|
||||||
Detail: fmt.Sprintf("compilation error: %v", compilationResult.Error),
|
|
||||||
Cause: compilationResult.Error,
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if compilationResult.Program == nil {
|
|
||||||
evaluation.Error = &cel.Error{
|
|
||||||
Type: cel.ErrorTypeInternal,
|
|
||||||
Detail: fmt.Sprintf("unexpected internal error compiling expression"),
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
t1 := time.Now()
|
|
||||||
evalResult, evalDetails, err := compilationResult.Program.ContextEval(ctx, va)
|
|
||||||
// budget may be spent due to lazy evaluation of composited variables
|
|
||||||
if compositionCtx != nil {
|
|
||||||
compositionCost := compositionCtx.GetAndResetCost()
|
|
||||||
if compositionCost > remainingBudget {
|
|
||||||
return nil, -1, &cel.Error{
|
|
||||||
Type: cel.ErrorTypeInvalid,
|
|
||||||
Detail: fmt.Sprintf("validation failed due to running out of cost budget, no further validation rules will be run"),
|
|
||||||
Cause: cel.ErrOutOfBudget,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
remainingBudget -= compositionCost
|
|
||||||
}
|
|
||||||
elapsed := time.Since(t1)
|
|
||||||
evaluation.Elapsed = elapsed
|
|
||||||
if evalDetails == nil {
|
|
||||||
return nil, -1, &cel.Error{
|
|
||||||
Type: cel.ErrorTypeInternal,
|
|
||||||
Detail: fmt.Sprintf("runtime cost could not be calculated for expression: %v, no further expression will be run", compilationResult.ExpressionAccessor.GetExpression()),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
rtCost := evalDetails.ActualCost()
|
|
||||||
if rtCost == nil {
|
|
||||||
return nil, -1, &cel.Error{
|
|
||||||
Type: cel.ErrorTypeInvalid,
|
|
||||||
Detail: fmt.Sprintf("runtime cost could not be calculated for expression: %v, no further expression will be run", compilationResult.ExpressionAccessor.GetExpression()),
|
|
||||||
Cause: cel.ErrOutOfBudget,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if *rtCost > math.MaxInt64 || int64(*rtCost) > remainingBudget {
|
|
||||||
return nil, -1, &cel.Error{
|
|
||||||
Type: cel.ErrorTypeInvalid,
|
|
||||||
Detail: fmt.Sprintf("validation failed due to running out of cost budget, no further validation rules will be run"),
|
|
||||||
Cause: cel.ErrOutOfBudget,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
remainingBudget -= int64(*rtCost)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
evaluation.Error = &cel.Error{
|
|
||||||
Type: cel.ErrorTypeInvalid,
|
|
||||||
Detail: fmt.Sprintf("expression '%v' resulted in error: %v", compilationResult.ExpressionAccessor.GetExpression(), err),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
evaluation.EvalResult = evalResult
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return evaluations, remainingBudget, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: to reuse https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/request/admissionreview.go#L154
|
|
||||||
func CreateAdmissionRequest(attr admission.Attributes, equivalentGVR metav1.GroupVersionResource, equivalentKind metav1.GroupVersionKind) *admissionv1.AdmissionRequest {
|
|
||||||
// Attempting to use same logic as webhook for constructing resource
|
|
||||||
// GVK, GVR, subresource
|
|
||||||
// Use the GVK, GVR that the matcher decided was equivalent to that of the request
|
|
||||||
// https://github.com/kubernetes/kubernetes/blob/90c362b3430bcbbf8f245fadbcd521dab39f1d7c/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/generic/webhook.go#L182-L210
|
|
||||||
gvk := equivalentKind
|
|
||||||
gvr := equivalentGVR
|
|
||||||
subresource := attr.GetSubresource()
|
|
||||||
|
|
||||||
requestGVK := attr.GetKind()
|
|
||||||
requestGVR := attr.GetResource()
|
|
||||||
requestSubResource := attr.GetSubresource()
|
|
||||||
|
|
||||||
aUserInfo := attr.GetUserInfo()
|
|
||||||
var userInfo authenticationv1.UserInfo
|
|
||||||
if aUserInfo != nil {
|
|
||||||
userInfo = authenticationv1.UserInfo{
|
|
||||||
Extra: make(map[string]authenticationv1.ExtraValue),
|
|
||||||
Groups: aUserInfo.GetGroups(),
|
|
||||||
UID: aUserInfo.GetUID(),
|
|
||||||
Username: aUserInfo.GetName(),
|
|
||||||
}
|
|
||||||
// Convert the extra information in the user object
|
|
||||||
for key, val := range aUserInfo.GetExtra() {
|
|
||||||
userInfo.Extra[key] = authenticationv1.ExtraValue(val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dryRun := attr.IsDryRun()
|
|
||||||
|
|
||||||
return &admissionv1.AdmissionRequest{
|
|
||||||
Kind: metav1.GroupVersionKind{
|
|
||||||
Group: gvk.Group,
|
|
||||||
Kind: gvk.Kind,
|
|
||||||
Version: gvk.Version,
|
|
||||||
},
|
|
||||||
Resource: metav1.GroupVersionResource{
|
|
||||||
Group: gvr.Group,
|
|
||||||
Resource: gvr.Resource,
|
|
||||||
Version: gvr.Version,
|
|
||||||
},
|
|
||||||
SubResource: subresource,
|
|
||||||
RequestKind: &metav1.GroupVersionKind{
|
|
||||||
Group: requestGVK.Group,
|
|
||||||
Kind: requestGVK.Kind,
|
|
||||||
Version: requestGVK.Version,
|
|
||||||
},
|
|
||||||
RequestResource: &metav1.GroupVersionResource{
|
|
||||||
Group: requestGVR.Group,
|
|
||||||
Resource: requestGVR.Resource,
|
|
||||||
Version: requestGVR.Version,
|
|
||||||
},
|
|
||||||
RequestSubResource: requestSubResource,
|
|
||||||
Name: attr.GetName(),
|
|
||||||
Namespace: attr.GetNamespace(),
|
|
||||||
Operation: admissionv1.Operation(attr.GetOperation()),
|
|
||||||
UserInfo: userInfo,
|
|
||||||
// Leave Object and OldObject unset since we don't provide access to them via request
|
|
||||||
DryRun: &dryRun,
|
|
||||||
Options: runtime.RawExtension{
|
|
||||||
Object: attr.GetOperationOptions(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateNamespaceObject creates a Namespace object that is suitable for the CEL evaluation.
|
|
||||||
// If the namespace is nil, CreateNamespaceObject returns nil
|
|
||||||
func CreateNamespaceObject(namespace *v1.Namespace) *v1.Namespace {
|
|
||||||
if namespace == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return &v1.Namespace{
|
|
||||||
Status: namespace.Status,
|
|
||||||
Spec: namespace.Spec,
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: namespace.Name,
|
|
||||||
GenerateName: namespace.GenerateName,
|
|
||||||
Namespace: namespace.Namespace,
|
|
||||||
UID: namespace.UID,
|
|
||||||
ResourceVersion: namespace.ResourceVersion,
|
|
||||||
Generation: namespace.Generation,
|
|
||||||
CreationTimestamp: namespace.CreationTimestamp,
|
|
||||||
DeletionTimestamp: namespace.DeletionTimestamp,
|
|
||||||
DeletionGracePeriodSeconds: namespace.DeletionGracePeriodSeconds,
|
|
||||||
Labels: namespace.Labels,
|
|
||||||
Annotations: namespace.Annotations,
|
|
||||||
Finalizers: namespace.Finalizers,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CompilationErrors returns a list of all the errors from the compilation of the evaluator
|
|
||||||
func (e *filter) CompilationErrors() []error {
|
|
||||||
compilationErrors := []error{}
|
|
||||||
for _, result := range e.compilationResults {
|
|
||||||
if result.Error != nil {
|
|
||||||
compilationErrors = append(compilationErrors, result.Error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return compilationErrors
|
|
||||||
}
|
|
|
@ -63,12 +63,15 @@ type OptionalVariableDeclarations struct {
|
||||||
HasAuthorizer bool
|
HasAuthorizer bool
|
||||||
// StrictCost specifies if the CEL cost limitation is strict for extended libraries as well as native libraries.
|
// StrictCost specifies if the CEL cost limitation is strict for extended libraries as well as native libraries.
|
||||||
StrictCost bool
|
StrictCost bool
|
||||||
|
// HasPatchTypes specifies if JSONPatch, Object, Object.metadata and similar types are available in CEL. These can be used
|
||||||
|
// to initialize the typed objects in CEL required to create patches.
|
||||||
|
HasPatchTypes bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// FilterCompiler contains a function to assist with converting types and values to/from CEL-typed values.
|
// ConditionCompiler contains a function to assist with converting types and values to/from CEL-typed values.
|
||||||
type FilterCompiler interface {
|
type ConditionCompiler interface {
|
||||||
// Compile is used for the cel expression compilation
|
// CompileCondition is used for the cel expression compilation
|
||||||
Compile(expressions []ExpressionAccessor, optionalDecls OptionalVariableDeclarations, envType environment.Type) Filter
|
CompileCondition(expressions []ExpressionAccessor, optionalDecls OptionalVariableDeclarations, envType environment.Type) ConditionEvaluator
|
||||||
}
|
}
|
||||||
|
|
||||||
// OptionalVariableBindings provides expression bindings for optional CEL variables.
|
// OptionalVariableBindings provides expression bindings for optional CEL variables.
|
||||||
|
@ -82,16 +85,38 @@ type OptionalVariableBindings struct {
|
||||||
Authorizer authorizer.Authorizer
|
Authorizer authorizer.Authorizer
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter contains a function to evaluate compiled CEL-typed values
|
// ConditionEvaluator contains the result of compiling a CEL expression
|
||||||
|
// that evaluates to a condition. This is used both for validation and pre-conditions.
|
||||||
// It expects the inbound object to already have been converted to the version expected
|
// It expects the inbound object to already have been converted to the version expected
|
||||||
// by the underlying CEL code (which is indicated by the match criteria of a policy definition).
|
// by the underlying CEL code (which is indicated by the match criteria of a policy definition).
|
||||||
// versionedParams may be nil.
|
// versionedParams may be nil.
|
||||||
type Filter interface {
|
type ConditionEvaluator interface {
|
||||||
// ForInput converts compiled CEL-typed values into evaluated CEL-typed value.
|
// ForInput converts compiled CEL-typed values into evaluated CEL-typed value.
|
||||||
// runtimeCELCostBudget was added for testing purpose only. Callers should always use const RuntimeCELCostBudget from k8s.io/apiserver/pkg/apis/cel/config.go as input.
|
// runtimeCELCostBudget was added for testing purpose only. Callers should always use const RuntimeCELCostBudget from k8s.io/apiserver/pkg/apis/cel/config.go as input.
|
||||||
// If cost budget is calculated, the filter should return the remaining budget.
|
// If cost budget is calculated, the condition should return the remaining budget.
|
||||||
ForInput(ctx context.Context, versionedAttr *admission.VersionedAttributes, request *v1.AdmissionRequest, optionalVars OptionalVariableBindings, namespace *corev1.Namespace, runtimeCELCostBudget int64) ([]EvaluationResult, int64, error)
|
ForInput(ctx context.Context, versionedAttr *admission.VersionedAttributes, request *v1.AdmissionRequest, optionalVars OptionalVariableBindings, namespace *corev1.Namespace, runtimeCELCostBudget int64) ([]EvaluationResult, int64, error)
|
||||||
|
|
||||||
// CompilationErrors returns a list of errors from the compilation of the evaluator
|
// CompilationErrors returns a list of errors from the compilation of the mutatingEvaluator
|
||||||
|
CompilationErrors() []error
|
||||||
|
}
|
||||||
|
|
||||||
|
// MutatingCompiler contains a function to assist with converting types and values to/from CEL-typed values.
|
||||||
|
type MutatingCompiler interface {
|
||||||
|
// CompileMutatingEvaluator is used for the cel expression compilation
|
||||||
|
CompileMutatingEvaluator(expression ExpressionAccessor, optionalDecls OptionalVariableDeclarations, envType environment.Type) MutatingEvaluator
|
||||||
|
}
|
||||||
|
|
||||||
|
// MutatingEvaluator contains the result of compiling a CEL expression
|
||||||
|
// that evaluates to a mutation.
|
||||||
|
// It expects the inbound object to already have been converted to the version expected
|
||||||
|
// by the underlying CEL code (which is indicated by the match criteria of a policy definition).
|
||||||
|
// versionedParams may be nil.
|
||||||
|
type MutatingEvaluator interface {
|
||||||
|
// ForInput converts compiled CEL-typed values into a CEL-typed value representing a mutation.
|
||||||
|
// runtimeCELCostBudget was added for testing purpose only. Callers should always use const RuntimeCELCostBudget from k8s.io/apiserver/pkg/apis/cel/config.go as input.
|
||||||
|
// If cost budget is calculated, the condition should return the remaining budget.
|
||||||
|
ForInput(ctx context.Context, versionedAttr *admission.VersionedAttributes, request *v1.AdmissionRequest, optionalVars OptionalVariableBindings, namespace *corev1.Namespace, runtimeCELCostBudget int64) (EvaluationResult, int64, error)
|
||||||
|
|
||||||
|
// CompilationErrors returns a list of errors from the compilation of the mutatingEvaluator
|
||||||
CompilationErrors() []error
|
CompilationErrors() []error
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
/*
|
||||||
|
Copyright 2024 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package cel
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
admissionv1 "k8s.io/api/admission/v1"
|
||||||
|
v1 "k8s.io/api/core/v1"
|
||||||
|
"k8s.io/apiserver/pkg/admission"
|
||||||
|
"k8s.io/apiserver/pkg/cel/environment"
|
||||||
|
)
|
||||||
|
|
||||||
|
// mutatingCompiler provides a MutatingCompiler implementation.
|
||||||
|
type mutatingCompiler struct {
|
||||||
|
compiler Compiler
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompileMutatingEvaluator compiles a CEL expression for admission plugins and returns an MutatingEvaluator for executing the
|
||||||
|
// compiled CEL expression.
|
||||||
|
func (p *mutatingCompiler) CompileMutatingEvaluator(expressionAccessor ExpressionAccessor, options OptionalVariableDeclarations, mode environment.Type) MutatingEvaluator {
|
||||||
|
compilationResult := p.compiler.CompileCELExpression(expressionAccessor, options, mode)
|
||||||
|
return NewMutatingEvaluator(compilationResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
type mutatingEvaluator struct {
|
||||||
|
compilationResult CompilationResult
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMutatingEvaluator(compilationResult CompilationResult) MutatingEvaluator {
|
||||||
|
return &mutatingEvaluator{compilationResult}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForInput evaluates the compiled CEL expression and returns an evaluation result
|
||||||
|
// errors per evaluation are returned in the evaluation result
|
||||||
|
// runtimeCELCostBudget was added for testing purpose only. Callers should always use const RuntimeCELCostBudget from k8s.io/apiserver/pkg/apis/cel/config.go as input.
|
||||||
|
func (p *mutatingEvaluator) ForInput(ctx context.Context, versionedAttr *admission.VersionedAttributes, request *admissionv1.AdmissionRequest, inputs OptionalVariableBindings, namespace *v1.Namespace, runtimeCELCostBudget int64) (EvaluationResult, int64, error) {
|
||||||
|
// if this activation supports composition, we will need the compositionCtx. It may be nil.
|
||||||
|
compositionCtx, _ := ctx.(CompositionContext)
|
||||||
|
|
||||||
|
activation, err := newActivation(compositionCtx, versionedAttr, request, inputs, namespace)
|
||||||
|
if err != nil {
|
||||||
|
return EvaluationResult{}, -1, err
|
||||||
|
}
|
||||||
|
evaluation, remainingBudget, err := activation.Evaluate(ctx, compositionCtx, p.compilationResult, runtimeCELCostBudget)
|
||||||
|
if err != nil {
|
||||||
|
return evaluation, -1, err
|
||||||
|
}
|
||||||
|
return evaluation, remainingBudget, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompilationErrors returns a list of all the errors from the compilation of the mutatingEvaluator
|
||||||
|
func (p *mutatingEvaluator) CompilationErrors() (compilationErrors []error) {
|
||||||
|
if p.compilationResult.Error != nil {
|
||||||
|
return []error{p.compilationResult.Error}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -50,7 +50,7 @@ type WebhookAccessor interface {
|
||||||
GetRESTClient(clientManager *webhookutil.ClientManager) (*rest.RESTClient, error)
|
GetRESTClient(clientManager *webhookutil.ClientManager) (*rest.RESTClient, error)
|
||||||
|
|
||||||
// GetCompiledMatcher gets the compiled matcher object
|
// GetCompiledMatcher gets the compiled matcher object
|
||||||
GetCompiledMatcher(compiler cel.FilterCompiler) matchconditions.Matcher
|
GetCompiledMatcher(compiler cel.ConditionCompiler) matchconditions.Matcher
|
||||||
|
|
||||||
// GetName gets the webhook Name field. Note that the name is scoped to the webhook
|
// GetName gets the webhook Name field. Note that the name is scoped to the webhook
|
||||||
// configuration and does not provide a globally unique identity, if a unique identity is
|
// configuration and does not provide a globally unique identity, if a unique identity is
|
||||||
|
@ -132,7 +132,7 @@ func (m *mutatingWebhookAccessor) GetType() string {
|
||||||
return "admit"
|
return "admit"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mutatingWebhookAccessor) GetCompiledMatcher(compiler cel.FilterCompiler) matchconditions.Matcher {
|
func (m *mutatingWebhookAccessor) GetCompiledMatcher(compiler cel.ConditionCompiler) matchconditions.Matcher {
|
||||||
m.compileMatcher.Do(func() {
|
m.compileMatcher.Do(func() {
|
||||||
expressions := make([]cel.ExpressionAccessor, len(m.MutatingWebhook.MatchConditions))
|
expressions := make([]cel.ExpressionAccessor, len(m.MutatingWebhook.MatchConditions))
|
||||||
for i, matchCondition := range m.MutatingWebhook.MatchConditions {
|
for i, matchCondition := range m.MutatingWebhook.MatchConditions {
|
||||||
|
@ -145,7 +145,7 @@ func (m *mutatingWebhookAccessor) GetCompiledMatcher(compiler cel.FilterCompiler
|
||||||
if utilfeature.DefaultFeatureGate.Enabled(features.StrictCostEnforcementForWebhooks) {
|
if utilfeature.DefaultFeatureGate.Enabled(features.StrictCostEnforcementForWebhooks) {
|
||||||
strictCost = true
|
strictCost = true
|
||||||
}
|
}
|
||||||
m.compiledMatcher = matchconditions.NewMatcher(compiler.Compile(
|
m.compiledMatcher = matchconditions.NewMatcher(compiler.CompileCondition(
|
||||||
expressions,
|
expressions,
|
||||||
cel.OptionalVariableDeclarations{
|
cel.OptionalVariableDeclarations{
|
||||||
HasParams: false,
|
HasParams: false,
|
||||||
|
@ -265,7 +265,7 @@ func (v *validatingWebhookAccessor) GetRESTClient(clientManager *webhookutil.Cli
|
||||||
return v.client, v.clientErr
|
return v.client, v.clientErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *validatingWebhookAccessor) GetCompiledMatcher(compiler cel.FilterCompiler) matchconditions.Matcher {
|
func (v *validatingWebhookAccessor) GetCompiledMatcher(compiler cel.ConditionCompiler) matchconditions.Matcher {
|
||||||
v.compileMatcher.Do(func() {
|
v.compileMatcher.Do(func() {
|
||||||
expressions := make([]cel.ExpressionAccessor, len(v.ValidatingWebhook.MatchConditions))
|
expressions := make([]cel.ExpressionAccessor, len(v.ValidatingWebhook.MatchConditions))
|
||||||
for i, matchCondition := range v.ValidatingWebhook.MatchConditions {
|
for i, matchCondition := range v.ValidatingWebhook.MatchConditions {
|
||||||
|
@ -278,7 +278,7 @@ func (v *validatingWebhookAccessor) GetCompiledMatcher(compiler cel.FilterCompil
|
||||||
if utilfeature.DefaultFeatureGate.Enabled(features.StrictCostEnforcementForWebhooks) {
|
if utilfeature.DefaultFeatureGate.Enabled(features.StrictCostEnforcementForWebhooks) {
|
||||||
strictCost = true
|
strictCost = true
|
||||||
}
|
}
|
||||||
v.compiledMatcher = matchconditions.NewMatcher(compiler.Compile(
|
v.compiledMatcher = matchconditions.NewMatcher(compiler.CompileCondition(
|
||||||
expressions,
|
expressions,
|
||||||
cel.OptionalVariableDeclarations{
|
cel.OptionalVariableDeclarations{
|
||||||
HasParams: false,
|
HasParams: false,
|
||||||
|
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"k8s.io/apiserver/pkg/cel/environment"
|
"k8s.io/apiserver/pkg/cel/environment"
|
||||||
"k8s.io/apiserver/pkg/features"
|
"k8s.io/apiserver/pkg/features"
|
||||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
|
@ -56,7 +57,7 @@ type Webhook struct {
|
||||||
namespaceMatcher *namespace.Matcher
|
namespaceMatcher *namespace.Matcher
|
||||||
objectMatcher *object.Matcher
|
objectMatcher *object.Matcher
|
||||||
dispatcher Dispatcher
|
dispatcher Dispatcher
|
||||||
filterCompiler cel.FilterCompiler
|
filterCompiler cel.ConditionCompiler
|
||||||
authorizer authorizer.Authorizer
|
authorizer authorizer.Authorizer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,7 +102,7 @@ func NewWebhook(handler *admission.Handler, configFile io.Reader, sourceFactory
|
||||||
namespaceMatcher: &namespace.Matcher{},
|
namespaceMatcher: &namespace.Matcher{},
|
||||||
objectMatcher: &object.Matcher{},
|
objectMatcher: &object.Matcher{},
|
||||||
dispatcher: dispatcherFactory(&cm),
|
dispatcher: dispatcherFactory(&cm),
|
||||||
filterCompiler: cel.NewFilterCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), utilfeature.DefaultFeatureGate.Enabled(features.StrictCostEnforcementForWebhooks))),
|
filterCompiler: cel.NewConditionCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), utilfeature.DefaultFeatureGate.Enabled(features.StrictCostEnforcementForWebhooks))),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -76,7 +76,7 @@ type fakeWebhookAccessor struct {
|
||||||
matchResult bool
|
matchResult bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fakeWebhookAccessor) GetCompiledMatcher(compiler cel.FilterCompiler) matchconditions.Matcher {
|
func (f *fakeWebhookAccessor) GetCompiledMatcher(compiler cel.ConditionCompiler) matchconditions.Matcher {
|
||||||
return &fakeMatcher{
|
return &fakeMatcher{
|
||||||
throwError: f.throwError,
|
throwError: f.throwError,
|
||||||
matchResult: f.matchResult,
|
matchResult: f.matchResult,
|
||||||
|
|
|
@ -54,14 +54,14 @@ var _ Matcher = &matcher{}
|
||||||
|
|
||||||
// matcher evaluates compiled cel expressions and determines if they match the given request or not
|
// matcher evaluates compiled cel expressions and determines if they match the given request or not
|
||||||
type matcher struct {
|
type matcher struct {
|
||||||
filter celplugin.Filter
|
filter celplugin.ConditionEvaluator
|
||||||
failPolicy v1.FailurePolicyType
|
failPolicy v1.FailurePolicyType
|
||||||
matcherType string
|
matcherType string
|
||||||
matcherKind string
|
matcherKind string
|
||||||
objectName string
|
objectName string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMatcher(filter celplugin.Filter, failPolicy *v1.FailurePolicyType, matcherKind, matcherType, objectName string) Matcher {
|
func NewMatcher(filter celplugin.ConditionEvaluator, failPolicy *v1.FailurePolicyType, matcherKind, matcherType, objectName string) Matcher {
|
||||||
var f v1.FailurePolicyType
|
var f v1.FailurePolicyType
|
||||||
if failPolicy == nil {
|
if failPolicy == nil {
|
||||||
f = v1.Fail
|
f = v1.Fail
|
||||||
|
|
|
@ -35,7 +35,7 @@ import (
|
||||||
"k8s.io/apiserver/pkg/admission/plugin/cel"
|
"k8s.io/apiserver/pkg/admission/plugin/cel"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ cel.Filter = &fakeCelFilter{}
|
var _ cel.ConditionEvaluator = &fakeCelFilter{}
|
||||||
|
|
||||||
type fakeCelFilter struct {
|
type fakeCelFilter struct {
|
||||||
evaluations []cel.EvaluationResult
|
evaluations []cel.EvaluationResult
|
||||||
|
|
Loading…
Reference in New Issue