Merge pull request #119409 from alexzielenski/apiserver/policy/vap-tests
Add test cases for ValidatingAdmissionPolicy
This commit is contained in:
		@@ -1206,7 +1206,7 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
 | 
			
		||||
 | 
			
		||||
	genericfeatures.APIResponseCompression: {Default: true, PreRelease: featuregate.Beta},
 | 
			
		||||
 | 
			
		||||
	genericfeatures.ValidatingAdmissionPolicy: {Default: false, PreRelease: featuregate.Alpha},
 | 
			
		||||
	genericfeatures.ValidatingAdmissionPolicy: {Default: false, PreRelease: featuregate.Beta},
 | 
			
		||||
 | 
			
		||||
	genericfeatures.CustomResourceValidationExpressions: {Default: true, PreRelease: featuregate.Beta},
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -23,6 +23,7 @@ import (
 | 
			
		||||
 | 
			
		||||
	"github.com/google/cel-go/cel"
 | 
			
		||||
 | 
			
		||||
	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
			
		||||
	"k8s.io/apiserver/pkg/admission"
 | 
			
		||||
	celconfig "k8s.io/apiserver/pkg/apis/cel"
 | 
			
		||||
	"k8s.io/apiserver/pkg/cel/environment"
 | 
			
		||||
@@ -141,7 +142,7 @@ func TestCompositedPolicies(t *testing.T) {
 | 
			
		||||
			if costBudget == 0 {
 | 
			
		||||
				costBudget = celconfig.RuntimeCELCostBudget
 | 
			
		||||
			}
 | 
			
		||||
			result, _, err := f.ForInput(context.Background(), versionedAttr, CreateAdmissionRequest(versionedAttr.Attributes), optionalVars, nil, costBudget)
 | 
			
		||||
			result, _, err := f.ForInput(context.Background(), versionedAttr, CreateAdmissionRequest(versionedAttr.Attributes, v1.GroupVersionResource(tc.attributes.GetResource()), v1.GroupVersionKind(versionedAttr.VersionedKind)), optionalVars, nil, costBudget)
 | 
			
		||||
			if !tc.expectErr && err != nil {
 | 
			
		||||
				t.Fatalf("failed evaluation: %v", err)
 | 
			
		||||
			}
 | 
			
		||||
 
 | 
			
		||||
@@ -253,10 +253,13 @@ func (f *filter) ForInput(ctx context.Context, versionedAttr *admission.Versione
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO: to reuse https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/request/admissionreview.go#L154
 | 
			
		||||
func CreateAdmissionRequest(attr admission.Attributes) *admissionv1.AdmissionRequest {
 | 
			
		||||
	// FIXME: how to get resource GVK, GVR and subresource?
 | 
			
		||||
	gvk := attr.GetKind()
 | 
			
		||||
	gvr := attr.GetResource()
 | 
			
		||||
func CreateAdmissionRequest(attr admission.Attributes, equivalentGVR metav1.GroupVersionResource, equivalentKind metav1.GroupVersionKind) *admissionv1.AdmissionRequest {
 | 
			
		||||
	// Attempting to use same logic as webhook for constructing resource
 | 
			
		||||
	// GVK, GVR, subresource
 | 
			
		||||
	// Use the GVK, GVR that the matcher decided was equivalent to that of the request
 | 
			
		||||
	// https://github.com/kubernetes/kubernetes/blob/90c362b3430bcbbf8f245fadbcd521dab39f1d7c/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/generic/webhook.go#L182-L210
 | 
			
		||||
	gvk := equivalentKind
 | 
			
		||||
	gvr := equivalentGVR
 | 
			
		||||
	subresource := attr.GetSubresource()
 | 
			
		||||
 | 
			
		||||
	requestGVK := attr.GetKind()
 | 
			
		||||
 
 | 
			
		||||
@@ -787,7 +787,7 @@ func TestFilter(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
			optionalVars := OptionalVariableBindings{VersionedParams: tc.params, Authorizer: tc.authorizer}
 | 
			
		||||
			ctx := context.TODO()
 | 
			
		||||
			evalResults, _, err := f.ForInput(ctx, versionedAttr, CreateAdmissionRequest(versionedAttr.Attributes), optionalVars, CreateNamespaceObject(tc.namespaceObject), celconfig.RuntimeCELCostBudget)
 | 
			
		||||
			evalResults, _, err := f.ForInput(ctx, versionedAttr, CreateAdmissionRequest(versionedAttr.Attributes, metav1.GroupVersionResource(versionedAttr.GetResource()), metav1.GroupVersionKind(versionedAttr.VersionedKind)), optionalVars, CreateNamespaceObject(tc.namespaceObject), celconfig.RuntimeCELCostBudget)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				t.Fatalf("unexpected error: %v", err)
 | 
			
		||||
			}
 | 
			
		||||
@@ -933,7 +933,7 @@ func TestRuntimeCELCostBudget(t *testing.T) {
 | 
			
		||||
			}
 | 
			
		||||
			optionalVars := OptionalVariableBindings{VersionedParams: tc.params, Authorizer: tc.authorizer}
 | 
			
		||||
			ctx := context.TODO()
 | 
			
		||||
			evalResults, remaining, err := f.ForInput(ctx, versionedAttr, CreateAdmissionRequest(versionedAttr.Attributes), optionalVars, nil, tc.testRuntimeCELCostBudget)
 | 
			
		||||
			evalResults, remaining, err := f.ForInput(ctx, versionedAttr, CreateAdmissionRequest(versionedAttr.Attributes, metav1.GroupVersionResource(versionedAttr.GetResource()), metav1.GroupVersionKind(versionedAttr.VersionedKind)), optionalVars, nil, tc.testRuntimeCELCostBudget)
 | 
			
		||||
			if tc.exceedBudget && err == nil {
 | 
			
		||||
				t.Errorf("Expected RuntimeCELCostBudge to be exceeded but got nil")
 | 
			
		||||
			}
 | 
			
		||||
 
 | 
			
		||||
@@ -318,10 +318,10 @@ var _ Validator = &fakeValidator{}
 | 
			
		||||
 | 
			
		||||
type fakeValidator struct {
 | 
			
		||||
	validationFilter, auditAnnotationFilter, messageFilter *fakeFilter
 | 
			
		||||
	ValidateFunc                                           func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult
 | 
			
		||||
	ValidateFunc                                           func(ctx context.Context, matchResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (f *fakeValidator) RegisterDefinition(definition *v1beta1.ValidatingAdmissionPolicy, validateFunc func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult) {
 | 
			
		||||
func (f *fakeValidator) RegisterDefinition(definition *v1beta1.ValidatingAdmissionPolicy, validateFunc func(ctx context.Context, matchResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult) {
 | 
			
		||||
	//Key must be something that we can decipher from the inputs to Validate so using message which will be on the validationCondition object of evalResult
 | 
			
		||||
	var key string
 | 
			
		||||
	if len(definition.Spec.Validations) > 0 {
 | 
			
		||||
@@ -338,8 +338,8 @@ func (f *fakeValidator) RegisterDefinition(definition *v1beta1.ValidatingAdmissi
 | 
			
		||||
	validatorMap[key] = f
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (f *fakeValidator) Validate(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
 | 
			
		||||
	return f.ValidateFunc(ctx, versionedAttr, versionedParams, namespace, runtimeCELCostBudget, authz)
 | 
			
		||||
func (f *fakeValidator) Validate(ctx context.Context, matchResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
 | 
			
		||||
	return f.ValidateFunc(ctx, matchResource, versionedAttr, versionedParams, namespace, runtimeCELCostBudget, authz)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var _ Matcher = &fakeMatcher{}
 | 
			
		||||
@@ -390,18 +390,18 @@ func (f *fakeMatcher) RegisterBinding(binding *v1beta1.ValidatingAdmissionPolicy
 | 
			
		||||
 | 
			
		||||
// Matches says whether this policy definition matches the provided admission
 | 
			
		||||
// resource request
 | 
			
		||||
func (f *fakeMatcher) DefinitionMatches(a admission.Attributes, o admission.ObjectInterfaces, definition *v1beta1.ValidatingAdmissionPolicy) (bool, schema.GroupVersionKind, error) {
 | 
			
		||||
func (f *fakeMatcher) DefinitionMatches(a admission.Attributes, o admission.ObjectInterfaces, definition *v1beta1.ValidatingAdmissionPolicy) (bool, schema.GroupVersionResource, schema.GroupVersionKind, error) {
 | 
			
		||||
	namespace, name := definition.Namespace, definition.Name
 | 
			
		||||
	key := namespacedName{
 | 
			
		||||
		name:      name,
 | 
			
		||||
		namespace: namespace,
 | 
			
		||||
	}
 | 
			
		||||
	if fun, ok := f.DefinitionMatchFuncs[key]; ok {
 | 
			
		||||
		return fun(definition, a), a.GetKind(), nil
 | 
			
		||||
		return fun(definition, a), a.GetResource(), a.GetKind(), nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Default is match everything
 | 
			
		||||
	return f.DefaultMatch, a.GetKind(), nil
 | 
			
		||||
	return f.DefaultMatch, a.GetResource(), a.GetKind(), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Matches says whether this policy definition matches the provided admission
 | 
			
		||||
@@ -834,7 +834,7 @@ func TestBasicPolicyDefinitionFailure(t *testing.T) {
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
 | 
			
		||||
	validator.RegisterDefinition(denyPolicy, func(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
 | 
			
		||||
		return ValidateResult{
 | 
			
		||||
			Decisions: []PolicyDecision{
 | 
			
		||||
				{
 | 
			
		||||
@@ -904,7 +904,7 @@ func TestDefinitionDoesntMatch(t *testing.T) {
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
 | 
			
		||||
	validator.RegisterDefinition(denyPolicy, func(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
 | 
			
		||||
		return ValidateResult{
 | 
			
		||||
			Decisions: []PolicyDecision{
 | 
			
		||||
				{
 | 
			
		||||
@@ -1019,7 +1019,7 @@ func TestReconfigureBinding(t *testing.T) {
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
 | 
			
		||||
	validator.RegisterDefinition(denyPolicy, func(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
 | 
			
		||||
		return ValidateResult{
 | 
			
		||||
			Decisions: []PolicyDecision{
 | 
			
		||||
				{
 | 
			
		||||
@@ -1130,7 +1130,7 @@ func TestRemoveDefinition(t *testing.T) {
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
 | 
			
		||||
	validator.RegisterDefinition(denyPolicy, func(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
 | 
			
		||||
		return ValidateResult{
 | 
			
		||||
			Decisions: []PolicyDecision{
 | 
			
		||||
				{
 | 
			
		||||
@@ -1199,7 +1199,7 @@ func TestRemoveBinding(t *testing.T) {
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
 | 
			
		||||
	validator.RegisterDefinition(denyPolicy, func(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
 | 
			
		||||
		return ValidateResult{
 | 
			
		||||
			Decisions: []PolicyDecision{
 | 
			
		||||
				{
 | 
			
		||||
@@ -1309,7 +1309,7 @@ func TestInvalidParamSourceInstanceName(t *testing.T) {
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
 | 
			
		||||
	validator.RegisterDefinition(denyPolicy, func(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
 | 
			
		||||
		return ValidateResult{
 | 
			
		||||
			Decisions: []PolicyDecision{
 | 
			
		||||
				{
 | 
			
		||||
@@ -1370,7 +1370,7 @@ func TestEmptyParamRef(t *testing.T) {
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
 | 
			
		||||
	validator.RegisterDefinition(denyPolicy, func(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
 | 
			
		||||
		// Versioned params must be nil to pass the test
 | 
			
		||||
		if versionedParams != nil {
 | 
			
		||||
			return ValidateResult{
 | 
			
		||||
@@ -1447,7 +1447,7 @@ func TestEmptyParamSource(t *testing.T) {
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
 | 
			
		||||
	validator.RegisterDefinition(denyPolicy, func(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
 | 
			
		||||
		return ValidateResult{
 | 
			
		||||
			Decisions: []PolicyDecision{
 | 
			
		||||
				{
 | 
			
		||||
@@ -1549,7 +1549,7 @@ func TestMultiplePoliciesSharedParamType(t *testing.T) {
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	validator1.RegisterDefinition(&policy1, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
 | 
			
		||||
	validator1.RegisterDefinition(&policy1, func(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
 | 
			
		||||
		evaluations1.Add(1)
 | 
			
		||||
		return ValidateResult{
 | 
			
		||||
			Decisions: []PolicyDecision{
 | 
			
		||||
@@ -1568,7 +1568,7 @@ func TestMultiplePoliciesSharedParamType(t *testing.T) {
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	validator2.RegisterDefinition(&policy2, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
 | 
			
		||||
	validator2.RegisterDefinition(&policy2, func(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
 | 
			
		||||
		evaluations2.Add(1)
 | 
			
		||||
		return ValidateResult{
 | 
			
		||||
			Decisions: []PolicyDecision{
 | 
			
		||||
@@ -1678,7 +1678,7 @@ func TestNativeTypeParam(t *testing.T) {
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	validator.RegisterDefinition(&nativeTypeParamPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
 | 
			
		||||
	validator.RegisterDefinition(&nativeTypeParamPolicy, func(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
 | 
			
		||||
		evaluations.Add(1)
 | 
			
		||||
		if _, ok := versionedParams.(*v1.ConfigMap); ok {
 | 
			
		||||
			return ValidateResult{
 | 
			
		||||
@@ -1760,7 +1760,7 @@ func TestAuditValidationAction(t *testing.T) {
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
 | 
			
		||||
	validator.RegisterDefinition(denyPolicy, func(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
 | 
			
		||||
		return ValidateResult{
 | 
			
		||||
			Decisions: []PolicyDecision{
 | 
			
		||||
				{
 | 
			
		||||
@@ -1831,7 +1831,7 @@ func TestWarnValidationAction(t *testing.T) {
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
 | 
			
		||||
	validator.RegisterDefinition(denyPolicy, func(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
 | 
			
		||||
		return ValidateResult{
 | 
			
		||||
			Decisions: []PolicyDecision{
 | 
			
		||||
				{
 | 
			
		||||
@@ -1890,7 +1890,7 @@ func TestAllValidationActions(t *testing.T) {
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
 | 
			
		||||
	validator.RegisterDefinition(denyPolicy, func(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
 | 
			
		||||
		return ValidateResult{
 | 
			
		||||
			Decisions: []PolicyDecision{
 | 
			
		||||
				{
 | 
			
		||||
@@ -1977,7 +1977,7 @@ func TestNamespaceParamRefName(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	lock := sync.Mutex{}
 | 
			
		||||
	observedParamNamespaces := []string{}
 | 
			
		||||
	validator.RegisterDefinition(&nativeTypeParamPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
 | 
			
		||||
	validator.RegisterDefinition(&nativeTypeParamPolicy, func(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
 | 
			
		||||
		lock.Lock()
 | 
			
		||||
		defer lock.Unlock()
 | 
			
		||||
 | 
			
		||||
@@ -2264,7 +2264,7 @@ func testParamRefCase(t *testing.T, paramIsClusterScoped, nameIsSet, namespaceIs
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	validator.RegisterDefinition(&policy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
 | 
			
		||||
	validator.RegisterDefinition(&policy, func(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
 | 
			
		||||
		observeParam(versionedParams)
 | 
			
		||||
		return ValidateResult{
 | 
			
		||||
			Decisions: []PolicyDecision{
 | 
			
		||||
@@ -2505,7 +2505,7 @@ func TestNamespaceParamRefClusterScopedParamError(t *testing.T) {
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	validator.RegisterDefinition(&nativeTypeParamPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
 | 
			
		||||
	validator.RegisterDefinition(&nativeTypeParamPolicy, func(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
 | 
			
		||||
		evaluations.Add(1)
 | 
			
		||||
		if _, ok := versionedParams.(*v1beta1.ValidatingAdmissionPolicy); ok {
 | 
			
		||||
			return ValidateResult{
 | 
			
		||||
@@ -2570,7 +2570,7 @@ func TestAuditAnnotations(t *testing.T) {
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	validator.RegisterDefinition(denyPolicy, func(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
 | 
			
		||||
	validator.RegisterDefinition(denyPolicy, func(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
 | 
			
		||||
		o, err := meta.Accessor(versionedParams)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Fatal(err)
 | 
			
		||||
 
 | 
			
		||||
@@ -244,7 +244,7 @@ func (c *celAdmissionController) Validate(
 | 
			
		||||
		var versionedAttr *admission.VersionedAttributes
 | 
			
		||||
 | 
			
		||||
		definition := definitionInfo.lastReconciledValue
 | 
			
		||||
		matches, matchKind, err := c.policyController.matcher.DefinitionMatches(a, o, definition)
 | 
			
		||||
		matches, matchResource, matchKind, err := c.policyController.matcher.DefinitionMatches(a, o, definition)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			// Configuration error.
 | 
			
		||||
			addConfigError(err, definition, nil)
 | 
			
		||||
@@ -323,7 +323,7 @@ func (c *celAdmissionController) Validate(
 | 
			
		||||
						nested: param,
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				validationResults = append(validationResults, bindingInfo.validator.Validate(ctx, versionedAttr, p, namespace, celconfig.RuntimeCELCostBudget, authz))
 | 
			
		||||
				validationResults = append(validationResults, bindingInfo.validator.Validate(ctx, matchResource, versionedAttr, p, namespace, celconfig.RuntimeCELCostBudget, authz))
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			for _, validationResult := range validationResults {
 | 
			
		||||
 
 | 
			
		||||
@@ -86,7 +86,7 @@ type Matcher interface {
 | 
			
		||||
 | 
			
		||||
	// DefinitionMatches says whether this policy definition matches the provided admission
 | 
			
		||||
	// resource request
 | 
			
		||||
	DefinitionMatches(a admission.Attributes, o admission.ObjectInterfaces, definition *v1beta1.ValidatingAdmissionPolicy) (bool, schema.GroupVersionKind, error)
 | 
			
		||||
	DefinitionMatches(a admission.Attributes, o admission.ObjectInterfaces, definition *v1beta1.ValidatingAdmissionPolicy) (bool, schema.GroupVersionResource, schema.GroupVersionKind, error)
 | 
			
		||||
 | 
			
		||||
	// BindingMatches says whether this policy definition matches the provided admission
 | 
			
		||||
	// resource request
 | 
			
		||||
@@ -109,5 +109,5 @@ type ValidateResult struct {
 | 
			
		||||
type Validator interface {
 | 
			
		||||
	// Validate is used to take cel evaluations and convert into decisions
 | 
			
		||||
	// runtimeCELCostBudget was added for testing purpose only. Callers should always use const RuntimeCELCostBudget from k8s.io/apiserver/pkg/apis/cel/config.go as input.
 | 
			
		||||
	Validate(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *corev1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult
 | 
			
		||||
	Validate(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *corev1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -63,7 +63,7 @@ func (c *matcher) ValidateInitialization() error {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DefinitionMatches returns whether this ValidatingAdmissionPolicy matches the provided admission resource request
 | 
			
		||||
func (c *matcher) DefinitionMatches(a admission.Attributes, o admission.ObjectInterfaces, definition *v1beta1.ValidatingAdmissionPolicy) (bool, schema.GroupVersionKind, error) {
 | 
			
		||||
func (c *matcher) DefinitionMatches(a admission.Attributes, o admission.ObjectInterfaces, definition *v1beta1.ValidatingAdmissionPolicy) (bool, schema.GroupVersionResource, schema.GroupVersionKind, error) {
 | 
			
		||||
	criteria := matchCriteria{constraints: definition.Spec.MatchConstraints}
 | 
			
		||||
	return c.Matcher.Matches(a, o, &criteria)
 | 
			
		||||
}
 | 
			
		||||
@@ -74,7 +74,7 @@ func (c *matcher) BindingMatches(a admission.Attributes, o admission.ObjectInter
 | 
			
		||||
		return true, nil
 | 
			
		||||
	}
 | 
			
		||||
	criteria := matchCriteria{constraints: binding.Spec.MatchResources}
 | 
			
		||||
	isMatch, _, err := c.Matcher.Matches(a, o, &criteria)
 | 
			
		||||
	isMatch, _, _, err := c.Matcher.Matches(a, o, &criteria)
 | 
			
		||||
	return isMatch, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -71,56 +71,60 @@ func (m *Matcher) ValidateInitialization() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *Matcher) Matches(attr admission.Attributes, o admission.ObjectInterfaces, criteria MatchCriteria) (bool, schema.GroupVersionKind, error) {
 | 
			
		||||
func (m *Matcher) Matches(attr admission.Attributes, o admission.ObjectInterfaces, criteria MatchCriteria) (bool, schema.GroupVersionResource, schema.GroupVersionKind, error) {
 | 
			
		||||
	matches, matchNsErr := m.namespaceMatcher.MatchNamespaceSelector(criteria, attr)
 | 
			
		||||
	// Should not return an error here for policy which do not apply to the request, even if err is an unexpected scenario.
 | 
			
		||||
	if !matches && matchNsErr == nil {
 | 
			
		||||
		return false, schema.GroupVersionKind{}, nil
 | 
			
		||||
		return false, schema.GroupVersionResource{}, schema.GroupVersionKind{}, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	matches, matchObjErr := m.objectMatcher.MatchObjectSelector(criteria, attr)
 | 
			
		||||
	// Should not return an error here for policy which do not apply to the request, even if err is an unexpected scenario.
 | 
			
		||||
	if !matches && matchObjErr == nil {
 | 
			
		||||
		return false, schema.GroupVersionKind{}, nil
 | 
			
		||||
		return false, schema.GroupVersionResource{}, schema.GroupVersionKind{}, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	matchResources := criteria.GetMatchResources()
 | 
			
		||||
	matchPolicy := matchResources.MatchPolicy
 | 
			
		||||
	if isExcluded, _, err := matchesResourceRules(matchResources.ExcludeResourceRules, matchPolicy, attr, o); isExcluded || err != nil {
 | 
			
		||||
		return false, schema.GroupVersionKind{}, err
 | 
			
		||||
	if isExcluded, _, _, err := matchesResourceRules(matchResources.ExcludeResourceRules, matchPolicy, attr, o); isExcluded || err != nil {
 | 
			
		||||
		return false, schema.GroupVersionResource{}, schema.GroupVersionKind{}, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var (
 | 
			
		||||
		isMatch   bool
 | 
			
		||||
		matchKind schema.GroupVersionKind
 | 
			
		||||
		matchErr  error
 | 
			
		||||
		isMatch       bool
 | 
			
		||||
		matchResource schema.GroupVersionResource
 | 
			
		||||
		matchKind     schema.GroupVersionKind
 | 
			
		||||
		matchErr      error
 | 
			
		||||
	)
 | 
			
		||||
	if len(matchResources.ResourceRules) == 0 {
 | 
			
		||||
		isMatch = true
 | 
			
		||||
		matchKind = attr.GetKind()
 | 
			
		||||
		matchResource = attr.GetResource()
 | 
			
		||||
	} else {
 | 
			
		||||
		isMatch, matchKind, matchErr = matchesResourceRules(matchResources.ResourceRules, matchPolicy, attr, o)
 | 
			
		||||
		isMatch, matchResource, matchKind, matchErr = matchesResourceRules(matchResources.ResourceRules, matchPolicy, attr, o)
 | 
			
		||||
	}
 | 
			
		||||
	if matchErr != nil {
 | 
			
		||||
		return false, schema.GroupVersionKind{}, matchErr
 | 
			
		||||
		return false, schema.GroupVersionResource{}, schema.GroupVersionKind{}, matchErr
 | 
			
		||||
	}
 | 
			
		||||
	if !isMatch {
 | 
			
		||||
		return false, schema.GroupVersionKind{}, nil
 | 
			
		||||
		return false, schema.GroupVersionResource{}, schema.GroupVersionKind{}, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// now that we know this applies to this request otherwise, if there were selector errors, return them
 | 
			
		||||
	if matchNsErr != nil {
 | 
			
		||||
		return false, schema.GroupVersionKind{}, matchNsErr
 | 
			
		||||
		return false, schema.GroupVersionResource{}, schema.GroupVersionKind{}, matchNsErr
 | 
			
		||||
	}
 | 
			
		||||
	if matchObjErr != nil {
 | 
			
		||||
		return false, schema.GroupVersionKind{}, matchObjErr
 | 
			
		||||
		return false, schema.GroupVersionResource{}, schema.GroupVersionKind{}, matchObjErr
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return true, matchKind, nil
 | 
			
		||||
	return true, matchResource, matchKind, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func matchesResourceRules(namedRules []v1beta1.NamedRuleWithOperations, matchPolicy *v1beta1.MatchPolicyType, attr admission.Attributes, o admission.ObjectInterfaces) (bool, schema.GroupVersionKind, error) {
 | 
			
		||||
func matchesResourceRules(namedRules []v1beta1.NamedRuleWithOperations, matchPolicy *v1beta1.MatchPolicyType, attr admission.Attributes, o admission.ObjectInterfaces) (bool, schema.GroupVersionResource, schema.GroupVersionKind, error) {
 | 
			
		||||
	matchKind := attr.GetKind()
 | 
			
		||||
	matchResource := attr.GetResource()
 | 
			
		||||
 | 
			
		||||
	for _, namedRule := range namedRules {
 | 
			
		||||
		rule := v1.RuleWithOperations(namedRule.RuleWithOperations)
 | 
			
		||||
		ruleMatcher := rules.Matcher{
 | 
			
		||||
@@ -132,14 +136,14 @@ func matchesResourceRules(namedRules []v1beta1.NamedRuleWithOperations, matchPol
 | 
			
		||||
		}
 | 
			
		||||
		// an empty name list always matches
 | 
			
		||||
		if len(namedRule.ResourceNames) == 0 {
 | 
			
		||||
			return true, matchKind, nil
 | 
			
		||||
			return true, matchResource, matchKind, nil
 | 
			
		||||
		}
 | 
			
		||||
		// TODO: GetName() can return an empty string if the user is relying on
 | 
			
		||||
		// the API server to generate the name... figure out what to do for this edge case
 | 
			
		||||
		name := attr.GetName()
 | 
			
		||||
		for _, matchedName := range namedRule.ResourceNames {
 | 
			
		||||
			if name == matchedName {
 | 
			
		||||
				return true, matchKind, nil
 | 
			
		||||
				return true, matchResource, matchKind, nil
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -147,7 +151,7 @@ func matchesResourceRules(namedRules []v1beta1.NamedRuleWithOperations, matchPol
 | 
			
		||||
	// if match policy is undefined or exact, don't perform fuzzy matching
 | 
			
		||||
	// note that defaulting to fuzzy matching is set by the API
 | 
			
		||||
	if matchPolicy == nil || *matchPolicy == v1beta1.Exact {
 | 
			
		||||
		return false, schema.GroupVersionKind{}, nil
 | 
			
		||||
		return false, schema.GroupVersionResource{}, schema.GroupVersionKind{}, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	attrWithOverride := &attrWithResourceOverride{Attributes: attr}
 | 
			
		||||
@@ -169,11 +173,11 @@ func matchesResourceRules(namedRules []v1beta1.NamedRuleWithOperations, matchPol
 | 
			
		||||
			}
 | 
			
		||||
			matchKind = o.GetEquivalentResourceMapper().KindFor(equivalent, attr.GetSubresource())
 | 
			
		||||
			if matchKind.Empty() {
 | 
			
		||||
				return false, schema.GroupVersionKind{}, fmt.Errorf("unable to convert to %v: unknown kind", equivalent)
 | 
			
		||||
				return false, schema.GroupVersionResource{}, schema.GroupVersionKind{}, fmt.Errorf("unable to convert to %v: unknown kind", equivalent)
 | 
			
		||||
			}
 | 
			
		||||
			// an empty name list always matches
 | 
			
		||||
			if len(namedRule.ResourceNames) == 0 {
 | 
			
		||||
				return true, matchKind, nil
 | 
			
		||||
				return true, equivalent, matchKind, nil
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// TODO: GetName() can return an empty string if the user is relying on
 | 
			
		||||
@@ -181,12 +185,12 @@ func matchesResourceRules(namedRules []v1beta1.NamedRuleWithOperations, matchPol
 | 
			
		||||
			name := attr.GetName()
 | 
			
		||||
			for _, matchedName := range namedRule.ResourceNames {
 | 
			
		||||
				if name == matchedName {
 | 
			
		||||
					return true, matchKind, nil
 | 
			
		||||
					return true, equivalent, matchKind, nil
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false, schema.GroupVersionKind{}, nil
 | 
			
		||||
	return false, schema.GroupVersionResource{}, schema.GroupVersionKind{}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type attrWithResourceOverride struct {
 | 
			
		||||
 
 | 
			
		||||
@@ -98,9 +98,10 @@ func TestMatcher(t *testing.T) {
 | 
			
		||||
		criteria *v1beta1.MatchResources
 | 
			
		||||
		attrs    admission.Attributes
 | 
			
		||||
 | 
			
		||||
		expectMatches   bool
 | 
			
		||||
		expectMatchKind schema.GroupVersionKind
 | 
			
		||||
		expectErr       string
 | 
			
		||||
		expectMatches       bool
 | 
			
		||||
		expectMatchKind     schema.GroupVersionKind
 | 
			
		||||
		expectMatchResource schema.GroupVersionResource
 | 
			
		||||
		expectErr           string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name:          "no rules (just write)",
 | 
			
		||||
@@ -204,9 +205,10 @@ func TestMatcher(t *testing.T) {
 | 
			
		||||
						Rule:       v1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments"}, Scope: &allScopes},
 | 
			
		||||
					},
 | 
			
		||||
				}}},
 | 
			
		||||
			attrs:           admission.NewAttributesRecord(nil, nil, gvk("apps", "v1", "Deployment"), "ns", "name", gvr("apps", "v1", "deployments"), "", admission.Create, &metav1.CreateOptions{}, false, nil),
 | 
			
		||||
			expectMatches:   true,
 | 
			
		||||
			expectMatchKind: gvk("extensions", "v1beta1", "Deployment"),
 | 
			
		||||
			attrs:               admission.NewAttributesRecord(nil, nil, gvk("apps", "v1", "Deployment"), "ns", "name", gvr("apps", "v1", "deployments"), "", admission.Create, &metav1.CreateOptions{}, false, nil),
 | 
			
		||||
			expectMatches:       true,
 | 
			
		||||
			expectMatchResource: gvr("extensions", "v1beta1", "deployments"),
 | 
			
		||||
			expectMatchKind:     gvk("extensions", "v1beta1", "Deployment"),
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "specific rules, equivalent match, prefer apps",
 | 
			
		||||
@@ -225,9 +227,10 @@ func TestMatcher(t *testing.T) {
 | 
			
		||||
						Rule:       v1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments"}, Scope: &allScopes},
 | 
			
		||||
					},
 | 
			
		||||
				}}},
 | 
			
		||||
			attrs:           admission.NewAttributesRecord(nil, nil, gvk("apps", "v1", "Deployment"), "ns", "name", gvr("apps", "v1", "deployments"), "", admission.Create, &metav1.CreateOptions{}, false, nil),
 | 
			
		||||
			expectMatches:   true,
 | 
			
		||||
			expectMatchKind: gvk("apps", "v1beta1", "Deployment"),
 | 
			
		||||
			attrs:               admission.NewAttributesRecord(nil, nil, gvk("apps", "v1", "Deployment"), "ns", "name", gvr("apps", "v1", "deployments"), "", admission.Create, &metav1.CreateOptions{}, false, nil),
 | 
			
		||||
			expectMatches:       true,
 | 
			
		||||
			expectMatchResource: gvr("apps", "v1beta1", "deployments"),
 | 
			
		||||
			expectMatchKind:     gvk("apps", "v1beta1", "Deployment"),
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		{
 | 
			
		||||
@@ -311,9 +314,10 @@ func TestMatcher(t *testing.T) {
 | 
			
		||||
						Rule:       v1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes},
 | 
			
		||||
					},
 | 
			
		||||
				}}},
 | 
			
		||||
			attrs:           admission.NewAttributesRecord(nil, nil, gvk("autoscaling", "v1", "Scale"), "ns", "name", gvr("apps", "v1", "deployments"), "scale", admission.Create, &metav1.CreateOptions{}, false, nil),
 | 
			
		||||
			expectMatches:   true,
 | 
			
		||||
			expectMatchKind: gvk("extensions", "v1beta1", "Scale"),
 | 
			
		||||
			attrs:               admission.NewAttributesRecord(nil, nil, gvk("autoscaling", "v1", "Scale"), "ns", "name", gvr("apps", "v1", "deployments"), "scale", admission.Create, &metav1.CreateOptions{}, false, nil),
 | 
			
		||||
			expectMatches:       true,
 | 
			
		||||
			expectMatchResource: gvr("extensions", "v1beta1", "deployments"),
 | 
			
		||||
			expectMatchKind:     gvk("extensions", "v1beta1", "Scale"),
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "specific rules, subresource equivalent match, prefer apps",
 | 
			
		||||
@@ -332,9 +336,10 @@ func TestMatcher(t *testing.T) {
 | 
			
		||||
						Rule:       v1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes},
 | 
			
		||||
					},
 | 
			
		||||
				}}},
 | 
			
		||||
			attrs:           admission.NewAttributesRecord(nil, nil, gvk("autoscaling", "v1", "Scale"), "ns", "name", gvr("apps", "v1", "deployments"), "scale", admission.Create, &metav1.CreateOptions{}, false, nil),
 | 
			
		||||
			expectMatches:   true,
 | 
			
		||||
			expectMatchKind: gvk("apps", "v1beta1", "Scale"),
 | 
			
		||||
			attrs:               admission.NewAttributesRecord(nil, nil, gvk("autoscaling", "v1", "Scale"), "ns", "name", gvr("apps", "v1", "deployments"), "scale", admission.Create, &metav1.CreateOptions{}, false, nil),
 | 
			
		||||
			expectMatches:       true,
 | 
			
		||||
			expectMatchResource: gvr("apps", "v1beta1", "deployments"),
 | 
			
		||||
			expectMatchKind:     gvk("apps", "v1beta1", "Scale"),
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "specific rules, prefer exact match and name match",
 | 
			
		||||
@@ -380,9 +385,10 @@ func TestMatcher(t *testing.T) {
 | 
			
		||||
						Rule:       v1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes},
 | 
			
		||||
					},
 | 
			
		||||
				}}},
 | 
			
		||||
			attrs:           admission.NewAttributesRecord(nil, nil, gvk("autoscaling", "v1", "Scale"), "ns", "name", gvr("extensions", "v1beta1", "deployments"), "scale", admission.Create, &metav1.CreateOptions{}, false, nil),
 | 
			
		||||
			expectMatches:   true,
 | 
			
		||||
			expectMatchKind: gvk("autoscaling", "v1", "Scale"),
 | 
			
		||||
			attrs:               admission.NewAttributesRecord(nil, nil, gvk("autoscaling", "v1", "Scale"), "ns", "name", gvr("extensions", "v1beta1", "deployments"), "scale", admission.Create, &metav1.CreateOptions{}, false, nil),
 | 
			
		||||
			expectMatches:       true,
 | 
			
		||||
			expectMatchResource: gvr("apps", "v1", "deployments"),
 | 
			
		||||
			expectMatchKind:     gvk("autoscaling", "v1", "Scale"),
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "specific rules, subresource equivalent match, prefer extensions and name match miss",
 | 
			
		||||
@@ -536,7 +542,7 @@ func TestMatcher(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	for _, testcase := range testcases {
 | 
			
		||||
		t.Run(testcase.name, func(t *testing.T) {
 | 
			
		||||
			matches, matchKind, err := a.Matches(testcase.attrs, interfaces, &fakeCriteria{matchResources: *testcase.criteria})
 | 
			
		||||
			matches, matchResource, matchKind, err := a.Matches(testcase.attrs, interfaces, &fakeCriteria{matchResources: *testcase.criteria})
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				if len(testcase.expectErr) == 0 {
 | 
			
		||||
					t.Fatal(err)
 | 
			
		||||
@@ -558,6 +564,22 @@ func TestMatcher(t *testing.T) {
 | 
			
		||||
			if matches != testcase.expectMatches {
 | 
			
		||||
				t.Fatalf("expected matches = %v; got %v", testcase.expectMatches, matches)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			expectResource := testcase.expectMatchResource
 | 
			
		||||
			if !expectResource.Empty() && !matches {
 | 
			
		||||
				t.Fatalf("expectResource is non-empty, but did not match")
 | 
			
		||||
			} else if expectResource.Empty() {
 | 
			
		||||
				// Test for exact match by default. Tests that expect an equivalent
 | 
			
		||||
				// resource to match should explicitly state so by supplying
 | 
			
		||||
				// expectMatchResource
 | 
			
		||||
				expectResource = testcase.attrs.GetResource()
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if matches {
 | 
			
		||||
				if matchResource != expectResource {
 | 
			
		||||
					t.Fatalf("expected matchResource %v, got %v", expectResource, matchResource)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -27,6 +27,7 @@ import (
 | 
			
		||||
	corev1 "k8s.io/api/core/v1"
 | 
			
		||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/runtime"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/runtime/schema"
 | 
			
		||||
	"k8s.io/apiserver/pkg/admission"
 | 
			
		||||
	"k8s.io/apiserver/pkg/admission/plugin/cel"
 | 
			
		||||
	"k8s.io/apiserver/pkg/admission/plugin/webhook/matchconditions"
 | 
			
		||||
@@ -72,7 +73,7 @@ func auditAnnotationEvaluationForError(f v1.FailurePolicyType) PolicyAuditAnnota
 | 
			
		||||
// Validate takes a list of Evaluation and a failure policy and converts them into actionable PolicyDecisions
 | 
			
		||||
// runtimeCELCostBudget was added for testing purpose only. Callers should always use const RuntimeCELCostBudget from k8s.io/apiserver/pkg/apis/cel/config.go as input.
 | 
			
		||||
 | 
			
		||||
func (v *validator) Validate(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *corev1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
 | 
			
		||||
func (v *validator) Validate(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *corev1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) ValidateResult {
 | 
			
		||||
	var f v1.FailurePolicyType
 | 
			
		||||
	if v.failPolicy == nil {
 | 
			
		||||
		f = v1.Fail
 | 
			
		||||
@@ -102,7 +103,7 @@ func (v *validator) Validate(ctx context.Context, versionedAttr *admission.Versi
 | 
			
		||||
 | 
			
		||||
	optionalVars := cel.OptionalVariableBindings{VersionedParams: versionedParams, Authorizer: authz}
 | 
			
		||||
	expressionOptionalVars := cel.OptionalVariableBindings{VersionedParams: versionedParams}
 | 
			
		||||
	admissionRequest := cel.CreateAdmissionRequest(versionedAttr.Attributes)
 | 
			
		||||
	admissionRequest := cel.CreateAdmissionRequest(versionedAttr.Attributes, metav1.GroupVersionResource(matchedResource), metav1.GroupVersionKind(versionedAttr.VersionedKind))
 | 
			
		||||
	// Decide which fields are exposed
 | 
			
		||||
	ns := cel.CreateNamespaceObject(namespace)
 | 
			
		||||
	evalResults, remainingBudget, err := v.validationFilter.ForInput(ctx, versionedAttr, admissionRequest, optionalVars, ns, runtimeCELCostBudget)
 | 
			
		||||
@@ -195,7 +196,7 @@ func (v *validator) Validate(ctx context.Context, versionedAttr *admission.Versi
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	options := cel.OptionalVariableBindings{VersionedParams: versionedParams}
 | 
			
		||||
	auditAnnotationEvalResults, _, err := v.auditAnnotationFilter.ForInput(ctx, versionedAttr, cel.CreateAdmissionRequest(versionedAttr.Attributes), options, namespace, runtimeCELCostBudget)
 | 
			
		||||
	auditAnnotationEvalResults, _, err := v.auditAnnotationFilter.ForInput(ctx, versionedAttr, admissionRequest, options, namespace, runtimeCELCostBudget)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return ValidateResult{
 | 
			
		||||
			Decisions: []PolicyDecision{
 | 
			
		||||
 
 | 
			
		||||
@@ -893,7 +893,7 @@ func TestValidate(t *testing.T) {
 | 
			
		||||
			if tc.costBudget != 0 {
 | 
			
		||||
				budget = tc.costBudget
 | 
			
		||||
			}
 | 
			
		||||
			validateResult := v.Validate(ctx, fakeVersionedAttr, nil, nil, budget, nil)
 | 
			
		||||
			validateResult := v.Validate(ctx, fakeVersionedAttr.GetResource(), fakeVersionedAttr, nil, nil, budget, nil)
 | 
			
		||||
 | 
			
		||||
			require.Equal(t, len(validateResult.Decisions), len(tc.policyDecision))
 | 
			
		||||
 | 
			
		||||
@@ -945,7 +945,7 @@ func TestContextCanceled(t *testing.T) {
 | 
			
		||||
	}
 | 
			
		||||
	ctx, cancel := context.WithCancel(context.TODO())
 | 
			
		||||
	cancel()
 | 
			
		||||
	validationResult := v.Validate(ctx, fakeVersionedAttr, nil, nil, celconfig.RuntimeCELCostBudget, nil)
 | 
			
		||||
	validationResult := v.Validate(ctx, fakeVersionedAttr.GetResource(), fakeVersionedAttr, nil, nil, celconfig.RuntimeCELCostBudget, nil)
 | 
			
		||||
	if len(validationResult.Decisions) != 1 || !strings.Contains(validationResult.Decisions[0].Message, "operation interrupted") {
 | 
			
		||||
		t.Errorf("Expected 'operation interrupted' but got %v", validationResult.Decisions)
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -26,6 +26,7 @@ import (
 | 
			
		||||
	celtypes "github.com/google/cel-go/common/types"
 | 
			
		||||
 | 
			
		||||
	v1 "k8s.io/api/admissionregistration/v1"
 | 
			
		||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/runtime"
 | 
			
		||||
	utilerrors "k8s.io/apimachinery/pkg/util/errors"
 | 
			
		||||
	"k8s.io/apiserver/pkg/admission"
 | 
			
		||||
@@ -78,7 +79,7 @@ func NewMatcher(filter celplugin.Filter, failPolicy *v1.FailurePolicyType, match
 | 
			
		||||
 | 
			
		||||
func (m *matcher) Match(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, authz authorizer.Authorizer) MatchResult {
 | 
			
		||||
	t := time.Now()
 | 
			
		||||
	evalResults, _, err := m.filter.ForInput(ctx, versionedAttr, celplugin.CreateAdmissionRequest(versionedAttr.Attributes), celplugin.OptionalVariableBindings{
 | 
			
		||||
	evalResults, _, err := m.filter.ForInput(ctx, versionedAttr, celplugin.CreateAdmissionRequest(versionedAttr.Attributes, metav1.GroupVersionResource(versionedAttr.GetResource()), metav1.GroupVersionKind(versionedAttr.VersionedKind)), celplugin.OptionalVariableBindings{
 | 
			
		||||
		VersionedParams: versionedParams,
 | 
			
		||||
		Authorizer:      authz,
 | 
			
		||||
	}, nil, celconfig.RuntimeCELCostBudgetMatchConditions)
 | 
			
		||||
 
 | 
			
		||||
@@ -248,7 +248,7 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
 | 
			
		||||
 | 
			
		||||
	APIServerTracing: {Default: true, PreRelease: featuregate.Beta},
 | 
			
		||||
 | 
			
		||||
	ValidatingAdmissionPolicy: {Default: false, PreRelease: featuregate.Alpha},
 | 
			
		||||
	ValidatingAdmissionPolicy: {Default: false, PreRelease: featuregate.Beta},
 | 
			
		||||
 | 
			
		||||
	CustomResourceValidationExpressions: {Default: true, PreRelease: featuregate.Beta},
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										714
									
								
								test/integration/apiserver/cel/admission_policy_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										714
									
								
								test/integration/apiserver/cel/admission_policy_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,714 @@
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2023 The Kubernetes Authors.
 | 
			
		||||
 | 
			
		||||
Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
you may not use this file except in compliance with the License.
 | 
			
		||||
You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
    http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
See the License for the specific language governing permissions and
 | 
			
		||||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package cel
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"context"
 | 
			
		||||
	"encoding/csv"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"k8s.io/api/admission/v1beta1"
 | 
			
		||||
	corev1 "k8s.io/api/core/v1"
 | 
			
		||||
	apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
 | 
			
		||||
	genericfeatures "k8s.io/apiserver/pkg/features"
 | 
			
		||||
	utilfeature "k8s.io/apiserver/pkg/util/feature"
 | 
			
		||||
	featuregatetesting "k8s.io/component-base/featuregate/testing"
 | 
			
		||||
 | 
			
		||||
	apiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/apis/admissionregistration"
 | 
			
		||||
	admissionregistrationv1alpha1apis "k8s.io/kubernetes/pkg/apis/admissionregistration/v1alpha1"
 | 
			
		||||
	admissionregistrationv1beta1apis "k8s.io/kubernetes/pkg/apis/admissionregistration/v1beta1"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/features"
 | 
			
		||||
	"k8s.io/kubernetes/test/integration/etcd"
 | 
			
		||||
	"k8s.io/kubernetes/test/integration/framework"
 | 
			
		||||
 | 
			
		||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/runtime/schema"
 | 
			
		||||
	"k8s.io/client-go/dynamic"
 | 
			
		||||
	clientset "k8s.io/client-go/kubernetes"
 | 
			
		||||
 | 
			
		||||
	admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
 | 
			
		||||
	admissionregistrationv1alpha1 "k8s.io/api/admissionregistration/v1alpha1"
 | 
			
		||||
	admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	beginSentinel   = "###___BEGIN_SENTINEL___###"
 | 
			
		||||
	recordSeparator = `###$$$###`
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Policy registration helpers
 | 
			
		||||
var testSpec admissionregistration.ValidatingAdmissionPolicy = admissionregistration.ValidatingAdmissionPolicy{
 | 
			
		||||
	Spec: admissionregistration.ValidatingAdmissionPolicySpec{
 | 
			
		||||
		ParamKind: &admissionregistration.ParamKind{
 | 
			
		||||
			APIVersion: "v1",
 | 
			
		||||
			Kind:       "ConfigMap",
 | 
			
		||||
		},
 | 
			
		||||
		Variables: []admissionregistration.Variable{
 | 
			
		||||
			{
 | 
			
		||||
				Name:       "shouldFail",
 | 
			
		||||
				Expression: `true`,
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				Name:       "resourceGroup",
 | 
			
		||||
				Expression: `has(request.resource.group) ? request.resource.group : ""`,
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				Name:       "resourceVersion",
 | 
			
		||||
				Expression: `has(request.resource.version) ? request.resource.version : ""`,
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				Name:       "resourceResource",
 | 
			
		||||
				Expression: `has(request.resource.resource) ? request.resource.resource : ""`,
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				Name:       "subresource",
 | 
			
		||||
				Expression: `has(request.subResource) ? request.subResource : ""`,
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				Name:       "operation",
 | 
			
		||||
				Expression: `has(request.operation) ? request.operation : ""`,
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				Name:       "name",
 | 
			
		||||
				Expression: `has(request.name) ? request.name : ""`,
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				Name:       "namespaceName",
 | 
			
		||||
				Expression: `has(request.namespace) ? request.namespace : ""`,
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				Name:       "objectExists",
 | 
			
		||||
				Expression: `object != null ? "true" : "false"`,
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				Name:       "objectAPIVersion",
 | 
			
		||||
				Expression: `(object != null && has(object.apiVersion)) ? object.apiVersion : ""`,
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				Name:       "objectKind",
 | 
			
		||||
				Expression: `(object != null && has(object.kind)) ? object.kind : ""`,
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				Name:       "oldObjectExists",
 | 
			
		||||
				Expression: `oldObject != null ? "true" : "false"`,
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				Name:       "oldObjectAPIVersion",
 | 
			
		||||
				Expression: `(oldObject != null && has(oldObject.apiVersion)) ? oldObject.apiVersion : ""`,
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				Name:       "oldObjectKind",
 | 
			
		||||
				Expression: `(oldObject != null && has(oldObject.kind)) ? oldObject.kind : ""`,
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				Name:       "optionsExists",
 | 
			
		||||
				Expression: `(has(request.options) && request.options != null) ? "true" : "false"`,
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				Name:       "optionsKind",
 | 
			
		||||
				Expression: `(has(request.options) && has(request.options.kind)) ? request.options.kind : ""`,
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				Name:       "optionsAPIVersion",
 | 
			
		||||
				Expression: `(has(request.options) && has(request.options.apiVersion)) ? request.options.apiVersion : ""`,
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				Name:       "paramsPhase",
 | 
			
		||||
				Expression: `params.data.phase`,
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				Name:       "paramsVersion",
 | 
			
		||||
				Expression: `params.data.version`,
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				Name:       "paramsConvert",
 | 
			
		||||
				Expression: `params.data.convert`,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		// Would be nice to use CEL to create a single map
 | 
			
		||||
		// and stringify it. Unfortunately those library functions
 | 
			
		||||
		// are not yet available, so we must create a map
 | 
			
		||||
		// like so
 | 
			
		||||
		Validations: []admissionregistration.Validation{
 | 
			
		||||
			{
 | 
			
		||||
				// newlines forbidden so use recordSeparator
 | 
			
		||||
				Expression:        "!variables.shouldFail",
 | 
			
		||||
				MessageExpression: `"` + beginSentinel + `resourceGroup,resourceVersion,resourceResource,subresource,operation,name,namespace,objectExists,objectKind,objectAPIVersion,oldObjectExists,oldObjectKind,oldObjectAPIVersion,optionsExists,optionsKind,optionsAPIVersion,paramsPhase,paramsVersion,paramsConvert` + recordSeparator + `"+variables.resourceGroup + "," + variables.resourceVersion + "," + variables.resourceResource + "," + variables.subresource + "," + variables.operation + "," + variables.name + "," + variables.namespaceName + "," + variables.objectExists + "," + variables.objectKind + "," + variables.objectAPIVersion + "," + variables.oldObjectExists + "," + variables.oldObjectKind + "," + variables.oldObjectAPIVersion + "," + variables.optionsExists + "," + variables.optionsKind + "," + variables.optionsAPIVersion + "," + variables.paramsPhase + "," + variables.paramsVersion + "," + variables.paramsConvert`,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		MatchConditions: []admissionregistration.MatchCondition{
 | 
			
		||||
			{
 | 
			
		||||
				Name:       "testclient-only",
 | 
			
		||||
				Expression: `request.userInfo.username == "` + testClientUsername + `"`,
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				Name:       "ignore-test-config",
 | 
			
		||||
				Expression: `object == null || !has(object.metadata) || !has(object.metadata.annotations) || !has(object.metadata.annotations.skipMatch) || object.metadata.annotations.skipMatch != "yes"`,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func createV1beta1ValidatingPolicyAndBinding(client clientset.Interface, convertedRules []admissionregistrationv1beta1.NamedRuleWithOperations) error {
 | 
			
		||||
	denyAction := admissionregistrationv1beta1.DenyAction
 | 
			
		||||
	exact := admissionregistrationv1beta1.Exact
 | 
			
		||||
	equivalent := admissionregistrationv1beta1.Equivalent
 | 
			
		||||
 | 
			
		||||
	var outSpec admissionregistrationv1beta1.ValidatingAdmissionPolicy
 | 
			
		||||
	if err := admissionregistrationv1beta1apis.Convert_admissionregistration_ValidatingAdmissionPolicy_To_v1beta1_ValidatingAdmissionPolicy(&testSpec, &outSpec, nil); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	exactPolicyTemplate := outSpec.DeepCopy()
 | 
			
		||||
	convertedPolicyTemplate := outSpec.DeepCopy()
 | 
			
		||||
 | 
			
		||||
	exactPolicyTemplate.SetName("test-policy-v1beta1")
 | 
			
		||||
	exactPolicyTemplate.Spec.MatchConstraints = &admissionregistrationv1beta1.MatchResources{
 | 
			
		||||
		ResourceRules: []admissionregistrationv1beta1.NamedRuleWithOperations{
 | 
			
		||||
			{
 | 
			
		||||
				RuleWithOperations: admissionregistrationv1.RuleWithOperations{
 | 
			
		||||
					Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.OperationAll},
 | 
			
		||||
					Rule:       admissionregistrationv1.Rule{APIGroups: []string{"*"}, APIVersions: []string{"*"}, Resources: []string{"*/*"}},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		MatchPolicy: &exact,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	convertedPolicyTemplate.SetName("test-policy-v1beta1-convert")
 | 
			
		||||
	convertedPolicyTemplate.Spec.MatchConstraints = &admissionregistrationv1beta1.MatchResources{
 | 
			
		||||
		ResourceRules: convertedRules,
 | 
			
		||||
		MatchPolicy:   &equivalent,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	exactPolicy, err := client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicies().Create(context.TODO(), exactPolicyTemplate, metav1.CreateOptions{})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	convertPolicy, err := client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicies().Create(context.TODO(), convertedPolicyTemplate, metav1.CreateOptions{})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Create a param that holds the options for this
 | 
			
		||||
	configuration, err := client.CoreV1().ConfigMaps("default").Create(context.TODO(), &corev1.ConfigMap{
 | 
			
		||||
		ObjectMeta: metav1.ObjectMeta{
 | 
			
		||||
			Name:      "test-policy-v1beta1-param",
 | 
			
		||||
			Namespace: "default",
 | 
			
		||||
			Annotations: map[string]string{
 | 
			
		||||
				"skipMatch": "yes",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		Data: map[string]string{
 | 
			
		||||
			"version": "v1beta1",
 | 
			
		||||
			"phase":   validation,
 | 
			
		||||
			"convert": "false",
 | 
			
		||||
		},
 | 
			
		||||
	}, metav1.CreateOptions{})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	configurationConvert, err := client.CoreV1().ConfigMaps("default").Create(context.TODO(), &corev1.ConfigMap{
 | 
			
		||||
		ObjectMeta: metav1.ObjectMeta{
 | 
			
		||||
			Name:      "test-policy-v1beta1-convert-param",
 | 
			
		||||
			Namespace: "default",
 | 
			
		||||
			Annotations: map[string]string{
 | 
			
		||||
				"skipMatch": "yes",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		Data: map[string]string{
 | 
			
		||||
			"version": "v1beta1",
 | 
			
		||||
			"phase":   validation,
 | 
			
		||||
			"convert": "true",
 | 
			
		||||
		},
 | 
			
		||||
	}, metav1.CreateOptions{})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err = client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicyBindings().Create(context.TODO(), &admissionregistrationv1beta1.ValidatingAdmissionPolicyBinding{
 | 
			
		||||
		ObjectMeta: metav1.ObjectMeta{
 | 
			
		||||
			Name: "test-policy-v1beta1-binding",
 | 
			
		||||
		},
 | 
			
		||||
		Spec: admissionregistrationv1beta1.ValidatingAdmissionPolicyBindingSpec{
 | 
			
		||||
			PolicyName:        exactPolicy.GetName(),
 | 
			
		||||
			ValidationActions: []admissionregistrationv1beta1.ValidationAction{admissionregistrationv1beta1.Warn},
 | 
			
		||||
			ParamRef: &admissionregistrationv1beta1.ParamRef{
 | 
			
		||||
				Name:                    configuration.GetName(),
 | 
			
		||||
				Namespace:               configuration.GetNamespace(),
 | 
			
		||||
				ParameterNotFoundAction: &denyAction,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}, metav1.CreateOptions{})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	_, err = client.AdmissionregistrationV1beta1().ValidatingAdmissionPolicyBindings().Create(context.TODO(), &admissionregistrationv1beta1.ValidatingAdmissionPolicyBinding{
 | 
			
		||||
		ObjectMeta: metav1.ObjectMeta{
 | 
			
		||||
			Name: "test-policy-v1beta1-convert-binding",
 | 
			
		||||
		},
 | 
			
		||||
		Spec: admissionregistrationv1beta1.ValidatingAdmissionPolicyBindingSpec{
 | 
			
		||||
			PolicyName:        convertPolicy.GetName(),
 | 
			
		||||
			ValidationActions: []admissionregistrationv1beta1.ValidationAction{admissionregistrationv1beta1.Warn},
 | 
			
		||||
			ParamRef: &admissionregistrationv1beta1.ParamRef{
 | 
			
		||||
				Name:                    configurationConvert.GetName(),
 | 
			
		||||
				Namespace:               configurationConvert.GetNamespace(),
 | 
			
		||||
				ParameterNotFoundAction: &denyAction,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}, metav1.CreateOptions{})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func createV1alpha1ValidatingPolicyAndBinding(client clientset.Interface, convertedRules []admissionregistrationv1alpha1.NamedRuleWithOperations) error {
 | 
			
		||||
	exact := admissionregistrationv1alpha1.Exact
 | 
			
		||||
	equivalent := admissionregistrationv1alpha1.Equivalent
 | 
			
		||||
	denyAction := admissionregistrationv1alpha1.DenyAction
 | 
			
		||||
 | 
			
		||||
	var outSpec admissionregistrationv1alpha1.ValidatingAdmissionPolicy
 | 
			
		||||
	if err := admissionregistrationv1alpha1apis.Convert_admissionregistration_ValidatingAdmissionPolicy_To_v1alpha1_ValidatingAdmissionPolicy(&testSpec, &outSpec, nil); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	exactPolicyTemplate := outSpec.DeepCopy()
 | 
			
		||||
	convertedPolicyTemplate := outSpec.DeepCopy()
 | 
			
		||||
 | 
			
		||||
	exactPolicyTemplate.SetName("test-policy-v1alpha1")
 | 
			
		||||
	exactPolicyTemplate.Spec.MatchConstraints = &admissionregistrationv1alpha1.MatchResources{
 | 
			
		||||
		ResourceRules: []admissionregistrationv1alpha1.NamedRuleWithOperations{
 | 
			
		||||
			{
 | 
			
		||||
				RuleWithOperations: admissionregistrationv1.RuleWithOperations{
 | 
			
		||||
					Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.OperationAll},
 | 
			
		||||
					Rule:       admissionregistrationv1.Rule{APIGroups: []string{"*"}, APIVersions: []string{"*"}, Resources: []string{"*/*"}},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		MatchPolicy: &exact,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	convertedPolicyTemplate.SetName("test-policy-v1alpha1-convert")
 | 
			
		||||
	convertedPolicyTemplate.Spec.MatchConstraints = &admissionregistrationv1alpha1.MatchResources{
 | 
			
		||||
		ResourceRules: convertedRules,
 | 
			
		||||
		MatchPolicy:   &equivalent,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	exactPolicy, err := client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicies().Create(context.TODO(), exactPolicyTemplate, metav1.CreateOptions{})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	convertPolicy, err := client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicies().Create(context.TODO(), convertedPolicyTemplate, metav1.CreateOptions{})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Create a param that holds the options for this
 | 
			
		||||
	configuration, err := client.CoreV1().ConfigMaps("default").Create(context.TODO(), &corev1.ConfigMap{
 | 
			
		||||
		ObjectMeta: metav1.ObjectMeta{
 | 
			
		||||
			Name:      "test-policy-v1alpha1-param",
 | 
			
		||||
			Namespace: "default",
 | 
			
		||||
			Annotations: map[string]string{
 | 
			
		||||
				"skipMatch": "yes",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		Data: map[string]string{
 | 
			
		||||
			"version": "v1alpha1",
 | 
			
		||||
			"phase":   validation,
 | 
			
		||||
			"convert": "false",
 | 
			
		||||
		},
 | 
			
		||||
	}, metav1.CreateOptions{})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	configurationConvert, err := client.CoreV1().ConfigMaps("default").Create(context.TODO(), &corev1.ConfigMap{
 | 
			
		||||
		ObjectMeta: metav1.ObjectMeta{
 | 
			
		||||
			Name:      "test-policy-v1alpha1-convert-param",
 | 
			
		||||
			Namespace: "default",
 | 
			
		||||
			Annotations: map[string]string{
 | 
			
		||||
				"skipMatch": "yes",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		Data: map[string]string{
 | 
			
		||||
			"version": "v1alpha1",
 | 
			
		||||
			"phase":   validation,
 | 
			
		||||
			"convert": "true",
 | 
			
		||||
		},
 | 
			
		||||
	}, metav1.CreateOptions{})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err = client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicyBindings().Create(context.TODO(), &admissionregistrationv1alpha1.ValidatingAdmissionPolicyBinding{
 | 
			
		||||
		ObjectMeta: metav1.ObjectMeta{
 | 
			
		||||
			Name: "test-policy-v1alpha1-binding",
 | 
			
		||||
		},
 | 
			
		||||
		Spec: admissionregistrationv1alpha1.ValidatingAdmissionPolicyBindingSpec{
 | 
			
		||||
			PolicyName:        exactPolicy.GetName(),
 | 
			
		||||
			ValidationActions: []admissionregistrationv1alpha1.ValidationAction{admissionregistrationv1alpha1.Warn},
 | 
			
		||||
			ParamRef: &admissionregistrationv1alpha1.ParamRef{
 | 
			
		||||
				Name:                    configuration.GetName(),
 | 
			
		||||
				Namespace:               configuration.GetNamespace(),
 | 
			
		||||
				ParameterNotFoundAction: &denyAction,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}, metav1.CreateOptions{})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	_, err = client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicyBindings().Create(context.TODO(), &admissionregistrationv1alpha1.ValidatingAdmissionPolicyBinding{
 | 
			
		||||
		ObjectMeta: metav1.ObjectMeta{
 | 
			
		||||
			Name: "test-policy-v1alpha1-convert-binding",
 | 
			
		||||
		},
 | 
			
		||||
		Spec: admissionregistrationv1alpha1.ValidatingAdmissionPolicyBindingSpec{
 | 
			
		||||
			PolicyName:        convertPolicy.GetName(),
 | 
			
		||||
			ValidationActions: []admissionregistrationv1alpha1.ValidationAction{admissionregistrationv1alpha1.Warn},
 | 
			
		||||
			ParamRef: &admissionregistrationv1alpha1.ParamRef{
 | 
			
		||||
				Name:                    configurationConvert.GetName(),
 | 
			
		||||
				Namespace:               configurationConvert.GetNamespace(),
 | 
			
		||||
				ParameterNotFoundAction: &denyAction,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}, metav1.CreateOptions{})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// This test shows that policy intercepts all requests for all resources,
 | 
			
		||||
// subresources, verbs, and input versions of policy/binding.
 | 
			
		||||
//
 | 
			
		||||
// This test tries to mirror very closely the same test for webhook admission
 | 
			
		||||
// test/integration/apiserver/admissionwebhook/admission_test.go testWebhookAdmission
 | 
			
		||||
func TestPolicyAdmission(t *testing.T) {
 | 
			
		||||
	defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ValidatingAdmissionPolicy, true)()
 | 
			
		||||
	defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.APISelfSubjectReview, true)()
 | 
			
		||||
 | 
			
		||||
	holder := &policyExpectationHolder{
 | 
			
		||||
		holder: holder{
 | 
			
		||||
			t:                 t,
 | 
			
		||||
			gvrToConvertedGVR: map[metav1.GroupVersionResource]metav1.GroupVersionResource{},
 | 
			
		||||
			gvrToConvertedGVK: map[metav1.GroupVersionResource]schema.GroupVersionKind{},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	server := apiservertesting.StartTestServerOrDie(t, nil, []string{
 | 
			
		||||
		"--enable-admission-plugins", "ValidatingAdmissionPolicy",
 | 
			
		||||
		// turn off admission plugins that add finalizers
 | 
			
		||||
		"--disable-admission-plugins=ServiceAccount,StorageObjectInUseProtection",
 | 
			
		||||
		// force enable all resources so we can check storage.
 | 
			
		||||
		"--runtime-config=api/all=true",
 | 
			
		||||
	}, framework.SharedEtcd())
 | 
			
		||||
	defer server.TearDownFn()
 | 
			
		||||
 | 
			
		||||
	// Create admission policy & binding that match everything
 | 
			
		||||
	clientConfig := server.ClientConfig
 | 
			
		||||
	clientConfig.Impersonate.UserName = testClientUsername
 | 
			
		||||
	clientConfig.Impersonate.Groups = []string{"system:masters", "system:authenticated"}
 | 
			
		||||
	clientConfig.WarningHandler = holder
 | 
			
		||||
	client, err := clientset.NewForConfig(clientConfig)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// create CRDs
 | 
			
		||||
	etcd.CreateTestCRDs(t, apiextensionsclientset.NewForConfigOrDie(server.ClientConfig), false, etcd.GetCustomResourceDefinitionData()...)
 | 
			
		||||
 | 
			
		||||
	if _, err := client.CoreV1().Namespaces().Create(context.TODO(), &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: testNamespace}}, metav1.CreateOptions{}); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// gather resources to test
 | 
			
		||||
	dynamicClient, err := dynamic.NewForConfig(clientConfig)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, resources, err := client.Discovery().ServerGroupsAndResources()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("Failed to get ServerGroupsAndResources with error: %+v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	gvrsToTest := []schema.GroupVersionResource{}
 | 
			
		||||
	resourcesByGVR := map[schema.GroupVersionResource]metav1.APIResource{}
 | 
			
		||||
 | 
			
		||||
	for _, list := range resources {
 | 
			
		||||
		defaultGroupVersion, err := schema.ParseGroupVersion(list.GroupVersion)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Errorf("Failed to get GroupVersion for: %+v", list)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		for _, resource := range list.APIResources {
 | 
			
		||||
			if resource.Group == "" {
 | 
			
		||||
				resource.Group = defaultGroupVersion.Group
 | 
			
		||||
			}
 | 
			
		||||
			if resource.Version == "" {
 | 
			
		||||
				resource.Version = defaultGroupVersion.Version
 | 
			
		||||
			}
 | 
			
		||||
			gvr := defaultGroupVersion.WithResource(resource.Name)
 | 
			
		||||
			resourcesByGVR[gvr] = resource
 | 
			
		||||
			if shouldTestResource(gvr, resource) {
 | 
			
		||||
				gvrsToTest = append(gvrsToTest, gvr)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sort.SliceStable(gvrsToTest, func(i, j int) bool {
 | 
			
		||||
		if gvrsToTest[i].Group < gvrsToTest[j].Group {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
		if gvrsToTest[i].Group > gvrsToTest[j].Group {
 | 
			
		||||
			return false
 | 
			
		||||
		}
 | 
			
		||||
		if gvrsToTest[i].Version < gvrsToTest[j].Version {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
		if gvrsToTest[i].Version > gvrsToTest[j].Version {
 | 
			
		||||
			return false
 | 
			
		||||
		}
 | 
			
		||||
		if gvrsToTest[i].Resource < gvrsToTest[j].Resource {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
		if gvrsToTest[i].Resource > gvrsToTest[j].Resource {
 | 
			
		||||
			return false
 | 
			
		||||
		}
 | 
			
		||||
		return true
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// map unqualified resource names to the fully qualified resource we will expect to be converted to
 | 
			
		||||
	// Note: this only works because there are no overlapping resource names in-process that are not co-located
 | 
			
		||||
	convertedResources := map[string]schema.GroupVersionResource{}
 | 
			
		||||
	// build the webhook rules enumerating the specific group/version/resources we want
 | 
			
		||||
	convertedV1beta1Rules := []admissionregistrationv1beta1.NamedRuleWithOperations{}
 | 
			
		||||
	convertedV1alpha1Rules := []admissionregistrationv1alpha1.NamedRuleWithOperations{}
 | 
			
		||||
	for _, gvr := range gvrsToTest {
 | 
			
		||||
		metaGVR := metav1.GroupVersionResource{Group: gvr.Group, Version: gvr.Version, Resource: gvr.Resource}
 | 
			
		||||
 | 
			
		||||
		convertedGVR, ok := convertedResources[gvr.Resource]
 | 
			
		||||
		if !ok {
 | 
			
		||||
			// this is the first time we've seen this resource
 | 
			
		||||
			// record the fully qualified resource we expect
 | 
			
		||||
			convertedGVR = gvr
 | 
			
		||||
			convertedResources[gvr.Resource] = gvr
 | 
			
		||||
			// add an admission rule indicating we can receive this version
 | 
			
		||||
			convertedV1beta1Rules = append(convertedV1beta1Rules, admissionregistrationv1beta1.NamedRuleWithOperations{
 | 
			
		||||
				RuleWithOperations: admissionregistrationv1.RuleWithOperations{
 | 
			
		||||
					Operations: []admissionregistrationv1beta1.OperationType{admissionregistrationv1beta1.OperationAll},
 | 
			
		||||
					Rule:       admissionregistrationv1beta1.Rule{APIGroups: []string{gvr.Group}, APIVersions: []string{gvr.Version}, Resources: []string{gvr.Resource}},
 | 
			
		||||
				},
 | 
			
		||||
			})
 | 
			
		||||
			convertedV1alpha1Rules = append(convertedV1alpha1Rules, admissionregistrationv1alpha1.NamedRuleWithOperations{
 | 
			
		||||
				RuleWithOperations: admissionregistrationv1.RuleWithOperations{
 | 
			
		||||
					Operations: []admissionregistrationv1alpha1.OperationType{admissionregistrationv1alpha1.OperationAll},
 | 
			
		||||
					Rule:       admissionregistrationv1alpha1.Rule{APIGroups: []string{gvr.Group}, APIVersions: []string{gvr.Version}, Resources: []string{gvr.Resource}},
 | 
			
		||||
				},
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// record the expected resource and kind
 | 
			
		||||
		holder.gvrToConvertedGVR[metaGVR] = metav1.GroupVersionResource{Group: convertedGVR.Group, Version: convertedGVR.Version, Resource: convertedGVR.Resource}
 | 
			
		||||
		holder.gvrToConvertedGVK[metaGVR] = schema.GroupVersionKind{Group: resourcesByGVR[convertedGVR].Group, Version: resourcesByGVR[convertedGVR].Version, Kind: resourcesByGVR[convertedGVR].Kind}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := createV1alpha1ValidatingPolicyAndBinding(client, convertedV1alpha1Rules); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := createV1beta1ValidatingPolicyAndBinding(client, convertedV1beta1Rules); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Allow the policy & binding to establish
 | 
			
		||||
	time.Sleep(1 * time.Second)
 | 
			
		||||
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	count := 0
 | 
			
		||||
 | 
			
		||||
	// Test admission on all resources, subresources, and verbs
 | 
			
		||||
	for _, gvr := range gvrsToTest {
 | 
			
		||||
		resource := resourcesByGVR[gvr]
 | 
			
		||||
		t.Run(gvr.Group+"."+gvr.Version+"."+strings.ReplaceAll(resource.Name, "/", "."), func(t *testing.T) {
 | 
			
		||||
			for _, verb := range []string{"create", "update", "patch", "connect", "delete", "deletecollection"} {
 | 
			
		||||
				if shouldTestResourceVerb(gvr, resource, verb) {
 | 
			
		||||
					t.Run(verb, func(t *testing.T) {
 | 
			
		||||
						count++
 | 
			
		||||
						holder.reset(t)
 | 
			
		||||
						testFunc := getTestFunc(gvr, verb)
 | 
			
		||||
						testFunc(&testContext{
 | 
			
		||||
							t:               t,
 | 
			
		||||
							admissionHolder: holder,
 | 
			
		||||
							client:          dynamicClient,
 | 
			
		||||
							clientset:       client,
 | 
			
		||||
							verb:            verb,
 | 
			
		||||
							gvr:             gvr,
 | 
			
		||||
							resource:        resource,
 | 
			
		||||
							resources:       resourcesByGVR,
 | 
			
		||||
						})
 | 
			
		||||
						holder.verify(t)
 | 
			
		||||
					})
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if count >= 10 {
 | 
			
		||||
		duration := time.Since(start)
 | 
			
		||||
		perResourceDuration := time.Duration(int(duration) / count)
 | 
			
		||||
		if perResourceDuration >= 150*time.Millisecond {
 | 
			
		||||
			t.Errorf("expected resources to process in < 150ms, average was %v", perResourceDuration)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Policy admission holder for test framework
 | 
			
		||||
 | 
			
		||||
type policyExpectationHolder struct {
 | 
			
		||||
	holder
 | 
			
		||||
	warningLock sync.Mutex
 | 
			
		||||
	warnings    []string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *policyExpectationHolder) reset(t *testing.T) {
 | 
			
		||||
	p.warningLock.Lock()
 | 
			
		||||
	defer p.warningLock.Unlock()
 | 
			
		||||
	p.warnings = nil
 | 
			
		||||
 | 
			
		||||
	p.holder.reset(t)
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
func (p *policyExpectationHolder) expect(gvr schema.GroupVersionResource, gvk, optionsGVK schema.GroupVersionKind, operation v1beta1.Operation, name, namespace string, object, oldObject, options bool) {
 | 
			
		||||
	p.holder.expect(gvr, gvk, optionsGVK, operation, name, namespace, object, oldObject, options)
 | 
			
		||||
 | 
			
		||||
	p.lock.Lock()
 | 
			
		||||
	defer p.lock.Unlock()
 | 
			
		||||
	// Set up the recorded map with nil records for all combinations
 | 
			
		||||
	p.recorded = map[webhookOptions]*admissionRequest{}
 | 
			
		||||
	for _, phase := range []string{validation} {
 | 
			
		||||
		for _, converted := range []bool{true, false} {
 | 
			
		||||
			for _, version := range []string{"v1alpha1", "v1beta1"} {
 | 
			
		||||
				p.recorded[webhookOptions{version: version, phase: phase, converted: converted}] = nil
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *policyExpectationHolder) verify(t *testing.T) {
 | 
			
		||||
	p.warningLock.Lock()
 | 
			
		||||
	defer p.warningLock.Unlock()
 | 
			
		||||
 | 
			
		||||
	// Process all detected warnings and record in the nested handler
 | 
			
		||||
	for _, w := range p.warnings {
 | 
			
		||||
		var currentRequest *admissionRequest
 | 
			
		||||
		var currentParams webhookOptions
 | 
			
		||||
		if idx := strings.Index(w, beginSentinel); idx >= 0 {
 | 
			
		||||
 | 
			
		||||
			csvData := strings.ReplaceAll(w[idx+len(beginSentinel):], recordSeparator, "\n")
 | 
			
		||||
 | 
			
		||||
			b := bytes.Buffer{}
 | 
			
		||||
			b.WriteString(csvData)
 | 
			
		||||
			reader := csv.NewReader(&b)
 | 
			
		||||
			csvRecords, err := reader.ReadAll()
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				t.Fatal(err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			mappedCSV := []map[string]string{}
 | 
			
		||||
			var header []string
 | 
			
		||||
			for line, record := range csvRecords {
 | 
			
		||||
				if line == 0 {
 | 
			
		||||
					header = record
 | 
			
		||||
				} else {
 | 
			
		||||
					line := map[string]string{}
 | 
			
		||||
					for i := 0; i < len(record); i++ {
 | 
			
		||||
						line[header[i]] = record[i]
 | 
			
		||||
					}
 | 
			
		||||
					mappedCSV = append(mappedCSV, line)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if len(mappedCSV) != 1 {
 | 
			
		||||
				t.Fatal("incorrect # CSV elements in parsed warning")
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			data := mappedCSV[0]
 | 
			
		||||
			currentRequest = &admissionRequest{
 | 
			
		||||
				Operation: data["operation"],
 | 
			
		||||
				Name:      data["name"],
 | 
			
		||||
				Namespace: data["namespace"],
 | 
			
		||||
				Resource: metav1.GroupVersionResource{
 | 
			
		||||
					Group:    data["resourceGroup"],
 | 
			
		||||
					Version:  data["resourceVersion"],
 | 
			
		||||
					Resource: data["resourceResource"],
 | 
			
		||||
				},
 | 
			
		||||
				SubResource: data["subresource"],
 | 
			
		||||
			}
 | 
			
		||||
			currentParams = webhookOptions{
 | 
			
		||||
				version:   data["paramsVersion"],
 | 
			
		||||
				phase:     data["paramsPhase"],
 | 
			
		||||
				converted: data["paramsConvert"] == "true",
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if e, ok := data["objectExists"]; ok && e == "true" {
 | 
			
		||||
				currentRequest.Object.Object = &unstructured.Unstructured{}
 | 
			
		||||
				currentRequest.Object.Object.(*unstructured.Unstructured).SetAPIVersion(data["objectAPIVersion"])
 | 
			
		||||
				currentRequest.Object.Object.(*unstructured.Unstructured).SetKind(data["objectKind"])
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if e, ok := data["oldObjectExists"]; ok && e == "true" {
 | 
			
		||||
				currentRequest.OldObject.Object = &unstructured.Unstructured{}
 | 
			
		||||
				currentRequest.OldObject.Object.(*unstructured.Unstructured).SetAPIVersion(data["oldObjectAPIVersion"])
 | 
			
		||||
				currentRequest.OldObject.Object.(*unstructured.Unstructured).SetKind(data["oldObjectKind"])
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if e, ok := data["optionsExists"]; ok && e == "true" {
 | 
			
		||||
				currentRequest.Options.Object = &unstructured.Unstructured{}
 | 
			
		||||
				currentRequest.Options.Object.(*unstructured.Unstructured).SetAPIVersion(data["optionsAPIVersion"])
 | 
			
		||||
				currentRequest.Options.Object.(*unstructured.Unstructured).SetKind(data["optionsKind"])
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			p.holder.record(currentParams.version, currentParams.phase, currentParams.converted, currentRequest)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	p.holder.verify(t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *policyExpectationHolder) HandleWarningHeader(code int, agent string, message string) {
 | 
			
		||||
	if code != 299 || len(message) == 0 {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	p.warningLock.Lock()
 | 
			
		||||
	defer p.warningLock.Unlock()
 | 
			
		||||
	p.warnings = append(p.warnings, message)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1095
									
								
								test/integration/apiserver/cel/admission_test_util.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1095
									
								
								test/integration/apiserver/cel/admission_test_util.go
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Reference in New Issue
	
	Block a user