mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 19:56:01 +00:00
Merge pull request #115747 from cici37/rc
Apply cost constraints to ValidatingAdmissionPolicy
This commit is contained in:
commit
8c61473f1c
@ -21,8 +21,6 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy"
|
|
||||||
|
|
||||||
genericvalidation "k8s.io/apimachinery/pkg/api/validation"
|
genericvalidation "k8s.io/apimachinery/pkg/api/validation"
|
||||||
"k8s.io/apimachinery/pkg/api/validation/path"
|
"k8s.io/apimachinery/pkg/api/validation/path"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
@ -31,6 +29,8 @@ import (
|
|||||||
utilvalidation "k8s.io/apimachinery/pkg/util/validation"
|
utilvalidation "k8s.io/apimachinery/pkg/util/validation"
|
||||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
plugincel "k8s.io/apiserver/pkg/admission/plugin/cel"
|
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/cel"
|
||||||
"k8s.io/apiserver/pkg/util/webhook"
|
"k8s.io/apiserver/pkg/util/webhook"
|
||||||
|
|
||||||
@ -740,7 +740,7 @@ func validateValidation(v *admissionregistration.Validation, paramKind *admissio
|
|||||||
Expression: trimmedExpression,
|
Expression: trimmedExpression,
|
||||||
Message: v.Message,
|
Message: v.Message,
|
||||||
Reason: v.Reason,
|
Reason: v.Reason,
|
||||||
}, plugincel.OptionalVariableDeclarations{HasParams: paramKind != nil, HasAuthorizer: true})
|
}, plugincel.OptionalVariableDeclarations{HasParams: paramKind != nil, HasAuthorizer: true}, celconfig.PerCallLimit)
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
switch result.Error.Type {
|
switch result.Error.Type {
|
||||||
case cel.ErrorTypeRequired:
|
case cel.ErrorTypeRequired:
|
||||||
|
@ -35,6 +35,7 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/util/sets"
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
utilvalidation "k8s.io/apimachinery/pkg/util/validation"
|
utilvalidation "k8s.io/apimachinery/pkg/util/validation"
|
||||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
|
celconfig "k8s.io/apiserver/pkg/apis/cel"
|
||||||
apiservercel "k8s.io/apiserver/pkg/cel"
|
apiservercel "k8s.io/apiserver/pkg/cel"
|
||||||
"k8s.io/apiserver/pkg/util/webhook"
|
"k8s.io/apiserver/pkg/util/webhook"
|
||||||
|
|
||||||
@ -1026,7 +1027,7 @@ func ValidateCustomResourceDefinitionOpenAPISchema(schema *apiextensions.JSONSch
|
|||||||
} else if typeInfo == nil {
|
} else if typeInfo == nil {
|
||||||
allErrs.CELErrors = append(allErrs.CELErrors, field.InternalError(fldPath.Child("x-kubernetes-validations"), fmt.Errorf("internal error: failed to retrieve type information for x-kubernetes-validations")))
|
allErrs.CELErrors = append(allErrs.CELErrors, field.InternalError(fldPath.Child("x-kubernetes-validations"), fmt.Errorf("internal error: failed to retrieve type information for x-kubernetes-validations")))
|
||||||
} else {
|
} else {
|
||||||
compResults, err := cel.Compile(typeInfo.Schema, typeInfo.DeclType, cel.PerCallLimit)
|
compResults, err := cel.Compile(typeInfo.Schema, typeInfo.DeclType, celconfig.PerCallLimit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
allErrs.CELErrors = append(allErrs.CELErrors, field.InternalError(fldPath.Child("x-kubernetes-validations"), err))
|
allErrs.CELErrors = append(allErrs.CELErrors, field.InternalError(fldPath.Child("x-kubernetes-validations"), err))
|
||||||
} else {
|
} else {
|
||||||
|
@ -24,6 +24,7 @@ import (
|
|||||||
|
|
||||||
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
||||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
|
celconfig "k8s.io/apiserver/pkg/apis/cel"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCelCostStability(t *testing.T) {
|
func TestCelCostStability(t *testing.T) {
|
||||||
@ -1096,16 +1097,16 @@ func TestCelCostStability(t *testing.T) {
|
|||||||
t.Run(testName, func(t *testing.T) {
|
t.Run(testName, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
s := withRule(*tt.schema, validRule)
|
s := withRule(*tt.schema, validRule)
|
||||||
celValidator := NewValidator(&s, true, PerCallLimit)
|
celValidator := NewValidator(&s, true, celconfig.PerCallLimit)
|
||||||
if celValidator == nil {
|
if celValidator == nil {
|
||||||
t.Fatal("expected non nil validator")
|
t.Fatal("expected non nil validator")
|
||||||
}
|
}
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
errs, remainingBudegt := celValidator.Validate(ctx, field.NewPath("root"), &s, tt.obj, nil, RuntimeCELCostBudget)
|
errs, remainingBudegt := celValidator.Validate(ctx, field.NewPath("root"), &s, tt.obj, nil, celconfig.RuntimeCELCostBudget)
|
||||||
for _, err := range errs {
|
for _, err := range errs {
|
||||||
t.Errorf("unexpected error: %v", err)
|
t.Errorf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
rtCost := RuntimeCELCostBudget - remainingBudegt
|
rtCost := celconfig.RuntimeCELCostBudget - remainingBudegt
|
||||||
if rtCost != expectedCost {
|
if rtCost != expectedCost {
|
||||||
t.Fatalf("runtime cost %d does not match expected runtime cost %d", rtCost, expectedCost)
|
t.Fatalf("runtime cost %d does not match expected runtime cost %d", rtCost, expectedCost)
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ import (
|
|||||||
|
|
||||||
apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||||
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
||||||
|
celconfig "k8s.io/apiserver/pkg/apis/cel"
|
||||||
apiservercel "k8s.io/apiserver/pkg/cel"
|
apiservercel "k8s.io/apiserver/pkg/cel"
|
||||||
"k8s.io/apiserver/pkg/cel/library"
|
"k8s.io/apiserver/pkg/cel/library"
|
||||||
"k8s.io/apiserver/pkg/cel/metrics"
|
"k8s.io/apiserver/pkg/cel/metrics"
|
||||||
@ -40,22 +41,6 @@ const (
|
|||||||
// OldScopedVarName is the variable name assigned to the existing value of the locally scoped data element of a
|
// OldScopedVarName is the variable name assigned to the existing value of the locally scoped data element of a
|
||||||
// CEL validation expression.
|
// CEL validation expression.
|
||||||
OldScopedVarName = "oldSelf"
|
OldScopedVarName = "oldSelf"
|
||||||
|
|
||||||
// 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 CustomResource
|
|
||||||
// current RuntimeCELCostBudget gives roughly 1 seconds for CR 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
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// CompilationResult represents the cel compilation result for one rule
|
// CompilationResult represents the cel compilation result for one rule
|
||||||
@ -103,7 +88,7 @@ func getBaseEnv() (*cel.Env, error) {
|
|||||||
// - nil Program, non-nil Error: Compilation resulted in an error
|
// - nil Program, non-nil Error: Compilation resulted in an error
|
||||||
// - nil Program, nil Error: The provided rule was empty so compilation was not attempted
|
// - nil Program, nil Error: The provided rule was empty so compilation was not attempted
|
||||||
//
|
//
|
||||||
// perCallLimit was added for testing purpose only. Callers should always use const PerCallLimit as input.
|
// 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 Compile(s *schema.Structural, declType *apiservercel.DeclType, perCallLimit uint64) ([]CompilationResult, error) {
|
func Compile(s *schema.Structural, declType *apiservercel.DeclType, perCallLimit uint64) ([]CompilationResult, error) {
|
||||||
t := time.Now()
|
t := time.Now()
|
||||||
defer func() {
|
defer func() {
|
||||||
@ -195,7 +180,7 @@ func compileRule(rule apiextensions.ValidationRule, env *cel.Env, perCallLimit u
|
|||||||
cel.CostLimit(perCallLimit),
|
cel.CostLimit(perCallLimit),
|
||||||
cel.CostTracking(estimator),
|
cel.CostTracking(estimator),
|
||||||
cel.OptimizeRegex(library.ExtensionLibRegexOptimizations...),
|
cel.OptimizeRegex(library.ExtensionLibRegexOptimizations...),
|
||||||
cel.InterruptCheckFrequency(checkFrequency),
|
cel.InterruptCheckFrequency(celconfig.CheckFrequency),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
compilationResult.Error = &apiservercel.Error{Type: apiservercel.ErrorTypeInvalid, Detail: "program instantiation failed: " + err.Error()}
|
compilationResult.Error = &apiservercel.Error{Type: apiservercel.ErrorTypeInvalid, Detail: "program instantiation failed: " + err.Error()}
|
||||||
@ -274,5 +259,5 @@ func (c *sizeEstimator) EstimateCallCost(function, overloadID string, target *ch
|
|||||||
// this function.
|
// this function.
|
||||||
func maxCardinality(minSize int64) uint64 {
|
func maxCardinality(minSize int64) uint64 {
|
||||||
sz := minSize + 1 // assume at least one comma between elements
|
sz := minSize + 1 // assume at least one comma between elements
|
||||||
return uint64(maxRequestSizeBytes / sz)
|
return uint64(celconfig.MaxRequestSizeBytes / sz)
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ import (
|
|||||||
apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||||
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
||||||
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model"
|
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model"
|
||||||
|
celconfig "k8s.io/apiserver/pkg/apis/cel"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -645,7 +646,7 @@ func TestCelCompilation(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range cases {
|
for _, tt := range cases {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
compilationResults, err := Compile(&tt.input, model.SchemaDeclType(&tt.input, false), PerCallLimit)
|
compilationResults, err := Compile(&tt.input, model.SchemaDeclType(&tt.input, false), celconfig.PerCallLimit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Expected no error, but got: %v", err)
|
t.Errorf("Expected no error, but got: %v", err)
|
||||||
}
|
}
|
||||||
@ -1081,7 +1082,7 @@ func genMapWithCustomItemRule(item *schema.Structural, rule string) func(maxProp
|
|||||||
// if expectedCostExceedsLimit is non-zero. Typically, only expectedCost or expectedCostExceedsLimit is non-zero, not both.
|
// if expectedCostExceedsLimit is non-zero. Typically, only expectedCost or expectedCostExceedsLimit is non-zero, not both.
|
||||||
func schemaChecker(schema *schema.Structural, expectedCost uint64, expectedCostExceedsLimit uint64, t *testing.T) func(t *testing.T) {
|
func schemaChecker(schema *schema.Structural, expectedCost uint64, expectedCostExceedsLimit uint64, t *testing.T) func(t *testing.T) {
|
||||||
return func(t *testing.T) {
|
return func(t *testing.T) {
|
||||||
compilationResults, err := Compile(schema, model.SchemaDeclType(schema, false), PerCallLimit)
|
compilationResults, err := Compile(schema, model.SchemaDeclType(schema, false), celconfig.PerCallLimit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Expected no error, got: %v", err)
|
t.Errorf("Expected no error, got: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -64,7 +64,7 @@ type Validator struct {
|
|||||||
// of the Structural schema and returns a custom resource validator that contains nested
|
// of the Structural schema and returns a custom resource validator that contains nested
|
||||||
// validators for all items, properties and additionalProperties that transitively contain validator rules.
|
// validators for all items, properties and additionalProperties that transitively contain validator rules.
|
||||||
// Returns nil if there are no validator rules in the Structural schema. May return a validator containing only errors.
|
// Returns nil if there are no validator rules in the Structural schema. May return a validator containing only errors.
|
||||||
// Adding perCallLimit as input arg for testing purpose only. Callers should always use const PerCallLimit as input
|
// Adding perCallLimit as input arg for testing purpose only. Callers should always use const PerCallLimit from k8s.io/apiserver/pkg/apis/cel/config.go as input
|
||||||
func NewValidator(s *schema.Structural, isResourceRoot bool, perCallLimit uint64) *Validator {
|
func NewValidator(s *schema.Structural, isResourceRoot bool, perCallLimit uint64) *Validator {
|
||||||
if !hasXValidations(s) {
|
if !hasXValidations(s) {
|
||||||
return nil
|
return nil
|
||||||
@ -75,6 +75,7 @@ func NewValidator(s *schema.Structural, isResourceRoot bool, perCallLimit uint64
|
|||||||
// validator creates a Validator for all x-kubernetes-validations at the level of the provided schema and lower and
|
// validator creates a Validator for all x-kubernetes-validations at the level of the provided schema and lower and
|
||||||
// returns the Validator if any x-kubernetes-validations exist in the schema, or nil if no x-kubernetes-validations
|
// returns the Validator if any x-kubernetes-validations exist in the schema, or nil if no x-kubernetes-validations
|
||||||
// exist. declType is expected to be a CEL DeclType corresponding to the structural schema.
|
// exist. declType is expected to be a CEL DeclType corresponding to the structural schema.
|
||||||
|
// 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 validator(s *schema.Structural, isResourceRoot bool, declType *cel.DeclType, perCallLimit uint64) *Validator {
|
func validator(s *schema.Structural, isResourceRoot bool, declType *cel.DeclType, perCallLimit uint64) *Validator {
|
||||||
compiledRules, err := Compile(s, declType, perCallLimit)
|
compiledRules, err := Compile(s, declType, perCallLimit)
|
||||||
var itemsValidator, additionalPropertiesValidator *Validator
|
var itemsValidator, additionalPropertiesValidator *Validator
|
||||||
|
@ -30,6 +30,7 @@ import (
|
|||||||
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
||||||
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model"
|
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model"
|
||||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
|
celconfig "k8s.io/apiserver/pkg/apis/cel"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestValidationExpressions tests CEL integration with custom resource values and OpenAPIv3.
|
// TestValidationExpressions tests CEL integration with custom resource values and OpenAPIv3.
|
||||||
@ -1766,7 +1767,7 @@ func TestValidationExpressions(t *testing.T) {
|
|||||||
t.Run(tests[i].name, func(t *testing.T) {
|
t.Run(tests[i].name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
tt := tests[i]
|
tt := tests[i]
|
||||||
tt.costBudget = RuntimeCELCostBudget
|
tt.costBudget = celconfig.RuntimeCELCostBudget
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
for j := range tt.valid {
|
for j := range tt.valid {
|
||||||
validRule := tt.valid[j]
|
validRule := tt.valid[j]
|
||||||
@ -1777,7 +1778,7 @@ func TestValidationExpressions(t *testing.T) {
|
|||||||
t.Run(testName, func(t *testing.T) {
|
t.Run(testName, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
s := withRule(*tt.schema, validRule)
|
s := withRule(*tt.schema, validRule)
|
||||||
celValidator := validator(&s, tt.isRoot, model.SchemaDeclType(&s, tt.isRoot), PerCallLimit)
|
celValidator := validator(&s, tt.isRoot, model.SchemaDeclType(&s, tt.isRoot), celconfig.PerCallLimit)
|
||||||
if celValidator == nil {
|
if celValidator == nil {
|
||||||
t.Fatal("expected non nil validator")
|
t.Fatal("expected non nil validator")
|
||||||
}
|
}
|
||||||
@ -1801,7 +1802,7 @@ func TestValidationExpressions(t *testing.T) {
|
|||||||
}
|
}
|
||||||
t.Run(testName, func(t *testing.T) {
|
t.Run(testName, func(t *testing.T) {
|
||||||
s := withRule(*tt.schema, rule)
|
s := withRule(*tt.schema, rule)
|
||||||
celValidator := NewValidator(&s, true, PerCallLimit)
|
celValidator := NewValidator(&s, true, celconfig.PerCallLimit)
|
||||||
if celValidator == nil {
|
if celValidator == nil {
|
||||||
t.Fatal("expected non nil validator")
|
t.Fatal("expected non nil validator")
|
||||||
}
|
}
|
||||||
@ -2015,7 +2016,7 @@ func TestValidationExpressionsAtSchemaLevels(t *testing.T) {
|
|||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
celValidator := validator(tt.schema, true, model.SchemaDeclType(tt.schema, true), PerCallLimit)
|
celValidator := validator(tt.schema, true, model.SchemaDeclType(tt.schema, true), celconfig.PerCallLimit)
|
||||||
if celValidator == nil {
|
if celValidator == nil {
|
||||||
t.Fatal("expected non nil validator")
|
t.Fatal("expected non nil validator")
|
||||||
}
|
}
|
||||||
@ -2082,7 +2083,7 @@ func TestCELValidationLimit(t *testing.T) {
|
|||||||
t.Run(validRule, func(t *testing.T) {
|
t.Run(validRule, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
s := withRule(*tt.schema, validRule)
|
s := withRule(*tt.schema, validRule)
|
||||||
celValidator := validator(&s, false, model.SchemaDeclType(&s, false), PerCallLimit)
|
celValidator := validator(&s, false, model.SchemaDeclType(&s, false), celconfig.PerCallLimit)
|
||||||
|
|
||||||
// test with cost budget exceeded
|
// test with cost budget exceeded
|
||||||
errs, _ := celValidator.Validate(ctx, field.NewPath("root"), &s, tt.obj, nil, 0)
|
errs, _ := celValidator.Validate(ctx, field.NewPath("root"), &s, tt.obj, nil, 0)
|
||||||
@ -2107,7 +2108,7 @@ func TestCELValidationLimit(t *testing.T) {
|
|||||||
if celValidator == nil {
|
if celValidator == nil {
|
||||||
t.Fatal("expected non nil validator")
|
t.Fatal("expected non nil validator")
|
||||||
}
|
}
|
||||||
errs, _ = celValidator.Validate(ctx, field.NewPath("root"), &s, tt.obj, nil, RuntimeCELCostBudget)
|
errs, _ = celValidator.Validate(ctx, field.NewPath("root"), &s, tt.obj, nil, celconfig.RuntimeCELCostBudget)
|
||||||
for _, err := range errs {
|
for _, err := range errs {
|
||||||
if err.Type == field.ErrorTypeInvalid && strings.Contains(err.Error(), "no further validation rules will be run due to call cost exceeds limit for rule") {
|
if err.Type == field.ErrorTypeInvalid && strings.Contains(err.Error(), "no further validation rules will be run due to call cost exceeds limit for rule") {
|
||||||
found = true
|
found = true
|
||||||
@ -2150,11 +2151,11 @@ func TestCELValidationContextCancellation(t *testing.T) {
|
|||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
s := withRule(*tt.schema, tt.rule)
|
s := withRule(*tt.schema, tt.rule)
|
||||||
celValidator := NewValidator(&s, true, PerCallLimit)
|
celValidator := NewValidator(&s, true, celconfig.PerCallLimit)
|
||||||
if celValidator == nil {
|
if celValidator == nil {
|
||||||
t.Fatal("expected non nil validator")
|
t.Fatal("expected non nil validator")
|
||||||
}
|
}
|
||||||
errs, _ := celValidator.Validate(ctx, field.NewPath("root"), &s, tt.obj, nil, RuntimeCELCostBudget)
|
errs, _ := celValidator.Validate(ctx, field.NewPath("root"), &s, tt.obj, nil, celconfig.RuntimeCELCostBudget)
|
||||||
for _, err := range errs {
|
for _, err := range errs {
|
||||||
t.Errorf("unexpected error: %v", err)
|
t.Errorf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
@ -2163,7 +2164,7 @@ func TestCELValidationContextCancellation(t *testing.T) {
|
|||||||
found := false
|
found := false
|
||||||
evalCtx, cancel := context.WithTimeout(ctx, time.Microsecond)
|
evalCtx, cancel := context.WithTimeout(ctx, time.Microsecond)
|
||||||
cancel()
|
cancel()
|
||||||
errs, _ = celValidator.Validate(evalCtx, field.NewPath("root"), &s, tt.obj, nil, RuntimeCELCostBudget)
|
errs, _ = celValidator.Validate(evalCtx, field.NewPath("root"), &s, tt.obj, nil, celconfig.RuntimeCELCostBudget)
|
||||||
for _, err := range errs {
|
for _, err := range errs {
|
||||||
if err.Type == field.ErrorTypeInvalid && strings.Contains(err.Error(), "operation interrupted") {
|
if err.Type == field.ErrorTypeInvalid && strings.Contains(err.Error(), "operation interrupted") {
|
||||||
found = true
|
found = true
|
||||||
@ -2208,7 +2209,7 @@ func TestCELMaxRecursionDepth(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
tt.costBudget = RuntimeCELCostBudget
|
tt.costBudget = celconfig.RuntimeCELCostBudget
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
for j := range tt.valid {
|
for j := range tt.valid {
|
||||||
validRule := tt.valid[j]
|
validRule := tt.valid[j]
|
||||||
@ -2216,7 +2217,7 @@ func TestCELMaxRecursionDepth(t *testing.T) {
|
|||||||
t.Run(testName, func(t *testing.T) {
|
t.Run(testName, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
s := withRule(*tt.schema, validRule)
|
s := withRule(*tt.schema, validRule)
|
||||||
celValidator := validator(&s, tt.isRoot, model.SchemaDeclType(&s, tt.isRoot), PerCallLimit)
|
celValidator := validator(&s, tt.isRoot, model.SchemaDeclType(&s, tt.isRoot), celconfig.PerCallLimit)
|
||||||
if celValidator == nil {
|
if celValidator == nil {
|
||||||
t.Fatal("expected non nil validator")
|
t.Fatal("expected non nil validator")
|
||||||
}
|
}
|
||||||
@ -2240,7 +2241,7 @@ func TestCELMaxRecursionDepth(t *testing.T) {
|
|||||||
}
|
}
|
||||||
t.Run(testName, func(t *testing.T) {
|
t.Run(testName, func(t *testing.T) {
|
||||||
s := withRule(*tt.schema, rule)
|
s := withRule(*tt.schema, rule)
|
||||||
celValidator := NewValidator(&s, true, PerCallLimit)
|
celValidator := NewValidator(&s, true, celconfig.PerCallLimit)
|
||||||
if celValidator == nil {
|
if celValidator == nil {
|
||||||
t.Fatal("expected non nil validator")
|
t.Fatal("expected non nil validator")
|
||||||
}
|
}
|
||||||
@ -2285,12 +2286,12 @@ func BenchmarkCELValidationWithContext(b *testing.B) {
|
|||||||
b.Run(tt.name, func(b *testing.B) {
|
b.Run(tt.name, func(b *testing.B) {
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
s := withRule(*tt.schema, tt.rule)
|
s := withRule(*tt.schema, tt.rule)
|
||||||
celValidator := NewValidator(&s, true, PerCallLimit)
|
celValidator := NewValidator(&s, true, celconfig.PerCallLimit)
|
||||||
if celValidator == nil {
|
if celValidator == nil {
|
||||||
b.Fatal("expected non nil validator")
|
b.Fatal("expected non nil validator")
|
||||||
}
|
}
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
errs, _ := celValidator.Validate(ctx, field.NewPath("root"), &s, tt.obj, nil, RuntimeCELCostBudget)
|
errs, _ := celValidator.Validate(ctx, field.NewPath("root"), &s, tt.obj, nil, celconfig.RuntimeCELCostBudget)
|
||||||
for _, err := range errs {
|
for _, err := range errs {
|
||||||
b.Fatalf("validation failed: %v", err)
|
b.Fatalf("validation failed: %v", err)
|
||||||
}
|
}
|
||||||
@ -2325,14 +2326,14 @@ func BenchmarkCELValidationWithCancelledContext(b *testing.B) {
|
|||||||
b.Run(tt.name, func(b *testing.B) {
|
b.Run(tt.name, func(b *testing.B) {
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
s := withRule(*tt.schema, tt.rule)
|
s := withRule(*tt.schema, tt.rule)
|
||||||
celValidator := NewValidator(&s, true, PerCallLimit)
|
celValidator := NewValidator(&s, true, celconfig.PerCallLimit)
|
||||||
if celValidator == nil {
|
if celValidator == nil {
|
||||||
b.Fatal("expected non nil validator")
|
b.Fatal("expected non nil validator")
|
||||||
}
|
}
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
evalCtx, cancel := context.WithTimeout(ctx, time.Microsecond)
|
evalCtx, cancel := context.WithTimeout(ctx, time.Microsecond)
|
||||||
cancel()
|
cancel()
|
||||||
errs, _ := celValidator.Validate(evalCtx, field.NewPath("root"), &s, tt.obj, nil, RuntimeCELCostBudget)
|
errs, _ := celValidator.Validate(evalCtx, field.NewPath("root"), &s, tt.obj, nil, celconfig.RuntimeCELCostBudget)
|
||||||
//found := false
|
//found := false
|
||||||
//for _, err := range errs {
|
//for _, err := range errs {
|
||||||
// if err.Type == field.ErrorTypeInvalid && strings.Contains(err.Error(), "operation interrupted") {
|
// if err.Type == field.ErrorTypeInvalid && strings.Contains(err.Error(), "operation interrupted") {
|
||||||
@ -2379,7 +2380,7 @@ func BenchmarkCELValidationWithAndWithoutOldSelfReference(b *testing.B) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
validator := NewValidator(s, true, PerCallLimit)
|
validator := NewValidator(s, true, celconfig.PerCallLimit)
|
||||||
if validator == nil {
|
if validator == nil {
|
||||||
b.Fatal("expected non nil validator")
|
b.Fatal("expected non nil validator")
|
||||||
}
|
}
|
||||||
@ -2390,7 +2391,7 @@ func BenchmarkCELValidationWithAndWithoutOldSelfReference(b *testing.B) {
|
|||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
errs, _ := validator.Validate(ctx, root, s, obj, obj, RuntimeCELCostBudget)
|
errs, _ := validator.Validate(ctx, root, s, obj, obj, celconfig.RuntimeCELCostBudget)
|
||||||
for _, err := range errs {
|
for _, err := range errs {
|
||||||
b.Errorf("unexpected error: %v", err)
|
b.Errorf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,7 @@ import (
|
|||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
|
celconfig "k8s.io/apiserver/pkg/apis/cel"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ValidateDefaults checks that default values validate and are properly pruned.
|
// ValidateDefaults checks that default values validate and are properly pruned.
|
||||||
@ -50,7 +51,7 @@ func ValidateDefaults(ctx context.Context, pth *field.Path, s *structuralschema.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
allErr, error, _ := validate(ctx, pth, s, s, f, false, requirePrunedDefaults, cel.RuntimeCELCostBudget)
|
allErr, error, _ := validate(ctx, pth, s, s, f, false, requirePrunedDefaults, celconfig.RuntimeCELCostBudget)
|
||||||
return allErr, error
|
return allErr, error
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,7 +92,7 @@ func validate(ctx context.Context, pth *field.Path, s *structuralschema.Structur
|
|||||||
allErrs = append(allErrs, field.Invalid(pth.Child("default"), s.Default.Object, fmt.Sprintf("must result in valid metadata: %v", errs.ToAggregate())))
|
allErrs = append(allErrs, field.Invalid(pth.Child("default"), s.Default.Object, fmt.Sprintf("must result in valid metadata: %v", errs.ToAggregate())))
|
||||||
} else if errs := apiservervalidation.ValidateCustomResource(pth.Child("default"), s.Default.Object, validator); len(errs) > 0 {
|
} else if errs := apiservervalidation.ValidateCustomResource(pth.Child("default"), s.Default.Object, validator); len(errs) > 0 {
|
||||||
allErrs = append(allErrs, errs...)
|
allErrs = append(allErrs, errs...)
|
||||||
} else if celValidator := cel.NewValidator(s, isResourceRoot, cel.PerCallLimit); celValidator != nil {
|
} else if celValidator := cel.NewValidator(s, isResourceRoot, celconfig.PerCallLimit); celValidator != nil {
|
||||||
celErrs, rmCost := celValidator.Validate(ctx, pth.Child("default"), s, s.Default.Object, s.Default.Object, remainingCost)
|
celErrs, rmCost := celValidator.Validate(ctx, pth.Child("default"), s, s.Default.Object, s.Default.Object, remainingCost)
|
||||||
remainingCost = rmCost
|
remainingCost = rmCost
|
||||||
allErrs = append(allErrs, celErrs...)
|
allErrs = append(allErrs, celErrs...)
|
||||||
@ -116,7 +117,7 @@ func validate(ctx context.Context, pth *field.Path, s *structuralschema.Structur
|
|||||||
allErrs = append(allErrs, errs...)
|
allErrs = append(allErrs, errs...)
|
||||||
} else if errs := apiservervalidation.ValidateCustomResource(pth.Child("default"), s.Default.Object, validator); len(errs) > 0 {
|
} else if errs := apiservervalidation.ValidateCustomResource(pth.Child("default"), s.Default.Object, validator); len(errs) > 0 {
|
||||||
allErrs = append(allErrs, errs...)
|
allErrs = append(allErrs, errs...)
|
||||||
} else if celValidator := cel.NewValidator(s, isResourceRoot, cel.PerCallLimit); celValidator != nil {
|
} else if celValidator := cel.NewValidator(s, isResourceRoot, celconfig.PerCallLimit); celValidator != nil {
|
||||||
celErrs, rmCost := celValidator.Validate(ctx, pth.Child("default"), s, s.Default.Object, s.Default.Object, remainingCost)
|
celErrs, rmCost := celValidator.Validate(ctx, pth.Child("default"), s, s.Default.Object, s.Default.Object, remainingCost)
|
||||||
remainingCost = rmCost
|
remainingCost = rmCost
|
||||||
allErrs = append(allErrs, celErrs...)
|
allErrs = append(allErrs, celErrs...)
|
||||||
|
@ -42,6 +42,7 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||||
"k8s.io/apimachinery/pkg/util/json"
|
"k8s.io/apimachinery/pkg/util/json"
|
||||||
"k8s.io/apimachinery/pkg/util/sets"
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
|
celconfig "k8s.io/apiserver/pkg/apis/cel"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestRoundTrip checks the conversion to go-openapi types.
|
// TestRoundTrip checks the conversion to go-openapi types.
|
||||||
@ -608,7 +609,7 @@ func TestValidateCustomResource(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
celValidator := cel.NewValidator(structural, false, cel.PerCallLimit)
|
celValidator := cel.NewValidator(structural, false, celconfig.PerCallLimit)
|
||||||
for i, obj := range tt.objects {
|
for i, obj := range tt.objects {
|
||||||
var oldObject interface{}
|
var oldObject interface{}
|
||||||
if len(tt.oldObjects) == len(tt.objects) {
|
if len(tt.oldObjects) == len(tt.objects) {
|
||||||
@ -617,14 +618,14 @@ func TestValidateCustomResource(t *testing.T) {
|
|||||||
if errs := ValidateCustomResource(nil, obj, validator); len(errs) > 0 {
|
if errs := ValidateCustomResource(nil, obj, validator); len(errs) > 0 {
|
||||||
t.Errorf("unexpected validation error for %v: %v", obj, errs)
|
t.Errorf("unexpected validation error for %v: %v", obj, errs)
|
||||||
}
|
}
|
||||||
errs, _ := celValidator.Validate(context.TODO(), nil, structural, obj, oldObject, cel.RuntimeCELCostBudget)
|
errs, _ := celValidator.Validate(context.TODO(), nil, structural, obj, oldObject, celconfig.RuntimeCELCostBudget)
|
||||||
if len(errs) > 0 {
|
if len(errs) > 0 {
|
||||||
t.Errorf(errs.ToAggregate().Error())
|
t.Errorf(errs.ToAggregate().Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for i, failingObject := range tt.failingObjects {
|
for i, failingObject := range tt.failingObjects {
|
||||||
errs := ValidateCustomResource(nil, failingObject.object, validator)
|
errs := ValidateCustomResource(nil, failingObject.object, validator)
|
||||||
celErrs, _ := celValidator.Validate(context.TODO(), nil, structural, failingObject.object, failingObject.oldObject, cel.RuntimeCELCostBudget)
|
celErrs, _ := celValidator.Validate(context.TODO(), nil, structural, failingObject.object, failingObject.oldObject, celconfig.RuntimeCELCostBudget)
|
||||||
errs = append(errs, celErrs...)
|
errs = append(errs, celErrs...)
|
||||||
if len(errs) == 0 {
|
if len(errs) == 0 {
|
||||||
t.Errorf("missing error for %v", failingObject.object)
|
t.Errorf("missing error for %v", failingObject.object)
|
||||||
|
@ -21,11 +21,11 @@ import (
|
|||||||
|
|
||||||
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
|
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
|
||||||
|
|
||||||
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel"
|
|
||||||
structurallisttype "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/listtype"
|
structurallisttype "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/listtype"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
|
celconfig "k8s.io/apiserver/pkg/apis/cel"
|
||||||
)
|
)
|
||||||
|
|
||||||
type statusStrategy struct {
|
type statusStrategy struct {
|
||||||
@ -113,7 +113,7 @@ func (a statusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Obj
|
|||||||
if has, err := hasBlockingErr(errs); has {
|
if has, err := hasBlockingErr(errs); has {
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
} else {
|
} else {
|
||||||
err, _ := celValidator.Validate(ctx, nil, a.customResourceStrategy.structuralSchemas[v], uNew.Object, oldObject, cel.RuntimeCELCostBudget)
|
err, _ := celValidator.Validate(ctx, nil, a.customResourceStrategy.structuralSchemas[v], uNew.Object, oldObject, celconfig.RuntimeCELCostBudget)
|
||||||
errs = append(errs, err...)
|
errs = append(errs, err...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,7 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
|
celconfig "k8s.io/apiserver/pkg/apis/cel"
|
||||||
"k8s.io/apiserver/pkg/features"
|
"k8s.io/apiserver/pkg/features"
|
||||||
apiserverstorage "k8s.io/apiserver/pkg/storage"
|
apiserverstorage "k8s.io/apiserver/pkg/storage"
|
||||||
"k8s.io/apiserver/pkg/storage/names"
|
"k8s.io/apiserver/pkg/storage/names"
|
||||||
@ -61,7 +62,7 @@ func NewStrategy(typer runtime.ObjectTyper, namespaceScoped bool, kind schema.Gr
|
|||||||
celValidators := map[string]*cel.Validator{}
|
celValidators := map[string]*cel.Validator{}
|
||||||
if utilfeature.DefaultFeatureGate.Enabled(features.CustomResourceValidationExpressions) {
|
if utilfeature.DefaultFeatureGate.Enabled(features.CustomResourceValidationExpressions) {
|
||||||
for name, s := range structuralSchemas {
|
for name, s := range structuralSchemas {
|
||||||
v := cel.NewValidator(s, true, cel.PerCallLimit) // CEL programs are compiled and cached here
|
v := cel.NewValidator(s, true, celconfig.PerCallLimit) // CEL programs are compiled and cached here
|
||||||
if v != nil {
|
if v != nil {
|
||||||
celValidators[name] = v
|
celValidators[name] = v
|
||||||
}
|
}
|
||||||
@ -178,7 +179,7 @@ func (a customResourceStrategy) Validate(ctx context.Context, obj runtime.Object
|
|||||||
if has, err := hasBlockingErr(errs); has {
|
if has, err := hasBlockingErr(errs); has {
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
} else {
|
} else {
|
||||||
err, _ := celValidator.Validate(ctx, nil, a.structuralSchemas[v], u.Object, nil, cel.RuntimeCELCostBudget)
|
err, _ := celValidator.Validate(ctx, nil, a.structuralSchemas[v], u.Object, nil, celconfig.RuntimeCELCostBudget)
|
||||||
errs = append(errs, err...)
|
errs = append(errs, err...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -235,7 +236,7 @@ func (a customResourceStrategy) ValidateUpdate(ctx context.Context, obj, old run
|
|||||||
if has, err := hasBlockingErr(errs); has {
|
if has, err := hasBlockingErr(errs); has {
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
} else {
|
} else {
|
||||||
err, _ := celValidator.Validate(ctx, nil, a.structuralSchemas[v], uNew.Object, uOld.Object, cel.RuntimeCELCostBudget)
|
err, _ := celValidator.Validate(ctx, nil, a.structuralSchemas[v], uNew.Object, uOld.Object, celconfig.RuntimeCELCostBudget)
|
||||||
errs = append(errs, err...)
|
errs = append(errs, err...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ package cel
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
celconfig "k8s.io/apiserver/pkg/apis/cel"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/google/cel-go/cel"
|
"github.com/google/cel-go/cel"
|
||||||
@ -33,8 +34,6 @@ const (
|
|||||||
RequestVarName = "request"
|
RequestVarName = "request"
|
||||||
AuthorizerVarName = "authorizer"
|
AuthorizerVarName = "authorizer"
|
||||||
RequestResourceAuthorizerVarName = "authorizer.requestResource"
|
RequestResourceAuthorizerVarName = "authorizer.requestResource"
|
||||||
|
|
||||||
checkFrequency = 100
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -190,7 +189,8 @@ type CompilationResult struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CompileCELExpression returns a compiled CEL expression.
|
// 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
|
var env *cel.Env
|
||||||
envs, err := getEnvs()
|
envs, err := getEnvs()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -245,9 +245,10 @@ func CompileCELExpression(expressionAccessor ExpressionAccessor, optionalVars Op
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
prog, err := env.Program(ast,
|
prog, err := env.Program(ast,
|
||||||
cel.EvalOptions(cel.OptOptimize),
|
cel.EvalOptions(cel.OptOptimize, cel.OptTrackCost),
|
||||||
cel.OptimizeRegex(library.ExtensionLibRegexOptimizations...),
|
cel.OptimizeRegex(library.ExtensionLibRegexOptimizations...),
|
||||||
cel.InterruptCheckFrequency(checkFrequency),
|
cel.InterruptCheckFrequency(celconfig.CheckFrequency),
|
||||||
|
cel.CostLimit(perCallLimit),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return CompilationResult{
|
return CompilationResult{
|
||||||
|
@ -19,6 +19,8 @@ package cel
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
celconfig "k8s.io/apiserver/pkg/apis/cel"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCompileValidatingPolicyExpression(t *testing.T) {
|
func TestCompileValidatingPolicyExpression(t *testing.T) {
|
||||||
@ -120,7 +122,7 @@ func TestCompileValidatingPolicyExpression(t *testing.T) {
|
|||||||
for _, expr := range tc.expressions {
|
for _, expr := range tc.expressions {
|
||||||
result := CompileCELExpression(&fakeExpressionAccessor{
|
result := CompileCELExpression(&fakeExpressionAccessor{
|
||||||
expr,
|
expr,
|
||||||
}, OptionalVariableDeclarations{HasParams: tc.hasParams, HasAuthorizer: true})
|
}, OptionalVariableDeclarations{HasParams: tc.hasParams, HasAuthorizer: true}, celconfig.PerCallLimit)
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
t.Errorf("Unexpected error: %v", result.Error)
|
t.Errorf("Unexpected error: %v", result.Error)
|
||||||
}
|
}
|
||||||
@ -128,7 +130,7 @@ func TestCompileValidatingPolicyExpression(t *testing.T) {
|
|||||||
for expr, expectErr := range tc.errorExpressions {
|
for expr, expectErr := range tc.errorExpressions {
|
||||||
result := CompileCELExpression(&fakeExpressionAccessor{
|
result := CompileCELExpression(&fakeExpressionAccessor{
|
||||||
expr,
|
expr,
|
||||||
}, OptionalVariableDeclarations{HasParams: tc.hasParams, HasAuthorizer: tc.hasAuthorizer})
|
}, OptionalVariableDeclarations{HasParams: tc.hasParams, HasAuthorizer: tc.hasAuthorizer}, celconfig.PerCallLimit)
|
||||||
if result.Error == nil {
|
if result.Error == nil {
|
||||||
t.Errorf("Expected expression '%s' to contain '%v' but got no error", expr, expectErr)
|
t.Errorf("Expected expression '%s' to contain '%v' but got no error", expr, expectErr)
|
||||||
continue
|
continue
|
||||||
|
@ -19,6 +19,7 @@ package cel
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"reflect"
|
"reflect"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -74,13 +75,14 @@ func (a *evaluationActivation) Parent() interpreter.Activation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Compile compiles the cel expressions defined in the ExpressionAccessors into a Filter
|
// 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 {
|
if len(expressionAccessors) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
compilationResults := make([]CompilationResult, len(expressionAccessors))
|
compilationResults := make([]CompilationResult, len(expressionAccessors))
|
||||||
for i, expressionAccessor := range expressionAccessors {
|
for i, expressionAccessor := range expressionAccessors {
|
||||||
compilationResults[i] = CompileCELExpression(expressionAccessor, options)
|
compilationResults[i] = CompileCELExpression(expressionAccessor, options, perCallLimit)
|
||||||
}
|
}
|
||||||
return NewFilter(compilationResults)
|
return NewFilter(compilationResults)
|
||||||
}
|
}
|
||||||
@ -120,7 +122,8 @@ func objectToResolveVal(r runtime.Object) (interface{}, error) {
|
|||||||
|
|
||||||
// ForInput evaluates the compiled CEL expressions converting them into CELEvaluations
|
// ForInput evaluates the compiled CEL expressions converting them into CELEvaluations
|
||||||
// errors per evaluation are returned on the Evaluation object
|
// 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
|
// TODO: replace unstructured with ref.Val for CEL variables when native type support is available
|
||||||
evaluations := make([]EvaluationResult, len(f.compilationResults))
|
evaluations := make([]EvaluationResult, len(f.compilationResults))
|
||||||
var err error
|
var err error
|
||||||
@ -159,6 +162,7 @@ func (f *filter) ForInput(versionedAttr *generic.VersionedAttributes, request *a
|
|||||||
requestResourceAuthorizer: requestResourceAuthorizerVal,
|
requestResourceAuthorizer: requestResourceAuthorizerVal,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
remainingBudget := runtimeCELCostBudget
|
||||||
for i, compilationResult := range f.compilationResults {
|
for i, compilationResult := range f.compilationResults {
|
||||||
var evaluation = &evaluations[i]
|
var evaluation = &evaluations[i]
|
||||||
evaluation.ExpressionAccessor = compilationResult.ExpressionAccessor
|
evaluation.ExpressionAccessor = compilationResult.ExpressionAccessor
|
||||||
@ -171,9 +175,22 @@ func (f *filter) ForInput(versionedAttr *generic.VersionedAttributes, request *a
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
t1 := time.Now()
|
t1 := time.Now()
|
||||||
evalResult, _, err := compilationResult.Program.Eval(va)
|
evalResult, evalDetails, err := compilationResult.Program.Eval(va)
|
||||||
elapsed := time.Since(t1)
|
elapsed := time.Since(t1)
|
||||||
evaluation.Elapsed = elapsed
|
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 {
|
if err != nil {
|
||||||
evaluation.Error = errors.New(fmt.Sprintf("expression '%v' resulted in error: %v", compilationResult.ExpressionAccessor.GetExpression(), err))
|
evaluation.Error = errors.New(fmt.Sprintf("expression '%v' resulted in error: %v", compilationResult.ExpressionAccessor.GetExpression(), err))
|
||||||
} else {
|
} else {
|
||||||
|
@ -27,6 +27,7 @@ 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"
|
||||||
|
|
||||||
|
celconfig "k8s.io/apiserver/pkg/apis/cel"
|
||||||
"k8s.io/apiserver/pkg/authentication/user"
|
"k8s.io/apiserver/pkg/authentication/user"
|
||||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||||
apiservercel "k8s.io/apiserver/pkg/cel"
|
apiservercel "k8s.io/apiserver/pkg/cel"
|
||||||
@ -87,7 +88,7 @@ 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) {
|
||||||
var c filterCompiler
|
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 {
|
if e == nil {
|
||||||
t.Fatalf("unexpected nil validator")
|
t.Fatalf("unexpected nil validator")
|
||||||
}
|
}
|
||||||
@ -151,6 +152,7 @@ func TestFilter(t *testing.T) {
|
|||||||
results []EvaluationResult
|
results []EvaluationResult
|
||||||
hasParamKind bool
|
hasParamKind bool
|
||||||
authorizer authorizer.Authorizer
|
authorizer authorizer.Authorizer
|
||||||
|
testPerCallLimit uint64
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "valid syntax for object",
|
name: "valid syntax for object",
|
||||||
@ -616,12 +618,32 @@ func TestFilter(t *testing.T) {
|
|||||||
APIVersion: "*",
|
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 {
|
for _, tc := range cases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
c := filterCompiler{}
|
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 {
|
if f == nil {
|
||||||
t.Fatalf("unexpected nil validator")
|
t.Fatalf("unexpected nil validator")
|
||||||
}
|
}
|
||||||
@ -635,7 +657,7 @@ func TestFilter(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
optionalVars := OptionalVariableBindings{VersionedParams: tc.params, Authorizer: tc.authorizer}
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
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.
|
// newObjectInterfacesForTest returns an ObjectInterfaces appropriate for test cases in this file.
|
||||||
func newObjectInterfacesForTest() admission.ObjectInterfaces {
|
func newObjectInterfacesForTest() admission.ObjectInterfaces {
|
||||||
scheme := runtime.NewScheme()
|
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.
|
// FilterCompiler contains a function to assist with converting types and values to/from CEL-typed values.
|
||||||
type FilterCompiler interface {
|
type FilterCompiler interface {
|
||||||
// Compile is used for the cel expression compilation
|
// 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.
|
// 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).
|
// by the underlying CEL code (which is indicated by the match criteria of a policy definition).
|
||||||
type Filter interface {
|
type Filter interface {
|
||||||
// ForInput converts compiled CEL-typed values into evaluated CEL-typed values
|
// 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 returns a list of errors from the compilation of the evaluator
|
||||||
CompilationErrors() []error
|
CompilationErrors() []error
|
||||||
|
@ -172,6 +172,7 @@ func (f *fakeCompiler) HasSynced() bool {
|
|||||||
func (f *fakeCompiler) Compile(
|
func (f *fakeCompiler) Compile(
|
||||||
expressions []cel.ExpressionAccessor,
|
expressions []cel.ExpressionAccessor,
|
||||||
options cel.OptionalVariableDeclarations,
|
options cel.OptionalVariableDeclarations,
|
||||||
|
perCallLimit uint64,
|
||||||
) cel.Filter {
|
) cel.Filter {
|
||||||
key := expressions[0].GetExpression()
|
key := expressions[0].GetExpression()
|
||||||
if fun, ok := f.CompileFuncs[key]; ok {
|
if fun, ok := f.CompileFuncs[key]; ok {
|
||||||
@ -208,7 +209,7 @@ type fakeFilter struct {
|
|||||||
keyId string
|
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
|
return []cel.EvaluationResult{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,10 +221,10 @@ var _ Validator = &fakeValidator{}
|
|||||||
|
|
||||||
type fakeValidator struct {
|
type fakeValidator struct {
|
||||||
*fakeFilter
|
*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
|
//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
|
validateKey := definition.Spec.Validations[0].Expression
|
||||||
if validatorMap == nil {
|
if validatorMap == nil {
|
||||||
@ -234,8 +235,8 @@ func (f *fakeValidator) RegisterDefinition(definition *v1alpha1.ValidatingAdmiss
|
|||||||
validatorMap[validateKey] = f
|
validatorMap[validateKey] = f
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fakeValidator) Validate(versionedAttr *whgeneric.VersionedAttributes, versionedParams runtime.Object) []PolicyDecision {
|
func (f *fakeValidator) Validate(versionedAttr *whgeneric.VersionedAttributes, versionedParams runtime.Object, runtimeCELCostBudget int64) []PolicyDecision {
|
||||||
return f.ValidateFunc(versionedAttr, versionedParams)
|
return f.ValidateFunc(versionedAttr, versionedParams, runtimeCELCostBudget)
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Matcher = &fakeMatcher{}
|
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{
|
return []PolicyDecision{
|
||||||
{
|
{
|
||||||
Action: ActionDeny,
|
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{
|
return []PolicyDecision{
|
||||||
{
|
{
|
||||||
Action: ActionDeny,
|
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{
|
return []PolicyDecision{
|
||||||
{
|
{
|
||||||
Action: ActionDeny,
|
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{
|
return []PolicyDecision{
|
||||||
{
|
{
|
||||||
Action: ActionDeny,
|
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{
|
return []PolicyDecision{
|
||||||
{
|
{
|
||||||
Action: ActionDeny,
|
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{
|
return []PolicyDecision{
|
||||||
{
|
{
|
||||||
Action: ActionDeny,
|
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{
|
return []PolicyDecision{
|
||||||
{
|
{
|
||||||
Action: ActionDeny,
|
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)
|
evaluations1.Add(1)
|
||||||
return []PolicyDecision{
|
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)
|
evaluations2.Add(1)
|
||||||
return []PolicyDecision{
|
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)
|
evaluations.Add(1)
|
||||||
if _, ok := versionedParams.(*v1.ConfigMap); ok {
|
if _, ok := versionedParams.(*v1.ConfigMap); ok {
|
||||||
return []PolicyDecision{
|
return []PolicyDecision{
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
celconfig "k8s.io/apiserver/pkg/apis/cel"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
@ -320,7 +321,7 @@ func (c *celAdmissionController) Validate(
|
|||||||
versionedAttr = va
|
versionedAttr = va
|
||||||
}
|
}
|
||||||
|
|
||||||
decisions := bindingInfo.validator.Validate(versionedAttr, param)
|
decisions := bindingInfo.validator.Validate(versionedAttr, param, celconfig.RuntimeCELCostBudget)
|
||||||
|
|
||||||
for _, decision := range decisions {
|
for _, decision := range decisions {
|
||||||
switch decision.Action {
|
switch decision.Action {
|
||||||
|
@ -33,6 +33,7 @@ import (
|
|||||||
celmetrics "k8s.io/apiserver/pkg/admission/cel"
|
celmetrics "k8s.io/apiserver/pkg/admission/cel"
|
||||||
"k8s.io/apiserver/pkg/admission/plugin/cel"
|
"k8s.io/apiserver/pkg/admission/plugin/cel"
|
||||||
"k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/internal/generic"
|
"k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/internal/generic"
|
||||||
|
celconfig "k8s.io/apiserver/pkg/apis/cel"
|
||||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||||
"k8s.io/client-go/dynamic"
|
"k8s.io/client-go/dynamic"
|
||||||
"k8s.io/client-go/dynamic/dynamicinformer"
|
"k8s.io/client-go/dynamic/dynamicinformer"
|
||||||
@ -459,7 +460,7 @@ func (c *policyController) latestPolicyData() []policyData {
|
|||||||
}
|
}
|
||||||
optionalVars := cel.OptionalVariableDeclarations{HasParams: hasParam, HasAuthorizer: true}
|
optionalVars := cel.OptionalVariableDeclarations{HasParams: hasParam, HasAuthorizer: true}
|
||||||
bindingInfo.validator = c.newValidator(
|
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),
|
convertv1alpha1FailurePolicyTypeTov1FailurePolicyType(definitionInfo.lastReconciledValue.Spec.FailurePolicy),
|
||||||
c.authz,
|
c.authz,
|
||||||
)
|
)
|
||||||
|
@ -55,5 +55,6 @@ type Matcher interface {
|
|||||||
// Validator is contains logic for converting ValidationEvaluation to PolicyDecisions
|
// Validator is contains logic for converting ValidationEvaluation to PolicyDecisions
|
||||||
type Validator interface {
|
type Validator interface {
|
||||||
// Validate is used to take cel evaluations and convert into decisions
|
// 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
|
// 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
|
var f v1.FailurePolicyType
|
||||||
if v.failPolicy == nil {
|
if v.failPolicy == nil {
|
||||||
f = v1.Fail
|
f = v1.Fail
|
||||||
@ -63,7 +64,7 @@ func (v *validator) Validate(versionedAttr *generic.VersionedAttributes, version
|
|||||||
}
|
}
|
||||||
|
|
||||||
optionalVars := cel.OptionalVariableBindings{VersionedParams: versionedParams, Authorizer: v.authorizer}
|
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 {
|
if err != nil {
|
||||||
return []PolicyDecision{
|
return []PolicyDecision{
|
||||||
{
|
{
|
||||||
|
@ -32,6 +32,7 @@ import (
|
|||||||
"k8s.io/apiserver/pkg/admission"
|
"k8s.io/apiserver/pkg/admission"
|
||||||
"k8s.io/apiserver/pkg/admission/plugin/cel"
|
"k8s.io/apiserver/pkg/admission/plugin/cel"
|
||||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/generic"
|
"k8s.io/apiserver/pkg/admission/plugin/webhook/generic"
|
||||||
|
celconfig "k8s.io/apiserver/pkg/apis/cel"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ cel.Filter = &fakeCelFilter{}
|
var _ cel.Filter = &fakeCelFilter{}
|
||||||
@ -41,7 +42,7 @@ type fakeCelFilter struct {
|
|||||||
throwError bool
|
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, int64) ([]cel.EvaluationResult, error) {
|
||||||
if f.throwError {
|
if f.throwError {
|
||||||
return nil, errors.New("test error")
|
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))
|
require.Equal(t, len(policyResults), len(tc.policyDecision))
|
||||||
|
|
||||||
|
36
staging/src/k8s.io/apiserver/pkg/apis/cel/config.go
Normal file
36
staging/src/k8s.io/apiserver/pkg/apis/cel/config.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
// Note that even if server_run_options.go becomes configurable in the future, this cost constant should be fixed and it should be the max allowed request size for the server
|
||||||
|
MaxRequestSizeBytes = int64(3 * 1024 * 1024)
|
||||||
|
)
|
@ -16,9 +16,11 @@ limitations under the License.
|
|||||||
|
|
||||||
package cel
|
package cel
|
||||||
|
|
||||||
|
import celconfig "k8s.io/apiserver/pkg/apis/cel"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// DefaultMaxRequestSizeBytes is the size of the largest request that will be accepted
|
// DefaultMaxRequestSizeBytes is the size of the largest request that will be accepted
|
||||||
DefaultMaxRequestSizeBytes = int64(3 * 1024 * 1024)
|
DefaultMaxRequestSizeBytes = celconfig.MaxRequestSizeBytes
|
||||||
|
|
||||||
// MaxDurationSizeJSON
|
// MaxDurationSizeJSON
|
||||||
// OpenAPI duration strings follow RFC 3339, section 5.6 - see the comment on maxDatetimeSizeJSON
|
// OpenAPI duration strings follow RFC 3339, section 5.6 - see the comment on maxDatetimeSizeJSON
|
||||||
|
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/install
|
||||||
k8s.io/apiserver/pkg/apis/audit/v1
|
k8s.io/apiserver/pkg/apis/audit/v1
|
||||||
k8s.io/apiserver/pkg/apis/audit/validation
|
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
|
||||||
k8s.io/apiserver/pkg/apis/config/v1
|
k8s.io/apiserver/pkg/apis/config/v1
|
||||||
k8s.io/apiserver/pkg/apis/config/validation
|
k8s.io/apiserver/pkg/apis/config/validation
|
||||||
|
Loading…
Reference in New Issue
Block a user