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: '*'