1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-04-15 00:36:28 +00:00

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>
This commit is contained in:
Anushka Mittal 2024-01-31 21:16:53 +05:30 committed by GitHub
parent 141e7d056f
commit cfc9683033
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
34 changed files with 1143 additions and 8 deletions

View file

@ -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"},

View file

@ -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)
}
})
}
}

View file

@ -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 {

View file

@ -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)
}
})
}
}

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,9 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: add-apparmor-annotations
status:
conditions:
- reason: Succeeded
status: "True"
type: Ready

View file

@ -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

View file

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

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,9 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: add-apparmor-annotations
status:
conditions:
- reason: Succeeded
status: "True"
type: Ready

View file

@ -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

View file

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

View file

@ -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

View file

@ -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

View file

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

View file

@ -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

View file

@ -0,0 +1,9 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-labels
status:
conditions:
- reason: Succeeded
status: "True"
type: Ready

View file

@ -0,0 +1,9 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: add-apparmor-annotations
status:
conditions:
- reason: Succeeded
status: "True"
type: Ready

View file

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

View file

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

View file

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

View file

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

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,9 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-labels
status:
conditions:
- reason: Succeeded
status: "True"
type: Ready

View file

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

View file

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

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,9 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-labels
status:
conditions:
- reason: Succeeded
status: "True"
type: Ready

View file

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

View file

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