Update CRD validation rules path accordingly.

This commit is contained in:
Cici Huang 2023-03-05 20:43:58 +00:00
parent 244c63a2e6
commit 1f4a9dd918
12 changed files with 53 additions and 59 deletions

View File

@ -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 {

View File

@ -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)
} }

View File

@ -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)
} }

View File

@ -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)
} }

View File

@ -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

View File

@ -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)
} }

View File

@ -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...)

View File

@ -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)

View File

@ -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...)
} }
} }

View File

@ -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...)
} }
} }

View File

@ -16,8 +16,6 @@ limitations under the License.
package cel package cel
import apiservercel "k8s.io/apiserver/pkg/cel"
const ( const (
// PerCallLimit specify the actual cost limit per CEL validation call // PerCallLimit specify the actual cost limit per CEL validation call
// current PerCallLimit gives roughly 0.1 second for each expression validation call // current PerCallLimit gives roughly 0.1 second for each expression validation call
@ -33,5 +31,6 @@ const (
// MaxRequestSizeBytes is the maximum size of a request to the API server // 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 // TODO(DangerOnTheRanger): wire in MaxRequestBodyBytes from apiserver/pkg/server/options/server_run_options.go to make this configurable
MaxRequestSizeBytes = apiservercel.DefaultMaxRequestSizeBytes // 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)
) )

View File

@ -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