k8s.io/apiserver@v0.31.1/pkg/admission/plugin/cel/filter_test.go (about)

     1  /*
     2  Copyright 2019 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package cel
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"fmt"
    23  	"reflect"
    24  	"strings"
    25  	"testing"
    26  
    27  	celgo "github.com/google/cel-go/cel"
    28  	celtypes "github.com/google/cel-go/common/types"
    29  	"github.com/stretchr/testify/require"
    30  
    31  	pointer "k8s.io/utils/ptr"
    32  
    33  	corev1 "k8s.io/api/core/v1"
    34  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    35  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    36  	"k8s.io/apimachinery/pkg/fields"
    37  	"k8s.io/apimachinery/pkg/labels"
    38  	"k8s.io/apimachinery/pkg/runtime"
    39  	"k8s.io/apimachinery/pkg/runtime/schema"
    40  	"k8s.io/apimachinery/pkg/selection"
    41  	"k8s.io/apimachinery/pkg/util/version"
    42  	"k8s.io/apiserver/pkg/admission"
    43  	celconfig "k8s.io/apiserver/pkg/apis/cel"
    44  	"k8s.io/apiserver/pkg/authentication/user"
    45  	"k8s.io/apiserver/pkg/authorization/authorizer"
    46  	apiservercel "k8s.io/apiserver/pkg/cel"
    47  	"k8s.io/apiserver/pkg/cel/environment"
    48  	genericfeatures "k8s.io/apiserver/pkg/features"
    49  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    50  	featuregatetesting "k8s.io/component-base/featuregate/testing"
    51  )
    52  
    53  type condition struct {
    54  	Expression string
    55  }
    56  
    57  func (c *condition) GetExpression() string {
    58  	return c.Expression
    59  }
    60  
    61  func (v *condition) ReturnTypes() []*celgo.Type {
    62  	return []*celgo.Type{celgo.BoolType}
    63  }
    64  
    65  func TestCompile(t *testing.T) {
    66  	cases := []struct {
    67  		name             string
    68  		validation       []ExpressionAccessor
    69  		errorExpressions map[string]string
    70  	}{
    71  		{
    72  			name: "invalid syntax",
    73  			validation: []ExpressionAccessor{
    74  				&condition{
    75  					Expression: "1 < 'asdf'",
    76  				},
    77  				&condition{
    78  					Expression: "1 < 2",
    79  				},
    80  			},
    81  			errorExpressions: map[string]string{
    82  				"1 < 'asdf'": "found no matching overload for '_<_' applied to '(int, string)",
    83  			},
    84  		},
    85  		{
    86  			name: "valid syntax",
    87  			validation: []ExpressionAccessor{
    88  				&condition{
    89  					Expression: "1 < 2",
    90  				},
    91  				&condition{
    92  					Expression: "object.spec.string.matches('[0-9]+')",
    93  				},
    94  				&condition{
    95  					Expression: "request.kind.group == 'example.com' && request.kind.version == 'v1' && request.kind.kind == 'Fake'",
    96  				},
    97  			},
    98  		},
    99  	}
   100  
   101  	for _, tc := range cases {
   102  		t.Run(tc.name, func(t *testing.T) {
   103  			c := filterCompiler{compiler: NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), true))}
   104  			e := c.Compile(tc.validation, OptionalVariableDeclarations{HasParams: false, HasAuthorizer: false, StrictCost: true}, environment.NewExpressions)
   105  			if e == nil {
   106  				t.Fatalf("unexpected nil validator")
   107  			}
   108  			validations := tc.validation
   109  			CompilationResults := e.(*filter).compilationResults
   110  			require.Equal(t, len(validations), len(CompilationResults))
   111  
   112  			meets := make([]bool, len(validations))
   113  			for expr, expectErr := range tc.errorExpressions {
   114  				for i, result := range CompilationResults {
   115  					if validations[i].GetExpression() == expr {
   116  						if result.Error == nil {
   117  							t.Errorf("Expect expression '%s' to contain error '%v' but got no error", expr, expectErr)
   118  						} else if !strings.Contains(result.Error.Error(), expectErr) {
   119  							t.Errorf("Expected validations '%s' error to contain '%v' but got: %v", expr, expectErr, result.Error)
   120  						}
   121  						meets[i] = true
   122  					}
   123  				}
   124  			}
   125  			for i, meet := range meets {
   126  				if !meet && CompilationResults[i].Error != nil {
   127  					t.Errorf("Unexpected err '%v' for expression '%s'", CompilationResults[i].Error, validations[i].GetExpression())
   128  				}
   129  			}
   130  		})
   131  	}
   132  }
   133  
   134  func TestFilter(t *testing.T) {
   135  	simpleLabelSelector, err := labels.NewRequirement("apple", selection.Equals, []string{"banana"})
   136  	if err != nil {
   137  		panic(err)
   138  	}
   139  
   140  	configMapParams := &corev1.ConfigMap{
   141  		ObjectMeta: metav1.ObjectMeta{
   142  			Name: "foo",
   143  		},
   144  		Data: map[string]string{
   145  			"fakeString": "fake",
   146  		},
   147  	}
   148  	crdParams := &unstructured.Unstructured{
   149  		Object: map[string]interface{}{
   150  			"spec": map[string]interface{}{
   151  				"testSize": 10,
   152  			},
   153  		},
   154  	}
   155  	podObject := corev1.Pod{
   156  		ObjectMeta: metav1.ObjectMeta{
   157  			Name: "foo",
   158  		},
   159  		Spec: corev1.PodSpec{
   160  			NodeName: "testnode",
   161  		},
   162  	}
   163  
   164  	nsObject := &corev1.Namespace{
   165  		ObjectMeta: metav1.ObjectMeta{
   166  			Name: "test",
   167  			Labels: map[string]string{
   168  				"env": "test",
   169  				"foo": "demo",
   170  			},
   171  			Annotations: map[string]string{
   172  				"annotation1": "testAnnotation1",
   173  			},
   174  			Finalizers: []string{"f1"},
   175  		},
   176  		Spec: corev1.NamespaceSpec{
   177  			Finalizers: []corev1.FinalizerName{
   178  				corev1.FinalizerKubernetes,
   179  			},
   180  		},
   181  		Status: corev1.NamespaceStatus{
   182  			Phase: corev1.NamespaceActive,
   183  		},
   184  	}
   185  
   186  	v130 := version.MajorMinor(1, 30)
   187  	v131 := version.MajorMinor(1, 31)
   188  
   189  	var nilUnstructured *unstructured.Unstructured
   190  	cases := []struct {
   191  		name             string
   192  		attributes       admission.Attributes
   193  		params           runtime.Object
   194  		validations      []ExpressionAccessor
   195  		results          []EvaluationResult
   196  		hasParamKind     bool
   197  		authorizer       authorizer.Authorizer
   198  		testPerCallLimit uint64
   199  		namespaceObject  *corev1.Namespace
   200  		strictCost       bool
   201  		enableSelectors  bool
   202  
   203  		compatibilityVersion *version.Version
   204  	}{
   205  		{
   206  			name: "valid syntax for object",
   207  			validations: []ExpressionAccessor{
   208  				&condition{
   209  					Expression: "has(object.subsets) && object.subsets.size() < 2",
   210  				},
   211  			},
   212  			attributes: newValidAttribute(nil, false),
   213  			results: []EvaluationResult{
   214  				{
   215  					EvalResult: celtypes.True,
   216  				},
   217  			},
   218  			hasParamKind: false,
   219  		},
   220  		{
   221  			name: "valid syntax for metadata",
   222  			validations: []ExpressionAccessor{
   223  				&condition{
   224  					Expression: "object.metadata.name == 'endpoints1'",
   225  				},
   226  			},
   227  			attributes: newValidAttribute(nil, false),
   228  			results: []EvaluationResult{
   229  				{
   230  					EvalResult: celtypes.True,
   231  				},
   232  			},
   233  			hasParamKind: false,
   234  		},
   235  		{
   236  			name: "valid syntax for oldObject",
   237  			validations: []ExpressionAccessor{
   238  				&condition{
   239  					Expression: "oldObject == null",
   240  				},
   241  				&condition{
   242  					Expression: "object != null",
   243  				},
   244  			},
   245  			attributes: newValidAttribute(nil, false),
   246  			results: []EvaluationResult{
   247  				{
   248  					EvalResult: celtypes.True,
   249  				},
   250  				{
   251  					EvalResult: celtypes.True,
   252  				},
   253  			},
   254  			hasParamKind: false,
   255  		},
   256  		{
   257  			name: "valid syntax for request",
   258  			validations: []ExpressionAccessor{
   259  				&condition{
   260  					Expression: "request.operation == 'CREATE'",
   261  				},
   262  			},
   263  			attributes: newValidAttribute(nil, false),
   264  			results: []EvaluationResult{
   265  				{
   266  					EvalResult: celtypes.True,
   267  				},
   268  			},
   269  			hasParamKind: false,
   270  		},
   271  		{
   272  			name: "valid syntax for configMap",
   273  			validations: []ExpressionAccessor{
   274  				&condition{
   275  					Expression: "request.namespace != params.data.fakeString",
   276  				},
   277  			},
   278  			attributes: newValidAttribute(nil, false),
   279  			results: []EvaluationResult{
   280  				{
   281  					EvalResult: celtypes.True,
   282  				},
   283  			},
   284  			hasParamKind: true,
   285  			params:       configMapParams,
   286  		},
   287  		{
   288  			name: "test failure",
   289  			validations: []ExpressionAccessor{
   290  				&condition{
   291  					Expression: "object.subsets.size() > 2",
   292  				},
   293  			},
   294  			attributes: newValidAttribute(nil, false),
   295  			results: []EvaluationResult{
   296  				{
   297  					EvalResult: celtypes.False,
   298  				},
   299  			},
   300  			hasParamKind: true,
   301  			params: &corev1.ConfigMap{
   302  				ObjectMeta: metav1.ObjectMeta{
   303  					Name: "foo",
   304  				},
   305  				Data: map[string]string{
   306  					"fakeString": "fake",
   307  				},
   308  			},
   309  		},
   310  		{
   311  			name: "test failure with multiple validations",
   312  			validations: []ExpressionAccessor{
   313  				&condition{
   314  					Expression: "has(object.subsets)",
   315  				},
   316  				&condition{
   317  					Expression: "object.subsets.size() > 2",
   318  				},
   319  			},
   320  			attributes: newValidAttribute(nil, false),
   321  			results: []EvaluationResult{
   322  				{
   323  					EvalResult: celtypes.True,
   324  				},
   325  				{
   326  					EvalResult: celtypes.False,
   327  				},
   328  			},
   329  			hasParamKind: true,
   330  			params:       configMapParams,
   331  		},
   332  		{
   333  			name: "test failure policy with multiple failed validations",
   334  			validations: []ExpressionAccessor{
   335  				&condition{
   336  					Expression: "oldObject != null",
   337  				},
   338  				&condition{
   339  					Expression: "object.subsets.size() > 2",
   340  				},
   341  			},
   342  			attributes: newValidAttribute(nil, false),
   343  			results: []EvaluationResult{
   344  				{
   345  					EvalResult: celtypes.False,
   346  				},
   347  				{
   348  					EvalResult: celtypes.False,
   349  				},
   350  			},
   351  			hasParamKind: true,
   352  			params:       configMapParams,
   353  		},
   354  		{
   355  			name: "test Object null in delete",
   356  			validations: []ExpressionAccessor{
   357  				&condition{
   358  					Expression: "oldObject != null",
   359  				},
   360  				&condition{
   361  					Expression: "object == null",
   362  				},
   363  			},
   364  			attributes: newValidAttribute(nil, true),
   365  			results: []EvaluationResult{
   366  				{
   367  					EvalResult: celtypes.True,
   368  				},
   369  				{
   370  					EvalResult: celtypes.True,
   371  				},
   372  			},
   373  			hasParamKind: true,
   374  			params:       configMapParams,
   375  		},
   376  		{
   377  			name: "test runtime error",
   378  			validations: []ExpressionAccessor{
   379  				&condition{
   380  					Expression: "oldObject.x == 100",
   381  				},
   382  			},
   383  			attributes: newValidAttribute(nil, true),
   384  			results: []EvaluationResult{
   385  				{
   386  					Error: errors.New("expression 'oldObject.x == 100' resulted in error"),
   387  				},
   388  			},
   389  			hasParamKind: true,
   390  			params:       configMapParams,
   391  		},
   392  		{
   393  			name: "test against crd param",
   394  			validations: []ExpressionAccessor{
   395  				&condition{
   396  					Expression: "object.subsets.size() < params.spec.testSize",
   397  				},
   398  			},
   399  			attributes: newValidAttribute(nil, false),
   400  			results: []EvaluationResult{
   401  				{
   402  					EvalResult: celtypes.True,
   403  				},
   404  			},
   405  			hasParamKind: true,
   406  			params:       crdParams,
   407  		},
   408  		{
   409  			name: "test compile failure",
   410  			validations: []ExpressionAccessor{
   411  				&condition{
   412  					Expression: "fail to compile test",
   413  				},
   414  				&condition{
   415  					Expression: "object.subsets.size() > params.spec.testSize",
   416  				},
   417  			},
   418  			attributes: newValidAttribute(nil, false),
   419  			results: []EvaluationResult{
   420  				{
   421  					Error: errors.New("compilation error"),
   422  				},
   423  				{
   424  					EvalResult: celtypes.False,
   425  				},
   426  			},
   427  			hasParamKind: true,
   428  			params:       crdParams,
   429  		},
   430  		{
   431  			name: "test pod",
   432  			validations: []ExpressionAccessor{
   433  				&condition{
   434  					Expression: "object.spec.nodeName == 'testnode'",
   435  				},
   436  			},
   437  			attributes: newValidAttribute(&podObject, false),
   438  			results: []EvaluationResult{
   439  				{
   440  					EvalResult: celtypes.True,
   441  				},
   442  			},
   443  			hasParamKind: true,
   444  			params:       crdParams,
   445  		},
   446  		{
   447  			name: "test deny paramKind without paramRef",
   448  			validations: []ExpressionAccessor{
   449  				&condition{
   450  					Expression: "params != null",
   451  				},
   452  			},
   453  			attributes: newValidAttribute(&podObject, false),
   454  			results: []EvaluationResult{
   455  				{
   456  					EvalResult: celtypes.False,
   457  				},
   458  			},
   459  			hasParamKind: true,
   460  		},
   461  		{
   462  			name: "test allow paramKind without paramRef",
   463  			validations: []ExpressionAccessor{
   464  				&condition{
   465  					Expression: "params == null",
   466  				},
   467  			},
   468  			attributes: newValidAttribute(&podObject, false),
   469  			results: []EvaluationResult{
   470  				{
   471  					EvalResult: celtypes.True,
   472  				},
   473  			},
   474  			hasParamKind: true,
   475  			params:       runtime.Object(nilUnstructured),
   476  		},
   477  		{
   478  			name: "test authorizer allow resource check",
   479  			validations: []ExpressionAccessor{
   480  				&condition{
   481  					Expression: "authorizer.group('').resource('endpoints').check('create').allowed()",
   482  				},
   483  				&condition{
   484  					Expression: "authorizer.group('').resource('endpoints').check('create').errored()",
   485  				},
   486  			},
   487  			attributes: newValidAttribute(&podObject, false),
   488  			results: []EvaluationResult{
   489  				{
   490  					EvalResult: celtypes.True,
   491  				},
   492  				{
   493  					EvalResult: celtypes.False,
   494  				},
   495  			},
   496  			authorizer: newAuthzAllowMatch(authorizer.AttributesRecord{
   497  				ResourceRequest: true,
   498  				Resource:        "endpoints",
   499  				Verb:            "create",
   500  				APIVersion:      "*",
   501  			}),
   502  		},
   503  		{
   504  			name: "test authorizer error using fieldSelector with 1.30 compatibility",
   505  			validations: []ExpressionAccessor{
   506  				&condition{
   507  					Expression: "authorizer.group('apps').resource('deployments').fieldSelector('foo=bar').labelSelector('apple=banana').subresource('status').namespace('test').name('backend').check('create').allowed()",
   508  				},
   509  			},
   510  			attributes: newValidAttribute(&podObject, false),
   511  			results: []EvaluationResult{
   512  				{
   513  					Error: fmt.Errorf("fieldSelector"),
   514  				},
   515  			},
   516  			authorizer: newAuthzAllowMatch(authorizer.AttributesRecord{
   517  				ResourceRequest: true,
   518  				APIGroup:        "apps",
   519  				Resource:        "deployments",
   520  				Subresource:     "status",
   521  				Namespace:       "test",
   522  				Name:            "backend",
   523  				Verb:            "create",
   524  				APIVersion:      "*",
   525  				FieldSelectorRequirements: fields.Requirements{
   526  					{Operator: "=", Field: "foo", Value: "bar"},
   527  				},
   528  				LabelSelectorRequirements: labels.Requirements{
   529  					*simpleLabelSelector,
   530  				},
   531  			}),
   532  			enableSelectors:      true,
   533  			compatibilityVersion: v130,
   534  		},
   535  		{
   536  			name: "test authorizer allow resource check with all fields",
   537  			validations: []ExpressionAccessor{
   538  				&condition{
   539  					Expression: "authorizer.group('apps').resource('deployments').fieldSelector('foo=bar').labelSelector('apple=banana').subresource('status').namespace('test').name('backend').check('create').allowed()",
   540  				},
   541  			},
   542  			attributes: newValidAttribute(&podObject, false),
   543  			results: []EvaluationResult{
   544  				{
   545  					EvalResult: celtypes.True,
   546  				},
   547  			},
   548  			authorizer: newAuthzAllowMatch(authorizer.AttributesRecord{
   549  				ResourceRequest: true,
   550  				APIGroup:        "apps",
   551  				Resource:        "deployments",
   552  				Subresource:     "status",
   553  				Namespace:       "test",
   554  				Name:            "backend",
   555  				Verb:            "create",
   556  				APIVersion:      "*",
   557  				FieldSelectorRequirements: fields.Requirements{
   558  					{Operator: "=", Field: "foo", Value: "bar"},
   559  				},
   560  				LabelSelectorRequirements: labels.Requirements{
   561  					*simpleLabelSelector,
   562  				},
   563  			}),
   564  			enableSelectors:      true,
   565  			compatibilityVersion: v131,
   566  		},
   567  		{
   568  			name: "test authorizer allow resource check with parse failures",
   569  			validations: []ExpressionAccessor{
   570  				&condition{
   571  					Expression: "authorizer.group('apps').resource('deployments').fieldSelector('foo badoperator bar').labelSelector('apple badoperator banana').subresource('status').namespace('test').name('backend').check('create').allowed()",
   572  				},
   573  			},
   574  			attributes: newValidAttribute(&podObject, false),
   575  			results: []EvaluationResult{
   576  				{
   577  					EvalResult: celtypes.True,
   578  				},
   579  			},
   580  			authorizer: newAuthzAllowMatch(authorizer.AttributesRecord{
   581  				ResourceRequest:         true,
   582  				APIGroup:                "apps",
   583  				Resource:                "deployments",
   584  				Subresource:             "status",
   585  				Namespace:               "test",
   586  				Name:                    "backend",
   587  				Verb:                    "create",
   588  				APIVersion:              "*",
   589  				FieldSelectorParsingErr: errors.New("invalid selector: 'foo badoperator bar'; can't understand 'foo badoperator bar'"),
   590  				LabelSelectorParsingErr: errors.New("unable to parse requirement: found 'badoperator', expected: in, notin, =, ==, !=, gt, lt"),
   591  			}),
   592  			enableSelectors:      true,
   593  			compatibilityVersion: v131,
   594  		},
   595  		{
   596  			name: "test authorizer allow resource check with all fields, without gate",
   597  			validations: []ExpressionAccessor{
   598  				&condition{
   599  					Expression: "authorizer.group('apps').resource('deployments').fieldSelector('foo=bar').labelSelector('apple=banana').subresource('status').namespace('test').name('backend').check('create').allowed()",
   600  				},
   601  			},
   602  			attributes: newValidAttribute(&podObject, false),
   603  			results: []EvaluationResult{
   604  				{
   605  					EvalResult: celtypes.True,
   606  				},
   607  			},
   608  			authorizer: newAuthzAllowMatch(authorizer.AttributesRecord{
   609  				ResourceRequest: true,
   610  				APIGroup:        "apps",
   611  				Resource:        "deployments",
   612  				Subresource:     "status",
   613  				Namespace:       "test",
   614  				Name:            "backend",
   615  				Verb:            "create",
   616  				APIVersion:      "*",
   617  			}),
   618  			compatibilityVersion: v131,
   619  		},
   620  		{
   621  			name: "test authorizer not allowed resource check one incorrect field",
   622  			validations: []ExpressionAccessor{
   623  				&condition{
   624  
   625  					Expression: "authorizer.group('apps').resource('deployments').subresource('status').namespace('test').name('backend').check('create').allowed()",
   626  				},
   627  			},
   628  			attributes: newValidAttribute(&podObject, false),
   629  			results: []EvaluationResult{
   630  				{
   631  					EvalResult: celtypes.False,
   632  				},
   633  			},
   634  			authorizer: newAuthzAllowMatch(authorizer.AttributesRecord{
   635  				ResourceRequest: true,
   636  				APIGroup:        "apps",
   637  				Resource:        "deployments-xxxx",
   638  				Subresource:     "status",
   639  				Namespace:       "test",
   640  				Name:            "backend",
   641  				Verb:            "create",
   642  				APIVersion:      "*",
   643  			}),
   644  		},
   645  		{
   646  			name: "test authorizer reason",
   647  			validations: []ExpressionAccessor{
   648  				&condition{
   649  					Expression: "authorizer.group('').resource('endpoints').check('create').reason() == 'fake reason'",
   650  				},
   651  			},
   652  			attributes: newValidAttribute(&podObject, false),
   653  			results: []EvaluationResult{
   654  				{
   655  					EvalResult: celtypes.True,
   656  				},
   657  			},
   658  			authorizer: denyAll,
   659  		},
   660  		{
   661  			name: "test authorizer error",
   662  			validations: []ExpressionAccessor{
   663  				&condition{
   664  					Expression: "authorizer.group('').resource('endpoints').check('create').errored()",
   665  				},
   666  				&condition{
   667  					Expression: "authorizer.group('').resource('endpoints').check('create').error() == 'fake authz error'",
   668  				},
   669  				&condition{
   670  					Expression: "authorizer.group('').resource('endpoints').check('create').allowed()",
   671  				},
   672  			},
   673  			attributes: newValidAttribute(&podObject, false),
   674  			results: []EvaluationResult{
   675  				{
   676  					EvalResult: celtypes.True,
   677  				},
   678  				{
   679  					EvalResult: celtypes.True,
   680  				},
   681  				{
   682  					EvalResult: celtypes.False,
   683  				},
   684  			},
   685  			authorizer: errorAll,
   686  		},
   687  		{
   688  			name: "test authorizer allow path check",
   689  			validations: []ExpressionAccessor{
   690  				&condition{
   691  					Expression: "authorizer.path('/healthz').check('get').allowed()",
   692  				},
   693  			},
   694  			attributes: newValidAttribute(&podObject, false),
   695  			results: []EvaluationResult{
   696  				{
   697  					EvalResult: celtypes.True,
   698  				},
   699  			},
   700  			authorizer: newAuthzAllowMatch(authorizer.AttributesRecord{
   701  				Path: "/healthz",
   702  				Verb: "get",
   703  			}),
   704  		},
   705  		{
   706  			name: "test authorizer decision is denied path check",
   707  			validations: []ExpressionAccessor{
   708  				&condition{
   709  					Expression: "authorizer.path('/healthz').check('get').allowed() == false",
   710  				},
   711  			},
   712  			attributes: newValidAttribute(&podObject, false),
   713  			results: []EvaluationResult{
   714  				{
   715  					EvalResult: celtypes.True,
   716  				},
   717  			},
   718  			authorizer: denyAll,
   719  		},
   720  		{
   721  			name: "test request resource authorizer allow check",
   722  			validations: []ExpressionAccessor{
   723  				&condition{
   724  					Expression: "authorizer.requestResource.check('custom-verb').allowed()",
   725  				},
   726  			},
   727  			attributes: endpointCreateAttributes(),
   728  			results: []EvaluationResult{
   729  				{
   730  					EvalResult: celtypes.True,
   731  				},
   732  			},
   733  			authorizer: newAuthzAllowMatch(authorizer.AttributesRecord{
   734  				ResourceRequest: true,
   735  				APIGroup:        "",
   736  				Resource:        "endpoints",
   737  				Subresource:     "",
   738  				Namespace:       "default",
   739  				Name:            "endpoints1",
   740  				Verb:            "custom-verb",
   741  				APIVersion:      "*",
   742  			}),
   743  		},
   744  		{
   745  			name: "test subresource request resource authorizer allow check",
   746  			validations: []ExpressionAccessor{
   747  				&condition{
   748  					Expression: "authorizer.requestResource.check('custom-verb').allowed()",
   749  				},
   750  			},
   751  			attributes: endpointStatusUpdateAttributes(),
   752  			results: []EvaluationResult{
   753  				{
   754  					EvalResult: celtypes.True,
   755  				},
   756  			},
   757  			authorizer: newAuthzAllowMatch(authorizer.AttributesRecord{
   758  				ResourceRequest: true,
   759  				APIGroup:        "",
   760  				Resource:        "endpoints",
   761  				Subresource:     "status",
   762  				Namespace:       "default",
   763  				Name:            "endpoints1",
   764  				Verb:            "custom-verb",
   765  				APIVersion:      "*",
   766  			}),
   767  		},
   768  		{
   769  			name: "test serviceAccount authorizer allow check",
   770  			validations: []ExpressionAccessor{
   771  				&condition{
   772  					Expression: "authorizer.serviceAccount('default', 'test-serviceaccount').group('').resource('endpoints').namespace('default').name('endpoints1').check('custom-verb').allowed()",
   773  				},
   774  			},
   775  			attributes: endpointCreateAttributes(),
   776  			results: []EvaluationResult{
   777  				{
   778  					EvalResult: celtypes.True,
   779  				},
   780  			},
   781  			authorizer: newAuthzAllowMatch(authorizer.AttributesRecord{
   782  				User: &user.DefaultInfo{
   783  					Name:   "system:serviceaccount:default:test-serviceaccount",
   784  					Groups: []string{"system:serviceaccounts", "system:serviceaccounts:default"},
   785  				},
   786  				ResourceRequest: true,
   787  				APIGroup:        "",
   788  				Resource:        "endpoints",
   789  				Namespace:       "default",
   790  				Name:            "endpoints1",
   791  				Verb:            "custom-verb",
   792  				APIVersion:      "*",
   793  			}),
   794  		},
   795  		{
   796  			name: "test perCallLimit exceed",
   797  			validations: []ExpressionAccessor{
   798  				&condition{
   799  					Expression: "object.subsets.size() < params.spec.testSize",
   800  				},
   801  			},
   802  			attributes: newValidAttribute(nil, false),
   803  			results: []EvaluationResult{
   804  				{
   805  					Error: errors.New(fmt.Sprintf("operation cancelled: actual cost limit exceeded")),
   806  				},
   807  			},
   808  			hasParamKind:     true,
   809  			params:           crdParams,
   810  			testPerCallLimit: 1,
   811  		},
   812  		{
   813  			name: "test namespaceObject",
   814  			validations: []ExpressionAccessor{
   815  				&condition{
   816  					Expression: "namespaceObject.metadata.name == 'test'",
   817  				},
   818  				&condition{
   819  					Expression: "'env' in namespaceObject.metadata.labels && namespaceObject.metadata.labels.env == 'test'",
   820  				},
   821  				&condition{
   822  					Expression: "('fake' in namespaceObject.metadata.labels) && namespaceObject.metadata.labels.fake == 'test'",
   823  				},
   824  				&condition{
   825  					Expression: "namespaceObject.spec.finalizers[0] == 'kubernetes'",
   826  				},
   827  				&condition{
   828  					Expression: "namespaceObject.status.phase == 'Active'",
   829  				},
   830  				&condition{
   831  					Expression: "size(namespaceObject.metadata.managedFields) == 1",
   832  				},
   833  				&condition{
   834  					Expression: "size(namespaceObject.metadata.ownerReferences) == 1",
   835  				},
   836  				&condition{
   837  					Expression: "'env' in namespaceObject.metadata.annotations",
   838  				},
   839  			},
   840  			attributes: newValidAttribute(&podObject, false),
   841  			results: []EvaluationResult{
   842  				{
   843  					EvalResult: celtypes.True,
   844  				},
   845  				{
   846  					EvalResult: celtypes.True,
   847  				},
   848  				{
   849  					EvalResult: celtypes.False,
   850  				},
   851  				{
   852  					EvalResult: celtypes.True,
   853  				},
   854  				{
   855  					EvalResult: celtypes.True,
   856  				},
   857  				{
   858  					Error: errors.New("undefined field 'managedFields'"),
   859  				},
   860  				{
   861  					Error: errors.New("undefined field 'ownerReferences'"),
   862  				},
   863  				{
   864  					EvalResult: celtypes.False,
   865  				},
   866  			},
   867  			hasParamKind:    false,
   868  			namespaceObject: nsObject,
   869  		},
   870  	}
   871  
   872  	for _, tc := range cases {
   873  		t.Run(tc.name, func(t *testing.T) {
   874  			if tc.enableSelectors {
   875  				featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.AuthorizeWithSelectors, true)
   876  			}
   877  
   878  			if tc.testPerCallLimit == 0 {
   879  				tc.testPerCallLimit = celconfig.PerCallLimit
   880  			}
   881  			compatibilityVersion := tc.compatibilityVersion
   882  			if compatibilityVersion == nil {
   883  				compatibilityVersion = environment.DefaultCompatibilityVersion()
   884  			}
   885  			env, err := environment.MustBaseEnvSet(compatibilityVersion, tc.strictCost).Extend(
   886  				environment.VersionedOptions{
   887  					IntroducedVersion: compatibilityVersion,
   888  					ProgramOptions:    []celgo.ProgramOption{celgo.CostLimit(tc.testPerCallLimit)},
   889  				},
   890  			)
   891  			if err != nil {
   892  				t.Fatal(err)
   893  			}
   894  			c := NewFilterCompiler(env)
   895  			f := c.Compile(tc.validations, OptionalVariableDeclarations{HasParams: tc.hasParamKind, HasAuthorizer: tc.authorizer != nil, StrictCost: tc.strictCost}, environment.NewExpressions)
   896  			if f == nil {
   897  				t.Fatalf("unexpected nil validator")
   898  			}
   899  			validations := tc.validations
   900  			CompilationResults := f.(*filter).compilationResults
   901  			require.Equal(t, len(validations), len(CompilationResults))
   902  
   903  			versionedAttr, err := admission.NewVersionedAttributes(tc.attributes, tc.attributes.GetKind(), newObjectInterfacesForTest())
   904  			if err != nil {
   905  				t.Fatalf("unexpected error on conversion: %v", err)
   906  			}
   907  
   908  			optionalVars := OptionalVariableBindings{VersionedParams: tc.params, Authorizer: tc.authorizer}
   909  			ctx := context.TODO()
   910  			evalResults, _, err := f.ForInput(ctx, versionedAttr, CreateAdmissionRequest(versionedAttr.Attributes, metav1.GroupVersionResource(versionedAttr.GetResource()), metav1.GroupVersionKind(versionedAttr.VersionedKind)), optionalVars, CreateNamespaceObject(tc.namespaceObject), celconfig.RuntimeCELCostBudget)
   911  			if err != nil {
   912  				t.Fatalf("unexpected error: %v", err)
   913  			}
   914  			require.Equal(t, len(evalResults), len(tc.results))
   915  			for i, result := range tc.results {
   916  				if result.Error != nil && !strings.Contains(evalResults[i].Error.Error(), result.Error.Error()) {
   917  					t.Errorf("Expected result '%v' but got '%v'", result.Error, evalResults[i].Error)
   918  				}
   919  				if result.Error == nil && evalResults[i].Error != nil {
   920  					t.Errorf("Expected result '%v' but got error '%v'", result.EvalResult, evalResults[i].Error)
   921  					continue
   922  				}
   923  				if result.EvalResult != evalResults[i].EvalResult {
   924  					t.Errorf("Expected result '%v' but got '%v'", result.EvalResult, evalResults[i].EvalResult)
   925  				}
   926  			}
   927  		})
   928  	}
   929  }
   930  
   931  func TestRuntimeCELCostBudget(t *testing.T) {
   932  	configMapParams := &corev1.ConfigMap{
   933  		ObjectMeta: metav1.ObjectMeta{
   934  			Name: "foo",
   935  		},
   936  		Data: map[string]string{
   937  			"fakeString": "fake",
   938  		},
   939  	}
   940  
   941  	cases := []struct {
   942  		name                        string
   943  		attributes                  admission.Attributes
   944  		params                      runtime.Object
   945  		validations                 []ExpressionAccessor
   946  		hasParamKind                bool
   947  		authorizer                  authorizer.Authorizer
   948  		testRuntimeCELCostBudget    int64
   949  		exceedBudget                bool
   950  		expectRemainingBudget       *int64
   951  		enableStrictCostEnforcement bool
   952  		exceedPerCallLimit          bool
   953  	}{
   954  		{
   955  			name: "expression exceed RuntimeCELCostBudget at fist expression",
   956  			validations: []ExpressionAccessor{
   957  				&condition{
   958  					Expression: "has(object.subsets) && object.subsets.size() < 2",
   959  				},
   960  				&condition{
   961  					Expression: "has(object.subsets)",
   962  				},
   963  			},
   964  			attributes:               newValidAttribute(nil, false),
   965  			hasParamKind:             false,
   966  			testRuntimeCELCostBudget: 1,
   967  			exceedBudget:             true,
   968  		},
   969  		{
   970  			name: "expression exceed RuntimeCELCostBudget at last expression",
   971  			validations: []ExpressionAccessor{
   972  				&condition{
   973  					Expression: "has(object.subsets) && object.subsets.size() < 2",
   974  				},
   975  				&condition{
   976  					Expression: "object.subsets.size() > 2",
   977  				},
   978  			},
   979  			attributes:               newValidAttribute(nil, false),
   980  			hasParamKind:             true,
   981  			params:                   configMapParams,
   982  			testRuntimeCELCostBudget: 5,
   983  			exceedBudget:             true,
   984  		},
   985  		{
   986  			name: "test RuntimeCELCostBudge is not exceed",
   987  			validations: []ExpressionAccessor{
   988  				&condition{
   989  					Expression: "oldObject != null",
   990  				},
   991  				&condition{
   992  					Expression: "object.subsets.size() > 2",
   993  				},
   994  			},
   995  			attributes:               newValidAttribute(nil, false),
   996  			hasParamKind:             true,
   997  			params:                   configMapParams,
   998  			exceedBudget:             false,
   999  			testRuntimeCELCostBudget: 10,
  1000  			expectRemainingBudget:    pointer.To(int64(4)), // 10 - 6
  1001  		},
  1002  		{
  1003  			name: "test RuntimeCELCostBudge exactly covers",
  1004  			validations: []ExpressionAccessor{
  1005  				&condition{
  1006  					Expression: "oldObject != null",
  1007  				},
  1008  				&condition{
  1009  					Expression: "object.subsets.size() > 2",
  1010  				},
  1011  			},
  1012  			attributes:               newValidAttribute(nil, false),
  1013  			hasParamKind:             true,
  1014  			params:                   configMapParams,
  1015  			exceedBudget:             false,
  1016  			testRuntimeCELCostBudget: 6,
  1017  			expectRemainingBudget:    pointer.To(int64(0)),
  1018  		},
  1019  		{
  1020  			name: "test RuntimeCELCostBudge exactly covers then constant",
  1021  			validations: []ExpressionAccessor{
  1022  				&condition{
  1023  					Expression: "oldObject != null",
  1024  				},
  1025  				&condition{
  1026  					Expression: "object.subsets.size() > 2",
  1027  				},
  1028  				&condition{
  1029  					Expression: "true", // zero cost
  1030  				},
  1031  			},
  1032  			attributes:               newValidAttribute(nil, false),
  1033  			hasParamKind:             true,
  1034  			params:                   configMapParams,
  1035  			exceedBudget:             false,
  1036  			testRuntimeCELCostBudget: 6,
  1037  			expectRemainingBudget:    pointer.To(int64(0)),
  1038  		},
  1039  		{
  1040  			name: "Extended library cost: authz check",
  1041  			validations: []ExpressionAccessor{
  1042  				&condition{
  1043  					Expression: "!authorizer.group('').resource('endpoints').check('create').allowed()",
  1044  				},
  1045  			},
  1046  			attributes:               newValidAttribute(nil, false),
  1047  			hasParamKind:             false,
  1048  			exceedBudget:             false,
  1049  			testRuntimeCELCostBudget: 6,
  1050  			expectRemainingBudget:    pointer.To(int64(0)),
  1051  			authorizer:               denyAll,
  1052  		},
  1053  		{
  1054  			name: "Extended library cost: isSorted()",
  1055  			validations: []ExpressionAccessor{
  1056  				&condition{
  1057  					Expression: "[1,2,3,4].isSorted()",
  1058  				},
  1059  			},
  1060  			attributes:               newValidAttribute(nil, false),
  1061  			hasParamKind:             false,
  1062  			exceedBudget:             false,
  1063  			testRuntimeCELCostBudget: 1,
  1064  			expectRemainingBudget:    pointer.To(int64(0)),
  1065  		},
  1066  		{
  1067  			name: "Extended library cost: url",
  1068  			validations: []ExpressionAccessor{
  1069  				&condition{
  1070  					Expression: "url('https:://kubernetes.io/').getHostname() == 'kubernetes.io'",
  1071  				},
  1072  			},
  1073  			attributes:               newValidAttribute(nil, false),
  1074  			hasParamKind:             false,
  1075  			exceedBudget:             false,
  1076  			testRuntimeCELCostBudget: 2,
  1077  			expectRemainingBudget:    pointer.To(int64(0)),
  1078  		},
  1079  		{
  1080  			name: "Extended library cost: split",
  1081  			validations: []ExpressionAccessor{
  1082  				&condition{
  1083  					Expression: "size('abc 123 def 123'.split(' ')) > 0",
  1084  				},
  1085  			},
  1086  			attributes:               newValidAttribute(nil, false),
  1087  			hasParamKind:             false,
  1088  			exceedBudget:             false,
  1089  			testRuntimeCELCostBudget: 3,
  1090  			expectRemainingBudget:    pointer.To(int64(0)),
  1091  		},
  1092  		{
  1093  			name: "Extended library cost: join",
  1094  			validations: []ExpressionAccessor{
  1095  				&condition{
  1096  					Expression: "size(['aa', 'bb', 'cc', 'd', 'e', 'f', 'g', 'h', 'i', 'j'].join(' ')) > 0",
  1097  				},
  1098  			},
  1099  			attributes:               newValidAttribute(nil, false),
  1100  			hasParamKind:             false,
  1101  			exceedBudget:             false,
  1102  			testRuntimeCELCostBudget: 3,
  1103  			expectRemainingBudget:    pointer.To(int64(0)),
  1104  		},
  1105  		{
  1106  			name: "Extended library cost: find",
  1107  			validations: []ExpressionAccessor{
  1108  				&condition{
  1109  					Expression: "size('abc 123 def 123'.find('123')) > 0",
  1110  				},
  1111  			},
  1112  			attributes:               newValidAttribute(nil, false),
  1113  			hasParamKind:             false,
  1114  			exceedBudget:             false,
  1115  			testRuntimeCELCostBudget: 3,
  1116  			expectRemainingBudget:    pointer.To(int64(0)),
  1117  		},
  1118  		{
  1119  			name: "Extended library cost: quantity",
  1120  			validations: []ExpressionAccessor{
  1121  				&condition{
  1122  					Expression: "quantity(\"200M\") == quantity(\"0.2G\") && quantity(\"0.2G\") == quantity(\"200M\")",
  1123  				},
  1124  			},
  1125  			attributes:               newValidAttribute(nil, false),
  1126  			hasParamKind:             false,
  1127  			exceedBudget:             false,
  1128  			testRuntimeCELCostBudget: 6,
  1129  			expectRemainingBudget:    pointer.To(int64(0)),
  1130  		},
  1131  		{
  1132  			name: "With StrictCostEnforcementForVAP enabled: expression exceed RuntimeCELCostBudget at fist expression",
  1133  			validations: []ExpressionAccessor{
  1134  				&condition{
  1135  					Expression: "!authorizer.group('').resource('endpoints').check('create').allowed()",
  1136  				},
  1137  				&condition{
  1138  					Expression: "has(object.subsets)",
  1139  				},
  1140  			},
  1141  			attributes:                  newValidAttribute(nil, false),
  1142  			hasParamKind:                false,
  1143  			testRuntimeCELCostBudget:    35000,
  1144  			exceedBudget:                true,
  1145  			authorizer:                  denyAll,
  1146  			enableStrictCostEnforcement: true,
  1147  		},
  1148  		{
  1149  			name: "With StrictCostEnforcementForVAP enabled: expression exceed RuntimeCELCostBudget at last expression",
  1150  			validations: []ExpressionAccessor{
  1151  				&condition{
  1152  					Expression: "!authorizer.group('').resource('endpoints').check('create').allowed()",
  1153  				},
  1154  				&condition{
  1155  					Expression: "!authorizer.group('').resource('endpoints').check('create').allowed()",
  1156  				},
  1157  			},
  1158  			attributes:                  newValidAttribute(nil, false),
  1159  			hasParamKind:                false,
  1160  			testRuntimeCELCostBudget:    700000,
  1161  			exceedBudget:                true,
  1162  			authorizer:                  denyAll,
  1163  			enableStrictCostEnforcement: true,
  1164  		},
  1165  		{
  1166  			name: "With StrictCostEnforcementForVAP enabled: test RuntimeCELCostBudge is not exceed",
  1167  			validations: []ExpressionAccessor{
  1168  				&condition{
  1169  					Expression: "!authorizer.group('').resource('endpoints').check('create').allowed()",
  1170  				},
  1171  				&condition{
  1172  					Expression: "!authorizer.group('').resource('endpoints').check('create').allowed()",
  1173  				},
  1174  			},
  1175  			attributes:                  newValidAttribute(nil, false),
  1176  			hasParamKind:                true,
  1177  			params:                      configMapParams,
  1178  			exceedBudget:                false,
  1179  			testRuntimeCELCostBudget:    700011,
  1180  			expectRemainingBudget:       pointer.To(int64(1)), // 700011 - 700010
  1181  			authorizer:                  denyAll,
  1182  			enableStrictCostEnforcement: true,
  1183  		},
  1184  		{
  1185  			name: "With StrictCostEnforcementForVAP enabled: test RuntimeCELCostBudge exactly covers",
  1186  			validations: []ExpressionAccessor{
  1187  				&condition{
  1188  					Expression: "!authorizer.group('').resource('endpoints').check('create').allowed()",
  1189  				},
  1190  				&condition{
  1191  					Expression: "!authorizer.group('').resource('endpoints').check('create').allowed()",
  1192  				},
  1193  			},
  1194  			attributes:                  newValidAttribute(nil, false),
  1195  			hasParamKind:                true,
  1196  			params:                      configMapParams,
  1197  			exceedBudget:                false,
  1198  			testRuntimeCELCostBudget:    700010,
  1199  			expectRemainingBudget:       pointer.To(int64(0)),
  1200  			authorizer:                  denyAll,
  1201  			enableStrictCostEnforcement: true,
  1202  		},
  1203  		{
  1204  			name: "With StrictCostEnforcementForVAP enabled: per call limit exceeds",
  1205  			validations: []ExpressionAccessor{
  1206  				&condition{
  1207  					Expression: "!authorizer.group('').resource('endpoints').check('create').allowed() && !authorizer.group('').resource('endpoints').check('create').allowed() && !authorizer.group('').resource('endpoints').check('create').allowed()",
  1208  				},
  1209  			},
  1210  			attributes:                  newValidAttribute(nil, false),
  1211  			hasParamKind:                true,
  1212  			params:                      configMapParams,
  1213  			authorizer:                  denyAll,
  1214  			exceedPerCallLimit:          true,
  1215  			testRuntimeCELCostBudget:    -1,
  1216  			enableStrictCostEnforcement: true,
  1217  		},
  1218  		{
  1219  			name: "With StrictCostEnforcementForVAP enabled: Extended library cost: isSorted()",
  1220  			validations: []ExpressionAccessor{
  1221  				&condition{
  1222  					Expression: "[1,2,3,4].isSorted()",
  1223  				},
  1224  			},
  1225  			attributes:                  newValidAttribute(nil, false),
  1226  			hasParamKind:                false,
  1227  			exceedBudget:                false,
  1228  			testRuntimeCELCostBudget:    4,
  1229  			expectRemainingBudget:       pointer.To(int64(0)),
  1230  			enableStrictCostEnforcement: true,
  1231  		},
  1232  		{
  1233  			name: "With StrictCostEnforcementForVAP enabled: Extended library cost: url",
  1234  			validations: []ExpressionAccessor{
  1235  				&condition{
  1236  					Expression: "url('https:://kubernetes.io/').getHostname() == 'kubernetes.io'",
  1237  				},
  1238  			},
  1239  			attributes:                  newValidAttribute(nil, false),
  1240  			hasParamKind:                false,
  1241  			exceedBudget:                false,
  1242  			testRuntimeCELCostBudget:    4,
  1243  			expectRemainingBudget:       pointer.To(int64(0)),
  1244  			enableStrictCostEnforcement: true,
  1245  		},
  1246  		{
  1247  			name: "With StrictCostEnforcementForVAP enabled: Extended library cost: split",
  1248  			validations: []ExpressionAccessor{
  1249  				&condition{
  1250  					Expression: "size('abc 123 def 123'.split(' ')) > 0",
  1251  				},
  1252  			},
  1253  			attributes:                  newValidAttribute(nil, false),
  1254  			hasParamKind:                false,
  1255  			exceedBudget:                false,
  1256  			testRuntimeCELCostBudget:    5,
  1257  			expectRemainingBudget:       pointer.To(int64(0)),
  1258  			enableStrictCostEnforcement: true,
  1259  		},
  1260  		{
  1261  			name: "With StrictCostEnforcementForVAP enabled: Extended library cost: join",
  1262  			validations: []ExpressionAccessor{
  1263  				&condition{
  1264  					Expression: "size(['aa', 'bb', 'cc', 'd', 'e', 'f', 'g', 'h', 'i', 'j'].join(' ')) > 0",
  1265  				},
  1266  			},
  1267  			attributes:                  newValidAttribute(nil, false),
  1268  			hasParamKind:                false,
  1269  			exceedBudget:                false,
  1270  			testRuntimeCELCostBudget:    7,
  1271  			expectRemainingBudget:       pointer.To(int64(0)),
  1272  			enableStrictCostEnforcement: true,
  1273  		},
  1274  		{
  1275  			name: "With StrictCostEnforcementForVAP enabled: Extended library cost: find",
  1276  			validations: []ExpressionAccessor{
  1277  				&condition{
  1278  					Expression: "size('abc 123 def 123'.find('123')) > 0",
  1279  				},
  1280  			},
  1281  			attributes:                  newValidAttribute(nil, false),
  1282  			hasParamKind:                false,
  1283  			exceedBudget:                false,
  1284  			testRuntimeCELCostBudget:    4,
  1285  			expectRemainingBudget:       pointer.To(int64(0)),
  1286  			enableStrictCostEnforcement: true,
  1287  		},
  1288  		{
  1289  			name: "With StrictCostEnforcementForVAP enabled: Extended library cost: quantity",
  1290  			validations: []ExpressionAccessor{
  1291  				&condition{
  1292  					Expression: "quantity(\"200M\") == quantity(\"0.2G\") && quantity(\"0.2G\") == quantity(\"200M\")",
  1293  				},
  1294  			},
  1295  			attributes:                  newValidAttribute(nil, false),
  1296  			hasParamKind:                false,
  1297  			exceedBudget:                false,
  1298  			testRuntimeCELCostBudget:    6,
  1299  			expectRemainingBudget:       pointer.To(int64(0)),
  1300  			enableStrictCostEnforcement: true,
  1301  		},
  1302  	}
  1303  
  1304  	for _, tc := range cases {
  1305  		t.Run(tc.name, func(t *testing.T) {
  1306  			c := filterCompiler{compiler: NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), tc.enableStrictCostEnforcement))}
  1307  			f := c.Compile(tc.validations, OptionalVariableDeclarations{HasParams: tc.hasParamKind, HasAuthorizer: true, StrictCost: tc.enableStrictCostEnforcement}, environment.NewExpressions)
  1308  			if f == nil {
  1309  				t.Fatalf("unexpected nil validator")
  1310  			}
  1311  			validations := tc.validations
  1312  			CompilationResults := f.(*filter).compilationResults
  1313  			require.Equal(t, len(validations), len(CompilationResults))
  1314  
  1315  			versionedAttr, err := admission.NewVersionedAttributes(tc.attributes, tc.attributes.GetKind(), newObjectInterfacesForTest())
  1316  			if err != nil {
  1317  				t.Fatalf("unexpected error on conversion: %v", err)
  1318  			}
  1319  
  1320  			if tc.testRuntimeCELCostBudget < 0 {
  1321  				tc.testRuntimeCELCostBudget = celconfig.RuntimeCELCostBudget
  1322  			}
  1323  			optionalVars := OptionalVariableBindings{VersionedParams: tc.params, Authorizer: tc.authorizer}
  1324  			ctx := context.TODO()
  1325  			evalResults, remaining, err := f.ForInput(ctx, versionedAttr, CreateAdmissionRequest(versionedAttr.Attributes, metav1.GroupVersionResource(versionedAttr.GetResource()), metav1.GroupVersionKind(versionedAttr.VersionedKind)), optionalVars, nil, tc.testRuntimeCELCostBudget)
  1326  			if tc.exceedPerCallLimit {
  1327  				hasCostErr := false
  1328  				for _, evalResult := range evalResults {
  1329  					if evalResult.Error != nil && strings.Contains(evalResult.Error.Error(), "operation cancelled: actual cost limit exceeded") {
  1330  						hasCostErr = true
  1331  						break
  1332  					}
  1333  				}
  1334  				if !hasCostErr {
  1335  					t.Errorf("Expected per call limit exceeded error but didn't get one")
  1336  				}
  1337  			}
  1338  			if tc.exceedBudget && err == nil {
  1339  				t.Errorf("Expected RuntimeCELCostBudge to be exceeded but got nil")
  1340  			}
  1341  			if tc.exceedBudget && err != nil && !strings.Contains(err.Error(), "validation failed due to running out of cost budget, no further validation rules will be run") {
  1342  				t.Errorf("Expected RuntimeCELCostBudge exceeded error but got: %v", err)
  1343  			}
  1344  			if err != nil && remaining != -1 {
  1345  				t.Errorf("expected -1 remaining when error, but got %d", remaining)
  1346  			}
  1347  			if err != nil && !tc.exceedBudget {
  1348  				t.Fatalf("unexpected error: %v", err)
  1349  			}
  1350  			if tc.exceedBudget && len(evalResults) != 0 {
  1351  				t.Fatalf("unexpected result returned: %v", evalResults)
  1352  			}
  1353  			if tc.expectRemainingBudget != nil && *tc.expectRemainingBudget != remaining {
  1354  				t.Errorf("wrong remaining budget, expect %d, but got %d", *tc.expectRemainingBudget, remaining)
  1355  			}
  1356  		})
  1357  	}
  1358  }
  1359  
  1360  // newObjectInterfacesForTest returns an ObjectInterfaces appropriate for test cases in this file.
  1361  func newObjectInterfacesForTest() admission.ObjectInterfaces {
  1362  	scheme := runtime.NewScheme()
  1363  	corev1.AddToScheme(scheme)
  1364  	return admission.NewObjectInterfacesFromScheme(scheme)
  1365  }
  1366  
  1367  func newValidAttribute(object runtime.Object, isDelete bool) admission.Attributes {
  1368  	var oldObject runtime.Object
  1369  	if !isDelete {
  1370  		if object == nil {
  1371  			object = &corev1.Endpoints{
  1372  				ObjectMeta: metav1.ObjectMeta{
  1373  					Name: "endpoints1",
  1374  				},
  1375  				Subsets: []corev1.EndpointSubset{
  1376  					{
  1377  						Addresses: []corev1.EndpointAddress{{IP: "127.0.0.0"}},
  1378  					},
  1379  				},
  1380  			}
  1381  		}
  1382  	} else {
  1383  		object = nil
  1384  		oldObject = &corev1.Endpoints{
  1385  			Subsets: []corev1.EndpointSubset{
  1386  				{
  1387  					Addresses: []corev1.EndpointAddress{{IP: "127.0.0.0"}},
  1388  				},
  1389  			},
  1390  		}
  1391  	}
  1392  	return admission.NewAttributesRecord(object, oldObject, schema.GroupVersionKind{}, "default", "foo", schema.GroupVersionResource{}, "", admission.Create, &metav1.CreateOptions{}, false, nil)
  1393  
  1394  }
  1395  
  1396  func TestCompilationErrors(t *testing.T) {
  1397  	cases := []struct {
  1398  		name     string
  1399  		results  []CompilationResult
  1400  		expected []error
  1401  	}{
  1402  		{
  1403  			name:     "no errors, empty list",
  1404  			results:  []CompilationResult{},
  1405  			expected: []error{},
  1406  		},
  1407  		{
  1408  			name: "no errors, several results",
  1409  			results: []CompilationResult{
  1410  				{}, {}, {},
  1411  			},
  1412  			expected: []error{},
  1413  		},
  1414  		{
  1415  			name: "all errors",
  1416  			results: []CompilationResult{
  1417  				{
  1418  					Error: &apiservercel.Error{
  1419  						Detail: "error1",
  1420  					},
  1421  				},
  1422  				{
  1423  					Error: &apiservercel.Error{
  1424  						Detail: "error2",
  1425  					},
  1426  				},
  1427  				{
  1428  					Error: &apiservercel.Error{
  1429  						Detail: "error3",
  1430  					},
  1431  				},
  1432  			},
  1433  			expected: []error{
  1434  				errors.New("error1"),
  1435  				errors.New("error2"),
  1436  				errors.New("error3"),
  1437  			},
  1438  		},
  1439  		{
  1440  			name: "mixed errors and non errors",
  1441  			results: []CompilationResult{
  1442  				{},
  1443  				{
  1444  					Error: &apiservercel.Error{
  1445  						Detail: "error1",
  1446  					},
  1447  				},
  1448  				{},
  1449  				{
  1450  					Error: &apiservercel.Error{
  1451  						Detail: "error2",
  1452  					},
  1453  				},
  1454  				{},
  1455  				{},
  1456  				{
  1457  					Error: &apiservercel.Error{
  1458  						Detail: "error3",
  1459  					},
  1460  				},
  1461  				{},
  1462  			},
  1463  			expected: []error{
  1464  				errors.New("error1"),
  1465  				errors.New("error2"),
  1466  				errors.New("error3"),
  1467  			},
  1468  		},
  1469  	}
  1470  
  1471  	for _, tc := range cases {
  1472  		t.Run(tc.name, func(t *testing.T) {
  1473  			e := filter{
  1474  				compilationResults: tc.results,
  1475  			}
  1476  			compilationErrors := e.CompilationErrors()
  1477  			if compilationErrors == nil {
  1478  				t.Fatalf("unexpected nil value returned")
  1479  			}
  1480  			require.Equal(t, len(compilationErrors), len(tc.expected))
  1481  
  1482  			for i, expectedError := range tc.expected {
  1483  				if expectedError.Error() != compilationErrors[i].Error() {
  1484  					t.Errorf("Expected error '%v' but got '%v'", expectedError.Error(), compilationErrors[i].Error())
  1485  				}
  1486  			}
  1487  		})
  1488  	}
  1489  }
  1490  
  1491  var denyAll = fakeAuthorizer{defaultResult: authorizerResult{decision: authorizer.DecisionDeny, reason: "fake reason", err: nil}}
  1492  var errorAll = fakeAuthorizer{defaultResult: authorizerResult{decision: authorizer.DecisionNoOpinion, reason: "", err: fmt.Errorf("fake authz error")}}
  1493  
  1494  func newAuthzAllowMatch(match authorizer.AttributesRecord) fakeAuthorizer {
  1495  	return fakeAuthorizer{
  1496  		match: &authorizerMatch{
  1497  			match:            match,
  1498  			authorizerResult: authorizerResult{decision: authorizer.DecisionAllow, reason: "", err: nil},
  1499  		},
  1500  		defaultResult: authorizerResult{decision: authorizer.DecisionDeny, reason: "", err: nil},
  1501  	}
  1502  }
  1503  
  1504  type fakeAuthorizer struct {
  1505  	match         *authorizerMatch
  1506  	defaultResult authorizerResult
  1507  }
  1508  
  1509  type authorizerResult struct {
  1510  	decision authorizer.Decision
  1511  	reason   string
  1512  	err      error
  1513  }
  1514  
  1515  type authorizerMatch struct {
  1516  	authorizerResult
  1517  	match authorizer.AttributesRecord
  1518  }
  1519  
  1520  func (f fakeAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) {
  1521  	if f.match != nil {
  1522  		other, ok := a.(*authorizer.AttributesRecord)
  1523  		if !ok {
  1524  			panic(fmt.Sprintf("unsupported type: %T", a))
  1525  		}
  1526  
  1527  		if reflect.DeepEqual(f.match.match, *other) {
  1528  			return f.match.decision, f.match.reason, f.match.err
  1529  		}
  1530  	}
  1531  	return f.defaultResult.decision, f.defaultResult.reason, f.defaultResult.err
  1532  }
  1533  
  1534  func endpointCreateAttributes() admission.Attributes {
  1535  	name := "endpoints1"
  1536  	namespace := "default"
  1537  	var object, oldObject runtime.Object
  1538  	object = &corev1.Endpoints{
  1539  		TypeMeta: metav1.TypeMeta{
  1540  			Kind:       "Endpoints",
  1541  			APIVersion: "v1",
  1542  		},
  1543  		ObjectMeta: metav1.ObjectMeta{
  1544  			Name: name,
  1545  		},
  1546  		Subsets: []corev1.EndpointSubset{
  1547  			{
  1548  				Addresses: []corev1.EndpointAddress{{IP: "127.0.0.0"}},
  1549  			},
  1550  		},
  1551  	}
  1552  	gvk := schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Endpoints"}
  1553  	gvr := schema.GroupVersionResource{Group: "", Version: "v1", Resource: "endpoints"}
  1554  	return admission.NewAttributesRecord(object, oldObject, gvk, namespace, name, gvr, "", admission.Create, &metav1.CreateOptions{}, false, nil)
  1555  }
  1556  
  1557  func endpointStatusUpdateAttributes() admission.Attributes {
  1558  	attrs := endpointCreateAttributes()
  1559  	return admission.NewAttributesRecord(
  1560  		attrs.GetObject(), attrs.GetObject(), attrs.GetKind(), attrs.GetNamespace(), attrs.GetName(),
  1561  		attrs.GetResource(), "status", admission.Update, &metav1.UpdateOptions{}, false, nil)
  1562  }