mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-19 09:52:49 +00:00
Apply resource constraints to ValidatingAdmissionPolicy.
This commit is contained in:
parent
64259b43b8
commit
244c63a2e6
@ -21,8 +21,6 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy"
|
||||
|
||||
genericvalidation "k8s.io/apimachinery/pkg/api/validation"
|
||||
"k8s.io/apimachinery/pkg/api/validation/path"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@ -31,6 +29,8 @@ import (
|
||||
utilvalidation "k8s.io/apimachinery/pkg/util/validation"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
plugincel "k8s.io/apiserver/pkg/admission/plugin/cel"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy"
|
||||
celconfig "k8s.io/apiserver/pkg/apis/cel"
|
||||
"k8s.io/apiserver/pkg/cel"
|
||||
"k8s.io/apiserver/pkg/util/webhook"
|
||||
|
||||
@ -740,7 +740,7 @@ func validateValidation(v *admissionregistration.Validation, paramKind *admissio
|
||||
Expression: trimmedExpression,
|
||||
Message: v.Message,
|
||||
Reason: v.Reason,
|
||||
}, plugincel.OptionalVariableDeclarations{HasParams: paramKind != nil, HasAuthorizer: true})
|
||||
}, plugincel.OptionalVariableDeclarations{HasParams: paramKind != nil, HasAuthorizer: true}, celconfig.PerCallLimit)
|
||||
if result.Error != nil {
|
||||
switch result.Error.Type {
|
||||
case cel.ErrorTypeRequired:
|
||||
|
@ -18,6 +18,7 @@ package cel
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
celconfig "k8s.io/apiserver/pkg/apis/cel"
|
||||
"sync"
|
||||
|
||||
"github.com/google/cel-go/cel"
|
||||
@ -33,8 +34,6 @@ const (
|
||||
RequestVarName = "request"
|
||||
AuthorizerVarName = "authorizer"
|
||||
RequestResourceAuthorizerVarName = "authorizer.requestResource"
|
||||
|
||||
checkFrequency = 100
|
||||
)
|
||||
|
||||
var (
|
||||
@ -190,7 +189,8 @@ type CompilationResult struct {
|
||||
}
|
||||
|
||||
// CompileCELExpression returns a compiled CEL expression.
|
||||
func CompileCELExpression(expressionAccessor ExpressionAccessor, optionalVars OptionalVariableDeclarations) CompilationResult {
|
||||
// perCallLimit was added for testing purpose only. Callers should always use const PerCallLimit from k8s.io/apiserver/pkg/apis/cel/config.go as input.
|
||||
func CompileCELExpression(expressionAccessor ExpressionAccessor, optionalVars OptionalVariableDeclarations, perCallLimit uint64) CompilationResult {
|
||||
var env *cel.Env
|
||||
envs, err := getEnvs()
|
||||
if err != nil {
|
||||
@ -245,9 +245,10 @@ func CompileCELExpression(expressionAccessor ExpressionAccessor, optionalVars Op
|
||||
}
|
||||
}
|
||||
prog, err := env.Program(ast,
|
||||
cel.EvalOptions(cel.OptOptimize),
|
||||
cel.EvalOptions(cel.OptOptimize, cel.OptTrackCost),
|
||||
cel.OptimizeRegex(library.ExtensionLibRegexOptimizations...),
|
||||
cel.InterruptCheckFrequency(checkFrequency),
|
||||
cel.InterruptCheckFrequency(celconfig.CheckFrequency),
|
||||
cel.CostLimit(perCallLimit),
|
||||
)
|
||||
if err != nil {
|
||||
return CompilationResult{
|
||||
|
@ -19,6 +19,8 @@ package cel
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
celconfig "k8s.io/apiserver/pkg/apis/cel"
|
||||
)
|
||||
|
||||
func TestCompileValidatingPolicyExpression(t *testing.T) {
|
||||
@ -120,7 +122,7 @@ func TestCompileValidatingPolicyExpression(t *testing.T) {
|
||||
for _, expr := range tc.expressions {
|
||||
result := CompileCELExpression(&fakeExpressionAccessor{
|
||||
expr,
|
||||
}, OptionalVariableDeclarations{HasParams: tc.hasParams, HasAuthorizer: true})
|
||||
}, OptionalVariableDeclarations{HasParams: tc.hasParams, HasAuthorizer: true}, celconfig.PerCallLimit)
|
||||
if result.Error != nil {
|
||||
t.Errorf("Unexpected error: %v", result.Error)
|
||||
}
|
||||
@ -128,7 +130,7 @@ func TestCompileValidatingPolicyExpression(t *testing.T) {
|
||||
for expr, expectErr := range tc.errorExpressions {
|
||||
result := CompileCELExpression(&fakeExpressionAccessor{
|
||||
expr,
|
||||
}, OptionalVariableDeclarations{HasParams: tc.hasParams, HasAuthorizer: tc.hasAuthorizer})
|
||||
}, OptionalVariableDeclarations{HasParams: tc.hasParams, HasAuthorizer: tc.hasAuthorizer}, celconfig.PerCallLimit)
|
||||
if result.Error == nil {
|
||||
t.Errorf("Expected expression '%s' to contain '%v' but got no error", expr, expectErr)
|
||||
continue
|
||||
|
@ -19,6 +19,7 @@ package cel
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
@ -74,13 +75,14 @@ func (a *evaluationActivation) Parent() interpreter.Activation {
|
||||
}
|
||||
|
||||
// Compile compiles the cel expressions defined in the ExpressionAccessors into a Filter
|
||||
func (c *filterCompiler) Compile(expressionAccessors []ExpressionAccessor, options OptionalVariableDeclarations) Filter {
|
||||
// perCallLimit was added for testing purpose only. Callers should always use const PerCallLimit from k8s.io/apiserver/pkg/apis/cel/config.go as input.
|
||||
func (c *filterCompiler) Compile(expressionAccessors []ExpressionAccessor, options OptionalVariableDeclarations, perCallLimit uint64) Filter {
|
||||
if len(expressionAccessors) == 0 {
|
||||
return nil
|
||||
}
|
||||
compilationResults := make([]CompilationResult, len(expressionAccessors))
|
||||
for i, expressionAccessor := range expressionAccessors {
|
||||
compilationResults[i] = CompileCELExpression(expressionAccessor, options)
|
||||
compilationResults[i] = CompileCELExpression(expressionAccessor, options, perCallLimit)
|
||||
}
|
||||
return NewFilter(compilationResults)
|
||||
}
|
||||
@ -120,7 +122,8 @@ func objectToResolveVal(r runtime.Object) (interface{}, error) {
|
||||
|
||||
// ForInput evaluates the compiled CEL expressions converting them into CELEvaluations
|
||||
// errors per evaluation are returned on the Evaluation object
|
||||
func (f *filter) ForInput(versionedAttr *generic.VersionedAttributes, request *admissionv1.AdmissionRequest, inputs OptionalVariableBindings) ([]EvaluationResult, error) {
|
||||
// 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(versionedAttr *generic.VersionedAttributes, request *admissionv1.AdmissionRequest, inputs OptionalVariableBindings, runtimeCELCostBudget int64) ([]EvaluationResult, 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
|
||||
@ -159,6 +162,7 @@ func (f *filter) ForInput(versionedAttr *generic.VersionedAttributes, request *a
|
||||
requestResourceAuthorizer: requestResourceAuthorizerVal,
|
||||
}
|
||||
|
||||
remainingBudget := runtimeCELCostBudget
|
||||
for i, compilationResult := range f.compilationResults {
|
||||
var evaluation = &evaluations[i]
|
||||
evaluation.ExpressionAccessor = compilationResult.ExpressionAccessor
|
||||
@ -171,9 +175,22 @@ func (f *filter) ForInput(versionedAttr *generic.VersionedAttributes, request *a
|
||||
continue
|
||||
}
|
||||
t1 := time.Now()
|
||||
evalResult, _, err := compilationResult.Program.Eval(va)
|
||||
evalResult, evalDetails, err := compilationResult.Program.Eval(va)
|
||||
elapsed := time.Since(t1)
|
||||
evaluation.Elapsed = elapsed
|
||||
if evalDetails == nil {
|
||||
return nil, errors.New(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, errors.New(fmt.Sprintf("runtime cost could not be calculated for expression: %v, no further expression will be run", compilationResult.ExpressionAccessor.GetExpression()))
|
||||
} else {
|
||||
if *rtCost > math.MaxInt64 || int64(*rtCost) > remainingBudget {
|
||||
return nil, errors.New(fmt.Sprintf("validation failed due to running out of cost budget, no further validation rules will be run"))
|
||||
}
|
||||
remainingBudget -= int64(*rtCost)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
evaluation.Error = errors.New(fmt.Sprintf("expression '%v' resulted in error: %v", compilationResult.ExpressionAccessor.GetExpression(), err))
|
||||
} else {
|
||||
|
@ -27,6 +27,7 @@ import (
|
||||
celtypes "github.com/google/cel-go/common/types"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
celconfig "k8s.io/apiserver/pkg/apis/cel"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
apiservercel "k8s.io/apiserver/pkg/cel"
|
||||
@ -87,7 +88,7 @@ func TestCompile(t *testing.T) {
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var c filterCompiler
|
||||
e := c.Compile(tc.validation, OptionalVariableDeclarations{HasParams: false, HasAuthorizer: false})
|
||||
e := c.Compile(tc.validation, OptionalVariableDeclarations{HasParams: false, HasAuthorizer: false}, celconfig.PerCallLimit)
|
||||
if e == nil {
|
||||
t.Fatalf("unexpected nil validator")
|
||||
}
|
||||
@ -144,13 +145,14 @@ func TestFilter(t *testing.T) {
|
||||
|
||||
var nilUnstructured *unstructured.Unstructured
|
||||
cases := []struct {
|
||||
name string
|
||||
attributes admission.Attributes
|
||||
params runtime.Object
|
||||
validations []ExpressionAccessor
|
||||
results []EvaluationResult
|
||||
hasParamKind bool
|
||||
authorizer authorizer.Authorizer
|
||||
name string
|
||||
attributes admission.Attributes
|
||||
params runtime.Object
|
||||
validations []ExpressionAccessor
|
||||
results []EvaluationResult
|
||||
hasParamKind bool
|
||||
authorizer authorizer.Authorizer
|
||||
testPerCallLimit uint64
|
||||
}{
|
||||
{
|
||||
name: "valid syntax for object",
|
||||
@ -616,12 +618,32 @@ func TestFilter(t *testing.T) {
|
||||
APIVersion: "*",
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "test perCallLimit exceed",
|
||||
validations: []ExpressionAccessor{
|
||||
&condition{
|
||||
Expression: "object.subsets.size() < params.spec.testSize",
|
||||
},
|
||||
},
|
||||
attributes: newValidAttribute(nil, false),
|
||||
results: []EvaluationResult{
|
||||
{
|
||||
Error: errors.New(fmt.Sprintf("operation cancelled: actual cost limit exceeded")),
|
||||
},
|
||||
},
|
||||
hasParamKind: true,
|
||||
params: crdParams,
|
||||
testPerCallLimit: 1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
c := filterCompiler{}
|
||||
f := c.Compile(tc.validations, OptionalVariableDeclarations{HasParams: tc.hasParamKind, HasAuthorizer: tc.authorizer != nil})
|
||||
if tc.testPerCallLimit == 0 {
|
||||
tc.testPerCallLimit = celconfig.PerCallLimit
|
||||
}
|
||||
f := c.Compile(tc.validations, OptionalVariableDeclarations{HasParams: tc.hasParamKind, HasAuthorizer: tc.authorizer != nil}, tc.testPerCallLimit)
|
||||
if f == nil {
|
||||
t.Fatalf("unexpected nil validator")
|
||||
}
|
||||
@ -635,7 +657,7 @@ func TestFilter(t *testing.T) {
|
||||
}
|
||||
|
||||
optionalVars := OptionalVariableBindings{VersionedParams: tc.params, Authorizer: tc.authorizer}
|
||||
evalResults, err := f.ForInput(versionedAttr, CreateAdmissionRequest(versionedAttr.Attributes), optionalVars)
|
||||
evalResults, err := f.ForInput(versionedAttr, CreateAdmissionRequest(versionedAttr.Attributes), optionalVars, celconfig.RuntimeCELCostBudget)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
@ -652,6 +674,111 @@ func TestFilter(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRuntimeCELCostBudget(t *testing.T) {
|
||||
configMapParams := &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foo",
|
||||
},
|
||||
Data: map[string]string{
|
||||
"fakeString": "fake",
|
||||
},
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
attributes admission.Attributes
|
||||
params runtime.Object
|
||||
validations []ExpressionAccessor
|
||||
hasParamKind bool
|
||||
authorizer authorizer.Authorizer
|
||||
testRuntimeCELCostBudget int64
|
||||
exceedBudget bool
|
||||
}{
|
||||
{
|
||||
name: "expression exceed RuntimeCELCostBudget at fist expression",
|
||||
validations: []ExpressionAccessor{
|
||||
&condition{
|
||||
Expression: "has(object.subsets) && object.subsets.size() < 2",
|
||||
},
|
||||
&condition{
|
||||
Expression: "has(object.subsets)",
|
||||
},
|
||||
},
|
||||
attributes: newValidAttribute(nil, false),
|
||||
hasParamKind: false,
|
||||
testRuntimeCELCostBudget: 1,
|
||||
exceedBudget: true,
|
||||
},
|
||||
{
|
||||
name: "expression exceed RuntimeCELCostBudget at last expression",
|
||||
validations: []ExpressionAccessor{
|
||||
&condition{
|
||||
Expression: "has(object.subsets) && object.subsets.size() < 2",
|
||||
},
|
||||
&condition{
|
||||
Expression: "object.subsets.size() > 2",
|
||||
},
|
||||
},
|
||||
attributes: newValidAttribute(nil, false),
|
||||
hasParamKind: true,
|
||||
params: configMapParams,
|
||||
testRuntimeCELCostBudget: 5,
|
||||
exceedBudget: true,
|
||||
},
|
||||
{
|
||||
name: "test RuntimeCELCostBudge is not exceed",
|
||||
validations: []ExpressionAccessor{
|
||||
&condition{
|
||||
Expression: "oldObject != null",
|
||||
},
|
||||
&condition{
|
||||
Expression: "object.subsets.size() > 2",
|
||||
},
|
||||
},
|
||||
attributes: newValidAttribute(nil, false),
|
||||
hasParamKind: true,
|
||||
params: configMapParams,
|
||||
exceedBudget: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
c := filterCompiler{}
|
||||
f := c.Compile(tc.validations, OptionalVariableDeclarations{HasParams: tc.hasParamKind, HasAuthorizer: false}, celconfig.PerCallLimit)
|
||||
if f == nil {
|
||||
t.Fatalf("unexpected nil validator")
|
||||
}
|
||||
validations := tc.validations
|
||||
CompilationResults := f.(*filter).compilationResults
|
||||
require.Equal(t, len(validations), len(CompilationResults))
|
||||
|
||||
versionedAttr, err := generic.NewVersionedAttributes(tc.attributes, tc.attributes.GetKind(), newObjectInterfacesForTest())
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error on conversion: %v", err)
|
||||
}
|
||||
|
||||
if tc.testRuntimeCELCostBudget == 0 {
|
||||
tc.testRuntimeCELCostBudget = celconfig.RuntimeCELCostBudget
|
||||
}
|
||||
optionalVars := OptionalVariableBindings{VersionedParams: tc.params, Authorizer: tc.authorizer}
|
||||
evalResults, err := f.ForInput(versionedAttr, CreateAdmissionRequest(versionedAttr.Attributes), optionalVars, tc.testRuntimeCELCostBudget)
|
||||
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") {
|
||||
t.Errorf("Expected RuntimeCELCostBudge exceeded error but got: %v", err)
|
||||
}
|
||||
if err != nil && !tc.exceedBudget {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if tc.exceedBudget && len(evalResults) != 0 {
|
||||
t.Fatalf("unexpected result returned: %v", evalResults)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// newObjectInterfacesForTest returns an ObjectInterfaces appropriate for test cases in this file.
|
||||
func newObjectInterfacesForTest() admission.ObjectInterfaces {
|
||||
scheme := runtime.NewScheme()
|
||||
|
@ -65,7 +65,8 @@ type OptionalVariableDeclarations struct {
|
||||
// FilterCompiler contains a function to assist with converting types and values to/from CEL-typed values.
|
||||
type FilterCompiler interface {
|
||||
// Compile is used for the cel expression compilation
|
||||
Compile(expressions []ExpressionAccessor, optionalDecls OptionalVariableDeclarations) Filter
|
||||
// perCallLimit was added for testing purpose only. Callers should always use const PerCallLimit from k8s.io/apiserver/pkg/apis/cel/config.go as input.
|
||||
Compile(expressions []ExpressionAccessor, optionalDecls OptionalVariableDeclarations, perCallLimit uint64) Filter
|
||||
}
|
||||
|
||||
// OptionalVariableBindings provides expression bindings for optional CEL variables.
|
||||
@ -84,7 +85,8 @@ type OptionalVariableBindings struct {
|
||||
// by the underlying CEL code (which is indicated by the match criteria of a policy definition).
|
||||
type Filter interface {
|
||||
// ForInput converts compiled CEL-typed values into evaluated CEL-typed values
|
||||
ForInput(versionedAttr *generic.VersionedAttributes, request *v1.AdmissionRequest, optionalVars OptionalVariableBindings) ([]EvaluationResult, error)
|
||||
// runtimeCELCostBudget was added for testing purpose only. Callers should always use const RuntimeCELCostBudget from k8s.io/apiserver/pkg/apis/cel/config.go as input.
|
||||
ForInput(versionedAttr *generic.VersionedAttributes, request *v1.AdmissionRequest, optionalVars OptionalVariableBindings, runtimeCELCostBudget int64) ([]EvaluationResult, error)
|
||||
|
||||
// CompilationErrors returns a list of errors from the compilation of the evaluator
|
||||
CompilationErrors() []error
|
||||
|
@ -172,6 +172,7 @@ func (f *fakeCompiler) HasSynced() bool {
|
||||
func (f *fakeCompiler) Compile(
|
||||
expressions []cel.ExpressionAccessor,
|
||||
options cel.OptionalVariableDeclarations,
|
||||
perCallLimit uint64,
|
||||
) cel.Filter {
|
||||
key := expressions[0].GetExpression()
|
||||
if fun, ok := f.CompileFuncs[key]; ok {
|
||||
@ -208,7 +209,7 @@ type fakeFilter struct {
|
||||
keyId string
|
||||
}
|
||||
|
||||
func (f *fakeFilter) ForInput(versionedAttr *whgeneric.VersionedAttributes, request *admissionv1.AdmissionRequest, inputs cel.OptionalVariableBindings) ([]cel.EvaluationResult, error) {
|
||||
func (f *fakeFilter) ForInput(versionedAttr *whgeneric.VersionedAttributes, request *admissionv1.AdmissionRequest, inputs cel.OptionalVariableBindings, runtimeCELCostBudget int64) ([]cel.EvaluationResult, error) {
|
||||
return []cel.EvaluationResult{}, nil
|
||||
}
|
||||
|
||||
@ -220,10 +221,10 @@ var _ Validator = &fakeValidator{}
|
||||
|
||||
type fakeValidator struct {
|
||||
*fakeFilter
|
||||
ValidateFunc func(versionedAttr *whgeneric.VersionedAttributes, versionedParams runtime.Object) []PolicyDecision
|
||||
ValidateFunc func(versionedAttr *whgeneric.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64) []PolicyDecision
|
||||
}
|
||||
|
||||
func (f *fakeValidator) RegisterDefinition(definition *v1alpha1.ValidatingAdmissionPolicy, validateFunc func(versionedAttr *whgeneric.VersionedAttributes, versionedParams runtime.Object) []PolicyDecision) {
|
||||
func (f *fakeValidator) RegisterDefinition(definition *v1alpha1.ValidatingAdmissionPolicy, validateFunc func(versionedAttr *whgeneric.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64) []PolicyDecision) {
|
||||
//Key must be something that we can decipher from the inputs to Validate so using message which will be on the validationCondition object of evalResult
|
||||
validateKey := definition.Spec.Validations[0].Expression
|
||||
if validatorMap == nil {
|
||||
@ -234,8 +235,8 @@ func (f *fakeValidator) RegisterDefinition(definition *v1alpha1.ValidatingAdmiss
|
||||
validatorMap[validateKey] = f
|
||||
}
|
||||
|
||||
func (f *fakeValidator) Validate(versionedAttr *whgeneric.VersionedAttributes, versionedParams runtime.Object) []PolicyDecision {
|
||||
return f.ValidateFunc(versionedAttr, versionedParams)
|
||||
func (f *fakeValidator) Validate(versionedAttr *whgeneric.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64) []PolicyDecision {
|
||||
return f.ValidateFunc(versionedAttr, versionedParams, runtimeCELCostBudget)
|
||||
}
|
||||
|
||||
var _ Matcher = &fakeMatcher{}
|
||||
@ -715,7 +716,7 @@ func TestBasicPolicyDefinitionFailure(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
validator.RegisterDefinition(denyPolicy, func(versionedAttr *whgeneric.VersionedAttributes, versionedParams runtime.Object) []PolicyDecision {
|
||||
validator.RegisterDefinition(denyPolicy, func(versionedAttr *whgeneric.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64) []PolicyDecision {
|
||||
return []PolicyDecision{
|
||||
{
|
||||
Action: ActionDeny,
|
||||
@ -775,7 +776,7 @@ func TestDefinitionDoesntMatch(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
validator.RegisterDefinition(denyPolicy, func(versionedAttr *whgeneric.VersionedAttributes, versionedParams runtime.Object) []PolicyDecision {
|
||||
validator.RegisterDefinition(denyPolicy, func(versionedAttr *whgeneric.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64) []PolicyDecision {
|
||||
return []PolicyDecision{
|
||||
{
|
||||
Action: ActionDeny,
|
||||
@ -886,7 +887,7 @@ func TestReconfigureBinding(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
validator.RegisterDefinition(denyPolicy, func(versionedAttr *whgeneric.VersionedAttributes, versionedParams runtime.Object) []PolicyDecision {
|
||||
validator.RegisterDefinition(denyPolicy, func(versionedAttr *whgeneric.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64) []PolicyDecision {
|
||||
return []PolicyDecision{
|
||||
{
|
||||
Action: ActionDeny,
|
||||
@ -993,7 +994,7 @@ func TestRemoveDefinition(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
validator.RegisterDefinition(denyPolicy, func(versionedAttr *whgeneric.VersionedAttributes, versionedParams runtime.Object) []PolicyDecision {
|
||||
validator.RegisterDefinition(denyPolicy, func(versionedAttr *whgeneric.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64) []PolicyDecision {
|
||||
return []PolicyDecision{
|
||||
{
|
||||
Action: ActionDeny,
|
||||
@ -1060,7 +1061,7 @@ func TestRemoveBinding(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
validator.RegisterDefinition(denyPolicy, func(versionedAttr *whgeneric.VersionedAttributes, versionedParams runtime.Object) []PolicyDecision {
|
||||
validator.RegisterDefinition(denyPolicy, func(versionedAttr *whgeneric.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64) []PolicyDecision {
|
||||
return []PolicyDecision{
|
||||
{
|
||||
Action: ActionDeny,
|
||||
@ -1168,7 +1169,7 @@ func TestInvalidParamSourceInstanceName(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
validator.RegisterDefinition(denyPolicy, func(versionedAttr *whgeneric.VersionedAttributes, versionedParams runtime.Object) []PolicyDecision {
|
||||
validator.RegisterDefinition(denyPolicy, func(versionedAttr *whgeneric.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64) []PolicyDecision {
|
||||
return []PolicyDecision{
|
||||
{
|
||||
Action: ActionDeny,
|
||||
@ -1234,7 +1235,7 @@ func TestEmptyParamSource(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
validator.RegisterDefinition(denyPolicy, func(versionedAttr *whgeneric.VersionedAttributes, versionedParams runtime.Object) []PolicyDecision {
|
||||
validator.RegisterDefinition(denyPolicy, func(versionedAttr *whgeneric.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64) []PolicyDecision {
|
||||
return []PolicyDecision{
|
||||
{
|
||||
Action: ActionDeny,
|
||||
@ -1334,7 +1335,7 @@ func TestMultiplePoliciesSharedParamType(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
validator1.RegisterDefinition(&policy1, func(versionedAttr *whgeneric.VersionedAttributes, versionedParams runtime.Object) []PolicyDecision {
|
||||
validator1.RegisterDefinition(&policy1, func(versionedAttr *whgeneric.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64) []PolicyDecision {
|
||||
evaluations1.Add(1)
|
||||
return []PolicyDecision{
|
||||
{
|
||||
@ -1351,7 +1352,7 @@ func TestMultiplePoliciesSharedParamType(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
validator2.RegisterDefinition(&policy2, func(versionedAttr *whgeneric.VersionedAttributes, versionedParams runtime.Object) []PolicyDecision {
|
||||
validator2.RegisterDefinition(&policy2, func(versionedAttr *whgeneric.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64) []PolicyDecision {
|
||||
evaluations2.Add(1)
|
||||
return []PolicyDecision{
|
||||
{
|
||||
@ -1459,7 +1460,7 @@ func TestNativeTypeParam(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
validator.RegisterDefinition(&nativeTypeParamPolicy, func(versionedAttr *whgeneric.VersionedAttributes, versionedParams runtime.Object) []PolicyDecision {
|
||||
validator.RegisterDefinition(&nativeTypeParamPolicy, func(versionedAttr *whgeneric.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64) []PolicyDecision {
|
||||
evaluations.Add(1)
|
||||
if _, ok := versionedParams.(*v1.ConfigMap); ok {
|
||||
return []PolicyDecision{
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
celconfig "k8s.io/apiserver/pkg/apis/cel"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@ -320,7 +321,7 @@ func (c *celAdmissionController) Validate(
|
||||
versionedAttr = va
|
||||
}
|
||||
|
||||
decisions := bindingInfo.validator.Validate(versionedAttr, param)
|
||||
decisions := bindingInfo.validator.Validate(versionedAttr, param, celconfig.RuntimeCELCostBudget)
|
||||
|
||||
for _, decision := range decisions {
|
||||
switch decision.Action {
|
||||
|
@ -32,6 +32,7 @@ import (
|
||||
celmetrics "k8s.io/apiserver/pkg/admission/cel"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/cel"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/internal/generic"
|
||||
celconfig "k8s.io/apiserver/pkg/apis/cel"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/dynamic/dynamicinformer"
|
||||
@ -446,7 +447,7 @@ func (c *policyController) latestPolicyData() []policyData {
|
||||
}
|
||||
optionalVars := cel.OptionalVariableDeclarations{HasParams: hasParam, HasAuthorizer: true}
|
||||
bindingInfo.validator = c.newValidator(
|
||||
c.filterCompiler.Compile(convertv1alpha1Validations(definitionInfo.lastReconciledValue.Spec.Validations), optionalVars),
|
||||
c.filterCompiler.Compile(convertv1alpha1Validations(definitionInfo.lastReconciledValue.Spec.Validations), optionalVars, celconfig.PerCallLimit),
|
||||
convertv1alpha1FailurePolicyTypeTov1FailurePolicyType(definitionInfo.lastReconciledValue.Spec.FailurePolicy),
|
||||
c.authz,
|
||||
)
|
||||
|
@ -55,5 +55,6 @@ type Matcher interface {
|
||||
// Validator is contains logic for converting ValidationEvaluation to PolicyDecisions
|
||||
type Validator interface {
|
||||
// Validate is used to take cel evaluations and convert into decisions
|
||||
Validate(versionedAttr *generic.VersionedAttributes, versionedParams runtime.Object) []PolicyDecision
|
||||
// runtimeCELCostBudget was added for testing purpose only. Callers should always use const RuntimeCELCostBudget from k8s.io/apiserver/pkg/apis/cel/config.go as input.
|
||||
Validate(versionedAttr *generic.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64) []PolicyDecision
|
||||
}
|
||||
|
@ -54,7 +54,8 @@ func policyDecisionActionForError(f v1.FailurePolicyType) PolicyDecisionAction {
|
||||
}
|
||||
|
||||
// Validate takes a list of Evaluation and a failure policy and converts them into actionable PolicyDecisions
|
||||
func (v *validator) Validate(versionedAttr *generic.VersionedAttributes, versionedParams runtime.Object) []PolicyDecision {
|
||||
// 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 (v *validator) Validate(versionedAttr *generic.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64) []PolicyDecision {
|
||||
var f v1.FailurePolicyType
|
||||
if v.failPolicy == nil {
|
||||
f = v1.Fail
|
||||
@ -63,7 +64,7 @@ func (v *validator) Validate(versionedAttr *generic.VersionedAttributes, version
|
||||
}
|
||||
|
||||
optionalVars := cel.OptionalVariableBindings{VersionedParams: versionedParams, Authorizer: v.authorizer}
|
||||
evalResults, err := v.filter.ForInput(versionedAttr, cel.CreateAdmissionRequest(versionedAttr.Attributes), optionalVars)
|
||||
evalResults, err := v.filter.ForInput(versionedAttr, cel.CreateAdmissionRequest(versionedAttr.Attributes), optionalVars, runtimeCELCostBudget)
|
||||
if err != nil {
|
||||
return []PolicyDecision{
|
||||
{
|
||||
|
@ -28,6 +28,7 @@ import (
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
v1 "k8s.io/api/admissionregistration/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
celconfig "k8s.io/apiserver/pkg/apis/cel"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/cel"
|
||||
@ -41,7 +42,7 @@ type fakeCelFilter struct {
|
||||
throwError bool
|
||||
}
|
||||
|
||||
func (f *fakeCelFilter) ForInput(*generic.VersionedAttributes, *admissionv1.AdmissionRequest, cel.OptionalVariableBindings) ([]cel.EvaluationResult, error) {
|
||||
func (f *fakeCelFilter) ForInput(*generic.VersionedAttributes, *admissionv1.AdmissionRequest, cel.OptionalVariableBindings, runtimeCELCostBudget int64) ([]cel.EvaluationResult, error) {
|
||||
if f.throwError {
|
||||
return nil, errors.New("test error")
|
||||
}
|
||||
@ -465,7 +466,7 @@ func TestValidate(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
policyResults := v.Validate(fakeVersionedAttr, nil)
|
||||
policyResults := v.Validate(fakeVersionedAttr, nil, celconfig.RuntimeCELCostBudget)
|
||||
|
||||
require.Equal(t, len(policyResults), len(tc.policyDecision))
|
||||
|
||||
|
37
staging/src/k8s.io/apiserver/pkg/apis/cel/config.go
Normal file
37
staging/src/k8s.io/apiserver/pkg/apis/cel/config.go
Normal file
@ -0,0 +1,37 @@
|
||||
/*
|
||||
Copyright 2023 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 apiservercel "k8s.io/apiserver/pkg/cel"
|
||||
|
||||
const (
|
||||
// PerCallLimit specify the actual cost limit per CEL validation call
|
||||
// current PerCallLimit gives roughly 0.1 second for each expression validation call
|
||||
PerCallLimit = 1000000
|
||||
|
||||
// RuntimeCELCostBudget is the overall cost budget for runtime CEL validation cost per ValidatingAdmissionPolicyBinding or CustomResource
|
||||
// current RuntimeCELCostBudget gives roughly 1 seconds for the validation
|
||||
RuntimeCELCostBudget = 10000000
|
||||
|
||||
// CheckFrequency configures the number of iterations within a comprehension to evaluate
|
||||
// before checking whether the function evaluation has been interrupted
|
||||
CheckFrequency = 100
|
||||
|
||||
// MaxRequestSizeBytes is the maximum size of a request to the API server
|
||||
// TODO(DangerOnTheRanger): wire in MaxRequestBodyBytes from apiserver/pkg/server/options/server_run_options.go to make this configurable
|
||||
MaxRequestSizeBytes = apiservercel.DefaultMaxRequestSizeBytes
|
||||
)
|
1
vendor/modules.txt
vendored
1
vendor/modules.txt
vendored
@ -1474,6 +1474,7 @@ k8s.io/apiserver/pkg/apis/audit
|
||||
k8s.io/apiserver/pkg/apis/audit/install
|
||||
k8s.io/apiserver/pkg/apis/audit/v1
|
||||
k8s.io/apiserver/pkg/apis/audit/validation
|
||||
k8s.io/apiserver/pkg/apis/cel
|
||||
k8s.io/apiserver/pkg/apis/config
|
||||
k8s.io/apiserver/pkg/apis/config/v1
|
||||
k8s.io/apiserver/pkg/apis/config/validation
|
||||
|
Loading…
Reference in New Issue
Block a user