k8s.io/apiserver@v0.31.1/pkg/admission/plugin/policy/validating/admission_test.go (about)

     1  /*
     2  Copyright 2022 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 validating_test
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"strings"
    23  	"sync"
    24  	"sync/atomic"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/stretchr/testify/require"
    29  
    30  	admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
    31  	v1 "k8s.io/api/core/v1"
    32  	"k8s.io/apimachinery/pkg/api/meta"
    33  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    34  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    35  	"k8s.io/apimachinery/pkg/labels"
    36  	"k8s.io/apimachinery/pkg/runtime"
    37  	"k8s.io/apimachinery/pkg/runtime/schema"
    38  	"k8s.io/apimachinery/pkg/types"
    39  	utiljson "k8s.io/apimachinery/pkg/util/json"
    40  	"k8s.io/apimachinery/pkg/util/sets"
    41  	"k8s.io/apiserver/pkg/admission"
    42  	"k8s.io/apiserver/pkg/admission/plugin/policy/generic"
    43  	"k8s.io/apiserver/pkg/admission/plugin/policy/matching"
    44  	"k8s.io/apiserver/pkg/admission/plugin/policy/validating"
    45  	auditinternal "k8s.io/apiserver/pkg/apis/audit"
    46  	"k8s.io/apiserver/pkg/authorization/authorizer"
    47  	"k8s.io/apiserver/pkg/warning"
    48  )
    49  
    50  var (
    51  	clusterScopedParamsGVK schema.GroupVersionKind = schema.GroupVersionKind{
    52  		Group:   "example.com",
    53  		Version: "v1",
    54  		Kind:    "ClusterScopedParamsConfig",
    55  	}
    56  
    57  	paramsGVK schema.GroupVersionKind = schema.GroupVersionKind{
    58  		Group:   "example.com",
    59  		Version: "v1",
    60  		Kind:    "ParamsConfig",
    61  	}
    62  
    63  	// Common objects
    64  	denyPolicy *admissionregistrationv1.ValidatingAdmissionPolicy = &admissionregistrationv1.ValidatingAdmissionPolicy{
    65  		ObjectMeta: metav1.ObjectMeta{
    66  			Name:            "denypolicy.example.com",
    67  			ResourceVersion: "1",
    68  		},
    69  		Spec: admissionregistrationv1.ValidatingAdmissionPolicySpec{
    70  			ParamKind: &admissionregistrationv1.ParamKind{
    71  				APIVersion: paramsGVK.GroupVersion().String(),
    72  				Kind:       paramsGVK.Kind,
    73  			},
    74  			FailurePolicy: ptrTo(admissionregistrationv1.Fail),
    75  			Validations: []admissionregistrationv1.Validation{
    76  				{
    77  					Expression: "messageId for deny policy",
    78  				},
    79  			},
    80  		},
    81  	}
    82  
    83  	fakeParams *unstructured.Unstructured = &unstructured.Unstructured{
    84  		Object: map[string]interface{}{
    85  			"apiVersion": paramsGVK.GroupVersion().String(),
    86  			"kind":       paramsGVK.Kind,
    87  			"metadata": map[string]interface{}{
    88  				"name":            "replicas-test.example.com",
    89  				"namespace":       "default",
    90  				"resourceVersion": "1",
    91  			},
    92  			"maxReplicas": int64(3),
    93  		},
    94  	}
    95  
    96  	denyBinding *admissionregistrationv1.ValidatingAdmissionPolicyBinding = &admissionregistrationv1.ValidatingAdmissionPolicyBinding{
    97  		ObjectMeta: metav1.ObjectMeta{
    98  			Name:            "denybinding.example.com",
    99  			ResourceVersion: "1",
   100  		},
   101  		Spec: admissionregistrationv1.ValidatingAdmissionPolicyBindingSpec{
   102  			PolicyName: denyPolicy.Name,
   103  			ParamRef: &admissionregistrationv1.ParamRef{
   104  				Name:      fakeParams.GetName(),
   105  				Namespace: fakeParams.GetNamespace(),
   106  				// fake object tracker does not populate defaults
   107  				ParameterNotFoundAction: ptrTo(admissionregistrationv1.DenyAction),
   108  			},
   109  			ValidationActions: []admissionregistrationv1.ValidationAction{admissionregistrationv1.Deny},
   110  		},
   111  	}
   112  	denyBindingWithNoParamRef *admissionregistrationv1.ValidatingAdmissionPolicyBinding = &admissionregistrationv1.ValidatingAdmissionPolicyBinding{
   113  		ObjectMeta: metav1.ObjectMeta{
   114  			Name:            "denybinding.example.com",
   115  			ResourceVersion: "1",
   116  		},
   117  		Spec: admissionregistrationv1.ValidatingAdmissionPolicyBindingSpec{
   118  			PolicyName:        denyPolicy.Name,
   119  			ValidationActions: []admissionregistrationv1.ValidationAction{admissionregistrationv1.Deny},
   120  		},
   121  	}
   122  
   123  	denyBindingWithAudit = &admissionregistrationv1.ValidatingAdmissionPolicyBinding{
   124  		ObjectMeta: metav1.ObjectMeta{
   125  			Name:            "denybinding.example.com",
   126  			ResourceVersion: "1",
   127  		},
   128  		Spec: admissionregistrationv1.ValidatingAdmissionPolicyBindingSpec{
   129  			PolicyName:        denyPolicy.Name,
   130  			ValidationActions: []admissionregistrationv1.ValidationAction{admissionregistrationv1.Audit},
   131  		},
   132  	}
   133  	denyBindingWithWarn = &admissionregistrationv1.ValidatingAdmissionPolicyBinding{
   134  		ObjectMeta: metav1.ObjectMeta{
   135  			Name:            "denybinding.example.com",
   136  			ResourceVersion: "1",
   137  		},
   138  		Spec: admissionregistrationv1.ValidatingAdmissionPolicyBindingSpec{
   139  			PolicyName:        denyPolicy.Name,
   140  			ValidationActions: []admissionregistrationv1.ValidationAction{admissionregistrationv1.Warn},
   141  		},
   142  	}
   143  	denyBindingWithAll = &admissionregistrationv1.ValidatingAdmissionPolicyBinding{
   144  		ObjectMeta: metav1.ObjectMeta{
   145  			Name:            "denybinding.example.com",
   146  			ResourceVersion: "1",
   147  		},
   148  		Spec: admissionregistrationv1.ValidatingAdmissionPolicyBindingSpec{
   149  			PolicyName:        denyPolicy.Name,
   150  			ValidationActions: []admissionregistrationv1.ValidationAction{admissionregistrationv1.Deny, admissionregistrationv1.Warn, admissionregistrationv1.Audit},
   151  		},
   152  	}
   153  )
   154  
   155  func newParam(name, namespace string, labels map[string]string) *unstructured.Unstructured {
   156  	if len(namespace) == 0 {
   157  		namespace = metav1.NamespaceDefault
   158  	}
   159  	res := &unstructured.Unstructured{
   160  		Object: map[string]interface{}{
   161  			"apiVersion": paramsGVK.GroupVersion().String(),
   162  			"kind":       paramsGVK.Kind,
   163  			"metadata": map[string]interface{}{
   164  				"name":            name,
   165  				"namespace":       namespace,
   166  				"resourceVersion": "1",
   167  			},
   168  		},
   169  	}
   170  	res.SetLabels(labels)
   171  	return res
   172  }
   173  
   174  func newClusterScopedParam(name string, labels map[string]string) *unstructured.Unstructured {
   175  	res := &unstructured.Unstructured{
   176  		Object: map[string]interface{}{
   177  			"apiVersion": clusterScopedParamsGVK.GroupVersion().String(),
   178  			"kind":       clusterScopedParamsGVK.Kind,
   179  			"metadata": map[string]interface{}{
   180  				"name":            name,
   181  				"resourceVersion": "1",
   182  			},
   183  		},
   184  	}
   185  	res.SetLabels(labels)
   186  	return res
   187  }
   188  
   189  var _ validating.Validator = validateFunc(nil)
   190  
   191  type validateFunc func(
   192  	ctx context.Context,
   193  	matchResource schema.GroupVersionResource,
   194  	versionedAttr *admission.VersionedAttributes,
   195  	versionedParams runtime.Object,
   196  	namespace *v1.Namespace,
   197  	runtimeCELCostBudget int64,
   198  	authz authorizer.Authorizer) validating.ValidateResult
   199  
   200  type fakeCompiler struct {
   201  	ValidateFuncs map[types.NamespacedName]validating.Validator
   202  
   203  	lock        sync.Mutex
   204  	NumCompiles map[types.NamespacedName]int
   205  }
   206  
   207  func (f *fakeCompiler) getNumCompiles(p *validating.Policy) int {
   208  	f.lock.Lock()
   209  	defer f.lock.Unlock()
   210  	return f.NumCompiles[types.NamespacedName{
   211  		Name:      p.Name,
   212  		Namespace: p.Namespace,
   213  	}]
   214  }
   215  
   216  func (f *fakeCompiler) RegisterDefinition(definition *validating.Policy, vf validateFunc) {
   217  	if f.ValidateFuncs == nil {
   218  		f.ValidateFuncs = make(map[types.NamespacedName]validating.Validator)
   219  	}
   220  
   221  	f.ValidateFuncs[types.NamespacedName{
   222  		Name:      definition.Name,
   223  		Namespace: definition.Namespace,
   224  	}] = vf
   225  }
   226  
   227  func (f *fakeCompiler) CompilePolicy(policy *validating.Policy) validating.Validator {
   228  	nn := types.NamespacedName{
   229  		Name:      policy.Name,
   230  		Namespace: policy.Namespace,
   231  	}
   232  
   233  	defer func() {
   234  		f.lock.Lock()
   235  		defer f.lock.Unlock()
   236  		if f.NumCompiles == nil {
   237  			f.NumCompiles = make(map[types.NamespacedName]int)
   238  		}
   239  		f.NumCompiles[nn]++
   240  	}()
   241  	return f.ValidateFuncs[nn]
   242  }
   243  
   244  func (f validateFunc) Validate(
   245  	ctx context.Context,
   246  	matchResource schema.GroupVersionResource,
   247  	versionedAttr *admission.VersionedAttributes,
   248  	versionedParams runtime.Object,
   249  	namespace *v1.Namespace,
   250  	runtimeCELCostBudget int64,
   251  	authz authorizer.Authorizer,
   252  ) validating.ValidateResult {
   253  	return f(
   254  		ctx,
   255  		matchResource,
   256  		versionedAttr,
   257  		versionedParams,
   258  		namespace,
   259  		runtimeCELCostBudget,
   260  		authz,
   261  	)
   262  }
   263  
   264  var _ generic.PolicyMatcher = &fakeMatcher{}
   265  
   266  func (f *fakeMatcher) ValidateInitialization() error {
   267  	return nil
   268  }
   269  
   270  func (f *fakeMatcher) GetNamespace(name string) (*v1.Namespace, error) {
   271  	return nil, nil
   272  }
   273  
   274  type fakeMatcher struct {
   275  	DefaultMatch         bool
   276  	DefinitionMatchFuncs map[types.NamespacedName]func(generic.PolicyAccessor, admission.Attributes) bool
   277  	BindingMatchFuncs    map[types.NamespacedName]func(generic.BindingAccessor, admission.Attributes) bool
   278  }
   279  
   280  func (f *fakeMatcher) RegisterDefinition(definition *admissionregistrationv1.ValidatingAdmissionPolicy, matchFunc func(generic.PolicyAccessor, admission.Attributes) bool) {
   281  	namespace, name := definition.Namespace, definition.Name
   282  	key := types.NamespacedName{
   283  		Name:      name,
   284  		Namespace: namespace,
   285  	}
   286  
   287  	if matchFunc != nil {
   288  		if f.DefinitionMatchFuncs == nil {
   289  			f.DefinitionMatchFuncs = make(map[types.NamespacedName]func(generic.PolicyAccessor, admission.Attributes) bool)
   290  		}
   291  		f.DefinitionMatchFuncs[key] = matchFunc
   292  	}
   293  }
   294  
   295  func (f *fakeMatcher) RegisterBinding(binding *admissionregistrationv1.ValidatingAdmissionPolicyBinding, matchFunc func(generic.BindingAccessor, admission.Attributes) bool) {
   296  	namespace, name := binding.Namespace, binding.Name
   297  	key := types.NamespacedName{
   298  		Name:      name,
   299  		Namespace: namespace,
   300  	}
   301  
   302  	if matchFunc != nil {
   303  		if f.BindingMatchFuncs == nil {
   304  			f.BindingMatchFuncs = make(map[types.NamespacedName]func(generic.BindingAccessor, admission.Attributes) bool)
   305  		}
   306  		f.BindingMatchFuncs[key] = matchFunc
   307  	}
   308  }
   309  
   310  // Matches says whether this policy definition matches the provided admission
   311  // resource request
   312  func (f *fakeMatcher) DefinitionMatches(a admission.Attributes, o admission.ObjectInterfaces, definition generic.PolicyAccessor) (bool, schema.GroupVersionResource, schema.GroupVersionKind, error) {
   313  	namespace, name := definition.GetNamespace(), definition.GetName()
   314  	key := types.NamespacedName{
   315  		Name:      name,
   316  		Namespace: namespace,
   317  	}
   318  	if fun, ok := f.DefinitionMatchFuncs[key]; ok {
   319  		return fun(definition, a), a.GetResource(), a.GetKind(), nil
   320  	}
   321  
   322  	// Default is match everything
   323  	return f.DefaultMatch, a.GetResource(), a.GetKind(), nil
   324  }
   325  
   326  // Matches says whether this policy definition matches the provided admission
   327  // resource request
   328  func (f *fakeMatcher) BindingMatches(a admission.Attributes, o admission.ObjectInterfaces, binding generic.BindingAccessor) (bool, error) {
   329  	namespace, name := binding.GetNamespace(), binding.GetName()
   330  	key := types.NamespacedName{
   331  		Name:      name,
   332  		Namespace: namespace,
   333  	}
   334  	if fun, ok := f.BindingMatchFuncs[key]; ok {
   335  		return fun(binding, a), nil
   336  	}
   337  
   338  	// Default is match everything
   339  	return f.DefaultMatch, nil
   340  }
   341  
   342  func setupFakeTest(t *testing.T, comp *fakeCompiler, match *fakeMatcher) *generic.PolicyTestContext[*validating.Policy, *validating.PolicyBinding, validating.Validator] {
   343  	return setupTestCommon(t, comp, match, true)
   344  }
   345  
   346  // Starts CEL admission controller and sets up a plugin configured with it as well
   347  // as object trackers for manipulating the objects available to the system
   348  //
   349  // ParamTracker only knows the gvk `paramGVK`. If in the future we need to
   350  // support multiple types of params this function needs to be augmented
   351  //
   352  // PolicyTracker expects FakePolicyDefinition and FakePolicyBinding types
   353  // !TODO: refactor this test/framework to remove startInformers argument and
   354  // clean up the return args, and in general make it more accessible.
   355  func setupTestCommon(
   356  	t *testing.T,
   357  	compiler *fakeCompiler,
   358  	matcher generic.PolicyMatcher,
   359  	shouldStartInformers bool,
   360  ) *generic.PolicyTestContext[*validating.Policy, *validating.PolicyBinding, validating.Validator] {
   361  	testContext, testContextCancel, err := generic.NewPolicyTestContext(
   362  		validating.NewValidatingAdmissionPolicyAccessor,
   363  		validating.NewValidatingAdmissionPolicyBindingAccessor,
   364  		func(p *validating.Policy) validating.Validator {
   365  			return compiler.CompilePolicy(p)
   366  		},
   367  		func(a authorizer.Authorizer, m *matching.Matcher) generic.Dispatcher[validating.PolicyHook] {
   368  			coolMatcher := matcher
   369  			if coolMatcher == nil {
   370  				coolMatcher = generic.NewPolicyMatcher(m)
   371  			}
   372  			return validating.NewDispatcher(a, coolMatcher)
   373  		},
   374  		nil,
   375  		[]meta.RESTMapping{
   376  			{
   377  				Resource:         paramsGVK.GroupVersion().WithResource("paramsconfigs"),
   378  				GroupVersionKind: paramsGVK,
   379  				Scope:            meta.RESTScopeNamespace,
   380  			},
   381  			{
   382  				Resource:         clusterScopedParamsGVK.GroupVersion().WithResource("clusterscopedparamsconfigs"),
   383  				GroupVersionKind: clusterScopedParamsGVK,
   384  				Scope:            meta.RESTScopeRoot,
   385  			},
   386  			{
   387  				Resource:         schema.GroupVersionResource{Group: "admissionregistration.k8s.io", Version: "v1beta1", Resource: "validatingadmissionpolicies"},
   388  				GroupVersionKind: schema.GroupVersionKind{Group: "admissionregistration.k8s.io", Version: "v1beta1", Kind: "ValidatingAdmissionPolicy"},
   389  				Scope:            meta.RESTScopeRoot,
   390  			},
   391  		},
   392  	)
   393  	require.NoError(t, err)
   394  	t.Cleanup(testContextCancel)
   395  
   396  	if shouldStartInformers {
   397  		require.NoError(t, testContext.Start())
   398  	}
   399  
   400  	return testContext
   401  }
   402  
   403  func attributeRecord(
   404  	old, new runtime.Object,
   405  	operation admission.Operation,
   406  ) *FakeAttributes {
   407  	if old == nil && new == nil {
   408  		panic("both `old` and `new` may not be nil")
   409  	}
   410  
   411  	// one of old/new may be nil, but not both
   412  	example := new
   413  	if example == nil {
   414  		example = old
   415  	}
   416  
   417  	accessor, err := meta.Accessor(example)
   418  	if err != nil {
   419  		panic(err)
   420  	}
   421  
   422  	return &FakeAttributes{
   423  		Attributes: admission.NewAttributesRecord(
   424  			new,
   425  			old,
   426  			example.GetObjectKind().GroupVersionKind(),
   427  			accessor.GetNamespace(),
   428  			accessor.GetName(),
   429  			schema.GroupVersionResource{},
   430  			"",
   431  			operation,
   432  			nil,
   433  			false,
   434  			nil,
   435  		),
   436  	}
   437  }
   438  
   439  func ptrTo[T any](obj T) *T {
   440  	return &obj
   441  }
   442  
   443  // //////////////////////////////////////////////////////////////////////////////
   444  // Functionality Tests
   445  // //////////////////////////////////////////////////////////////////////////////
   446  
   447  func TestPluginNotReady(t *testing.T) {
   448  	compiler := &fakeCompiler{}
   449  	matcher := &fakeMatcher{
   450  		DefaultMatch: true,
   451  	}
   452  
   453  	// Show that an unstarted informer (or one that has failed its listwatch)
   454  	// will show proper error from plugin
   455  	ctx := setupTestCommon(t, compiler, matcher, false)
   456  	err := ctx.Plugin.Dispatch(
   457  		context.Background(),
   458  		// Object is irrelevant/unchecked for this test. Just test that
   459  		// the evaluator is executed, and returns a denial
   460  		attributeRecord(nil, fakeParams, admission.Create),
   461  		&admission.RuntimeObjectInterfaces{},
   462  	)
   463  
   464  	require.ErrorContains(t, err, "not yet ready to handle request")
   465  
   466  	// Show that by now starting the informer, the error is dissipated
   467  	ctx = setupTestCommon(t, compiler, matcher, true)
   468  	err = ctx.Plugin.Dispatch(
   469  		context.Background(),
   470  		// Object is irrelevant/unchecked for this test. Just test that
   471  		// the evaluator is executed, and returns a denial
   472  		attributeRecord(nil, fakeParams, admission.Create),
   473  		&admission.RuntimeObjectInterfaces{},
   474  	)
   475  
   476  	require.NoError(t, err)
   477  }
   478  
   479  func TestBasicPolicyDefinitionFailure(t *testing.T) {
   480  	datalock := sync.Mutex{}
   481  	numCompiles := 0
   482  
   483  	compiler := &fakeCompiler{}
   484  	matcher := &fakeMatcher{
   485  		DefaultMatch: true,
   486  	}
   487  
   488  	compiler.RegisterDefinition(denyPolicy, func(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) validating.ValidateResult {
   489  		datalock.Lock()
   490  		numCompiles += 1
   491  		datalock.Unlock()
   492  		return validating.ValidateResult{
   493  			Decisions: []validating.PolicyDecision{
   494  				{
   495  					Action:  validating.ActionDeny,
   496  					Message: "Denied",
   497  				},
   498  			},
   499  		}
   500  	})
   501  
   502  	testContext := setupFakeTest(t, compiler, matcher)
   503  	require.NoError(t, testContext.UpdateAndWait(fakeParams, denyPolicy, denyBinding))
   504  
   505  	warningRecorder := newWarningRecorder()
   506  	warnCtx := warning.WithWarningRecorder(testContext, warningRecorder)
   507  	attr := attributeRecord(nil, fakeParams, admission.Create)
   508  	err := testContext.Plugin.Dispatch(
   509  		warnCtx,
   510  		// Object is irrelevant/unchecked for this test. Just test that
   511  		// the evaluator is executed, and returns a denial
   512  		attr,
   513  		&admission.RuntimeObjectInterfaces{},
   514  	)
   515  
   516  	require.Equal(t, 0, warningRecorder.len())
   517  
   518  	annotations := attr.GetAnnotations(auditinternal.LevelMetadata)
   519  	require.Empty(t, annotations)
   520  
   521  	require.ErrorContains(t, err, `Denied`)
   522  }
   523  
   524  // Shows that if a definition does not match the input, it will not be used.
   525  // But with a different input it will be used.
   526  func TestDefinitionDoesntMatch(t *testing.T) {
   527  	compiler := &fakeCompiler{}
   528  	matcher := &fakeMatcher{
   529  		DefaultMatch: true,
   530  	}
   531  
   532  	testContext := setupFakeTest(t, compiler, matcher)
   533  
   534  	datalock := sync.Mutex{}
   535  	passedParams := []*unstructured.Unstructured{}
   536  	numCompiles := 0
   537  
   538  	compiler.RegisterDefinition(denyPolicy, func(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) validating.ValidateResult {
   539  		datalock.Lock()
   540  		numCompiles += 1
   541  		datalock.Unlock()
   542  		return validating.ValidateResult{
   543  			Decisions: []validating.PolicyDecision{
   544  				{
   545  					Action:  validating.ActionDeny,
   546  					Message: "Denied",
   547  				},
   548  			},
   549  		}
   550  	})
   551  
   552  	matcher.RegisterDefinition(denyPolicy, func(vap generic.PolicyAccessor, a admission.Attributes) bool {
   553  		// Match names with even-numbered length
   554  		obj := a.GetObject()
   555  
   556  		accessor, err := meta.Accessor(obj)
   557  		if err != nil {
   558  			t.Fatal(err)
   559  			return false
   560  		}
   561  
   562  		return len(accessor.GetName())%2 == 0
   563  	})
   564  
   565  	require.NoError(t, testContext.UpdateAndWait(fakeParams, denyPolicy, denyBinding))
   566  
   567  	// Validate a non-matching input.
   568  	// Should pass validation with no error.
   569  
   570  	nonMatchingParams := &unstructured.Unstructured{
   571  		Object: map[string]interface{}{
   572  			"apiVersion": paramsGVK.GroupVersion().String(),
   573  			"kind":       paramsGVK.Kind,
   574  			"metadata": map[string]interface{}{
   575  				"name":            "oddlength",
   576  				"resourceVersion": "1",
   577  			},
   578  		},
   579  	}
   580  	require.NoError(t,
   581  		testContext.Plugin.Dispatch(testContext,
   582  			attributeRecord(
   583  				nil, nonMatchingParams,
   584  				admission.Create), &admission.RuntimeObjectInterfaces{}))
   585  	require.Empty(t, passedParams)
   586  
   587  	// Validate a matching input.
   588  	// Should match and be denied.
   589  	matchingParams := &unstructured.Unstructured{
   590  		Object: map[string]interface{}{
   591  			"apiVersion": paramsGVK.GroupVersion().String(),
   592  			"kind":       paramsGVK.Kind,
   593  			"metadata": map[string]interface{}{
   594  				"name":            "evenlength",
   595  				"resourceVersion": "1",
   596  			},
   597  		},
   598  	}
   599  	require.ErrorContains(t,
   600  		testContext.Plugin.Dispatch(testContext,
   601  			attributeRecord(
   602  				nil, matchingParams,
   603  				admission.Create), &admission.RuntimeObjectInterfaces{}),
   604  		`Denied`)
   605  	require.Equal(t, numCompiles, 1)
   606  }
   607  
   608  func TestReconfigureBinding(t *testing.T) {
   609  	compiler := &fakeCompiler{}
   610  	matcher := &fakeMatcher{
   611  		DefaultMatch: true,
   612  	}
   613  
   614  	testContext := setupFakeTest(t, compiler, matcher)
   615  
   616  	datalock := sync.Mutex{}
   617  	numCompiles := 0
   618  
   619  	fakeParams2 := &unstructured.Unstructured{
   620  		Object: map[string]interface{}{
   621  			"apiVersion": paramsGVK.GroupVersion().String(),
   622  			"kind":       paramsGVK.Kind,
   623  			"metadata": map[string]interface{}{
   624  				"name": "replicas-test2.example.com",
   625  				// fake object tracker does not populate missing namespace
   626  				"namespace":       "default",
   627  				"resourceVersion": "2",
   628  			},
   629  			"maxReplicas": int64(35),
   630  		},
   631  	}
   632  
   633  	compiler.RegisterDefinition(denyPolicy, func(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) validating.ValidateResult {
   634  		datalock.Lock()
   635  		numCompiles += 1
   636  		datalock.Unlock()
   637  		return validating.ValidateResult{
   638  			Decisions: []validating.PolicyDecision{
   639  				{
   640  					Action:  validating.ActionDeny,
   641  					Message: "Denied",
   642  				},
   643  			},
   644  		}
   645  	})
   646  
   647  	denyBinding2 := &admissionregistrationv1.ValidatingAdmissionPolicyBinding{
   648  		ObjectMeta: metav1.ObjectMeta{
   649  			Name:            "denybinding.example.com",
   650  			ResourceVersion: "2",
   651  		},
   652  		Spec: admissionregistrationv1.ValidatingAdmissionPolicyBindingSpec{
   653  			PolicyName: denyPolicy.Name,
   654  			ParamRef: &admissionregistrationv1.ParamRef{
   655  				Name:                    fakeParams2.GetName(),
   656  				Namespace:               fakeParams2.GetNamespace(),
   657  				ParameterNotFoundAction: ptrTo(admissionregistrationv1.DenyAction),
   658  			},
   659  			ValidationActions: []admissionregistrationv1.ValidationAction{admissionregistrationv1.Deny},
   660  		},
   661  	}
   662  
   663  	require.NoError(t, testContext.UpdateAndWait(fakeParams, denyPolicy, denyBinding))
   664  
   665  	err := testContext.Plugin.Dispatch(
   666  		testContext,
   667  		attributeRecord(nil, fakeParams, admission.Create),
   668  		&admission.RuntimeObjectInterfaces{},
   669  	)
   670  
   671  	// Expect validation to fail for first time due to binding unconditionally
   672  	// failing
   673  	require.ErrorContains(t, err, `Denied`, "expect policy validation error")
   674  
   675  	// Expect `Compile` only called once
   676  	require.Equal(t, 1, numCompiles, "expect `Compile` to be called only once")
   677  
   678  	// Update the tracker to point at different params
   679  	require.NoError(t, testContext.UpdateAndWait(denyBinding2))
   680  
   681  	err = testContext.Plugin.Dispatch(
   682  		testContext,
   683  		attributeRecord(nil, fakeParams, admission.Create),
   684  		&admission.RuntimeObjectInterfaces{},
   685  	)
   686  
   687  	require.ErrorContains(t, err, "no params found for policy binding with `Deny` parameterNotFoundAction")
   688  
   689  	// Add the missing params
   690  	require.NoError(t, testContext.UpdateAndWait(fakeParams2))
   691  
   692  	// Expect validation to now fail again.
   693  	err = testContext.Plugin.Dispatch(
   694  		testContext,
   695  		attributeRecord(nil, fakeParams, admission.Create),
   696  		&admission.RuntimeObjectInterfaces{},
   697  	)
   698  
   699  	// Expect validation to fail the third time due to validation failure
   700  	require.ErrorContains(t, err, `Denied`, "expected a true policy failure, not a configuration error")
   701  	// require.Equal(t, []*unstructured.Unstructured{fakeParams, fakeParams2}, passedParams, "expected call to `Validate` to cause call to evaluator")
   702  	require.Equal(t, 2, numCompiles, "expect changing binding causes a recompile")
   703  }
   704  
   705  // Shows that a policy which is in effect will stop being in effect when removed
   706  func TestRemoveDefinition(t *testing.T) {
   707  	compiler := &fakeCompiler{}
   708  	matcher := &fakeMatcher{
   709  		DefaultMatch: true,
   710  	}
   711  
   712  	testContext := setupFakeTest(t, compiler, matcher)
   713  
   714  	datalock := sync.Mutex{}
   715  	numCompiles := 0
   716  
   717  	compiler.RegisterDefinition(denyPolicy, func(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) validating.ValidateResult {
   718  		datalock.Lock()
   719  		numCompiles += 1
   720  		datalock.Unlock()
   721  
   722  		return validating.ValidateResult{
   723  			Decisions: []validating.PolicyDecision{
   724  				{
   725  					Action:  validating.ActionDeny,
   726  					Message: "Denied",
   727  				},
   728  			},
   729  		}
   730  	})
   731  
   732  	require.NoError(t, testContext.UpdateAndWait(fakeParams, denyPolicy, denyBinding))
   733  
   734  	record := attributeRecord(nil, fakeParams, admission.Create)
   735  	require.ErrorContains(t,
   736  		testContext.Plugin.Dispatch(
   737  			testContext,
   738  			record,
   739  			&admission.RuntimeObjectInterfaces{},
   740  		),
   741  		`Denied`)
   742  
   743  	require.NoError(t, testContext.DeleteAndWait(denyPolicy))
   744  
   745  	require.NoError(t, testContext.Plugin.Dispatch(
   746  		testContext,
   747  		// Object is irrelevant/unchecked for this test. Just test that
   748  		// the evaluator is executed, and returns a denial
   749  		record,
   750  		&admission.RuntimeObjectInterfaces{},
   751  	))
   752  }
   753  
   754  // Shows that a binding which is in effect will stop being in effect when removed
   755  func TestRemoveBinding(t *testing.T) {
   756  	compiler := &fakeCompiler{}
   757  	matcher := &fakeMatcher{
   758  		DefaultMatch: true,
   759  	}
   760  
   761  	testContext := setupFakeTest(t, compiler, matcher)
   762  
   763  	datalock := sync.Mutex{}
   764  	numCompiles := 0
   765  
   766  	compiler.RegisterDefinition(denyPolicy, func(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) validating.ValidateResult {
   767  		datalock.Lock()
   768  		numCompiles += 1
   769  		datalock.Unlock()
   770  
   771  		return validating.ValidateResult{
   772  			Decisions: []validating.PolicyDecision{
   773  				{
   774  					Action:  validating.ActionDeny,
   775  					Message: "Denied",
   776  				},
   777  			},
   778  		}
   779  	})
   780  
   781  	require.NoError(t, testContext.UpdateAndWait(fakeParams, denyPolicy, denyBinding))
   782  
   783  	record := attributeRecord(nil, fakeParams, admission.Create)
   784  
   785  	require.ErrorContains(t,
   786  		testContext.Plugin.Dispatch(
   787  			testContext,
   788  			record,
   789  			&admission.RuntimeObjectInterfaces{},
   790  		),
   791  		`Denied`)
   792  
   793  	require.NoError(t, testContext.DeleteAndWait(denyBinding))
   794  }
   795  
   796  // Shows that an error is surfaced if a paramSource specified in a binding does
   797  // not actually exist
   798  func TestInvalidParamSourceGVK(t *testing.T) {
   799  	compiler := &fakeCompiler{}
   800  	matcher := &fakeMatcher{
   801  		DefaultMatch: true,
   802  	}
   803  
   804  	testContext := setupFakeTest(t, compiler, matcher)
   805  	passedParams := make(chan *unstructured.Unstructured)
   806  
   807  	badPolicy := *denyPolicy
   808  	badPolicy.Spec.ParamKind = &admissionregistrationv1.ParamKind{
   809  		APIVersion: paramsGVK.GroupVersion().String(),
   810  		Kind:       "BadParamKind",
   811  	}
   812  
   813  	require.NoError(t, testContext.UpdateAndWait(&badPolicy, denyBinding))
   814  
   815  	err := testContext.Plugin.Dispatch(
   816  		testContext,
   817  		attributeRecord(nil, fakeParams, admission.Create),
   818  		&admission.RuntimeObjectInterfaces{},
   819  	)
   820  
   821  	// expect the specific error to be that the param was not found, not that CRD
   822  	// is not existing
   823  	require.ErrorContains(t, err,
   824  		`failed to configure policy: failed to find resource referenced by paramKind: 'example.com/v1, Kind=BadParamKind'`)
   825  
   826  	close(passedParams)
   827  	require.Empty(t, passedParams)
   828  }
   829  
   830  // Shows that an error is surfaced if a param specified in a binding does not
   831  // actually exist
   832  func TestInvalidParamSourceInstanceName(t *testing.T) {
   833  	compiler := &fakeCompiler{}
   834  	matcher := &fakeMatcher{
   835  		DefaultMatch: true,
   836  	}
   837  
   838  	testContext := setupFakeTest(t, compiler, matcher)
   839  
   840  	datalock := sync.Mutex{}
   841  	passedParams := []*unstructured.Unstructured{}
   842  	numCompiles := 0
   843  
   844  	compiler.RegisterDefinition(denyPolicy, func(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) validating.ValidateResult {
   845  		datalock.Lock()
   846  		numCompiles += 1
   847  		datalock.Unlock()
   848  
   849  		return validating.ValidateResult{
   850  			Decisions: []validating.PolicyDecision{
   851  				{
   852  					Action:  validating.ActionDeny,
   853  					Message: "Denied",
   854  				},
   855  			},
   856  		}
   857  	})
   858  
   859  	require.NoError(t, testContext.UpdateAndWait(denyPolicy, denyBinding))
   860  
   861  	err := testContext.Plugin.Dispatch(
   862  		testContext,
   863  		attributeRecord(nil, fakeParams, admission.Create),
   864  		&admission.RuntimeObjectInterfaces{},
   865  	)
   866  
   867  	// expect the specific error to be that the param was not found, not that CRD
   868  	// is not existing
   869  	require.ErrorContains(t, err,
   870  		"no params found for policy binding with `Deny` parameterNotFoundAction")
   871  	require.Empty(t, passedParams)
   872  }
   873  
   874  // Show that policy still gets evaluated with `nil` param if paramRef & namespaceParamRef
   875  // are both unset
   876  func TestEmptyParamRef(t *testing.T) {
   877  	compiler := &fakeCompiler{}
   878  	matcher := &fakeMatcher{
   879  		DefaultMatch: true,
   880  	}
   881  
   882  	testContext := setupFakeTest(t, compiler, matcher)
   883  
   884  	datalock := sync.Mutex{}
   885  	numCompiles := 0
   886  
   887  	compiler.RegisterDefinition(denyPolicy, func(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) validating.ValidateResult {
   888  		datalock.Lock()
   889  		numCompiles += 1
   890  		datalock.Unlock()
   891  
   892  		// Versioned params must be nil to pass the test
   893  		if versionedParams != nil {
   894  			return validating.ValidateResult{
   895  				Decisions: []validating.PolicyDecision{
   896  					{
   897  						Action: validating.ActionAdmit,
   898  					},
   899  				},
   900  			}
   901  		}
   902  		return validating.ValidateResult{
   903  			Decisions: []validating.PolicyDecision{
   904  				{
   905  					Action:  validating.ActionDeny,
   906  					Message: "Denied",
   907  				},
   908  			},
   909  		}
   910  	})
   911  
   912  	require.NoError(t, testContext.UpdateAndWait(denyPolicy, denyBindingWithNoParamRef))
   913  
   914  	err := testContext.Plugin.Dispatch(
   915  		testContext,
   916  		// Object is irrelevant/unchecked for this test. Just test that
   917  		// the evaluator is executed, and returns a denial
   918  		attributeRecord(nil, fakeParams, admission.Create),
   919  		&admission.RuntimeObjectInterfaces{},
   920  	)
   921  
   922  	require.ErrorContains(t, err, `Denied`)
   923  	require.Equal(t, 1, numCompiles)
   924  }
   925  
   926  // Shows that a definition with no param source works just fine, and has
   927  // nil params passed to its evaluator.
   928  //
   929  // Also shows that if binding has specified params in this instance then they
   930  // are silently ignored.
   931  func TestEmptyParamSource(t *testing.T) {
   932  	compiler := &fakeCompiler{}
   933  	matcher := &fakeMatcher{
   934  		DefaultMatch: true,
   935  	}
   936  
   937  	testContext := setupFakeTest(t, compiler, matcher)
   938  
   939  	datalock := sync.Mutex{}
   940  	numCompiles := 0
   941  
   942  	// Push some fake
   943  	noParamSourcePolicy := *denyPolicy
   944  	noParamSourcePolicy.Spec.ParamKind = nil
   945  
   946  	compiler.RegisterDefinition(denyPolicy, func(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) validating.ValidateResult {
   947  		datalock.Lock()
   948  		numCompiles += 1
   949  		datalock.Unlock()
   950  		return validating.ValidateResult{
   951  			Decisions: []validating.PolicyDecision{
   952  				{
   953  					Action:  validating.ActionDeny,
   954  					Message: "Denied",
   955  				},
   956  			},
   957  		}
   958  	})
   959  
   960  	require.NoError(t, testContext.UpdateAndWait(&noParamSourcePolicy, denyBindingWithNoParamRef))
   961  
   962  	err := testContext.Plugin.Dispatch(
   963  		testContext,
   964  		// Object is irrelevant/unchecked for this test. Just test that
   965  		// the evaluator is executed, and returns a denial
   966  		attributeRecord(nil, fakeParams, admission.Create),
   967  		&admission.RuntimeObjectInterfaces{},
   968  	)
   969  
   970  	require.ErrorContains(t, err, `Denied`)
   971  	require.Equal(t, 1, numCompiles)
   972  }
   973  
   974  // Shows what happens when multiple policies share one param type, then
   975  // one policy stops using the param. The expectation is the second policy
   976  // keeps behaving normally
   977  func TestMultiplePoliciesSharedParamType(t *testing.T) {
   978  	compiler := &fakeCompiler{}
   979  	matcher := &fakeMatcher{
   980  		DefaultMatch: true,
   981  	}
   982  
   983  	testContext := setupFakeTest(t, compiler, matcher)
   984  
   985  	// Use ConfigMap native-typed param
   986  	policy1 := *denyPolicy
   987  	policy1.Name = "denypolicy1.example.com"
   988  	policy1.Spec = admissionregistrationv1.ValidatingAdmissionPolicySpec{
   989  		ParamKind: &admissionregistrationv1.ParamKind{
   990  			APIVersion: paramsGVK.GroupVersion().String(),
   991  			Kind:       paramsGVK.Kind,
   992  		},
   993  		FailurePolicy: ptrTo(admissionregistrationv1.Fail),
   994  		Validations: []admissionregistrationv1.Validation{
   995  			{
   996  				Expression: "policy1",
   997  			},
   998  		},
   999  	}
  1000  
  1001  	policy2 := *denyPolicy
  1002  	policy2.Name = "denypolicy2.example.com"
  1003  	policy2.Spec = admissionregistrationv1.ValidatingAdmissionPolicySpec{
  1004  		ParamKind: &admissionregistrationv1.ParamKind{
  1005  			APIVersion: paramsGVK.GroupVersion().String(),
  1006  			Kind:       paramsGVK.Kind,
  1007  		},
  1008  		FailurePolicy: ptrTo(admissionregistrationv1.Fail),
  1009  		Validations: []admissionregistrationv1.Validation{
  1010  			{
  1011  				Expression: "policy2",
  1012  			},
  1013  		},
  1014  	}
  1015  
  1016  	binding1 := *denyBinding
  1017  	binding2 := *denyBinding
  1018  
  1019  	binding1.Name = "denybinding1.example.com"
  1020  	binding1.Spec.PolicyName = policy1.Name
  1021  	binding2.Name = "denybinding2.example.com"
  1022  	binding2.Spec.PolicyName = policy2.Name
  1023  
  1024  	evaluations1 := atomic.Int64{}
  1025  	evaluations2 := atomic.Int64{}
  1026  
  1027  	compiler.RegisterDefinition(&policy1, func(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) validating.ValidateResult {
  1028  		evaluations1.Add(1)
  1029  
  1030  		return validating.ValidateResult{
  1031  			Decisions: []validating.PolicyDecision{
  1032  				{
  1033  					Action: validating.ActionAdmit,
  1034  				},
  1035  			},
  1036  		}
  1037  	})
  1038  
  1039  	compiler.RegisterDefinition(&policy2, func(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) validating.ValidateResult {
  1040  		evaluations2.Add(1)
  1041  		return validating.ValidateResult{
  1042  			Decisions: []validating.PolicyDecision{
  1043  				{
  1044  					Action:  validating.ActionDeny,
  1045  					Message: "Policy2Denied",
  1046  				},
  1047  			},
  1048  		}
  1049  	})
  1050  
  1051  	require.NoError(t, testContext.UpdateAndWait(fakeParams, &policy1, &binding1))
  1052  
  1053  	// Make sure policy 1 is created and bound to the params type first
  1054  	require.NoError(t, testContext.UpdateAndWait(&policy2, &binding2))
  1055  
  1056  	err := testContext.Plugin.Dispatch(
  1057  		testContext,
  1058  		// Object is irrelevant/unchecked for this test. Just test that
  1059  		// the evaluator is executed, and returns admit meaning the params
  1060  		// passed was a configmap
  1061  		attributeRecord(nil, fakeParams, admission.Create),
  1062  		&admission.RuntimeObjectInterfaces{},
  1063  	)
  1064  
  1065  	require.ErrorContains(t, err, `Denied`)
  1066  	require.EqualValues(t, 1, compiler.getNumCompiles(&policy1))
  1067  	require.EqualValues(t, 1, evaluations1.Load())
  1068  	require.EqualValues(t, 1, compiler.getNumCompiles(&policy2))
  1069  	require.EqualValues(t, 1, evaluations2.Load())
  1070  
  1071  	// Remove param type from policy1
  1072  	// Show that policy2 evaluator is still being passed the configmaps
  1073  	policy1.Spec.ParamKind = nil
  1074  	policy1.ResourceVersion = "2"
  1075  
  1076  	binding1.Spec.ParamRef = nil
  1077  	binding1.ResourceVersion = "2"
  1078  
  1079  	require.NoError(t, testContext.UpdateAndWait(&policy1, &binding1))
  1080  
  1081  	err = testContext.Plugin.Dispatch(
  1082  		testContext,
  1083  		// Object is irrelevant/unchecked for this test. Just test that
  1084  		// the evaluator is executed, and returns admit meaning the params
  1085  		// passed was a configmap
  1086  		attributeRecord(nil, fakeParams, admission.Create),
  1087  		&admission.RuntimeObjectInterfaces{},
  1088  	)
  1089  
  1090  	require.ErrorContains(t, err, `Policy2Denied`)
  1091  	require.EqualValues(t, 2, compiler.getNumCompiles(&policy1))
  1092  	require.EqualValues(t, 2, evaluations1.Load())
  1093  	require.EqualValues(t, 1, compiler.getNumCompiles(&policy2))
  1094  	require.EqualValues(t, 2, evaluations2.Load())
  1095  }
  1096  
  1097  // Shows that we can refer to native-typed params just fine
  1098  // (as opposed to CRD params)
  1099  func TestNativeTypeParam(t *testing.T) {
  1100  	compiler := &fakeCompiler{}
  1101  	matcher := &fakeMatcher{
  1102  		DefaultMatch: true,
  1103  	}
  1104  	testContext := setupFakeTest(t, compiler, matcher)
  1105  	evaluations := atomic.Int64{}
  1106  
  1107  	// Use ConfigMap native-typed param
  1108  	nativeTypeParamPolicy := *denyPolicy
  1109  	nativeTypeParamPolicy.Spec.ParamKind = &admissionregistrationv1.ParamKind{
  1110  		APIVersion: "v1",
  1111  		Kind:       "ConfigMap",
  1112  	}
  1113  
  1114  	compiler.RegisterDefinition(&nativeTypeParamPolicy, func(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) validating.ValidateResult {
  1115  		evaluations.Add(1)
  1116  		if _, ok := versionedParams.(*v1.ConfigMap); ok {
  1117  			return validating.ValidateResult{
  1118  				Decisions: []validating.PolicyDecision{
  1119  					{
  1120  						Action:  validating.ActionDeny,
  1121  						Message: "correct type",
  1122  					},
  1123  				},
  1124  			}
  1125  		}
  1126  		return validating.ValidateResult{
  1127  			Decisions: []validating.PolicyDecision{
  1128  				{
  1129  					Action:  validating.ActionDeny,
  1130  					Message: "Incorrect param type",
  1131  				},
  1132  			},
  1133  		}
  1134  	})
  1135  
  1136  	configMapParam := &v1.ConfigMap{
  1137  		TypeMeta: metav1.TypeMeta{
  1138  			APIVersion: "v1",
  1139  			Kind:       "ConfigMap",
  1140  		},
  1141  		ObjectMeta: metav1.ObjectMeta{
  1142  			Name:            "replicas-test.example.com",
  1143  			Namespace:       "default",
  1144  			ResourceVersion: "1",
  1145  		},
  1146  		Data: map[string]string{
  1147  			"coolkey": "coolvalue",
  1148  		},
  1149  	}
  1150  	require.NoError(t, testContext.UpdateAndWait(&nativeTypeParamPolicy, denyBinding, configMapParam))
  1151  
  1152  	err := testContext.Plugin.Dispatch(
  1153  		testContext,
  1154  		// Object is irrelevant/unchecked for this test. Just test that
  1155  		// the evaluator is executed, and returns admit meaning the params
  1156  		// passed was a configmap
  1157  		attributeRecord(nil, fakeParams, admission.Create),
  1158  		&admission.RuntimeObjectInterfaces{},
  1159  	)
  1160  
  1161  	require.ErrorContains(t, err, "correct type")
  1162  	require.EqualValues(t, 1, compiler.getNumCompiles(&nativeTypeParamPolicy))
  1163  	require.EqualValues(t, 1, evaluations.Load())
  1164  }
  1165  
  1166  func TestAuditValidationAction(t *testing.T) {
  1167  	compiler := &fakeCompiler{}
  1168  	matcher := &fakeMatcher{
  1169  		DefaultMatch: true,
  1170  	}
  1171  	testContext := setupFakeTest(t, compiler, matcher)
  1172  
  1173  	// Push some fake
  1174  	noParamSourcePolicy := *denyPolicy
  1175  	noParamSourcePolicy.Spec.ParamKind = nil
  1176  
  1177  	compiler.RegisterDefinition(denyPolicy, func(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) validating.ValidateResult {
  1178  		return validating.ValidateResult{
  1179  			Decisions: []validating.PolicyDecision{
  1180  				{
  1181  					Action:  validating.ActionDeny,
  1182  					Message: "I'm sorry Dave",
  1183  				},
  1184  			},
  1185  		}
  1186  	})
  1187  
  1188  	require.NoError(t, testContext.UpdateAndWait(&noParamSourcePolicy, denyBindingWithAudit))
  1189  
  1190  	attr := attributeRecord(nil, fakeParams, admission.Create)
  1191  	warningRecorder := newWarningRecorder()
  1192  	warnCtx := warning.WithWarningRecorder(testContext, warningRecorder)
  1193  	err := testContext.Plugin.Dispatch(
  1194  		warnCtx,
  1195  		attr,
  1196  		&admission.RuntimeObjectInterfaces{},
  1197  	)
  1198  
  1199  	require.Equal(t, 0, warningRecorder.len())
  1200  
  1201  	annotations := attr.GetAnnotations(auditinternal.LevelMetadata)
  1202  	require.Len(t, annotations, 1)
  1203  	valueJson, ok := annotations["validation.policy.admission.k8s.io/validation_failure"]
  1204  	require.True(t, ok)
  1205  	var value []validating.ValidationFailureValue
  1206  	jsonErr := utiljson.Unmarshal([]byte(valueJson), &value)
  1207  	require.NoError(t, jsonErr)
  1208  	expected := []validating.ValidationFailureValue{{
  1209  		ExpressionIndex:   0,
  1210  		Message:           "I'm sorry Dave",
  1211  		ValidationActions: []admissionregistrationv1.ValidationAction{admissionregistrationv1.Audit},
  1212  		Binding:           "denybinding.example.com",
  1213  		Policy:            noParamSourcePolicy.Name,
  1214  	}}
  1215  	require.Equal(t, expected, value)
  1216  
  1217  	require.NoError(t, err)
  1218  }
  1219  
  1220  func TestWarnValidationAction(t *testing.T) {
  1221  	compiler := &fakeCompiler{}
  1222  	matcher := &fakeMatcher{
  1223  		DefaultMatch: true,
  1224  	}
  1225  	testContext := setupFakeTest(t, compiler, matcher)
  1226  
  1227  	// Push some fake
  1228  	noParamSourcePolicy := *denyPolicy
  1229  	noParamSourcePolicy.Spec.ParamKind = nil
  1230  
  1231  	compiler.RegisterDefinition(denyPolicy, func(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) validating.ValidateResult {
  1232  		return validating.ValidateResult{
  1233  			Decisions: []validating.PolicyDecision{
  1234  				{
  1235  					Action:  validating.ActionDeny,
  1236  					Message: "I'm sorry Dave",
  1237  				},
  1238  			},
  1239  		}
  1240  	})
  1241  
  1242  	require.NoError(t, testContext.UpdateAndWait(&noParamSourcePolicy, denyBindingWithWarn))
  1243  
  1244  	attr := attributeRecord(nil, fakeParams, admission.Create)
  1245  	warningRecorder := newWarningRecorder()
  1246  	warnCtx := warning.WithWarningRecorder(testContext, warningRecorder)
  1247  	err := testContext.Plugin.Dispatch(
  1248  		warnCtx,
  1249  		attr,
  1250  		&admission.RuntimeObjectInterfaces{},
  1251  	)
  1252  
  1253  	require.Equal(t, 1, warningRecorder.len())
  1254  	require.True(t, warningRecorder.hasWarning("Validation failed for ValidatingAdmissionPolicy 'denypolicy.example.com' with binding 'denybinding.example.com': I'm sorry Dave"))
  1255  
  1256  	annotations := attr.GetAnnotations(auditinternal.LevelMetadata)
  1257  	require.Empty(t, annotations)
  1258  
  1259  	require.NoError(t, err)
  1260  }
  1261  
  1262  func TestAllValidationActions(t *testing.T) {
  1263  	compiler := &fakeCompiler{}
  1264  	matcher := &fakeMatcher{
  1265  		DefaultMatch: true,
  1266  	}
  1267  	testContext := setupFakeTest(t, compiler, matcher)
  1268  
  1269  	// Push some fake
  1270  	noParamSourcePolicy := *denyPolicy
  1271  	noParamSourcePolicy.Spec.ParamKind = nil
  1272  
  1273  	compiler.RegisterDefinition(denyPolicy, func(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) validating.ValidateResult {
  1274  		return validating.ValidateResult{
  1275  			Decisions: []validating.PolicyDecision{
  1276  				{
  1277  					Action:  validating.ActionDeny,
  1278  					Message: "I'm sorry Dave",
  1279  				},
  1280  			},
  1281  		}
  1282  	})
  1283  
  1284  	require.NoError(t, testContext.UpdateAndWait(&noParamSourcePolicy, denyBindingWithAll))
  1285  
  1286  	attr := attributeRecord(nil, fakeParams, admission.Create)
  1287  	warningRecorder := newWarningRecorder()
  1288  	warnCtx := warning.WithWarningRecorder(testContext, warningRecorder)
  1289  	err := testContext.Plugin.Dispatch(
  1290  		warnCtx,
  1291  		attr,
  1292  		&admission.RuntimeObjectInterfaces{},
  1293  	)
  1294  
  1295  	require.Equal(t, 1, warningRecorder.len())
  1296  	require.True(t, warningRecorder.hasWarning("Validation failed for ValidatingAdmissionPolicy 'denypolicy.example.com' with binding 'denybinding.example.com': I'm sorry Dave"))
  1297  
  1298  	annotations := attr.GetAnnotations(auditinternal.LevelMetadata)
  1299  	require.Len(t, annotations, 1)
  1300  	valueJson, ok := annotations["validation.policy.admission.k8s.io/validation_failure"]
  1301  	require.True(t, ok)
  1302  	var value []validating.ValidationFailureValue
  1303  	jsonErr := utiljson.Unmarshal([]byte(valueJson), &value)
  1304  	require.NoError(t, jsonErr)
  1305  	expected := []validating.ValidationFailureValue{{
  1306  		ExpressionIndex:   0,
  1307  		Message:           "I'm sorry Dave",
  1308  		ValidationActions: []admissionregistrationv1.ValidationAction{admissionregistrationv1.Deny, admissionregistrationv1.Warn, admissionregistrationv1.Audit},
  1309  		Binding:           "denybinding.example.com",
  1310  		Policy:            noParamSourcePolicy.Name,
  1311  	}}
  1312  	require.Equal(t, expected, value)
  1313  
  1314  	require.ErrorContains(t, err, "I'm sorry Dave")
  1315  }
  1316  
  1317  func TestNamespaceParamRefName(t *testing.T) {
  1318  	compiler := &fakeCompiler{}
  1319  	matcher := &fakeMatcher{
  1320  		DefaultMatch: true,
  1321  	}
  1322  	testContext := setupFakeTest(t, compiler, matcher)
  1323  
  1324  	evaluations := atomic.Int64{}
  1325  
  1326  	// Use ConfigMap native-typed param
  1327  	nativeTypeParamPolicy := *denyPolicy
  1328  	nativeTypeParamPolicy.Spec.ParamKind = &admissionregistrationv1.ParamKind{
  1329  		APIVersion: "v1",
  1330  		Kind:       "ConfigMap",
  1331  	}
  1332  
  1333  	namespaceParamBinding := *denyBinding
  1334  	namespaceParamBinding.Spec.ParamRef = &admissionregistrationv1.ParamRef{
  1335  		Name: "replicas-test.example.com",
  1336  	}
  1337  	lock := sync.Mutex{}
  1338  	observedParamNamespaces := []string{}
  1339  	compiler.RegisterDefinition(&nativeTypeParamPolicy, func(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) validating.ValidateResult {
  1340  		lock.Lock()
  1341  		defer lock.Unlock()
  1342  
  1343  		evaluations.Add(1)
  1344  		if p, ok := versionedParams.(*v1.ConfigMap); ok {
  1345  			observedParamNamespaces = append(observedParamNamespaces, p.Namespace)
  1346  			return validating.ValidateResult{
  1347  				Decisions: []validating.PolicyDecision{
  1348  					{
  1349  						Action:  validating.ActionDeny,
  1350  						Message: "correct type",
  1351  					},
  1352  				},
  1353  			}
  1354  		}
  1355  		return validating.ValidateResult{
  1356  			Decisions: []validating.PolicyDecision{
  1357  				{
  1358  					Action:  validating.ActionDeny,
  1359  					Message: "Incorrect param type",
  1360  				},
  1361  			},
  1362  		}
  1363  	})
  1364  
  1365  	configMapParam := &v1.ConfigMap{
  1366  		TypeMeta: metav1.TypeMeta{
  1367  			APIVersion: "v1",
  1368  			Kind:       "ConfigMap",
  1369  		},
  1370  		ObjectMeta: metav1.ObjectMeta{
  1371  			Name:            "replicas-test.example.com",
  1372  			Namespace:       "default",
  1373  			ResourceVersion: "1",
  1374  		},
  1375  		Data: map[string]string{
  1376  			"coolkey": "default",
  1377  		},
  1378  	}
  1379  	configMapParam2 := &v1.ConfigMap{
  1380  		TypeMeta: metav1.TypeMeta{
  1381  			APIVersion: "v1",
  1382  			Kind:       "ConfigMap",
  1383  		},
  1384  		ObjectMeta: metav1.ObjectMeta{
  1385  			Name:            "replicas-test.example.com",
  1386  			Namespace:       "mynamespace",
  1387  			ResourceVersion: "1",
  1388  		},
  1389  		Data: map[string]string{
  1390  			"coolkey": "mynamespace",
  1391  		},
  1392  	}
  1393  	configMapParam3 := &v1.ConfigMap{
  1394  		TypeMeta: metav1.TypeMeta{
  1395  			APIVersion: "v1",
  1396  			Kind:       "ConfigMap",
  1397  		},
  1398  		ObjectMeta: metav1.ObjectMeta{
  1399  			Name:            "replicas-test.example.com",
  1400  			Namespace:       "othernamespace",
  1401  			ResourceVersion: "1",
  1402  		},
  1403  		Data: map[string]string{
  1404  			"coolkey": "othernamespace",
  1405  		},
  1406  	}
  1407  	require.NoError(t, testContext.UpdateAndWait(&nativeTypeParamPolicy, &namespaceParamBinding, configMapParam, configMapParam2, configMapParam3))
  1408  
  1409  	// Object is irrelevant/unchecked for this test. Just test that
  1410  	// the evaluator is executed with correct namespace, and returns admit
  1411  	// meaning the params passed was a configmap
  1412  	err := testContext.Plugin.Dispatch(
  1413  		testContext,
  1414  		attributeRecord(nil, configMapParam, admission.Create),
  1415  		&admission.RuntimeObjectInterfaces{},
  1416  	)
  1417  
  1418  	func() {
  1419  		lock.Lock()
  1420  		defer lock.Unlock()
  1421  		require.ErrorContains(t, err, "correct type")
  1422  		require.EqualValues(t, 1, compiler.getNumCompiles(&nativeTypeParamPolicy))
  1423  		require.EqualValues(t, 1, evaluations.Load())
  1424  	}()
  1425  
  1426  	err = testContext.Plugin.Dispatch(
  1427  		testContext,
  1428  		attributeRecord(nil, configMapParam2, admission.Create),
  1429  		&admission.RuntimeObjectInterfaces{},
  1430  	)
  1431  
  1432  	func() {
  1433  		lock.Lock()
  1434  		defer lock.Unlock()
  1435  		require.ErrorContains(t, err, "correct type")
  1436  		require.EqualValues(t, 1, compiler.getNumCompiles(&nativeTypeParamPolicy))
  1437  		require.EqualValues(t, 2, evaluations.Load())
  1438  	}()
  1439  
  1440  	err = testContext.Plugin.Dispatch(
  1441  		testContext,
  1442  		attributeRecord(nil, configMapParam3, admission.Create),
  1443  		&admission.RuntimeObjectInterfaces{},
  1444  	)
  1445  
  1446  	func() {
  1447  		lock.Lock()
  1448  		defer lock.Unlock()
  1449  		require.ErrorContains(t, err, "correct type")
  1450  		require.EqualValues(t, 1, compiler.getNumCompiles(&nativeTypeParamPolicy))
  1451  		require.EqualValues(t, 3, evaluations.Load())
  1452  	}()
  1453  
  1454  	err = testContext.Plugin.Dispatch(
  1455  		testContext,
  1456  		attributeRecord(nil, configMapParam, admission.Create),
  1457  		&admission.RuntimeObjectInterfaces{},
  1458  	)
  1459  
  1460  	func() {
  1461  		lock.Lock()
  1462  		defer lock.Unlock()
  1463  		require.ErrorContains(t, err, "correct type")
  1464  		require.EqualValues(t, []string{"default", "mynamespace", "othernamespace", "default"}, observedParamNamespaces)
  1465  		require.EqualValues(t, 1, compiler.getNumCompiles(&nativeTypeParamPolicy))
  1466  		require.EqualValues(t, 4, evaluations.Load())
  1467  	}()
  1468  }
  1469  
  1470  func TestParamRef(t *testing.T) {
  1471  	for _, paramIsClusterScoped := range []bool{false, true} {
  1472  		for _, nameIsSet := range []bool{false, true} {
  1473  			for _, namespaceIsSet := range []bool{false, true} {
  1474  				if paramIsClusterScoped && namespaceIsSet {
  1475  					// Skip invalid configuration
  1476  					continue
  1477  				}
  1478  
  1479  				for _, selectorIsSet := range []bool{false, true} {
  1480  					if selectorIsSet && nameIsSet {
  1481  						// SKip invalid configuration
  1482  						continue
  1483  					}
  1484  
  1485  					for _, denyNotFound := range []bool{false, true} {
  1486  
  1487  						name := "ParamRef"
  1488  
  1489  						if paramIsClusterScoped {
  1490  							name = "ClusterScoped" + name
  1491  						}
  1492  
  1493  						if nameIsSet {
  1494  							name = name + "WithName"
  1495  						} else if selectorIsSet {
  1496  							name = name + "WithLabelSelector"
  1497  						} else {
  1498  							name = name + "WithEverythingSelector"
  1499  						}
  1500  
  1501  						if namespaceIsSet {
  1502  							name = name + "WithNamespace"
  1503  						}
  1504  
  1505  						if denyNotFound {
  1506  							name = name + "DenyNotFound"
  1507  						} else {
  1508  							name = name + "AllowNotFound"
  1509  						}
  1510  
  1511  						t.Run(name, func(t *testing.T) {
  1512  							t.Parallel()
  1513  							// Test creating a policy with a cluster or namesapce-scoped param
  1514  							// and binding with the provided configuration. Test will ensure
  1515  							// that the provided configuration is capable of matching
  1516  							// params as expected, and not matching params when not expected.
  1517  							// Also ensures the NotFound setting works as expected with this particular
  1518  							// configuration of ParamRef when all the previously
  1519  							// matched params are deleted.
  1520  							testParamRefCase(t, paramIsClusterScoped, nameIsSet, namespaceIsSet, selectorIsSet, denyNotFound)
  1521  						})
  1522  					}
  1523  				}
  1524  			}
  1525  		}
  1526  	}
  1527  }
  1528  
  1529  // testParamRefCase constructs a ParamRef and policy with appropriate ParamKind
  1530  // for the given parameters, then constructs a scenario with several matching/non-matching params
  1531  // of varying names, namespaces, labels.
  1532  //
  1533  // Test then selects subset of params that should match provided configuration
  1534  // and ensuers those params are the only ones used.
  1535  //
  1536  // Also ensures NotFound action is enforced correctly by deleting all found
  1537  // params and ensuring the Action is used.
  1538  //
  1539  // This test is not meant to test every possible scenario of matching/not matching:
  1540  // only that each ParamRef CAN be evaluated correctly for both cluster scoped
  1541  // and namespace-scoped request kinds, and that the failure action is correctly
  1542  // applied.
  1543  func testParamRefCase(t *testing.T, paramIsClusterScoped, nameIsSet, namespaceIsSet, selectorIsSet, denyNotFound bool) {
  1544  	// Create a cluster scoped and a namespace scoped CRD
  1545  	policy := *denyPolicy
  1546  	binding := *denyBinding
  1547  	binding.Spec.ParamRef = &admissionregistrationv1.ParamRef{}
  1548  	paramRef := binding.Spec.ParamRef
  1549  
  1550  	shouldErrorOnClusterScopedRequests := !namespaceIsSet && !paramIsClusterScoped
  1551  
  1552  	matchingParamName := "replicas-test.example.com"
  1553  	matchingNamespace := "mynamespace"
  1554  	nonMatchingNamespace := "othernamespace"
  1555  
  1556  	matchingLabels := labels.Set{"doesitmatch": "yes"}
  1557  	nonmatchingLabels := labels.Set{"doesitmatch": "no"}
  1558  	otherNonmatchingLabels := labels.Set{"notaffiliated": "no"}
  1559  
  1560  	if paramIsClusterScoped {
  1561  		policy.Spec.ParamKind = &admissionregistrationv1.ParamKind{
  1562  			APIVersion: clusterScopedParamsGVK.GroupVersion().String(),
  1563  			Kind:       clusterScopedParamsGVK.Kind,
  1564  		}
  1565  	} else {
  1566  		policy.Spec.ParamKind = &admissionregistrationv1.ParamKind{
  1567  			APIVersion: paramsGVK.GroupVersion().String(),
  1568  			Kind:       paramsGVK.Kind,
  1569  		}
  1570  	}
  1571  
  1572  	if nameIsSet {
  1573  		paramRef.Name = matchingParamName
  1574  	} else if selectorIsSet {
  1575  		paramRef.Selector = metav1.SetAsLabelSelector(matchingLabels)
  1576  	} else {
  1577  		paramRef.Selector = &metav1.LabelSelector{}
  1578  	}
  1579  
  1580  	if namespaceIsSet {
  1581  		paramRef.Namespace = matchingNamespace
  1582  	}
  1583  
  1584  	if denyNotFound {
  1585  		paramRef.ParameterNotFoundAction = ptrTo(admissionregistrationv1.DenyAction)
  1586  	} else {
  1587  		paramRef.ParameterNotFoundAction = ptrTo(admissionregistrationv1.AllowAction)
  1588  	}
  1589  
  1590  	compiler := &fakeCompiler{}
  1591  	matcher := &fakeMatcher{
  1592  		DefaultMatch: true,
  1593  	}
  1594  
  1595  	var matchedParams []runtime.Object
  1596  	paramLock := sync.Mutex{}
  1597  	observeParam := func(p runtime.Object) {
  1598  		paramLock.Lock()
  1599  		defer paramLock.Unlock()
  1600  		matchedParams = append(matchedParams, p)
  1601  	}
  1602  	getAndResetObservedParams := func() []runtime.Object {
  1603  		paramLock.Lock()
  1604  		defer paramLock.Unlock()
  1605  		oldParams := matchedParams
  1606  		matchedParams = nil
  1607  		return oldParams
  1608  	}
  1609  
  1610  	compiler.RegisterDefinition(&policy, func(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) validating.ValidateResult {
  1611  		observeParam(versionedParams)
  1612  		return validating.ValidateResult{
  1613  			Decisions: []validating.PolicyDecision{
  1614  				{
  1615  					Action:  validating.ActionDeny,
  1616  					Message: "Denied by policy",
  1617  				},
  1618  			},
  1619  		}
  1620  	})
  1621  
  1622  	testContext := setupFakeTest(t, compiler, matcher)
  1623  
  1624  	// Create library of params to try to fool the controller
  1625  	params := []*unstructured.Unstructured{
  1626  		newParam(matchingParamName, v1.NamespaceDefault, nonmatchingLabels),
  1627  		newParam(matchingParamName, matchingNamespace, nonmatchingLabels),
  1628  		newParam(matchingParamName, nonMatchingNamespace, nonmatchingLabels),
  1629  
  1630  		newParam(matchingParamName+"1", v1.NamespaceDefault, matchingLabels),
  1631  		newParam(matchingParamName+"1", matchingNamespace, matchingLabels),
  1632  		newParam(matchingParamName+"1", nonMatchingNamespace, matchingLabels),
  1633  
  1634  		newParam(matchingParamName+"2", v1.NamespaceDefault, otherNonmatchingLabels),
  1635  		newParam(matchingParamName+"2", matchingNamespace, otherNonmatchingLabels),
  1636  		newParam(matchingParamName+"2", nonMatchingNamespace, otherNonmatchingLabels),
  1637  
  1638  		newParam(matchingParamName+"3", v1.NamespaceDefault, otherNonmatchingLabels),
  1639  		newParam(matchingParamName+"3", matchingNamespace, matchingLabels),
  1640  		newParam(matchingParamName+"3", nonMatchingNamespace, matchingLabels),
  1641  
  1642  		newClusterScopedParam(matchingParamName, matchingLabels),
  1643  		newClusterScopedParam(matchingParamName+"1", nonmatchingLabels),
  1644  		newClusterScopedParam(matchingParamName+"2", otherNonmatchingLabels),
  1645  		newClusterScopedParam(matchingParamName+"3", matchingLabels),
  1646  		newClusterScopedParam(matchingParamName+"4", nonmatchingLabels),
  1647  		newClusterScopedParam(matchingParamName+"5", otherNonmatchingLabels),
  1648  	}
  1649  
  1650  	for _, p := range params {
  1651  		// Don't wait for these sync the informers would not have been
  1652  		// created unless bound to a policy
  1653  		require.NoError(t, testContext.Update(p))
  1654  	}
  1655  
  1656  	require.NoError(t, testContext.UpdateAndWait(&policy, &binding))
  1657  
  1658  	namespacedRequestObject := newParam("some param", nonMatchingNamespace, nil)
  1659  	clusterScopedRequestObject := newClusterScopedParam("other param", nil)
  1660  
  1661  	// Validate a namespaced object, and verify that the params being validated
  1662  	// are the ones we would expect
  1663  	timeoutCtx, timeoutCancel := context.WithTimeout(testContext, 5*time.Second)
  1664  	defer timeoutCancel()
  1665  	var expectedParamsForNamespacedRequest []*unstructured.Unstructured
  1666  	for _, p := range params {
  1667  		if p.GetAPIVersion() != policy.Spec.ParamKind.APIVersion || p.GetKind() != policy.Spec.ParamKind.Kind {
  1668  			continue
  1669  		} else if len(paramRef.Name) > 0 && p.GetName() != paramRef.Name {
  1670  			continue
  1671  		} else if len(paramRef.Namespace) > 0 && p.GetNamespace() != paramRef.Namespace {
  1672  			continue
  1673  		}
  1674  
  1675  		if !paramIsClusterScoped {
  1676  			// If the paramRef has empty namespace and the kind is
  1677  			// namespaced-scoped, then it only matches params of the same
  1678  			// namespace
  1679  			if len(paramRef.Namespace) == 0 && p.GetNamespace() != namespacedRequestObject.GetNamespace() {
  1680  				continue
  1681  			}
  1682  		}
  1683  
  1684  		if paramRef.Selector != nil {
  1685  			ls := p.GetLabels()
  1686  			matched := true
  1687  
  1688  			for k, v := range paramRef.Selector.MatchLabels {
  1689  				if l, hasLabel := ls[k]; !hasLabel {
  1690  					matched = false
  1691  					break
  1692  				} else if l != v {
  1693  					matched = false
  1694  					break
  1695  				}
  1696  			}
  1697  
  1698  			// Empty selector matches everything
  1699  			if len(paramRef.Selector.MatchExpressions) == 0 && len(paramRef.Selector.MatchLabels) == 0 {
  1700  				matched = true
  1701  			}
  1702  
  1703  			if !matched {
  1704  				continue
  1705  			}
  1706  		}
  1707  
  1708  		expectedParamsForNamespacedRequest = append(expectedParamsForNamespacedRequest, p)
  1709  		require.NoError(t, testContext.WaitForReconcile(timeoutCtx, p))
  1710  	}
  1711  	require.NotEmpty(t, expectedParamsForNamespacedRequest, "all test cases should match at least one param")
  1712  	require.ErrorContains(t, testContext.Plugin.Dispatch(context.TODO(), attributeRecord(nil, namespacedRequestObject, admission.Create), &admission.RuntimeObjectInterfaces{}), "Denied by policy")
  1713  	require.ElementsMatch(t, expectedParamsForNamespacedRequest, getAndResetObservedParams(), "should exactly match expected params")
  1714  
  1715  	// Validate a cluster-scoped object, and verify that the params being validated
  1716  	// are the ones we would expect
  1717  	var expectedParamsForClusterScopedRequest []*unstructured.Unstructured
  1718  	timeoutCtx, timeoutCancel = context.WithTimeout(testContext, 5*time.Second)
  1719  	defer timeoutCancel()
  1720  	for _, p := range params {
  1721  		if shouldErrorOnClusterScopedRequests {
  1722  			continue
  1723  		} else if p.GetAPIVersion() != policy.Spec.ParamKind.APIVersion || p.GetKind() != policy.Spec.ParamKind.Kind {
  1724  			continue
  1725  		} else if len(paramRef.Name) > 0 && p.GetName() != paramRef.Name {
  1726  			continue
  1727  		} else if len(paramRef.Namespace) > 0 && p.GetNamespace() != paramRef.Namespace {
  1728  			continue
  1729  		} else if !paramIsClusterScoped && len(paramRef.Namespace) == 0 && p.GetNamespace() != v1.NamespaceDefault {
  1730  			continue
  1731  		}
  1732  
  1733  		if paramRef.Selector != nil {
  1734  			ls := p.GetLabels()
  1735  			matched := true
  1736  			for k, v := range paramRef.Selector.MatchLabels {
  1737  				if l, hasLabel := ls[k]; !hasLabel {
  1738  					matched = false
  1739  					break
  1740  				} else if l != v {
  1741  					matched = false
  1742  					break
  1743  				}
  1744  			}
  1745  
  1746  			// Empty selector matches everything
  1747  			if len(paramRef.Selector.MatchExpressions) == 0 && len(paramRef.Selector.MatchLabels) == 0 {
  1748  				matched = true
  1749  			}
  1750  
  1751  			if !matched {
  1752  				continue
  1753  			}
  1754  		}
  1755  
  1756  		expectedParamsForClusterScopedRequest = append(expectedParamsForClusterScopedRequest, p)
  1757  		require.NoError(t, testContext.WaitForReconcile(timeoutCtx, p))
  1758  
  1759  	}
  1760  
  1761  	err := testContext.Plugin.Dispatch(context.TODO(), attributeRecord(nil, clusterScopedRequestObject, admission.Create), &admission.RuntimeObjectInterfaces{})
  1762  	if shouldErrorOnClusterScopedRequests {
  1763  		// Cannot validate cliuster-scoped resources against a paramRef that sets namespace
  1764  		require.ErrorContains(t, err, "failed to configure binding: cannot use namespaced paramRef in policy binding that matches cluster-scoped resources")
  1765  	} else {
  1766  		require.NotEmpty(t, expectedParamsForClusterScopedRequest, "all test cases should match at least one param")
  1767  		require.ErrorContains(t, err, "Denied by policy")
  1768  	}
  1769  	require.ElementsMatch(t, expectedParamsForClusterScopedRequest, getAndResetObservedParams(), "should exactly match expected params")
  1770  
  1771  	// Remove all params matched by namespaced, and cluster-scoped validation.
  1772  	// Validate again to make sure NotFoundAction is respected
  1773  	var deleted []runtime.Object
  1774  	for _, p := range expectedParamsForNamespacedRequest {
  1775  		deleted = append(deleted, p)
  1776  	}
  1777  
  1778  	for _, p := range expectedParamsForClusterScopedRequest {
  1779  		deleted = append(deleted, p)
  1780  	}
  1781  
  1782  	require.NoError(t, testContext.DeleteAndWait(deleted...))
  1783  
  1784  	// Check that NotFound is working correctly for both namespaeed & non-namespaced
  1785  	// request object
  1786  	err = testContext.Plugin.Dispatch(context.TODO(), attributeRecord(nil, namespacedRequestObject, admission.Create), &admission.RuntimeObjectInterfaces{})
  1787  	if denyNotFound {
  1788  		require.ErrorContains(t, err, "no params found for policy binding with `Deny` parameterNotFoundAction")
  1789  	} else {
  1790  		require.NoError(t, err, "Allow not found expects no error when no params found. Policy should have been skipped")
  1791  	}
  1792  	require.Empty(t, getAndResetObservedParams(), "policy should not have been evaluated")
  1793  
  1794  	err = testContext.Plugin.Dispatch(context.TODO(), attributeRecord(nil, clusterScopedRequestObject, admission.Create), &admission.RuntimeObjectInterfaces{})
  1795  	if shouldErrorOnClusterScopedRequests {
  1796  		require.ErrorContains(t, err, "failed to configure binding: cannot use namespaced paramRef in policy binding that matches cluster-scoped resources")
  1797  
  1798  	} else if denyNotFound {
  1799  		require.ErrorContains(t, err, "no params found for policy binding with `Deny` parameterNotFoundAction")
  1800  	} else {
  1801  		require.NoError(t, err, "Allow not found expects no error when no params found. Policy should have been skipped")
  1802  	}
  1803  	require.Empty(t, getAndResetObservedParams(), "policy should not have been evaluated")
  1804  }
  1805  
  1806  // If the ParamKind is ClusterScoped, and namespace param is used.
  1807  // This is a Configuration Error of the policy
  1808  func TestNamespaceParamRefClusterScopedParamError(t *testing.T) {
  1809  	compiler := &fakeCompiler{}
  1810  	matcher := &fakeMatcher{
  1811  		DefaultMatch: true,
  1812  	}
  1813  	testContext := setupFakeTest(t, compiler, matcher)
  1814  
  1815  	evaluations := atomic.Int64{}
  1816  
  1817  	// Use ValidatingAdmissionPolicy for param type since it is cluster-scoped
  1818  	nativeTypeParamPolicy := *denyPolicy
  1819  	nativeTypeParamPolicy.Spec.ParamKind = &admissionregistrationv1.ParamKind{
  1820  		APIVersion: "admissionregistration.k8s.io/v1beta1",
  1821  		Kind:       "ValidatingAdmissionPolicy",
  1822  	}
  1823  
  1824  	namespaceParamBinding := *denyBinding
  1825  	namespaceParamBinding.Spec.ParamRef = &admissionregistrationv1.ParamRef{
  1826  		Name:      "other-param-to-use-with-no-label.example.com",
  1827  		Namespace: "mynamespace",
  1828  	}
  1829  
  1830  	compiler.RegisterDefinition(&nativeTypeParamPolicy, func(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) validating.ValidateResult {
  1831  		evaluations.Add(1)
  1832  		if _, ok := versionedParams.(*admissionregistrationv1.ValidatingAdmissionPolicy); ok {
  1833  			return validating.ValidateResult{
  1834  				Decisions: []validating.PolicyDecision{
  1835  					{
  1836  						Action:  validating.ActionAdmit,
  1837  						Message: "correct type",
  1838  					},
  1839  				},
  1840  			}
  1841  		}
  1842  		return validating.ValidateResult{
  1843  			Decisions: []validating.PolicyDecision{
  1844  				{
  1845  					Action:  validating.ActionDeny,
  1846  					Message: fmt.Sprintf("Incorrect param type %T", versionedParams),
  1847  				},
  1848  			},
  1849  		}
  1850  	})
  1851  
  1852  	require.NoError(t, testContext.UpdateAndWait(&nativeTypeParamPolicy, &namespaceParamBinding))
  1853  
  1854  	// Object is irrelevant/unchecked for this test. Just test that
  1855  	// the evaluator is executed with correct namespace, and returns admit
  1856  	// meaning the params passed was a configmap
  1857  	err := testContext.Plugin.Dispatch(
  1858  		testContext,
  1859  		attributeRecord(nil, fakeParams, admission.Create),
  1860  		&admission.RuntimeObjectInterfaces{},
  1861  	)
  1862  
  1863  	require.ErrorContains(t, err, "paramRef.namespace must not be provided for a cluster-scoped `paramKind`")
  1864  	require.EqualValues(t, 1, compiler.getNumCompiles(&nativeTypeParamPolicy))
  1865  	require.EqualValues(t, 0, evaluations.Load())
  1866  }
  1867  
  1868  func TestAuditAnnotations(t *testing.T) {
  1869  	compiler := &fakeCompiler{}
  1870  	matcher := &fakeMatcher{
  1871  		DefaultMatch: true,
  1872  	}
  1873  	testContext := setupFakeTest(t, compiler, matcher)
  1874  
  1875  	// Push some fake
  1876  	policy := *denyPolicy
  1877  	compiler.RegisterDefinition(denyPolicy, func(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) validating.ValidateResult {
  1878  		o, err := meta.Accessor(versionedParams)
  1879  		if err != nil {
  1880  			t.Fatal(err)
  1881  		}
  1882  		exampleValue := "normal-value"
  1883  		if o.GetName() == "replicas-test2.example.com" {
  1884  			exampleValue = "special-value"
  1885  		}
  1886  		return validating.ValidateResult{
  1887  			AuditAnnotations: []validating.PolicyAuditAnnotation{
  1888  				{
  1889  					Key:    "example-key",
  1890  					Value:  exampleValue,
  1891  					Action: validating.AuditAnnotationActionPublish,
  1892  				},
  1893  				{
  1894  					Key:    "excluded-key",
  1895  					Value:  "excluded-value",
  1896  					Action: validating.AuditAnnotationActionExclude,
  1897  				},
  1898  				{
  1899  					Key:    "error-key",
  1900  					Action: validating.AuditAnnotationActionError,
  1901  					Error:  "example error",
  1902  				},
  1903  			},
  1904  		}
  1905  	})
  1906  
  1907  	fakeParams2 := fakeParams.DeepCopy()
  1908  	fakeParams2.SetName("replicas-test2.example.com")
  1909  	denyBinding2 := denyBinding.DeepCopy()
  1910  	denyBinding2.SetName("denybinding2.example.com")
  1911  	denyBinding2.Spec.ParamRef.Name = fakeParams2.GetName()
  1912  
  1913  	fakeParams3 := fakeParams.DeepCopy()
  1914  	fakeParams3.SetName("replicas-test3.example.com")
  1915  	denyBinding3 := denyBinding.DeepCopy()
  1916  	denyBinding3.SetName("denybinding3.example.com")
  1917  	denyBinding3.Spec.ParamRef.Name = fakeParams3.GetName()
  1918  
  1919  	require.NoError(t, testContext.UpdateAndWait(fakeParams, fakeParams2, fakeParams3, &policy, denyBinding, denyBinding2, denyBinding3))
  1920  
  1921  	attr := attributeRecord(nil, fakeParams, admission.Create)
  1922  	err := testContext.Plugin.Dispatch(
  1923  		testContext,
  1924  		attr,
  1925  		&admission.RuntimeObjectInterfaces{},
  1926  	)
  1927  
  1928  	annotations := attr.GetAnnotations(auditinternal.LevelMetadata)
  1929  	require.Len(t, annotations, 1)
  1930  	value := annotations[policy.Name+"/example-key"]
  1931  	parts := strings.Split(value, ", ")
  1932  	require.Len(t, parts, 2)
  1933  	require.Contains(t, parts, "normal-value", "special-value")
  1934  
  1935  	require.ErrorContains(t, err, "example error")
  1936  }
  1937  
  1938  // FakeAttributes decorates admission.Attributes. It's used to trace the added annotations.
  1939  type FakeAttributes struct {
  1940  	admission.Attributes
  1941  	annotations map[string]string
  1942  	mutex       sync.Mutex
  1943  }
  1944  
  1945  // AddAnnotation adds an annotation key value pair to FakeAttributes
  1946  func (f *FakeAttributes) AddAnnotation(k, v string) error {
  1947  	return f.AddAnnotationWithLevel(k, v, auditinternal.LevelMetadata)
  1948  }
  1949  
  1950  // AddAnnotationWithLevel adds an annotation key value pair to FakeAttributes
  1951  func (f *FakeAttributes) AddAnnotationWithLevel(k, v string, _ auditinternal.Level) error {
  1952  	f.mutex.Lock()
  1953  	defer f.mutex.Unlock()
  1954  	if err := f.Attributes.AddAnnotation(k, v); err != nil {
  1955  		return err
  1956  	}
  1957  	if f.annotations == nil {
  1958  		f.annotations = make(map[string]string)
  1959  	}
  1960  	f.annotations[k] = v
  1961  	return nil
  1962  }
  1963  
  1964  // GetAnnotations reads annotations from FakeAttributes
  1965  func (f *FakeAttributes) GetAnnotations(_ auditinternal.Level) map[string]string {
  1966  	f.mutex.Lock()
  1967  	defer f.mutex.Unlock()
  1968  	annotations := make(map[string]string, len(f.annotations))
  1969  	for k, v := range f.annotations {
  1970  		annotations[k] = v
  1971  	}
  1972  	return annotations
  1973  }
  1974  
  1975  type warningRecorder struct {
  1976  	sync.Mutex
  1977  	warnings sets.Set[string]
  1978  }
  1979  
  1980  func newWarningRecorder() *warningRecorder {
  1981  	return &warningRecorder{warnings: sets.New[string]()}
  1982  }
  1983  
  1984  func (r *warningRecorder) AddWarning(_, text string) {
  1985  	r.Lock()
  1986  	defer r.Unlock()
  1987  	r.warnings.Insert(text)
  1988  	return
  1989  }
  1990  
  1991  func (r *warningRecorder) hasWarning(text string) bool {
  1992  	r.Lock()
  1993  	defer r.Unlock()
  1994  	return r.warnings.Has(text)
  1995  }
  1996  
  1997  func (r *warningRecorder) len() int {
  1998  	r.Lock()
  1999  	defer r.Unlock()
  2000  	return len(r.warnings)
  2001  }