From cfc9683033030435f7d331dee3b4d3e9ebca919c Mon Sep 17 00:00:00 2001
From: Anushka Mittal <138426011+anushkamittal2001@users.noreply.github.com>
Date: Wed, 31 Jan 2024 21:16:53 +0530
Subject: [PATCH] Changes to dynamically configure webhooks (#8437)

* Changes to dynamically configure webhooks

Signed-off-by: anushkamittal2001 <anushka@nirmata.com>

* Add unit tests

Signed-off-by: anushkamittal2001 <anushka@nirmata.com>

* Add kuttl tests

Signed-off-by: anushkamittal2001 <anushka@nirmata.com>

* Refactoring

Signed-off-by: anushkamittal2001 <anushka@nirmata.com>

* Correct unit test

Signed-off-by: anushkamittal2001 <anushka@nirmata.com>

* Change way of webhooks configured

Signed-off-by: anushkamittal2001 <anushka@nirmata.com>

* Correct tests with new changes

Signed-off-by: anushkamittal2001 <anushka@nirmata.com>

* Add delete operation by default

Signed-off-by: anushkamittal2001 <anushka@nirmata.com>

* Correct tests with new changes

Signed-off-by: anushkamittal2001 <anushka@nirmata.com>

* Correct order for operations

Signed-off-by: anushkamittal2001 <anushka@nirmata.com>

* Add corrections

Signed-off-by: anushkamittal2001 <anushka@nirmata.com>

* Add mutatingwebhookconfiguration test

Signed-off-by: anushkamittal2001 <anushka@nirmata.com>

* Correct unit test

Signed-off-by: anushkamittal2001 <anushka@nirmata.com>

* Added policy.yaml in mutate webhook test

Signed-off-by: anushkamittal2001 <anushka@nirmata.com>

* Add corrections in kuttl test and code

Signed-off-by: anushkamittal2001 <anushka@nirmata.com>

* Change name of test

Signed-off-by: anushkamittal2001 <anushka@nirmata.com>

* Changes to update webhooks manifest

Signed-off-by: anushkamittal2001 <anushka@nirmata.com>

* Add corrections for dynamic-op-mutate kuttl test

Signed-off-by: anushkamittal2001 <anushka@nirmata.com>

* Add minor changes; remove unnecessary file

Signed-off-by: anushkamittal2001 <anushka@nirmata.com>

* Correct adding operations for MutatingWebhookConf

Signed-off-by: anushkamittal2001 <anushka@nirmata.com>

* dynamic op mutate and validate added

Signed-off-by: anushkamittal2001 <anushka@nirmata.com>

* Resolve conflicts

Signed-off-by: anushkamittal2001 <anushka@nirmata.com>

* Filter rules for mutatingwebhookconf correctly

Signed-off-by: anushkamittal2001 <anushka@nirmata.com>

* replace TestStep with Test in chainsaw tests

Signed-off-by: anushkamittal2001 <anushka@nirmata.com>

* converted to new chainsaw-test format

Signed-off-by: anushkamittal2001 <anushka@nirmata.com>

* minor corrections

Signed-off-by: anushkamittal2001 <anushka@nirmata.com>

* remove isMutationEmpty()

Signed-off-by: anushkamittal2001 <anushka@nirmata.com>

* initial changes for dynamic opn enhancements

Signed-off-by: anushkamittal2001 <anushka@nirmata.com>

* rename variables

Signed-off-by: anushkamittal2001 <anushka@nirmata.com>

* resolve lint errors

Signed-off-by: anushkamittal2001 <anushka@nirmata.com>

* refactor code

Signed-off-by: anushkamittal2001 <anushka@nirmata.com>

* add changes for exclude operations

Signed-off-by: anushkamittal2001 <anushka@nirmata.com>

* add conformance tests

Signed-off-by: anushkamittal2001 <anushka@nirmata.com>

* add unit tests

Signed-off-by: anushkamittal2001 <anushka@nirmata.com>

* corrections in conformance tests

Signed-off-by: anushkamittal2001 <anushka@nirmata.com>

* modification in unit tests

Signed-off-by: anushkamittal2001 <anushka@nirmata.com>

* correction in conformance tests

Signed-off-by: anushkamittal2001 <anushka@nirmata.com>

* Update .vscode/launch.json

Signed-off-by: shuting <shuting@nirmata.com>

* update variable usage

Signed-off-by: anushkamittal2001 <anushka@nirmata.com>

* remove testresults

Signed-off-by: anushkamittal2001 <anushka@nirmata.com>

---------

Signed-off-by: anushkamittal2001 <anushka@nirmata.com>
Signed-off-by: shuting <shuting@nirmata.com>
Co-authored-by: Vishal Choudhary <sendtovishalchoudhary@gmail.com>
Co-authored-by: shuting <shuting@nirmata.com>
Co-authored-by: Jim Bugwadia <jim@nirmata.com>
Co-authored-by: Vishal Choudhary <vishal.choudhary@nirmata.com>
Co-authored-by: shuting <shutting06@gmail.com>
---
 pkg/controllers/webhook/controller.go         |  50 ++++-
 pkg/controllers/webhook/controller_test.go    | 129 +++++++++++
 pkg/controllers/webhook/utils.go              | 200 ++++++++++++++++-
 pkg/controllers/webhook/utils_test.go         | 202 ++++++++++++++++++
 .../webhooks/dyn-op-mutate-multiple/README.md |   8 +
 .../dyn-op-mutate-multiple/chainsaw-test.yaml |  18 ++
 .../dyn-op-mutate-multiple/policy-assert.yaml |   9 +
 .../dyn-op-mutate-multiple/policy.yaml        |  53 +++++
 .../dyn-op-mutate-multiple/webhooks.yaml      |  27 +++
 .../chainsaw/webhooks/dyn-op-mutate/README.md |   9 +
 .../webhooks/dyn-op-mutate/chainsaw-test.yaml |  18 ++
 .../webhooks/dyn-op-mutate/policy-assert.yaml |   9 +
 .../webhooks/dyn-op-mutate/policy.yaml        |  26 +++
 .../webhooks/dyn-op-mutate/webhooks.yaml      |  18 ++
 .../dyn-op-validate-and-mutate/README.md      |   9 +
 .../chainsaw-test.yaml                        |  28 +++
 .../dyn-op-validate-and-mutate/policy-01.yaml |  24 +++
 .../dyn-op-validate-and-mutate/policy-03.yaml |  26 +++
 .../policy-assert1.yaml                       |   9 +
 .../policy-assert2.yaml                       |   9 +
 .../dyn-op-validate-and-mutate/policy.yaml    |  24 +++
 .../webhooks-02.yaml                          |  17 ++
 .../webhooks-04.yaml                          |  18 ++
 .../dyn-op-validate-and-mutate/webhooks.yaml  |  17 ++
 .../dyn-op-validate-multiple/README.md        |   9 +
 .../chainsaw-test.yaml                        |  18 ++
 .../policy-assert.yaml                        |   9 +
 .../dyn-op-validate-multiple/policy.yaml      |  53 +++++
 .../dyn-op-validate-multiple/webhooks.yaml    |  28 +++
 .../webhooks/dyn-op-validate/README.md        |   9 +
 .../dyn-op-validate/chainsaw-test.yaml        |  18 ++
 .../dyn-op-validate/policy-assert.yaml        |   9 +
 .../webhooks/dyn-op-validate/policy.yaml      |  24 +++
 .../webhooks/dyn-op-validate/webhooks.yaml    |  17 ++
 34 files changed, 1143 insertions(+), 8 deletions(-)
 create mode 100644 pkg/controllers/webhook/controller_test.go
 create mode 100644 test/conformance/chainsaw/webhooks/dyn-op-mutate-multiple/README.md
 create mode 100644 test/conformance/chainsaw/webhooks/dyn-op-mutate-multiple/chainsaw-test.yaml
 create mode 100644 test/conformance/chainsaw/webhooks/dyn-op-mutate-multiple/policy-assert.yaml
 create mode 100644 test/conformance/chainsaw/webhooks/dyn-op-mutate-multiple/policy.yaml
 create mode 100644 test/conformance/chainsaw/webhooks/dyn-op-mutate-multiple/webhooks.yaml
 create mode 100644 test/conformance/chainsaw/webhooks/dyn-op-mutate/README.md
 create mode 100644 test/conformance/chainsaw/webhooks/dyn-op-mutate/chainsaw-test.yaml
 create mode 100644 test/conformance/chainsaw/webhooks/dyn-op-mutate/policy-assert.yaml
 create mode 100644 test/conformance/chainsaw/webhooks/dyn-op-mutate/policy.yaml
 create mode 100644 test/conformance/chainsaw/webhooks/dyn-op-mutate/webhooks.yaml
 create mode 100644 test/conformance/chainsaw/webhooks/dyn-op-validate-and-mutate/README.md
 create mode 100644 test/conformance/chainsaw/webhooks/dyn-op-validate-and-mutate/chainsaw-test.yaml
 create mode 100644 test/conformance/chainsaw/webhooks/dyn-op-validate-and-mutate/policy-01.yaml
 create mode 100644 test/conformance/chainsaw/webhooks/dyn-op-validate-and-mutate/policy-03.yaml
 create mode 100644 test/conformance/chainsaw/webhooks/dyn-op-validate-and-mutate/policy-assert1.yaml
 create mode 100644 test/conformance/chainsaw/webhooks/dyn-op-validate-and-mutate/policy-assert2.yaml
 create mode 100644 test/conformance/chainsaw/webhooks/dyn-op-validate-and-mutate/policy.yaml
 create mode 100644 test/conformance/chainsaw/webhooks/dyn-op-validate-and-mutate/webhooks-02.yaml
 create mode 100644 test/conformance/chainsaw/webhooks/dyn-op-validate-and-mutate/webhooks-04.yaml
 create mode 100644 test/conformance/chainsaw/webhooks/dyn-op-validate-and-mutate/webhooks.yaml
 create mode 100644 test/conformance/chainsaw/webhooks/dyn-op-validate-multiple/README.md
 create mode 100644 test/conformance/chainsaw/webhooks/dyn-op-validate-multiple/chainsaw-test.yaml
 create mode 100644 test/conformance/chainsaw/webhooks/dyn-op-validate-multiple/policy-assert.yaml
 create mode 100644 test/conformance/chainsaw/webhooks/dyn-op-validate-multiple/policy.yaml
 create mode 100644 test/conformance/chainsaw/webhooks/dyn-op-validate-multiple/webhooks.yaml
 create mode 100644 test/conformance/chainsaw/webhooks/dyn-op-validate/README.md
 create mode 100644 test/conformance/chainsaw/webhooks/dyn-op-validate/chainsaw-test.yaml
 create mode 100644 test/conformance/chainsaw/webhooks/dyn-op-validate/policy-assert.yaml
 create mode 100644 test/conformance/chainsaw/webhooks/dyn-op-validate/policy.yaml
 create mode 100644 test/conformance/chainsaw/webhooks/dyn-op-validate/webhooks.yaml

diff --git a/pkg/controllers/webhook/controller.go b/pkg/controllers/webhook/controller.go
index 95e945371b..1da11e40d3 100644
--- a/pkg/controllers/webhook/controller.go
+++ b/pkg/controllers/webhook/controller.go
@@ -51,6 +51,10 @@ const (
 	IdleDeadline              = tickerInterval * 10
 	maxRetries                = 10
 	tickerInterval            = 10 * time.Second
+	webhookCreate             = "CREATE"
+	webhookUpdate             = "UPDATE"
+	webhookDelete             = "DELETE"
+	webhookConnect            = "CONNECT"
 )
 
 var (
@@ -633,6 +637,7 @@ func (c *controller) buildResourceMutatingWebhookConfiguration(ctx context.Conte
 		ObjectMeta: objectMeta(config.MutatingWebhookConfigurationName, cfg.GetWebhookAnnotations(), cfg.GetWebhookLabels(), c.buildOwner()...),
 		Webhooks:   []admissionregistrationv1.MutatingWebhook{},
 	}
+	var mapResourceToOpnType map[string][]admissionregistrationv1.OperationType
 	if c.watchdogCheck() {
 		webhookCfg := config.WebhookConfig{}
 		webhookCfgs := cfg.GetWebhooks()
@@ -670,6 +675,8 @@ func (c *controller) buildResourceMutatingWebhookConfiguration(ctx context.Conte
 					} else {
 						c.mergeWebhook(failWebhook, p, false)
 					}
+					rules := p.GetSpec().Rules
+					mapResourceToOpnType = addOpnForMutatingWebhookConf(rules, mapResourceToOpnType)
 				}
 			}
 		}
@@ -677,14 +684,14 @@ func (c *controller) buildResourceMutatingWebhookConfiguration(ctx context.Conte
 		webhooks := []*webhook{ignoreWebhook, failWebhook}
 		webhooks = append(webhooks, fineGrainedIgnoreList...)
 		webhooks = append(webhooks, fineGrainedFailList...)
-		result.Webhooks = c.buildResourceMutatingWebhookRules(caBundle, webhookCfg, &noneOnDryRun, webhooks)
+		result.Webhooks = c.buildResourceMutatingWebhookRules(caBundle, webhookCfg, &noneOnDryRun, webhooks, mapResourceToOpnType)
 	} else {
 		c.recordPolicyState(config.MutatingWebhookConfigurationName)
 	}
 	return &result, nil
 }
 
-func (c *controller) buildResourceMutatingWebhookRules(caBundle []byte, webhookCfg config.WebhookConfig, sideEffects *admissionregistrationv1.SideEffectClass, webhooks []*webhook) []admissionregistrationv1.MutatingWebhook {
+func (c *controller) buildResourceMutatingWebhookRules(caBundle []byte, webhookCfg config.WebhookConfig, sideEffects *admissionregistrationv1.SideEffectClass, webhooks []*webhook, mapResourceToOpnType map[string][]admissionregistrationv1.OperationType) []admissionregistrationv1.MutatingWebhook {
 	var mutatingWebhooks []admissionregistrationv1.MutatingWebhook
 	for _, webhook := range webhooks {
 		if webhook.isEmpty() {
@@ -698,7 +705,7 @@ func (c *controller) buildResourceMutatingWebhookRules(caBundle []byte, webhookC
 			admissionregistrationv1.MutatingWebhook{
 				Name:                    name,
 				ClientConfig:            c.clientConfig(caBundle, path),
-				Rules:                   webhook.buildRulesWithOperations(admissionregistrationv1.Create, admissionregistrationv1.Update),
+				Rules:                   webhook.buildRulesWithOperations(mapResourceToOpnType, []admissionregistrationv1.OperationType{"CREATE", "UPDATE"}),
 				FailurePolicy:           &failurePolicy,
 				SideEffects:             sideEffects,
 				AdmissionReviewVersions: []string{"v1"},
@@ -765,11 +772,40 @@ func (c *controller) buildDefaultResourceValidatingWebhookConfiguration(_ contex
 		nil
 }
 
+func addOpnForMutatingWebhookConf(rules []kyvernov1.Rule, mapResourceToOpnType map[string][]admissionregistrationv1.OperationType) map[string][]admissionregistrationv1.OperationType {
+	var mapResourceToOpn map[string]map[string]bool
+	for _, r := range rules {
+		var resources []string
+		operationStatusMap := getOperationStatusMap()
+		operationStatusMap = computeOperationsForMutatingWebhookConf(r, operationStatusMap)
+		resources = computeResourcesOfRule(r)
+		for _, r := range resources {
+			mapResourceToOpn, mapResourceToOpnType = appendResource(r, mapResourceToOpn, operationStatusMap, mapResourceToOpnType)
+		}
+	}
+	return mapResourceToOpnType
+}
+
+func addOpnForValidatingWebhookConf(rules []kyvernov1.Rule, mapResourceToOpnType map[string][]admissionregistrationv1.OperationType) map[string][]admissionregistrationv1.OperationType {
+	var mapResourceToOpn map[string]map[string]bool
+	for _, r := range rules {
+		var resources []string
+		operationStatusMap := getOperationStatusMap()
+		operationStatusMap = computeOperationsForValidatingWebhookConf(r, operationStatusMap)
+		resources = computeResourcesOfRule(r)
+		for _, r := range resources {
+			mapResourceToOpn, mapResourceToOpnType = appendResource(r, mapResourceToOpn, operationStatusMap, mapResourceToOpnType)
+		}
+	}
+	return mapResourceToOpnType
+}
+
 func (c *controller) buildResourceValidatingWebhookConfiguration(ctx context.Context, cfg config.Configuration, caBundle []byte) (*admissionregistrationv1.ValidatingWebhookConfiguration, error) {
 	result := admissionregistrationv1.ValidatingWebhookConfiguration{
 		ObjectMeta: objectMeta(config.ValidatingWebhookConfigurationName, cfg.GetWebhookAnnotations(), cfg.GetWebhookLabels(), c.buildOwner()...),
 		Webhooks:   []admissionregistrationv1.ValidatingWebhook{},
 	}
+	var mapResourceToOpnType map[string][]admissionregistrationv1.OperationType
 	if c.watchdogCheck() {
 		webhookCfg := config.WebhookConfig{}
 		webhookCfgs := cfg.GetWebhooks()
@@ -810,6 +846,8 @@ func (c *controller) buildResourceValidatingWebhookConfiguration(ctx context.Con
 					}
 				}
 			}
+			rules := p.GetSpec().Rules
+			mapResourceToOpnType = addOpnForValidatingWebhookConf(rules, mapResourceToOpnType)
 		}
 
 		sideEffects := &none
@@ -820,14 +858,14 @@ func (c *controller) buildResourceValidatingWebhookConfiguration(ctx context.Con
 		webhooks := []*webhook{ignoreWebhook, failWebhook}
 		webhooks = append(webhooks, fineGrainedIgnoreList...)
 		webhooks = append(webhooks, fineGrainedFailList...)
-		result.Webhooks = c.buildResourceValidatingWebhookRules(caBundle, webhookCfg, sideEffects, webhooks)
+		result.Webhooks = c.buildResourceValidatingWebhookRules(caBundle, webhookCfg, sideEffects, webhooks, mapResourceToOpnType)
 	} else {
 		c.recordPolicyState(config.MutatingWebhookConfigurationName)
 	}
 	return &result, nil
 }
 
-func (c *controller) buildResourceValidatingWebhookRules(caBundle []byte, webhookCfg config.WebhookConfig, sideEffects *admissionregistrationv1.SideEffectClass, webhooks []*webhook) []admissionregistrationv1.ValidatingWebhook {
+func (c *controller) buildResourceValidatingWebhookRules(caBundle []byte, webhookCfg config.WebhookConfig, sideEffects *admissionregistrationv1.SideEffectClass, webhooks []*webhook, mapResourceToOpnType map[string][]admissionregistrationv1.OperationType) []admissionregistrationv1.ValidatingWebhook {
 	var validatingWebhooks []admissionregistrationv1.ValidatingWebhook
 	for _, webhook := range webhooks {
 		if webhook.isEmpty() {
@@ -841,7 +879,7 @@ func (c *controller) buildResourceValidatingWebhookRules(caBundle []byte, webhoo
 			admissionregistrationv1.ValidatingWebhook{
 				Name:                    name,
 				ClientConfig:            c.clientConfig(caBundle, path),
-				Rules:                   webhook.buildRulesWithOperations(admissionregistrationv1.Create, admissionregistrationv1.Update, admissionregistrationv1.Delete, admissionregistrationv1.Connect),
+				Rules:                   webhook.buildRulesWithOperations(mapResourceToOpnType, []admissionregistrationv1.OperationType{"CREATE", "UPDATE", "DELETE", "CONNECT"}),
 				FailurePolicy:           &failurePolicy,
 				SideEffects:             sideEffects,
 				AdmissionReviewVersions: []string{"v1"},
diff --git a/pkg/controllers/webhook/controller_test.go b/pkg/controllers/webhook/controller_test.go
new file mode 100644
index 0000000000..c0b47f9e78
--- /dev/null
+++ b/pkg/controllers/webhook/controller_test.go
@@ -0,0 +1,129 @@
+package webhook
+
+import (
+	"reflect"
+	"testing"
+
+	kyverno "github.com/kyverno/kyverno/api/kyverno/v1"
+	admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
+)
+
+func TestAddOperationsForValidatingWebhookConf(t *testing.T) {
+	testCases := []struct {
+		name           string
+		rules          []kyverno.Rule
+		expectedResult map[string][]admissionregistrationv1.OperationType
+	}{
+		{
+			name: "Test Case 1",
+			rules: []kyverno.Rule{
+				{
+					MatchResources: kyverno.MatchResources{
+						ResourceDescription: kyverno.ResourceDescription{
+							Kinds:      []string{"ConfigMap"},
+							Operations: []kyverno.AdmissionOperation{"CREATE"},
+						},
+					},
+				},
+			},
+			expectedResult: map[string][]admissionregistrationv1.OperationType{
+				"ConfigMap": {"CREATE"},
+			},
+		},
+		{
+			name: "Test Case 1",
+			rules: []kyverno.Rule{
+				{
+					MatchResources: kyverno.MatchResources{
+						ResourceDescription: kyverno.ResourceDescription{
+							Kinds: []string{"ConfigMap"},
+						},
+					},
+					ExcludeResources: kyverno.MatchResources{
+						ResourceDescription: kyverno.ResourceDescription{
+							Operations: []kyverno.AdmissionOperation{"DELETE", "CONNECT", "CREATE"},
+						},
+					},
+				},
+			},
+			expectedResult: map[string][]admissionregistrationv1.OperationType{
+				"ConfigMap": {"UPDATE"},
+			},
+		},
+	}
+
+	for _, testCase := range testCases {
+		t.Run(testCase.name, func(t *testing.T) {
+			var result map[string][]admissionregistrationv1.OperationType
+			var mapResourceToOpnType map[string][]admissionregistrationv1.OperationType
+			result = addOpnForValidatingWebhookConf(testCase.rules, mapResourceToOpnType)
+
+			if !reflect.DeepEqual(result, testCase.expectedResult) {
+				t.Errorf("Expected %v, but got %v", testCase.expectedResult, result)
+			}
+		})
+	}
+}
+
+func TestAddOperationsForMutatingtingWebhookConf(t *testing.T) {
+	testCases := []struct {
+		name           string
+		rules          []kyverno.Rule
+		expectedResult map[string][]admissionregistrationv1.OperationType
+	}{
+		{
+			name: "Test Case 1",
+			rules: []kyverno.Rule{
+				{
+					Mutation: kyverno.Mutation{
+						PatchesJSON6902: "add",
+					},
+					MatchResources: kyverno.MatchResources{
+						ResourceDescription: kyverno.ResourceDescription{
+							Kinds:      []string{"ConfigMap"},
+							Operations: []kyverno.AdmissionOperation{"CREATE"},
+						},
+					},
+				},
+			},
+			expectedResult: map[string][]admissionregistrationv1.OperationType{
+				"ConfigMap": {"CREATE"},
+			},
+		},
+		{
+			name: "Test Case 1",
+			rules: []kyverno.Rule{
+				{
+					Mutation: kyverno.Mutation{
+						PatchesJSON6902: "add",
+					},
+					MatchResources: kyverno.MatchResources{
+						ResourceDescription: kyverno.ResourceDescription{
+							Kinds: []string{"Secret"},
+						},
+					},
+					ExcludeResources: kyverno.MatchResources{
+						ResourceDescription: kyverno.ResourceDescription{
+							Operations: []kyverno.AdmissionOperation{"UPDATE"},
+						},
+					},
+				},
+			},
+			expectedResult: map[string][]admissionregistrationv1.OperationType{
+				"Secret": {"CREATE"},
+			},
+		},
+	}
+
+	for _, testCase := range testCases {
+		t.Run(testCase.name, func(t *testing.T) {
+			var result map[string][]admissionregistrationv1.OperationType
+			var mapResourceToOpnType map[string][]admissionregistrationv1.OperationType
+			result = addOpnForMutatingWebhookConf(testCase.rules, mapResourceToOpnType)
+
+			if !reflect.DeepEqual(result, testCase.expectedResult) {
+				t.Errorf("Expected %v, but got %v", testCase.expectedResult, result)
+			}
+		})
+	}
+}
diff --git a/pkg/controllers/webhook/utils.go b/pkg/controllers/webhook/utils.go
index f7c51b390c..710c58d7dc 100644
--- a/pkg/controllers/webhook/utils.go
+++ b/pkg/controllers/webhook/utils.go
@@ -50,6 +50,15 @@ func newWebhook(timeout int32, failurePolicy admissionregistrationv1.FailurePoli
 	}
 }
 
+func findKeyContainingSubstring(m map[string][]admissionregistrationv1.OperationType, substring string, defaultOpn []admissionregistrationv1.OperationType) []admissionregistrationv1.OperationType {
+	for key, value := range m {
+		if strings.Contains(strings.ToLower(key), strings.ToLower(substring)) || strings.Contains(strings.ToLower(substring), strings.ToLower(key)) {
+			return value
+		}
+	}
+	return defaultOpn
+}
+
 func newWebhookPerPolicy(timeout int32, failurePolicy admissionregistrationv1.FailurePolicyType, matchConditions []admissionregistrationv1.MatchCondition, policy kyvernov1.PolicyInterface) *webhook {
 	webhook := newWebhook(timeout, failurePolicy, matchConditions)
 	webhook.policyMeta = objectmeta.ObjectName{
@@ -62,10 +71,11 @@ func newWebhookPerPolicy(timeout int32, failurePolicy admissionregistrationv1.Fa
 	return webhook
 }
 
-func (wh *webhook) buildRulesWithOperations(ops ...admissionregistrationv1.OperationType) []admissionregistrationv1.RuleWithOperations {
+func (wh *webhook) buildRulesWithOperations(final map[string][]admissionregistrationv1.OperationType, defaultOpn []admissionregistrationv1.OperationType) []admissionregistrationv1.RuleWithOperations {
 	var rules []admissionregistrationv1.RuleWithOperations
 
 	for gv, resources := range wh.rules {
+		firstResource := sets.List(resources)[0]
 		// if we have pods, we add pods/ephemeralcontainers by default
 		if (gv.Group == "" || gv.Group == "*") && (gv.Version == "v1" || gv.Version == "*") && (resources.Has("pods") || resources.Has("*")) {
 			resources.Insert("pods/ephemeralcontainers")
@@ -77,7 +87,7 @@ func (wh *webhook) buildRulesWithOperations(ops ...admissionregistrationv1.Opera
 				Resources:   sets.List(resources),
 				Scope:       ptr.To(gv.scopeType),
 			},
-			Operations: ops,
+			Operations: findKeyContainingSubstring(final, firstResource, defaultOpn),
 		})
 	}
 	less := func(a []string, b []string) (int, bool) {
@@ -109,6 +119,42 @@ func (wh *webhook) buildRulesWithOperations(ops ...admissionregistrationv1.Opera
 	return rules
 }
 
+func scanResourceFilterForResources(resFilter kyvernov1.ResourceFilters) []string {
+	var resources []string
+	for _, rf := range resFilter {
+		if rf.ResourceDescription.Kinds != nil {
+			resources = append(resources, rf.ResourceDescription.Kinds...)
+		}
+	}
+	return resources
+}
+
+func scanResourceFilter(resFilter kyvernov1.ResourceFilters, operationStatusMap map[string]bool) (bool, map[string]bool) {
+	opFound := false
+	for _, rf := range resFilter {
+		if rf.ResourceDescription.Operations != nil {
+			for _, o := range rf.ResourceDescription.Operations {
+				opFound = true
+				operationStatusMap[string(o)] = true
+			}
+		}
+	}
+	return opFound, operationStatusMap
+}
+
+func scanResourceFilterForExclude(resFilter kyvernov1.ResourceFilters, operationStatusMap map[string]bool) (bool, map[string]bool) {
+	opFound := false
+	for _, rf := range resFilter {
+		if rf.ResourceDescription.Operations != nil {
+			for _, o := range rf.ResourceDescription.Operations {
+				opFound = true
+				operationStatusMap[string(o)] = false
+			}
+		}
+	}
+	return opFound, operationStatusMap
+}
+
 func (wh *webhook) set(gvrs GroupVersionResourceScope) {
 	gvs := groupVersionScope{
 		GroupVersion: gvrs.GroupVersion(),
@@ -165,6 +211,156 @@ func objectMeta(name string, annotations map[string]string, labels map[string]st
 	}
 }
 
+func computeOperationsForValidatingWebhookConf(r kyvernov1.Rule, operationStatusMap map[string]bool) map[string]bool {
+	var opFound bool
+	opFoundCount := 0
+	if len(r.MatchResources.Any) != 0 {
+		opFound, operationStatusMap = scanResourceFilter(r.MatchResources.Any, operationStatusMap)
+		opFoundCount = opFoundCountIncrement(opFound, opFoundCount)
+	}
+	if len(r.MatchResources.All) != 0 {
+		opFound, operationStatusMap = scanResourceFilter(r.MatchResources.All, operationStatusMap)
+		opFoundCount = opFoundCountIncrement(opFound, opFoundCount)
+	}
+	if r.MatchResources.ResourceDescription.Operations != nil {
+		for _, o := range r.MatchResources.ResourceDescription.Operations {
+			opFound = true
+			operationStatusMap[string(o)] = true
+			opFoundCount = opFoundCountIncrement(opFound, opFoundCount)
+		}
+	}
+	if !opFound {
+		operationStatusMap[webhookCreate] = true
+		operationStatusMap[webhookUpdate] = true
+		operationStatusMap[webhookConnect] = true
+		operationStatusMap[webhookDelete] = true
+	}
+	if r.ExcludeResources.ResourceDescription.Operations != nil {
+		for _, o := range r.ExcludeResources.ResourceDescription.Operations {
+			operationStatusMap[string(o)] = false
+		}
+	}
+	if len(r.ExcludeResources.Any) != 0 {
+		_, operationStatusMap = scanResourceFilterForExclude(r.ExcludeResources.Any, operationStatusMap)
+	}
+	if len(r.ExcludeResources.All) != 0 {
+		_, operationStatusMap = scanResourceFilterForExclude(r.ExcludeResources.All, operationStatusMap)
+	}
+	return operationStatusMap
+}
+
+func opFoundCountIncrement(opFound bool, opFoundCount int) int {
+	if opFound {
+		opFoundCount++
+	}
+	return opFoundCount
+}
+
+func computeOperationsForMutatingWebhookConf(r kyvernov1.Rule, operationStatusMap map[string]bool) map[string]bool {
+	if r.HasMutate() || r.HasVerifyImages() {
+		var opFound bool
+		opFoundCount := 0
+		if len(r.MatchResources.Any) != 0 {
+			opFound, operationStatusMap = scanResourceFilter(r.MatchResources.Any, operationStatusMap)
+			opFoundCount = opFoundCountIncrement(opFound, opFoundCount)
+		}
+		if len(r.MatchResources.All) != 0 {
+			opFound, operationStatusMap = scanResourceFilter(r.MatchResources.All, operationStatusMap)
+			opFoundCount = opFoundCountIncrement(opFound, opFoundCount)
+		}
+		if r.MatchResources.ResourceDescription.Operations != nil {
+			for _, o := range r.MatchResources.ResourceDescription.Operations {
+				opFound = true
+				operationStatusMap[string(o)] = true
+				opFoundCount = opFoundCountIncrement(opFound, opFoundCount)
+			}
+		}
+		if opFoundCount == 0 {
+			operationStatusMap[webhookCreate] = true
+			operationStatusMap[webhookUpdate] = true
+		}
+		if r.ExcludeResources.ResourceDescription.Operations != nil {
+			for _, o := range r.ExcludeResources.ResourceDescription.Operations {
+				operationStatusMap[string(o)] = false
+			}
+		}
+		if len(r.ExcludeResources.Any) != 0 {
+			_, operationStatusMap = scanResourceFilterForExclude(r.ExcludeResources.Any, operationStatusMap)
+		}
+		if len(r.ExcludeResources.All) != 0 {
+			_, operationStatusMap = scanResourceFilterForExclude(r.ExcludeResources.All, operationStatusMap)
+		}
+	}
+	return operationStatusMap
+}
+
+func getMinimumOperations(operationStatusMap map[string]bool) []admissionregistrationv1.OperationType {
+	operationReq := make([]admissionregistrationv1.OperationType, 0, 4)
+	for k, v := range operationStatusMap {
+		if v {
+			var oper admissionregistrationv1.OperationType = admissionregistrationv1.OperationType(k)
+			operationReq = append(operationReq, oper)
+		}
+	}
+	return operationReq
+}
+
+func getOperationStatusMap() map[string]bool {
+	operationStatusMap := make(map[string]bool)
+	operationStatusMap[webhookCreate] = false
+	operationStatusMap[webhookUpdate] = false
+	operationStatusMap[webhookDelete] = false
+	operationStatusMap[webhookConnect] = false
+	return operationStatusMap
+}
+
+func appendResource(r string, mapResourceToOpn map[string]map[string]bool, opnStatusMap map[string]bool, mapResourceToOpnType map[string][]admissionregistrationv1.OperationType) (map[string]map[string]bool, map[string][]admissionregistrationv1.OperationType) {
+	if _, exists := mapResourceToOpn[r]; exists {
+		opnStatMap1 := opnStatusMap
+		opnStatMap2 := mapResourceToOpn[r]
+		for opn := range opnStatusMap {
+			if opnStatMap1[opn] || opnStatMap2[opn] {
+				opnStatusMap[opn] = true
+			}
+		}
+		mapResourceToOpn[r] = opnStatusMap
+		mapResourceToOpnType[r] = getMinimumOperations(opnStatusMap)
+	} else {
+		if mapResourceToOpn == nil {
+			mapResourceToOpn = make(map[string]map[string]bool)
+		}
+		mapResourceToOpn[r] = opnStatusMap
+		if mapResourceToOpnType == nil {
+			mapResourceToOpnType = make(map[string][]admissionregistrationv1.OperationType)
+		}
+		mapResourceToOpnType[r] = getMinimumOperations(opnStatusMap)
+	}
+	return mapResourceToOpn, mapResourceToOpnType
+}
+
+func computeResourcesOfRule(r kyvernov1.Rule) []string {
+	var resources []string
+	if len(r.MatchResources.Any) != 0 {
+		resources = scanResourceFilterForResources(r.MatchResources.Any)
+	}
+	if len(r.MatchResources.All) != 0 {
+		resources = scanResourceFilterForResources(r.MatchResources.Any)
+	}
+	if len(r.ExcludeResources.Any) != 0 {
+		resources = scanResourceFilterForResources(r.MatchResources.Any)
+	}
+	if len(r.ExcludeResources.All) != 0 {
+		resources = scanResourceFilterForResources(r.MatchResources.Any)
+	}
+	if r.MatchResources.ResourceDescription.Kinds != nil {
+		resources = append(resources, r.MatchResources.ResourceDescription.Kinds...)
+	}
+	if r.ExcludeResources.ResourceDescription.Kinds != nil {
+		resources = append(resources, r.ExcludeResources.ResourceDescription.Kinds...)
+	}
+	return resources
+}
+
 func setRuleCount(rules []kyvernov1.Rule, status *kyvernov1.PolicyStatus) {
 	validateCount, generateCount, mutateCount, verifyImagesCount := 0, 0, 0, 0
 	for _, rule := range rules {
diff --git a/pkg/controllers/webhook/utils_test.go b/pkg/controllers/webhook/utils_test.go
index 7bdc3d85a3..f425c80dbf 100644
--- a/pkg/controllers/webhook/utils_test.go
+++ b/pkg/controllers/webhook/utils_test.go
@@ -2,9 +2,12 @@ package webhook
 
 import (
 	"encoding/json"
+	"reflect"
+	"sort"
 	"testing"
 
 	kyverno "github.com/kyverno/kyverno/api/kyverno/v1"
+	v1 "github.com/kyverno/kyverno/api/kyverno/v1"
 	"github.com/kyverno/kyverno/pkg/autogen"
 	"gotest.tools/assert"
 	admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
@@ -160,3 +163,202 @@ func Test_RuleCount(t *testing.T) {
 	assert.Equal(t, status.RuleCount.Mutate, 1)
 	assert.Equal(t, status.RuleCount.VerifyImages, 2)
 }
+
+func TestGetMinimumOperations(t *testing.T) {
+	testCases := []struct {
+		name           string
+		inputMap       map[string]bool
+		expectedResult []admissionregistrationv1.OperationType
+	}{
+		{
+			name: "Test Case 1",
+			inputMap: map[string]bool{
+				webhookCreate: true,
+				webhookUpdate: false,
+				webhookDelete: true,
+			},
+			expectedResult: []admissionregistrationv1.OperationType{webhookCreate, webhookDelete},
+		},
+		{
+			name: "Test Case 2",
+			inputMap: map[string]bool{
+				webhookCreate:  false,
+				webhookUpdate:  false,
+				webhookDelete:  false,
+				webhookConnect: true,
+			},
+			expectedResult: []admissionregistrationv1.OperationType{webhookConnect},
+		},
+	}
+
+	for _, testCase := range testCases {
+		t.Run(testCase.name, func(t *testing.T) {
+			result := getMinimumOperations(testCase.inputMap)
+			sort.Slice(result, func(i, j int) bool {
+				return result[i] < result[j]
+			})
+			sort.Slice(testCase.expectedResult, func(i, j int) bool {
+				return testCase.expectedResult[i] < testCase.expectedResult[j]
+			})
+
+			if !reflect.DeepEqual(result, testCase.expectedResult) {
+				t.Errorf("Expected %v, but got %v", testCase.expectedResult, result)
+			}
+		})
+	}
+}
+
+func TestComputeOperationsForMutatingWebhookConf(t *testing.T) {
+	testCases := []struct {
+		name           string
+		rules          []kyverno.Rule
+		expectedResult map[string]bool
+	}{
+		{
+			name: "Test Case 1",
+			rules: []kyverno.Rule{
+				{
+					Mutation: kyverno.Mutation{
+						PatchesJSON6902: "add",
+					},
+					MatchResources: kyverno.MatchResources{
+						ResourceDescription: kyverno.ResourceDescription{
+							Operations: []v1.AdmissionOperation{webhookCreate},
+						},
+					},
+				},
+			},
+			expectedResult: map[string]bool{
+				webhookCreate: true,
+			},
+		},
+		{
+			name: "Test Case 2",
+			rules: []kyverno.Rule{
+				{
+					Mutation: kyverno.Mutation{
+						PatchesJSON6902: "add",
+					},
+					MatchResources:   kyverno.MatchResources{},
+					ExcludeResources: kyverno.MatchResources{},
+				},
+				{
+					Mutation: kyverno.Mutation{
+						PatchesJSON6902: "add",
+					},
+					MatchResources:   kyverno.MatchResources{},
+					ExcludeResources: kyverno.MatchResources{},
+				},
+			},
+			expectedResult: map[string]bool{
+				webhookCreate: true,
+				webhookUpdate: true,
+			},
+		},
+		{
+			name: "Test Case 2",
+			rules: []kyverno.Rule{
+				{
+					Mutation: kyverno.Mutation{
+						PatchesJSON6902: "add",
+					},
+					MatchResources: kyverno.MatchResources{},
+					ExcludeResources: kyverno.MatchResources{
+						ResourceDescription: kyverno.ResourceDescription{
+							Operations: []v1.AdmissionOperation{webhookCreate},
+						},
+					},
+				},
+			},
+			expectedResult: map[string]bool{
+				webhookCreate: false,
+				webhookUpdate: true,
+			},
+		},
+	}
+
+	for _, testCase := range testCases {
+		t.Run(testCase.name, func(t *testing.T) {
+			var result map[string]bool
+			for _, r := range testCase.rules {
+				result = computeOperationsForMutatingWebhookConf(r, make(map[string]bool))
+			}
+			if !reflect.DeepEqual(result, testCase.expectedResult) {
+				t.Errorf("Expected %v, but got %v", testCase.expectedResult, result)
+			}
+		})
+	}
+}
+
+func TestComputeOperationsForValidatingWebhookConf(t *testing.T) {
+	testCases := []struct {
+		name           string
+		rules          []kyverno.Rule
+		expectedResult map[string]bool
+	}{
+		{
+			name: "Test Case 1",
+			rules: []kyverno.Rule{
+				{
+					MatchResources: kyverno.MatchResources{
+						ResourceDescription: kyverno.ResourceDescription{
+							Operations: []v1.AdmissionOperation{webhookCreate},
+						},
+					},
+				},
+			},
+			expectedResult: map[string]bool{
+				webhookCreate: true,
+			},
+		},
+		{
+			name: "Test Case 2",
+			rules: []kyverno.Rule{
+				{
+					MatchResources:   kyverno.MatchResources{},
+					ExcludeResources: kyverno.MatchResources{},
+				},
+			},
+			expectedResult: map[string]bool{
+				webhookCreate:  true,
+				webhookUpdate:  true,
+				webhookConnect: true,
+				webhookDelete:  true,
+			},
+		},
+		{
+			name: "Test Case 3",
+			rules: []kyverno.Rule{
+				{
+					MatchResources: kyverno.MatchResources{
+						ResourceDescription: kyverno.ResourceDescription{
+							Operations: []v1.AdmissionOperation{webhookCreate, webhookUpdate},
+						},
+					},
+					ExcludeResources: kyverno.MatchResources{
+						ResourceDescription: kyverno.ResourceDescription{
+							Operations: []v1.AdmissionOperation{webhookDelete},
+						},
+					},
+				},
+			},
+			expectedResult: map[string]bool{
+				webhookCreate: true,
+				webhookUpdate: true,
+				webhookDelete: false,
+			},
+		},
+	}
+
+	for _, testCase := range testCases {
+		t.Run(testCase.name, func(t *testing.T) {
+			var result map[string]bool
+			for _, r := range testCase.rules {
+				result = computeOperationsForValidatingWebhookConf(r, make(map[string]bool))
+			}
+			if !reflect.DeepEqual(result, testCase.expectedResult) {
+				t.Errorf("Expected %v, but got %v", testCase.expectedResult, result)
+			}
+		})
+	}
+}
diff --git a/test/conformance/chainsaw/webhooks/dyn-op-mutate-multiple/README.md b/test/conformance/chainsaw/webhooks/dyn-op-mutate-multiple/README.md
new file mode 100644
index 0000000000..0a2c9c528c
--- /dev/null
+++ b/test/conformance/chainsaw/webhooks/dyn-op-mutate-multiple/README.md
@@ -0,0 +1,8 @@
+## Description
+
+This test verifies that operations configured dynamically are correct in mutatingwebhookconfiguration with multiple policies
+## Steps
+
+1.  - Create 2 policies with mutate
+    - Assert policy gets ready
+2.  - Assert that the resource mutation webhook is configured correctly
diff --git a/test/conformance/chainsaw/webhooks/dyn-op-mutate-multiple/chainsaw-test.yaml b/test/conformance/chainsaw/webhooks/dyn-op-mutate-multiple/chainsaw-test.yaml
new file mode 100644
index 0000000000..c4168fe5cf
--- /dev/null
+++ b/test/conformance/chainsaw/webhooks/dyn-op-mutate-multiple/chainsaw-test.yaml
@@ -0,0 +1,18 @@
+---
+apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  creationTimestamp: null
+  name: dyn-op-mutate-multiple
+spec:
+  steps:
+  - name: step-01
+    try:
+    - apply:
+        file: policy.yaml
+    - assert:
+        file: policy-assert.yaml
+  - name: step-02
+    try:
+    - assert:
+        file: webhooks.yaml
diff --git a/test/conformance/chainsaw/webhooks/dyn-op-mutate-multiple/policy-assert.yaml b/test/conformance/chainsaw/webhooks/dyn-op-mutate-multiple/policy-assert.yaml
new file mode 100644
index 0000000000..0585d4d294
--- /dev/null
+++ b/test/conformance/chainsaw/webhooks/dyn-op-mutate-multiple/policy-assert.yaml
@@ -0,0 +1,9 @@
+apiVersion: kyverno.io/v1
+kind: ClusterPolicy
+metadata:
+  name: add-apparmor-annotations
+status:
+  conditions:
+    - reason: Succeeded
+      status: "True"
+      type: Ready
diff --git a/test/conformance/chainsaw/webhooks/dyn-op-mutate-multiple/policy.yaml b/test/conformance/chainsaw/webhooks/dyn-op-mutate-multiple/policy.yaml
new file mode 100644
index 0000000000..9304b46abc
--- /dev/null
+++ b/test/conformance/chainsaw/webhooks/dyn-op-mutate-multiple/policy.yaml
@@ -0,0 +1,53 @@
+apiVersion: kyverno.io/v1
+kind: ClusterPolicy
+metadata:
+  name: add-apparmor-annotations
+  annotations:
+    policies.kyverno.io/title: Add AppArmor Annotations
+    policies.kyverno.io/category: PSP Migration
+    policies.kyverno.io/subject: Pod,Annotation
+    kyverno.io/kyverno-version: 1.10.0    
+spec:
+  rules:
+  - name: apparmor-runtime-default
+    match:
+      any:
+      - resources:
+          kinds:
+          - '*/scale'
+          operations:
+          - CREATE
+    mutate:
+      foreach:
+      - list: request.object.spec.containers[]
+        patchStrategicMerge:
+          metadata:
+            annotations:
+              "container.apparmor.security.beta.kubernetes.io/{{element.name}}": runtime/default
+---
+apiVersion: kyverno.io/v1
+kind: ClusterPolicy
+metadata:
+  name: add-annotation
+  annotations:
+    policies.kyverno.io/title: Add AppArmor Annotations
+    policies.kyverno.io/category: PSP Migration
+    policies.kyverno.io/subject: Pod,Annotation
+    kyverno.io/kyverno-version: 1.10.0    
+spec:
+  rules:
+  - name: add-annotation
+    match:
+      any:
+      - resources:
+          kinds:
+          - Secret
+          operations:
+          - UPDATE
+    mutate:
+      foreach:
+      - list: request.object.spec.containers[]
+        patchStrategicMerge:
+          metadata:
+            annotations:
+              "container/{{element.name}}": runtime
diff --git a/test/conformance/chainsaw/webhooks/dyn-op-mutate-multiple/webhooks.yaml b/test/conformance/chainsaw/webhooks/dyn-op-mutate-multiple/webhooks.yaml
new file mode 100644
index 0000000000..2b526ceaa1
--- /dev/null
+++ b/test/conformance/chainsaw/webhooks/dyn-op-mutate-multiple/webhooks.yaml
@@ -0,0 +1,27 @@
+apiVersion: admissionregistration.k8s.io/v1
+kind: MutatingWebhookConfiguration
+metadata:
+  labels:
+    webhook.kyverno.io/managed-by: kyverno
+  name: kyverno-resource-mutating-webhook-cfg
+webhooks:
+- rules:
+  - apiGroups:
+    - ''
+    apiVersions:
+    - v1
+    operations:
+    - UPDATE
+    resources:
+    - secrets
+    scope: Namespaced
+  - apiGroups:
+    - '*'
+    apiVersions:
+    - '*'
+    operations:
+    - CREATE
+    resources:
+    - '*/scale'
+    scope: '*'
+
diff --git a/test/conformance/chainsaw/webhooks/dyn-op-mutate/README.md b/test/conformance/chainsaw/webhooks/dyn-op-mutate/README.md
new file mode 100644
index 0000000000..bde2ed21af
--- /dev/null
+++ b/test/conformance/chainsaw/webhooks/dyn-op-mutate/README.md
@@ -0,0 +1,9 @@
+## Description
+
+This test verifies that operations configured dynmically are correct in mutatingwebhookconfiguration
+
+## Steps
+
+1.  - Create a policy with mutate
+    - Assert policy gets ready
+2.  - Assert that the resource mutation webhook is configured correctly
diff --git a/test/conformance/chainsaw/webhooks/dyn-op-mutate/chainsaw-test.yaml b/test/conformance/chainsaw/webhooks/dyn-op-mutate/chainsaw-test.yaml
new file mode 100644
index 0000000000..d9e1bc53d2
--- /dev/null
+++ b/test/conformance/chainsaw/webhooks/dyn-op-mutate/chainsaw-test.yaml
@@ -0,0 +1,18 @@
+---
+apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  creationTimestamp: null
+  name: dyn-op-mutate
+spec:
+  steps:
+  - name: step-01
+    try:
+    - apply:
+        file: policy.yaml
+    - assert:
+        file: policy-assert.yaml
+  - name: step-02
+    try:
+    - assert:
+        file: webhooks.yaml
diff --git a/test/conformance/chainsaw/webhooks/dyn-op-mutate/policy-assert.yaml b/test/conformance/chainsaw/webhooks/dyn-op-mutate/policy-assert.yaml
new file mode 100644
index 0000000000..0585d4d294
--- /dev/null
+++ b/test/conformance/chainsaw/webhooks/dyn-op-mutate/policy-assert.yaml
@@ -0,0 +1,9 @@
+apiVersion: kyverno.io/v1
+kind: ClusterPolicy
+metadata:
+  name: add-apparmor-annotations
+status:
+  conditions:
+    - reason: Succeeded
+      status: "True"
+      type: Ready
diff --git a/test/conformance/chainsaw/webhooks/dyn-op-mutate/policy.yaml b/test/conformance/chainsaw/webhooks/dyn-op-mutate/policy.yaml
new file mode 100644
index 0000000000..293ed81d2f
--- /dev/null
+++ b/test/conformance/chainsaw/webhooks/dyn-op-mutate/policy.yaml
@@ -0,0 +1,26 @@
+apiVersion: kyverno.io/v1
+kind: ClusterPolicy
+metadata:
+  name: add-apparmor-annotations
+  annotations:
+    policies.kyverno.io/title: Add AppArmor Annotations
+    policies.kyverno.io/category: PSP Migration
+    policies.kyverno.io/subject: Pod,Annotation
+    kyverno.io/kyverno-version: 1.10.0    
+spec:
+  rules:
+  - name: apparmor-runtime-default
+    match:
+      any:
+      - resources:
+          kinds:
+          - '*/scale'
+          operations:
+          - CREATE
+    mutate:
+      foreach:
+      - list: request.object.spec.containers[]
+        patchStrategicMerge:
+          metadata:
+            annotations:
+              "container.apparmor.security.beta.kubernetes.io/{{element.name}}": runtime/default
diff --git a/test/conformance/chainsaw/webhooks/dyn-op-mutate/webhooks.yaml b/test/conformance/chainsaw/webhooks/dyn-op-mutate/webhooks.yaml
new file mode 100644
index 0000000000..a603a8ea1d
--- /dev/null
+++ b/test/conformance/chainsaw/webhooks/dyn-op-mutate/webhooks.yaml
@@ -0,0 +1,18 @@
+apiVersion: admissionregistration.k8s.io/v1
+kind: MutatingWebhookConfiguration
+metadata:
+  labels:
+    webhook.kyverno.io/managed-by: kyverno
+  name: kyverno-resource-mutating-webhook-cfg
+webhooks:
+- rules:
+  - apiGroups:
+    - '*'
+    apiVersions:
+    - '*'
+    operations:
+    - CREATE
+    resources:
+    - '*/scale'
+    scope: '*'
+
diff --git a/test/conformance/chainsaw/webhooks/dyn-op-validate-and-mutate/README.md b/test/conformance/chainsaw/webhooks/dyn-op-validate-and-mutate/README.md
new file mode 100644
index 0000000000..02288ae724
--- /dev/null
+++ b/test/conformance/chainsaw/webhooks/dyn-op-validate-and-mutate/README.md
@@ -0,0 +1,9 @@
+## Description
+
+This test verifies that operations configured dynmically are correct in both validatingadmissionwebhooks and mutating webhooks
+
+## Steps
+
+1.  - Create policies with validate block and mutate block
+    - Assert policies get ready
+2.  - Assert that the resource validation and mutation webhook are configured correctly
diff --git a/test/conformance/chainsaw/webhooks/dyn-op-validate-and-mutate/chainsaw-test.yaml b/test/conformance/chainsaw/webhooks/dyn-op-validate-and-mutate/chainsaw-test.yaml
new file mode 100644
index 0000000000..ada63e3c72
--- /dev/null
+++ b/test/conformance/chainsaw/webhooks/dyn-op-validate-and-mutate/chainsaw-test.yaml
@@ -0,0 +1,28 @@
+---
+apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  creationTimestamp: null
+  name: dyn-op-validate-and-mutate
+spec:
+  steps:
+  - name: step-01
+    try:
+    - apply:
+        file: policy-01.yaml
+    - assert:
+        file: policy-assert1.yaml
+  - name: step-02
+    try:
+    - assert:
+        file: webhooks-02.yaml
+  - name: step-03
+    try:
+    - apply:
+        file: policy-03.yaml
+    - assert:
+        file: policy-assert2.yaml
+  - name: step-04
+    try:
+    - assert:
+        file: webhooks-04.yaml
diff --git a/test/conformance/chainsaw/webhooks/dyn-op-validate-and-mutate/policy-01.yaml b/test/conformance/chainsaw/webhooks/dyn-op-validate-and-mutate/policy-01.yaml
new file mode 100644
index 0000000000..8e4bb6e996
--- /dev/null
+++ b/test/conformance/chainsaw/webhooks/dyn-op-validate-and-mutate/policy-01.yaml
@@ -0,0 +1,24 @@
+apiVersion: kyverno.io/v1
+kind: ClusterPolicy
+metadata:
+  name: require-labels
+  annotations:
+    pod-policies.kyverno.io/autogen-controllers: none
+spec:
+  validationFailureAction: Audit
+  background: false
+  rules:
+    - name: require-team
+      match:
+        any:
+          - resources:
+              kinds:
+                - '*/scale'
+              operations:
+              - CREATE
+      validate:
+        message: 'The label `team` is required.'
+        pattern:
+          metadata:
+            labels:
+              team: '?*'
diff --git a/test/conformance/chainsaw/webhooks/dyn-op-validate-and-mutate/policy-03.yaml b/test/conformance/chainsaw/webhooks/dyn-op-validate-and-mutate/policy-03.yaml
new file mode 100644
index 0000000000..293ed81d2f
--- /dev/null
+++ b/test/conformance/chainsaw/webhooks/dyn-op-validate-and-mutate/policy-03.yaml
@@ -0,0 +1,26 @@
+apiVersion: kyverno.io/v1
+kind: ClusterPolicy
+metadata:
+  name: add-apparmor-annotations
+  annotations:
+    policies.kyverno.io/title: Add AppArmor Annotations
+    policies.kyverno.io/category: PSP Migration
+    policies.kyverno.io/subject: Pod,Annotation
+    kyverno.io/kyverno-version: 1.10.0    
+spec:
+  rules:
+  - name: apparmor-runtime-default
+    match:
+      any:
+      - resources:
+          kinds:
+          - '*/scale'
+          operations:
+          - CREATE
+    mutate:
+      foreach:
+      - list: request.object.spec.containers[]
+        patchStrategicMerge:
+          metadata:
+            annotations:
+              "container.apparmor.security.beta.kubernetes.io/{{element.name}}": runtime/default
diff --git a/test/conformance/chainsaw/webhooks/dyn-op-validate-and-mutate/policy-assert1.yaml b/test/conformance/chainsaw/webhooks/dyn-op-validate-and-mutate/policy-assert1.yaml
new file mode 100644
index 0000000000..2993bbaa6e
--- /dev/null
+++ b/test/conformance/chainsaw/webhooks/dyn-op-validate-and-mutate/policy-assert1.yaml
@@ -0,0 +1,9 @@
+apiVersion: kyverno.io/v1
+kind: ClusterPolicy
+metadata:
+  name: require-labels
+status:
+  conditions:
+    - reason: Succeeded
+      status: "True"
+      type: Ready
diff --git a/test/conformance/chainsaw/webhooks/dyn-op-validate-and-mutate/policy-assert2.yaml b/test/conformance/chainsaw/webhooks/dyn-op-validate-and-mutate/policy-assert2.yaml
new file mode 100644
index 0000000000..0585d4d294
--- /dev/null
+++ b/test/conformance/chainsaw/webhooks/dyn-op-validate-and-mutate/policy-assert2.yaml
@@ -0,0 +1,9 @@
+apiVersion: kyverno.io/v1
+kind: ClusterPolicy
+metadata:
+  name: add-apparmor-annotations
+status:
+  conditions:
+    - reason: Succeeded
+      status: "True"
+      type: Ready
diff --git a/test/conformance/chainsaw/webhooks/dyn-op-validate-and-mutate/policy.yaml b/test/conformance/chainsaw/webhooks/dyn-op-validate-and-mutate/policy.yaml
new file mode 100644
index 0000000000..8e4bb6e996
--- /dev/null
+++ b/test/conformance/chainsaw/webhooks/dyn-op-validate-and-mutate/policy.yaml
@@ -0,0 +1,24 @@
+apiVersion: kyverno.io/v1
+kind: ClusterPolicy
+metadata:
+  name: require-labels
+  annotations:
+    pod-policies.kyverno.io/autogen-controllers: none
+spec:
+  validationFailureAction: Audit
+  background: false
+  rules:
+    - name: require-team
+      match:
+        any:
+          - resources:
+              kinds:
+                - '*/scale'
+              operations:
+              - CREATE
+      validate:
+        message: 'The label `team` is required.'
+        pattern:
+          metadata:
+            labels:
+              team: '?*'
diff --git a/test/conformance/chainsaw/webhooks/dyn-op-validate-and-mutate/webhooks-02.yaml b/test/conformance/chainsaw/webhooks/dyn-op-validate-and-mutate/webhooks-02.yaml
new file mode 100644
index 0000000000..8c59f7e6b9
--- /dev/null
+++ b/test/conformance/chainsaw/webhooks/dyn-op-validate-and-mutate/webhooks-02.yaml
@@ -0,0 +1,17 @@
+apiVersion: admissionregistration.k8s.io/v1
+kind: ValidatingWebhookConfiguration
+metadata:
+  labels:
+    webhook.kyverno.io/managed-by: kyverno
+  name: kyverno-resource-validating-webhook-cfg
+webhooks:
+- rules:
+  - apiGroups:
+    - '*'
+    apiVersions:
+    - '*'
+    operations:
+    - CREATE
+    resources:
+    - '*/scale'
+    scope: '*'
diff --git a/test/conformance/chainsaw/webhooks/dyn-op-validate-and-mutate/webhooks-04.yaml b/test/conformance/chainsaw/webhooks/dyn-op-validate-and-mutate/webhooks-04.yaml
new file mode 100644
index 0000000000..a603a8ea1d
--- /dev/null
+++ b/test/conformance/chainsaw/webhooks/dyn-op-validate-and-mutate/webhooks-04.yaml
@@ -0,0 +1,18 @@
+apiVersion: admissionregistration.k8s.io/v1
+kind: MutatingWebhookConfiguration
+metadata:
+  labels:
+    webhook.kyverno.io/managed-by: kyverno
+  name: kyverno-resource-mutating-webhook-cfg
+webhooks:
+- rules:
+  - apiGroups:
+    - '*'
+    apiVersions:
+    - '*'
+    operations:
+    - CREATE
+    resources:
+    - '*/scale'
+    scope: '*'
+
diff --git a/test/conformance/chainsaw/webhooks/dyn-op-validate-and-mutate/webhooks.yaml b/test/conformance/chainsaw/webhooks/dyn-op-validate-and-mutate/webhooks.yaml
new file mode 100644
index 0000000000..8c59f7e6b9
--- /dev/null
+++ b/test/conformance/chainsaw/webhooks/dyn-op-validate-and-mutate/webhooks.yaml
@@ -0,0 +1,17 @@
+apiVersion: admissionregistration.k8s.io/v1
+kind: ValidatingWebhookConfiguration
+metadata:
+  labels:
+    webhook.kyverno.io/managed-by: kyverno
+  name: kyverno-resource-validating-webhook-cfg
+webhooks:
+- rules:
+  - apiGroups:
+    - '*'
+    apiVersions:
+    - '*'
+    operations:
+    - CREATE
+    resources:
+    - '*/scale'
+    scope: '*'
diff --git a/test/conformance/chainsaw/webhooks/dyn-op-validate-multiple/README.md b/test/conformance/chainsaw/webhooks/dyn-op-validate-multiple/README.md
new file mode 100644
index 0000000000..a7fd15bc65
--- /dev/null
+++ b/test/conformance/chainsaw/webhooks/dyn-op-validate-multiple/README.md
@@ -0,0 +1,9 @@
+## Description
+
+This test verifies that operations configured dynamically are correct in validatingadmissionwebhooks with multiple policies
+
+## Steps
+
+1.  - Create a policy with validate block
+    - Assert policy gets ready
+2.  - Assert that the resource validation webhook is configured correctly
diff --git a/test/conformance/chainsaw/webhooks/dyn-op-validate-multiple/chainsaw-test.yaml b/test/conformance/chainsaw/webhooks/dyn-op-validate-multiple/chainsaw-test.yaml
new file mode 100644
index 0000000000..c30e4cd08d
--- /dev/null
+++ b/test/conformance/chainsaw/webhooks/dyn-op-validate-multiple/chainsaw-test.yaml
@@ -0,0 +1,18 @@
+---
+apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  creationTimestamp: null
+  name: dyn-op-validate-multiple
+spec:
+  steps:
+  - name: step-01
+    try:
+    - apply:
+        file: policy.yaml
+    - assert:
+        file: policy-assert.yaml
+  - name: step-02
+    try:
+    - assert:
+        file: webhooks.yaml
\ No newline at end of file
diff --git a/test/conformance/chainsaw/webhooks/dyn-op-validate-multiple/policy-assert.yaml b/test/conformance/chainsaw/webhooks/dyn-op-validate-multiple/policy-assert.yaml
new file mode 100644
index 0000000000..2993bbaa6e
--- /dev/null
+++ b/test/conformance/chainsaw/webhooks/dyn-op-validate-multiple/policy-assert.yaml
@@ -0,0 +1,9 @@
+apiVersion: kyverno.io/v1
+kind: ClusterPolicy
+metadata:
+  name: require-labels
+status:
+  conditions:
+    - reason: Succeeded
+      status: "True"
+      type: Ready
diff --git a/test/conformance/chainsaw/webhooks/dyn-op-validate-multiple/policy.yaml b/test/conformance/chainsaw/webhooks/dyn-op-validate-multiple/policy.yaml
new file mode 100644
index 0000000000..71dc17c05b
--- /dev/null
+++ b/test/conformance/chainsaw/webhooks/dyn-op-validate-multiple/policy.yaml
@@ -0,0 +1,53 @@
+apiVersion: kyverno.io/v1
+kind: ClusterPolicy
+metadata:
+  name: require-labels
+  annotations:
+    pod-policies.kyverno.io/autogen-controllers: none
+spec:
+  validationFailureAction: Audit
+  background: false
+  rules:
+    - name: require-team
+      match:
+        any:
+          - resources:
+              kinds:
+              - ConfigMap
+      exclude:
+        any:
+          - resources:
+              operations:
+              - DELETE
+      validate:
+        message: 'The label `team` is required.'
+        pattern:
+          metadata:
+            labels:
+              team: '?*'
+---
+apiVersion: kyverno.io/v1
+kind: ClusterPolicy
+metadata:
+  name: require-match
+  annotations:
+    pod-policies.kyverno.io/autogen-controllers: none
+spec:
+  validationFailureAction: Audit
+  background: false
+  rules:
+    - name: require-match
+      match:
+        any:
+          - resources:
+              kinds:
+                - '*/scale'
+              operations:
+              - CREATE
+      validate:
+        message: 'The label `match` is required.'
+        pattern:
+          metadata:
+            labels:
+              match: '?*'
+              
\ No newline at end of file
diff --git a/test/conformance/chainsaw/webhooks/dyn-op-validate-multiple/webhooks.yaml b/test/conformance/chainsaw/webhooks/dyn-op-validate-multiple/webhooks.yaml
new file mode 100644
index 0000000000..49a5840dcf
--- /dev/null
+++ b/test/conformance/chainsaw/webhooks/dyn-op-validate-multiple/webhooks.yaml
@@ -0,0 +1,28 @@
+apiVersion: admissionregistration.k8s.io/v1
+kind: ValidatingWebhookConfiguration
+metadata:
+  labels:
+    webhook.kyverno.io/managed-by: kyverno
+  name: kyverno-resource-validating-webhook-cfg
+webhooks:
+- rules:
+  - apiGroups:
+    - ''
+    apiVersions:
+    - v1
+    operations:
+    - CREATE
+    - UPDATE
+    - CONNECT
+    resources:
+    - configmaps
+    scope: Namespaced
+  - apiGroups:
+    - '*'
+    apiVersions:
+    - '*'
+    operations:
+    - CREATE
+    resources:
+    - '*/scale'
+    scope: '*'
diff --git a/test/conformance/chainsaw/webhooks/dyn-op-validate/README.md b/test/conformance/chainsaw/webhooks/dyn-op-validate/README.md
new file mode 100644
index 0000000000..7be680d10a
--- /dev/null
+++ b/test/conformance/chainsaw/webhooks/dyn-op-validate/README.md
@@ -0,0 +1,9 @@
+## Description
+
+This test verifies that operations configured dynmically are correct in validatingadmissionwebhooks
+
+## Steps
+
+1.  - Create a policy with validate block
+    - Assert policy gets ready
+2.  - Assert that the resource validation webhook is configured correctly
diff --git a/test/conformance/chainsaw/webhooks/dyn-op-validate/chainsaw-test.yaml b/test/conformance/chainsaw/webhooks/dyn-op-validate/chainsaw-test.yaml
new file mode 100644
index 0000000000..02b34e803c
--- /dev/null
+++ b/test/conformance/chainsaw/webhooks/dyn-op-validate/chainsaw-test.yaml
@@ -0,0 +1,18 @@
+---
+apiVersion: chainsaw.kyverno.io/v1alpha1
+kind: Test
+metadata:
+  creationTimestamp: null
+  name: dyn-op-validate
+spec:
+  steps:
+  - name: step-01
+    try:
+    - apply:
+        file: policy.yaml
+    - assert:
+        file: policy-assert.yaml
+  - name: step-02
+    try:
+    - assert:
+        file: webhooks.yaml
\ No newline at end of file
diff --git a/test/conformance/chainsaw/webhooks/dyn-op-validate/policy-assert.yaml b/test/conformance/chainsaw/webhooks/dyn-op-validate/policy-assert.yaml
new file mode 100644
index 0000000000..2993bbaa6e
--- /dev/null
+++ b/test/conformance/chainsaw/webhooks/dyn-op-validate/policy-assert.yaml
@@ -0,0 +1,9 @@
+apiVersion: kyverno.io/v1
+kind: ClusterPolicy
+metadata:
+  name: require-labels
+status:
+  conditions:
+    - reason: Succeeded
+      status: "True"
+      type: Ready
diff --git a/test/conformance/chainsaw/webhooks/dyn-op-validate/policy.yaml b/test/conformance/chainsaw/webhooks/dyn-op-validate/policy.yaml
new file mode 100644
index 0000000000..8e4bb6e996
--- /dev/null
+++ b/test/conformance/chainsaw/webhooks/dyn-op-validate/policy.yaml
@@ -0,0 +1,24 @@
+apiVersion: kyverno.io/v1
+kind: ClusterPolicy
+metadata:
+  name: require-labels
+  annotations:
+    pod-policies.kyverno.io/autogen-controllers: none
+spec:
+  validationFailureAction: Audit
+  background: false
+  rules:
+    - name: require-team
+      match:
+        any:
+          - resources:
+              kinds:
+                - '*/scale'
+              operations:
+              - CREATE
+      validate:
+        message: 'The label `team` is required.'
+        pattern:
+          metadata:
+            labels:
+              team: '?*'
diff --git a/test/conformance/chainsaw/webhooks/dyn-op-validate/webhooks.yaml b/test/conformance/chainsaw/webhooks/dyn-op-validate/webhooks.yaml
new file mode 100644
index 0000000000..8c59f7e6b9
--- /dev/null
+++ b/test/conformance/chainsaw/webhooks/dyn-op-validate/webhooks.yaml
@@ -0,0 +1,17 @@
+apiVersion: admissionregistration.k8s.io/v1
+kind: ValidatingWebhookConfiguration
+metadata:
+  labels:
+    webhook.kyverno.io/managed-by: kyverno
+  name: kyverno-resource-validating-webhook-cfg
+webhooks:
+- rules:
+  - apiGroups:
+    - '*'
+    apiVersions:
+    - '*'
+    operations:
+    - CREATE
+    resources:
+    - '*/scale'
+    scope: '*'