From e025ab4701e50cdfe8386d5b2e6f9f4ebbd61a28 Mon Sep 17 00:00:00 2001 From: Cici Huang Date: Wed, 1 May 2024 16:26:41 -0700 Subject: [PATCH] Adding the feature gates to fix cost for VAP and webhook matchConditions. Kubernetes-commit: 3d896724760a957e8059ff80e9f399248eacac66 --- pkg/admission/plugin/cel/compile.go | 64 ++-- pkg/admission/plugin/cel/compile_test.go | 4 +- pkg/admission/plugin/cel/composition_test.go | 52 ++- pkg/admission/plugin/cel/filter_test.go | 312 +++++++++++++++++- pkg/admission/plugin/cel/interface.go | 4 +- .../plugin/policy/validating/plugin.go | 27 +- .../plugin/policy/validating/typechecking.go | 5 +- .../policy/validating/validator_test.go | 2 +- pkg/admission/plugin/webhook/accessors.go | 12 + .../plugin/webhook/generic/webhook.go | 6 +- pkg/apis/apiserver/validation/validation.go | 6 +- .../apiserver/validation/validation_test.go | 2 +- pkg/authentication/cel/compile_test.go | 8 +- pkg/authorization/cel/compile_test.go | 2 +- pkg/cel/environment/base.go | 47 ++- pkg/cel/environment/base_test.go | 6 +- pkg/cel/environment/environment_test.go | 2 +- pkg/cel/lazy/lazy_test.go | 2 +- pkg/cel/mutation/env_test.go | 2 +- .../unstructured/typeresolver_test.go | 2 +- pkg/cel/openapi/compiling_test.go | 2 +- pkg/features/kube_features.go | 22 ++ 22 files changed, 495 insertions(+), 96 deletions(-) diff --git a/pkg/admission/plugin/cel/compile.go b/pkg/admission/plugin/cel/compile.go index b7b589d27..bb5e233d4 100644 --- a/pkg/admission/plugin/cel/compile.go +++ b/pkg/admission/plugin/cel/compile.go @@ -222,40 +222,48 @@ func (c compiler) CompileCELExpression(expressionAccessor ExpressionAccessor, op func mustBuildEnvs(baseEnv *environment.EnvSet) variableDeclEnvs { requestType := BuildRequestType() namespaceType := BuildNamespaceType() - envs := make(variableDeclEnvs, 4) // 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 _, hasAuthorizer := range []bool{false, true} { - var envOpts []cel.EnvOption - if hasParams { - envOpts = append(envOpts, cel.Variable(ParamsVarName, cel.DynType)) - } - if hasAuthorizer { + for _, strictCost := range []bool{false, true} { + var envOpts []cel.EnvOption + if hasParams { + 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(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())) + 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, + 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 { - panic(fmt.Sprintf("environment misconfigured: %v", err)) + ) + if err != nil { + panic(fmt.Sprintf("environment misconfigured: %v", err)) + } + if strictCost { + extended, err = extended.Extend(environment.StrictCostOpt) + if err != nil { + panic(fmt.Sprintf("environment misconfigured: %v", err)) + } + } + envs[OptionalVariableDeclarations{HasParams: hasParams, HasAuthorizer: hasAuthorizer, StrictCost: strictCost}] = extended } - envs[OptionalVariableDeclarations{HasParams: hasParams, HasAuthorizer: hasAuthorizer}] = extended } } return envs diff --git a/pkg/admission/plugin/cel/compile_test.go b/pkg/admission/plugin/cel/compile_test.go index 5caf82858..2059cc524 100644 --- a/pkg/admission/plugin/cel/compile_test.go +++ b/pkg/admission/plugin/cel/compile_test.go @@ -178,7 +178,7 @@ func TestCompileValidatingPolicyExpression(t *testing.T) { } // Include the test library, which includes the test() function in the storage environment during test - base := environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion()) + base := environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), true) extended, err := base.Extend(environment.VersionedOptions{ IntroducedVersion: version.MajorMinor(1, 999), EnvOptions: []celgo.EnvOption{library.Test()}, @@ -254,7 +254,7 @@ func TestCompileValidatingPolicyExpression(t *testing.T) { } func BenchmarkCompile(b *testing.B) { - compiler := NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion())) + compiler := NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), true)) b.ResetTimer() for i := 0; i < b.N; i++ { options := OptionalVariableDeclarations{HasParams: rand.Int()%2 == 0, HasAuthorizer: rand.Int()%2 == 0} diff --git a/pkg/admission/plugin/cel/composition_test.go b/pkg/admission/plugin/cel/composition_test.go index aedc7c969..53d9b8612 100644 --- a/pkg/admission/plugin/cel/composition_test.go +++ b/pkg/admission/plugin/cel/composition_test.go @@ -48,14 +48,15 @@ func (t *testVariable) GetName() string { func TestCompositedPolicies(t *testing.T) { cases := []struct { - name string - variables []NamedExpressionAccessor - expression string - attributes admission.Attributes - expectedResult any - expectErr bool - expectedErrorMessage string - runtimeCostBudget int64 + name string + variables []NamedExpressionAccessor + expression string + attributes admission.Attributes + expectedResult any + expectErr bool + expectedErrorMessage string + runtimeCostBudget int64 + strictCostEnforcement bool }{ { name: "simple", @@ -185,16 +186,45 @@ func TestCompositedPolicies(t *testing.T) { expectErr: true, expectedErrorMessage: "found no matching overload for '_==_' applied to '(string, int)'", }, + { + name: "with strictCostEnforcement on: exceeds cost budget", + variables: []NamedExpressionAccessor{ + &testVariable{ + name: "dict", + expression: "'abc 123 def 123'.split(' ')", + }, + }, + attributes: endpointCreateAttributes(), + expression: "size(variables.dict) > 0", + expectErr: true, + expectedErrorMessage: "validation failed due to running out of cost budget, no further validation rules will be run", + runtimeCostBudget: 5, + strictCostEnforcement: true, + }, + { + name: "with strictCostEnforcement off: not exceed cost budget", + variables: []NamedExpressionAccessor{ + &testVariable{ + name: "dict", + expression: "'abc 123 def 123'.split(' ')", + }, + }, + attributes: endpointCreateAttributes(), + expression: "size(variables.dict) > 0", + expectedResult: true, + runtimeCostBudget: 5, + strictCostEnforcement: false, + }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { - compiler, err := NewCompositedCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion())) + compiler, err := NewCompositedCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), tc.strictCostEnforcement)) if err != nil { t.Fatal(err) } - compiler.CompileAndStoreVariables(tc.variables, OptionalVariableDeclarations{HasParams: false, HasAuthorizer: false}, environment.NewExpressions) + compiler.CompileAndStoreVariables(tc.variables, OptionalVariableDeclarations{HasParams: false, HasAuthorizer: false, StrictCost: tc.strictCostEnforcement}, environment.NewExpressions) validations := []ExpressionAccessor{&condition{Expression: tc.expression}} - f := compiler.Compile(validations, OptionalVariableDeclarations{HasParams: false, HasAuthorizer: false}, environment.NewExpressions) + f := compiler.Compile(validations, OptionalVariableDeclarations{HasParams: false, HasAuthorizer: false, StrictCost: tc.strictCostEnforcement}, environment.NewExpressions) versionedAttr, err := admission.NewVersionedAttributes(tc.attributes, tc.attributes.GetKind(), newObjectInterfacesForTest()) if err != nil { t.Fatal(err) diff --git a/pkg/admission/plugin/cel/filter_test.go b/pkg/admission/plugin/cel/filter_test.go index 390c5d780..8ce7d4709 100644 --- a/pkg/admission/plugin/cel/filter_test.go +++ b/pkg/admission/plugin/cel/filter_test.go @@ -93,8 +93,8 @@ func TestCompile(t *testing.T) { for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { - c := filterCompiler{compiler: NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion()))} - e := c.Compile(tc.validation, OptionalVariableDeclarations{HasParams: false, HasAuthorizer: false}, environment.NewExpressions) + c := filterCompiler{compiler: NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), true))} + e := c.Compile(tc.validation, OptionalVariableDeclarations{HasParams: false, HasAuthorizer: false, StrictCost: true}, environment.NewExpressions) if e == nil { t.Fatalf("unexpected nil validator") } @@ -182,6 +182,7 @@ func TestFilter(t *testing.T) { authorizer authorizer.Authorizer testPerCallLimit uint64 namespaceObject *corev1.Namespace + strictCost bool }{ { name: "valid syntax for object", @@ -762,7 +763,7 @@ func TestFilter(t *testing.T) { if tc.testPerCallLimit == 0 { tc.testPerCallLimit = celconfig.PerCallLimit } - env, err := environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion()).Extend( + env, err := environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), tc.strictCost).Extend( environment.VersionedOptions{ IntroducedVersion: environment.DefaultCompatibilityVersion(), ProgramOptions: []celgo.ProgramOption{celgo.CostLimit(tc.testPerCallLimit)}, @@ -772,7 +773,7 @@ func TestFilter(t *testing.T) { t.Fatal(err) } c := NewFilterCompiler(env) - f := c.Compile(tc.validations, OptionalVariableDeclarations{HasParams: tc.hasParamKind, HasAuthorizer: tc.authorizer != nil}, environment.NewExpressions) + f := c.Compile(tc.validations, OptionalVariableDeclarations{HasParams: tc.hasParamKind, HasAuthorizer: tc.authorizer != nil, StrictCost: tc.strictCost}, environment.NewExpressions) if f == nil { t.Fatalf("unexpected nil validator") } @@ -815,15 +816,17 @@ func TestRuntimeCELCostBudget(t *testing.T) { } cases := []struct { - name string - attributes admission.Attributes - params runtime.Object - validations []ExpressionAccessor - hasParamKind bool - authorizer authorizer.Authorizer - testRuntimeCELCostBudget int64 - exceedBudget bool - expectRemainingBudget *int64 + name string + attributes admission.Attributes + params runtime.Object + validations []ExpressionAccessor + hasParamKind bool + authorizer authorizer.Authorizer + testRuntimeCELCostBudget int64 + exceedBudget bool + expectRemainingBudget *int64 + enableStrictCostEnforcement bool + exceedPerCallLimit bool }{ { name: "expression exceed RuntimeCELCostBudget at fist expression", @@ -910,12 +913,275 @@ func TestRuntimeCELCostBudget(t *testing.T) { testRuntimeCELCostBudget: 6, expectRemainingBudget: pointer.Int64(0), }, + { + name: "Extended library cost: authz check", + validations: []ExpressionAccessor{ + &condition{ + Expression: "!authorizer.group('').resource('endpoints').check('create').allowed()", + }, + }, + attributes: newValidAttribute(nil, false), + hasParamKind: false, + exceedBudget: false, + testRuntimeCELCostBudget: 6, + expectRemainingBudget: pointer.Int64(0), + authorizer: denyAll, + }, + { + name: "Extended library cost: isSorted()", + validations: []ExpressionAccessor{ + &condition{ + Expression: "[1,2,3,4].isSorted()", + }, + }, + attributes: newValidAttribute(nil, false), + hasParamKind: false, + exceedBudget: false, + testRuntimeCELCostBudget: 1, + expectRemainingBudget: pointer.Int64(0), + }, + { + name: "Extended library cost: url", + validations: []ExpressionAccessor{ + &condition{ + Expression: "url('https:://kubernetes.io/').getHostname() == 'kubernetes.io'", + }, + }, + attributes: newValidAttribute(nil, false), + hasParamKind: false, + exceedBudget: false, + testRuntimeCELCostBudget: 2, + expectRemainingBudget: pointer.Int64(0), + }, + { + name: "Extended library cost: split", + validations: []ExpressionAccessor{ + &condition{ + Expression: "size('abc 123 def 123'.split(' ')) > 0", + }, + }, + attributes: newValidAttribute(nil, false), + hasParamKind: false, + exceedBudget: false, + testRuntimeCELCostBudget: 3, + expectRemainingBudget: pointer.Int64(0), + }, + { + name: "Extended library cost: join", + validations: []ExpressionAccessor{ + &condition{ + Expression: "size(['aa', 'bb', 'cc', 'd', 'e', 'f', 'g', 'h', 'i', 'j'].join(' ')) > 0", + }, + }, + attributes: newValidAttribute(nil, false), + hasParamKind: false, + exceedBudget: false, + testRuntimeCELCostBudget: 3, + expectRemainingBudget: pointer.Int64(0), + }, + { + name: "Extended library cost: find", + validations: []ExpressionAccessor{ + &condition{ + Expression: "size('abc 123 def 123'.find('123')) > 0", + }, + }, + attributes: newValidAttribute(nil, false), + hasParamKind: false, + exceedBudget: false, + testRuntimeCELCostBudget: 3, + expectRemainingBudget: pointer.Int64(0), + }, + { + name: "Extended library cost: quantity", + validations: []ExpressionAccessor{ + &condition{ + Expression: "quantity(\"200M\") == quantity(\"0.2G\") && quantity(\"0.2G\") == quantity(\"200M\")", + }, + }, + attributes: newValidAttribute(nil, false), + hasParamKind: false, + exceedBudget: false, + testRuntimeCELCostBudget: 6, + expectRemainingBudget: pointer.Int64(0), + }, + { + name: "With StrictCostEnforcementForVAP enabled: expression exceed RuntimeCELCostBudget at fist expression", + validations: []ExpressionAccessor{ + &condition{ + Expression: "!authorizer.group('').resource('endpoints').check('create').allowed()", + }, + &condition{ + Expression: "has(object.subsets)", + }, + }, + attributes: newValidAttribute(nil, false), + hasParamKind: false, + testRuntimeCELCostBudget: 35000, + exceedBudget: true, + authorizer: denyAll, + enableStrictCostEnforcement: true, + }, + { + name: "With StrictCostEnforcementForVAP enabled: expression exceed RuntimeCELCostBudget at last expression", + validations: []ExpressionAccessor{ + &condition{ + Expression: "!authorizer.group('').resource('endpoints').check('create').allowed()", + }, + &condition{ + Expression: "!authorizer.group('').resource('endpoints').check('create').allowed()", + }, + }, + attributes: newValidAttribute(nil, false), + hasParamKind: false, + testRuntimeCELCostBudget: 700000, + exceedBudget: true, + authorizer: denyAll, + enableStrictCostEnforcement: true, + }, + { + name: "With StrictCostEnforcementForVAP enabled: test RuntimeCELCostBudge is not exceed", + validations: []ExpressionAccessor{ + &condition{ + Expression: "!authorizer.group('').resource('endpoints').check('create').allowed()", + }, + &condition{ + Expression: "!authorizer.group('').resource('endpoints').check('create').allowed()", + }, + }, + attributes: newValidAttribute(nil, false), + hasParamKind: true, + params: configMapParams, + exceedBudget: false, + testRuntimeCELCostBudget: 700011, + expectRemainingBudget: pointer.Int64(1), // 700011 - 700010 + authorizer: denyAll, + enableStrictCostEnforcement: true, + }, + { + name: "With StrictCostEnforcementForVAP enabled: test RuntimeCELCostBudge exactly covers", + validations: []ExpressionAccessor{ + &condition{ + Expression: "!authorizer.group('').resource('endpoints').check('create').allowed()", + }, + &condition{ + Expression: "!authorizer.group('').resource('endpoints').check('create').allowed()", + }, + }, + attributes: newValidAttribute(nil, false), + hasParamKind: true, + params: configMapParams, + exceedBudget: false, + testRuntimeCELCostBudget: 700010, + expectRemainingBudget: pointer.Int64(0), + authorizer: denyAll, + enableStrictCostEnforcement: true, + }, + { + name: "With StrictCostEnforcementForVAP enabled: per call limit exceeds", + validations: []ExpressionAccessor{ + &condition{ + Expression: "!authorizer.group('').resource('endpoints').check('create').allowed() && !authorizer.group('').resource('endpoints').check('create').allowed() && !authorizer.group('').resource('endpoints').check('create').allowed()", + }, + }, + attributes: newValidAttribute(nil, false), + hasParamKind: true, + params: configMapParams, + authorizer: denyAll, + exceedPerCallLimit: true, + testRuntimeCELCostBudget: -1, + enableStrictCostEnforcement: true, + }, + { + name: "With StrictCostEnforcementForVAP enabled: Extended library cost: isSorted()", + validations: []ExpressionAccessor{ + &condition{ + Expression: "[1,2,3,4].isSorted()", + }, + }, + attributes: newValidAttribute(nil, false), + hasParamKind: false, + exceedBudget: false, + testRuntimeCELCostBudget: 4, + expectRemainingBudget: pointer.Int64(0), + enableStrictCostEnforcement: true, + }, + { + name: "With StrictCostEnforcementForVAP enabled: Extended library cost: url", + validations: []ExpressionAccessor{ + &condition{ + Expression: "url('https:://kubernetes.io/').getHostname() == 'kubernetes.io'", + }, + }, + attributes: newValidAttribute(nil, false), + hasParamKind: false, + exceedBudget: false, + testRuntimeCELCostBudget: 4, + expectRemainingBudget: pointer.Int64(0), + enableStrictCostEnforcement: true, + }, + { + name: "With StrictCostEnforcementForVAP enabled: Extended library cost: split", + validations: []ExpressionAccessor{ + &condition{ + Expression: "size('abc 123 def 123'.split(' ')) > 0", + }, + }, + attributes: newValidAttribute(nil, false), + hasParamKind: false, + exceedBudget: false, + testRuntimeCELCostBudget: 5, + expectRemainingBudget: pointer.Int64(0), + enableStrictCostEnforcement: true, + }, + { + name: "With StrictCostEnforcementForVAP enabled: Extended library cost: join", + validations: []ExpressionAccessor{ + &condition{ + Expression: "size(['aa', 'bb', 'cc', 'd', 'e', 'f', 'g', 'h', 'i', 'j'].join(' ')) > 0", + }, + }, + attributes: newValidAttribute(nil, false), + hasParamKind: false, + exceedBudget: false, + testRuntimeCELCostBudget: 7, + expectRemainingBudget: pointer.Int64(0), + enableStrictCostEnforcement: true, + }, + { + name: "With StrictCostEnforcementForVAP enabled: Extended library cost: find", + validations: []ExpressionAccessor{ + &condition{ + Expression: "size('abc 123 def 123'.find('123')) > 0", + }, + }, + attributes: newValidAttribute(nil, false), + hasParamKind: false, + exceedBudget: false, + testRuntimeCELCostBudget: 4, + expectRemainingBudget: pointer.Int64(0), + enableStrictCostEnforcement: true, + }, + { + name: "With StrictCostEnforcementForVAP enabled: Extended library cost: quantity", + validations: []ExpressionAccessor{ + &condition{ + Expression: "quantity(\"200M\") == quantity(\"0.2G\") && quantity(\"0.2G\") == quantity(\"200M\")", + }, + }, + attributes: newValidAttribute(nil, false), + hasParamKind: false, + exceedBudget: false, + testRuntimeCELCostBudget: 6, + expectRemainingBudget: pointer.Int64(0), + enableStrictCostEnforcement: true, + }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { - c := filterCompiler{compiler: NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion()))} - f := c.Compile(tc.validations, OptionalVariableDeclarations{HasParams: tc.hasParamKind, HasAuthorizer: false}, environment.NewExpressions) + c := filterCompiler{compiler: NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), tc.enableStrictCostEnforcement))} + f := c.Compile(tc.validations, OptionalVariableDeclarations{HasParams: tc.hasParamKind, HasAuthorizer: true, StrictCost: tc.enableStrictCostEnforcement}, environment.NewExpressions) if f == nil { t.Fatalf("unexpected nil validator") } @@ -928,16 +1194,28 @@ func TestRuntimeCELCostBudget(t *testing.T) { t.Fatalf("unexpected error on conversion: %v", err) } - if tc.testRuntimeCELCostBudget == 0 { + if tc.testRuntimeCELCostBudget < 0 { tc.testRuntimeCELCostBudget = celconfig.RuntimeCELCostBudget } optionalVars := OptionalVariableBindings{VersionedParams: tc.params, Authorizer: tc.authorizer} ctx := context.TODO() evalResults, remaining, err := f.ForInput(ctx, versionedAttr, CreateAdmissionRequest(versionedAttr.Attributes, metav1.GroupVersionResource(versionedAttr.GetResource()), metav1.GroupVersionKind(versionedAttr.VersionedKind)), optionalVars, nil, tc.testRuntimeCELCostBudget) + if tc.exceedPerCallLimit { + hasCostErr := false + for _, evalResult := range evalResults { + if evalResult.Error != nil && strings.Contains(evalResult.Error.Error(), "operation cancelled: actual cost limit exceeded") { + hasCostErr = true + break + } + } + if !hasCostErr { + t.Errorf("Expected per call limit exceeded error but didn't get one") + } + } if tc.exceedBudget && err == nil { t.Errorf("Expected RuntimeCELCostBudge to be exceeded but got nil") } - if tc.exceedBudget && !strings.Contains(err.Error(), "validation failed due to running out of cost budget, no further validation rules will be run") { + if tc.exceedBudget && err != nil && !strings.Contains(err.Error(), "validation failed due to running out of cost budget, no further validation rules will be run") { t.Errorf("Expected RuntimeCELCostBudge exceeded error but got: %v", err) } if err != nil && remaining != -1 { diff --git a/pkg/admission/plugin/cel/interface.go b/pkg/admission/plugin/cel/interface.go index c9f4e6336..ae61dc826 100644 --- a/pkg/admission/plugin/cel/interface.go +++ b/pkg/admission/plugin/cel/interface.go @@ -57,10 +57,12 @@ type OptionalVariableDeclarations struct { // HasParams specifies if the "params" variable is declared. // The "params" variable may still be bound to "null" when declared. HasParams bool - // HasAuthorizer specifies if the"authorizer" and "authorizer.requestResource" + // HasAuthorizer specifies if the "authorizer" and "authorizer.requestResource" // variables are declared. When declared, the authorizer variables are // expected to be non-null. HasAuthorizer bool + // StrictCost specifies if the CEL cost limitation is strict for extended libraries as well as native libraries. + StrictCost bool } // FilterCompiler contains a function to assist with converting types and values to/from CEL-typed values. diff --git a/pkg/admission/plugin/policy/validating/plugin.go b/pkg/admission/plugin/policy/validating/plugin.go index c286cffbd..fb097737a 100644 --- a/pkg/admission/plugin/policy/validating/plugin.go +++ b/pkg/admission/plugin/policy/validating/plugin.go @@ -31,6 +31,7 @@ import ( "k8s.io/apiserver/pkg/authorization/authorizer" "k8s.io/apiserver/pkg/cel/environment" "k8s.io/apiserver/pkg/features" + utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/client-go/dynamic" "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes" @@ -43,13 +44,21 @@ const ( ) var ( - compositionEnvTemplate *cel.CompositionEnv = func() *cel.CompositionEnv { - compositionEnvTemplate, err := cel.NewCompositionEnv(cel.VariablesTypeName, environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion())) + compositionEnvTemplateWithStrictCost *cel.CompositionEnv = func() *cel.CompositionEnv { + compositionEnvTemplateWithStrictCost, err := cel.NewCompositionEnv(cel.VariablesTypeName, environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), true)) if err != nil { panic(err) } - return compositionEnvTemplate + return compositionEnvTemplateWithStrictCost + }() + compositionEnvTemplateWithoutStrictCost *cel.CompositionEnv = func() *cel.CompositionEnv { + compositionEnvTemplateWithoutStrictCost, err := cel.NewCompositionEnv(cel.VariablesTypeName, environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), false)) + if err != nil { + panic(err) + } + + return compositionEnvTemplateWithoutStrictCost }() ) @@ -114,12 +123,18 @@ func compilePolicy(policy *Policy) Validator { if policy.Spec.ParamKind != nil { hasParam = true } - optionalVars := cel.OptionalVariableDeclarations{HasParams: hasParam, HasAuthorizer: true} - expressionOptionalVars := cel.OptionalVariableDeclarations{HasParams: hasParam, HasAuthorizer: false} + strictCost := utilfeature.DefaultFeatureGate.Enabled(features.StrictCostEnforcementForVAP) + optionalVars := cel.OptionalVariableDeclarations{HasParams: hasParam, HasAuthorizer: true, StrictCost: strictCost} + expressionOptionalVars := cel.OptionalVariableDeclarations{HasParams: hasParam, HasAuthorizer: false, StrictCost: strictCost} failurePolicy := policy.Spec.FailurePolicy var matcher matchconditions.Matcher = nil matchConditions := policy.Spec.MatchConditions - + var compositionEnvTemplate *cel.CompositionEnv + if strictCost { + compositionEnvTemplate = compositionEnvTemplateWithStrictCost + } else { + compositionEnvTemplate = compositionEnvTemplateWithoutStrictCost + } filterCompiler := cel.NewCompositedCompilerFromTemplate(compositionEnvTemplate) filterCompiler.CompileAndStoreVariables(convertv1beta1Variables(policy.Spec.Variables), optionalVars, environment.StoredExpressions) diff --git a/pkg/admission/plugin/policy/validating/typechecking.go b/pkg/admission/plugin/policy/validating/typechecking.go index 16184b4ba..192be9621 100644 --- a/pkg/admission/plugin/policy/validating/typechecking.go +++ b/pkg/admission/plugin/policy/validating/typechecking.go @@ -39,6 +39,8 @@ import ( "k8s.io/apiserver/pkg/cel/library" "k8s.io/apiserver/pkg/cel/openapi" "k8s.io/apiserver/pkg/cel/openapi/resolver" + "k8s.io/apiserver/pkg/features" + utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/klog/v2" ) @@ -210,6 +212,7 @@ func (c *TypeChecker) CheckExpression(ctx *TypeCheckingContext, expression strin options := plugincel.OptionalVariableDeclarations{ HasParams: ctx.paramDeclType != nil, HasAuthorizer: true, + StrictCost: utilfeature.DefaultFeatureGate.Enabled(features.StrictCostEnforcementForVAP), } compiler.CompileAndStoreVariables(convertv1beta1Variables(ctx.variables), options, environment.StoredExpressions) result := compiler.CompileCELExpression(celExpression(expression), options, environment.StoredExpressions) @@ -391,7 +394,7 @@ func (c *TypeChecker) tryRefreshRESTMapper() { } func buildEnvSet(hasParams bool, hasAuthorizer bool, types typeOverwrite) (*environment.EnvSet, error) { - baseEnv := environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion()) + baseEnv := environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), utilfeature.DefaultFeatureGate.Enabled(features.StrictCostEnforcementForVAP)) requestType := plugincel.BuildRequestType() namespaceType := plugincel.BuildNamespaceType() diff --git a/pkg/admission/plugin/policy/validating/validator_test.go b/pkg/admission/plugin/policy/validating/validator_test.go index ba4f1ca86..99b3556b5 100644 --- a/pkg/admission/plugin/policy/validating/validator_test.go +++ b/pkg/admission/plugin/policy/validating/validator_test.go @@ -931,7 +931,7 @@ func TestContextCanceled(t *testing.T) { fakeAttr := admission.NewAttributesRecord(nil, nil, schema.GroupVersionKind{}, "default", "foo", schema.GroupVersionResource{}, "", admission.Create, nil, false, nil) fakeVersionedAttr, _ := admission.NewVersionedAttributes(fakeAttr, schema.GroupVersionKind{}, nil) - fc := cel.NewFilterCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion())) + fc := cel.NewFilterCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), true)) f := fc.Compile([]cel.ExpressionAccessor{&ValidationCondition{Expression: "[1,2,3,4,5,6,7,8,9,10].map(x, [1,2,3,4,5,6,7,8,9,10].map(y, x*y)) == []"}}, cel.OptionalVariableDeclarations{HasParams: false, HasAuthorizer: false}, environment.StoredExpressions) v := validator{ failPolicy: &fail, diff --git a/pkg/admission/plugin/webhook/accessors.go b/pkg/admission/plugin/webhook/accessors.go index e60d245a6..f23580cc0 100644 --- a/pkg/admission/plugin/webhook/accessors.go +++ b/pkg/admission/plugin/webhook/accessors.go @@ -27,6 +27,8 @@ import ( "k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/namespace" "k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/object" "k8s.io/apiserver/pkg/cel/environment" + "k8s.io/apiserver/pkg/features" + utilfeature "k8s.io/apiserver/pkg/util/feature" webhookutil "k8s.io/apiserver/pkg/util/webhook" "k8s.io/client-go/rest" ) @@ -139,11 +141,16 @@ func (m *mutatingWebhookAccessor) GetCompiledMatcher(compiler cel.FilterCompiler Expression: matchCondition.Expression, } } + strictCost := false + if utilfeature.DefaultFeatureGate.Enabled(features.StrictCostEnforcementForWebhooks) { + strictCost = true + } m.compiledMatcher = matchconditions.NewMatcher(compiler.Compile( expressions, cel.OptionalVariableDeclarations{ HasParams: false, HasAuthorizer: true, + StrictCost: strictCost, }, environment.StoredExpressions, ), m.FailurePolicy, "webhook", "admit", m.Name) @@ -267,11 +274,16 @@ func (v *validatingWebhookAccessor) GetCompiledMatcher(compiler cel.FilterCompil Expression: matchCondition.Expression, } } + strictCost := false + if utilfeature.DefaultFeatureGate.Enabled(features.StrictCostEnforcementForWebhooks) { + strictCost = true + } v.compiledMatcher = matchconditions.NewMatcher(compiler.Compile( expressions, cel.OptionalVariableDeclarations{ HasParams: false, HasAuthorizer: true, + StrictCost: strictCost, }, environment.StoredExpressions, ), v.FailurePolicy, "webhook", "validating", v.Name) diff --git a/pkg/admission/plugin/webhook/generic/webhook.go b/pkg/admission/plugin/webhook/generic/webhook.go index 6a513f1c1..f067b3f72 100644 --- a/pkg/admission/plugin/webhook/generic/webhook.go +++ b/pkg/admission/plugin/webhook/generic/webhook.go @@ -21,7 +21,6 @@ import ( "fmt" "io" - admissionmetrics "k8s.io/apiserver/pkg/admission/metrics" "k8s.io/klog/v2" admissionv1 "k8s.io/api/admission/v1" @@ -31,6 +30,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apiserver/pkg/admission" genericadmissioninit "k8s.io/apiserver/pkg/admission/initializer" + admissionmetrics "k8s.io/apiserver/pkg/admission/metrics" "k8s.io/apiserver/pkg/admission/plugin/cel" "k8s.io/apiserver/pkg/admission/plugin/webhook" "k8s.io/apiserver/pkg/admission/plugin/webhook/config" @@ -39,6 +39,8 @@ import ( "k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/rules" "k8s.io/apiserver/pkg/authorization/authorizer" "k8s.io/apiserver/pkg/cel/environment" + "k8s.io/apiserver/pkg/features" + utilfeature "k8s.io/apiserver/pkg/util/feature" webhookutil "k8s.io/apiserver/pkg/util/webhook" "k8s.io/client-go/informers" clientset "k8s.io/client-go/kubernetes" @@ -100,7 +102,7 @@ func NewWebhook(handler *admission.Handler, configFile io.Reader, sourceFactory namespaceMatcher: &namespace.Matcher{}, objectMatcher: &object.Matcher{}, dispatcher: dispatcherFactory(&cm), - filterCompiler: cel.NewFilterCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion())), + filterCompiler: cel.NewFilterCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), utilfeature.DefaultFeatureGate.Enabled(features.StrictCostEnforcementForWebhooks))), }, nil } diff --git a/pkg/apis/apiserver/validation/validation.go b/pkg/apis/apiserver/validation/validation.go index 35ee8a450..471eb4a74 100644 --- a/pkg/apis/apiserver/validation/validation.go +++ b/pkg/apis/apiserver/validation/validation.go @@ -91,7 +91,8 @@ func CompileAndValidateJWTAuthenticator(authenticator api.JWTAuthenticator, disa func validateJWTAuthenticator(authenticator api.JWTAuthenticator, fldPath *field.Path, disallowedIssuers sets.Set[string], structuredAuthnFeatureEnabled bool) (authenticationcel.CELMapper, field.ErrorList) { var allErrs field.ErrorList - compiler := authenticationcel.NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion())) + // strictCost is set to true which enables the strict cost for CEL validation. + compiler := authenticationcel.NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), true)) state := &validationState{} allErrs = append(allErrs, validateIssuer(authenticator.Issuer, disallowedIssuers, fldPath.Child("issuer"))...) @@ -722,7 +723,8 @@ func compileMatchConditions(matchConditions []api.WebhookMatchCondition, fldPath return nil, allErrs } - compiler := authorizationcel.NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion())) + // strictCost is set to true which enables the strict cost for CEL validation. + compiler := authorizationcel.NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), true)) seenExpressions := sets.NewString() var compilationResults []authorizationcel.CompilationResult diff --git a/pkg/apis/apiserver/validation/validation_test.go b/pkg/apis/apiserver/validation/validation_test.go index a38d5116b..d847f9355 100644 --- a/pkg/apis/apiserver/validation/validation_test.go +++ b/pkg/apis/apiserver/validation/validation_test.go @@ -43,7 +43,7 @@ import ( ) var ( - compiler = authenticationcel.NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion())) + compiler = authenticationcel.NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), true)) ) func TestValidateAuthenticationConfiguration(t *testing.T) { diff --git a/pkg/authentication/cel/compile_test.go b/pkg/authentication/cel/compile_test.go index c8659aa83..5615f4766 100644 --- a/pkg/authentication/cel/compile_test.go +++ b/pkg/authentication/cel/compile_test.go @@ -57,7 +57,7 @@ func TestCompileClaimsExpression(t *testing.T) { }, } - compiler := NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion())) + compiler := NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), true)) for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { @@ -86,7 +86,7 @@ func TestCompileUserExpression(t *testing.T) { }, } - compiler := NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion())) + compiler := NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), true)) for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { @@ -135,7 +135,7 @@ func TestCompileClaimsExpressionError(t *testing.T) { }, } - compiler := NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion())) + compiler := NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), true)) for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { @@ -205,7 +205,7 @@ func TestCompileUserExpressionError(t *testing.T) { }, } - compiler := NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion())) + compiler := NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), true)) for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { diff --git a/pkg/authorization/cel/compile_test.go b/pkg/authorization/cel/compile_test.go index ec7975b90..aecb31463 100644 --- a/pkg/authorization/cel/compile_test.go +++ b/pkg/authorization/cel/compile_test.go @@ -59,7 +59,7 @@ func TestCompileCELExpression(t *testing.T) { }, } - compiler := NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion())) + compiler := NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), true)) for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { diff --git a/pkg/cel/environment/base.go b/pkg/cel/environment/base.go index c108bdd64..2cea83c2e 100644 --- a/pkg/cel/environment/base.go +++ b/pkg/cel/environment/base.go @@ -46,7 +46,9 @@ func DefaultCompatibilityVersion() *version.Version { return version.MajorMinor(1, 29) } -var baseOpts = []VersionedOptions{ +var baseOpts = append(baseOptsWithoutStrictCost, StrictCostOpt) + +var baseOptsWithoutStrictCost = []VersionedOptions{ { // CEL epoch was actually 1.23, but we artificially set it to 1.0 because these // options should always be present. @@ -132,6 +134,14 @@ var baseOpts = []VersionedOptions{ }, } +var StrictCostOpt = VersionedOptions{ + // This is to configure the cost calculation for extended libraries + IntroducedVersion: version.MajorMinor(1, 0), + ProgramOptions: []cel.ProgramOption{ + cel.CostTracking(&library.CostEstimator{}), + }, +} + // MustBaseEnvSet returns the common CEL base environments for Kubernetes for Version, or panics // if the version is nil, or does not have major and minor components. // @@ -141,7 +151,8 @@ var baseOpts = []VersionedOptions{ // The returned environment contains no CEL variable definitions or custom type declarations and // should be extended to construct environments with the appropriate variable definitions, // type declarations and any other needed configuration. -func MustBaseEnvSet(ver *version.Version) *EnvSet { +// strictCost is used to determine whether to enforce strict cost calculation for CEL expressions. +func MustBaseEnvSet(ver *version.Version, strictCost bool) *EnvSet { if ver == nil { panic("version must be non-nil") } @@ -149,19 +160,33 @@ func MustBaseEnvSet(ver *version.Version) *EnvSet { panic(fmt.Sprintf("version must contain an major and minor component, but got: %s", ver.String())) } key := strconv.FormatUint(uint64(ver.Major()), 10) + "." + strconv.FormatUint(uint64(ver.Minor()), 10) - if entry, ok := baseEnvs.Load(key); ok { - return entry.(*EnvSet) + var entry interface{} + if strictCost { + if entry, ok := baseEnvs.Load(key); ok { + return entry.(*EnvSet) + } + entry, _, _ = baseEnvsSingleflight.Do(key, func() (interface{}, error) { + entry := mustNewEnvSet(ver, baseOpts) + baseEnvs.Store(key, entry) + return entry, nil + }) + } else { + if entry, ok := baseEnvsWithOption.Load(key); ok { + return entry.(*EnvSet) + } + entry, _, _ = baseEnvsWithOptionSingleflight.Do(key, func() (interface{}, error) { + entry := mustNewEnvSet(ver, baseOptsWithoutStrictCost) + baseEnvsWithOption.Store(key, entry) + return entry, nil + }) } - entry, _, _ := baseEnvsSingleflight.Do(key, func() (interface{}, error) { - entry := mustNewEnvSet(ver, baseOpts) - baseEnvs.Store(key, entry) - return entry, nil - }) return entry.(*EnvSet) } var ( - baseEnvs = sync.Map{} - baseEnvsSingleflight = &singleflight.Group{} + baseEnvs = sync.Map{} + baseEnvsWithOption = sync.Map{} + baseEnvsSingleflight = &singleflight.Group{} + baseEnvsWithOptionSingleflight = &singleflight.Group{} ) diff --git a/pkg/cel/environment/base_test.go b/pkg/cel/environment/base_test.go index 4bfb76457..a21891115 100644 --- a/pkg/cel/environment/base_test.go +++ b/pkg/cel/environment/base_test.go @@ -29,10 +29,10 @@ import ( // a cached environment is loaded for each MustBaseEnvSet call. func BenchmarkLoadBaseEnv(b *testing.B) { ver := DefaultCompatibilityVersion() - MustBaseEnvSet(ver) + MustBaseEnvSet(ver, true) b.ResetTimer() for i := 0; i < b.N; i++ { - MustBaseEnvSet(ver) + MustBaseEnvSet(ver, true) } } @@ -41,7 +41,7 @@ func BenchmarkLoadBaseEnv(b *testing.B) { func BenchmarkLoadBaseEnvDifferentVersions(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { - MustBaseEnvSet(version.MajorMinor(1, uint(i))) + MustBaseEnvSet(version.MajorMinor(1, uint(i)), true) } } diff --git a/pkg/cel/environment/environment_test.go b/pkg/cel/environment/environment_test.go index e5b29371a..88e2c521d 100644 --- a/pkg/cel/environment/environment_test.go +++ b/pkg/cel/environment/environment_test.go @@ -262,7 +262,7 @@ func TestBaseEnvironment(t *testing.T) { for _, tv := range tc.typeVersionCombinations { t.Run(fmt.Sprintf("version=%s,envType=%s", tv.version.String(), tv.envType), func(t *testing.T) { - envSet := MustBaseEnvSet(tv.version) + envSet := MustBaseEnvSet(tv.version, true) if tc.opts != nil { var err error envSet, err = envSet.Extend(tc.opts...) diff --git a/pkg/cel/lazy/lazy_test.go b/pkg/cel/lazy/lazy_test.go index 19de8e991..7650e03ba 100644 --- a/pkg/cel/lazy/lazy_test.go +++ b/pkg/cel/lazy/lazy_test.go @@ -129,7 +129,7 @@ func compileAndRun(env *cel.Env, activation *testActivation, exp string) (ref.Va func buildTestEnv() (*cel.Env, *apiservercel.DeclType, error) { variablesType := apiservercel.NewMapType(apiservercel.StringType, apiservercel.AnyType, 0) variablesType.Fields = make(map[string]*apiservercel.DeclField) - envSet, err := environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion()).Extend( + envSet, err := environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), true).Extend( environment.VersionedOptions{ IntroducedVersion: version.MajorMinor(1, 28), EnvOptions: []cel.EnvOption{ diff --git a/pkg/cel/mutation/env_test.go b/pkg/cel/mutation/env_test.go index 48bd85c92..911ae5db2 100644 --- a/pkg/cel/mutation/env_test.go +++ b/pkg/cel/mutation/env_test.go @@ -28,7 +28,7 @@ import ( // mustCreateEnv creates the default env for testing, with given option. // it fatally fails the test if the env fails to set up. func mustCreateEnv(t testing.TB, envOptions ...cel.EnvOption) *cel.Env { - envSet, err := environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion()). + envSet, err := environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), true). Extend(environment.VersionedOptions{ IntroducedVersion: version.MajorMinor(1, 30), EnvOptions: envOptions, diff --git a/pkg/cel/mutation/unstructured/typeresolver_test.go b/pkg/cel/mutation/unstructured/typeresolver_test.go index 4c7477614..7ca743b27 100644 --- a/pkg/cel/mutation/unstructured/typeresolver_test.go +++ b/pkg/cel/mutation/unstructured/typeresolver_test.go @@ -140,7 +140,7 @@ func TestTypeProvider(t *testing.T) { } } func mustCreateEnv(t testing.TB, envOptions ...cel.EnvOption) *cel.Env { - envSet, err := environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion()). + envSet, err := environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), true). Extend(environment.VersionedOptions{ IntroducedVersion: version.MajorMinor(1, 30), EnvOptions: envOptions, diff --git a/pkg/cel/openapi/compiling_test.go b/pkg/cel/openapi/compiling_test.go index 1799c2b0d..ca6db71a4 100644 --- a/pkg/cel/openapi/compiling_test.go +++ b/pkg/cel/openapi/compiling_test.go @@ -106,7 +106,7 @@ func buildTestEnv() (*cel.Env, error) { fooType := common.SchemaDeclType(simpleMapSchema("foo", spec.StringProperty()), true).MaybeAssignTypeName("fooType") barType := common.SchemaDeclType(simpleMapSchema("bar", spec.Int64Property()), true).MaybeAssignTypeName("barType") - env, err := environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion()).Extend( + env, err := environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), true).Extend( environment.VersionedOptions{ IntroducedVersion: version.MajorMinor(1, 26), EnvOptions: []cel.EnvOption{ diff --git a/pkg/features/kube_features.go b/pkg/features/kube_features.go index dbd41b8c5..bae04d954 100644 --- a/pkg/features/kube_features.go +++ b/pkg/features/kube_features.go @@ -220,6 +220,24 @@ const ( // if the generated name conflicts with an existing resource name, up to a maximum number of 7 retries. RetryGenerateName featuregate.Feature = "RetryGenerateName" + // owner: @cici37 + // alpha: v1.30 + // + // StrictCostEnforcementForVAP is used to apply strict CEL cost validation for ValidatingAdmissionPolicy. + // It will be set to off by default for certain time of period to prevent the impact on the existing users. + // It is strongly recommended to enable this feature gate as early as possible. + // The strict cost is specific for the extended libraries whose cost defined under k8s/apiserver/pkg/cel/library. + StrictCostEnforcementForVAP featuregate.Feature = "StrictCostEnforcementForVAP" + + // owner: @cici37 + // alpha: v1.30 + // + // StrictCostEnforcementForWebhooks is used to apply strict CEL cost validation for matchConditions in Webhooks. + // It will be set to off by default for certain time of period to prevent the impact on the existing users. + // It is strongly recommended to enable this feature gate as early as possible. + // The strict cost is specific for the extended libraries whose cost defined under k8s/apiserver/pkg/cel/library. + StrictCostEnforcementForWebhooks featuregate.Feature = "StrictCostEnforcementForWebhooks" + // owner: @caesarxuchao @roycaihw // alpha: v1.20 // @@ -347,6 +365,10 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS StorageVersionHash: {Default: true, PreRelease: featuregate.Beta}, + StrictCostEnforcementForVAP: {Default: false, PreRelease: featuregate.Beta}, + + StrictCostEnforcementForWebhooks: {Default: false, PreRelease: featuregate.Beta}, + StructuredAuthenticationConfiguration: {Default: true, PreRelease: featuregate.Beta}, StructuredAuthorizationConfiguration: {Default: true, PreRelease: featuregate.Beta},