1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-07 00:17:13 +00:00
kyverno/pkg/controllers/webhook/utils_test.go
Charles-Edouard Brétéché 3580034fa1
feat: improve webhooks rules generation (#11419)
* feat: improve webhooks rules generation

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* iterate per rule

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* reduce rules

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* rework default operations

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* consider subresource

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* aggregate operations

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* sort rules

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* ephemeralcontainers

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* operations

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* aggregation

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* operations type

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* generate rules

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* nits

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* generate

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* all operations

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* collector changes

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* account for exclusions

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* unit tests

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix exclusions when no operations specified

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* unit tests

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

---------

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
Co-authored-by: shuting <shuting@nirmata.com>
2024-10-21 12:56:21 +00:00

637 lines
20 KiB
Go

package webhook
import (
"encoding/json"
"testing"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
autogenv1 "github.com/kyverno/kyverno/pkg/autogen/v1"
"github.com/stretchr/testify/assert"
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/utils/ptr"
)
func Test_webhook_isEmpty(t *testing.T) {
empty := newWebhook(DefaultWebhookTimeout, admissionregistrationv1.Ignore, []admissionregistrationv1.MatchCondition{})
assert.Equal(t, empty.isEmpty(), true)
notEmpty := newWebhook(DefaultWebhookTimeout, admissionregistrationv1.Ignore, []admissionregistrationv1.MatchCondition{})
notEmpty.set("", "v1", "pods", "", admissionregistrationv1.NamespacedScope, kyvernov1.Create)
assert.Equal(t, notEmpty.isEmpty(), false)
}
var policy = `
{
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {
"name": "disallow-unsigned-images"
},
"spec": {
"background": false,
"rules": [
{
"name": "replace-image-registry",
"match": {
"any": [
{
"resources": {
"kinds": [
"Pod"
]
}
}
]
},
"mutate": {
"foreach": [
{
"list": "request.object.spec.containers",
"patchStrategicMerge": {
"spec": {
"containers": [
{
"name": "{{ element.name }}",
"image": "{{ regex_replace_all_literal('.*(.*)/', '{{element.image}}', 'pratikrshah/' )}}"
}
]
}
}
}
]
}
},
{
"name": "disallow-unsigned-images-rule",
"match": {
"any": [
{
"resources": {
"kinds": [
"Pod"
]
}
}
]
},
"verifyImages": [
{
"imageReferences": [
"*"
],
"verifyDigest": false,
"required": null,
"mutateDigest": false,
"attestors": [
{
"count": 1,
"entries": [
{
"keys": {
"publicKeys": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEHsra9WSDxt9qv84KF4McNVCGjMFq\ne96mWCQxGimL9Ltj6F3iXmlo8sUalKfJ7SBXpy8hwyBfXBBAmCalsp5xEw==\n-----END PUBLIC KEY-----"
}
}
]
}
]
}
]
},
{
"name": "check-image",
"match": {
"any": [
{
"resources": {
"kinds": [
"Pod"
]
}
}
]
},
"context": [
{
"name": "keys",
"configMap": {
"name": "keys",
"namespace": "default"
}
}
],
"verifyImages": [
{
"imageReferences": [
"ghcr.io/myorg/myimage*"
],
"required": true,
"attestors": [
{
"count": 1,
"entries": [
{
"keys": {
"publicKeys": "{{ keys.data.production }}"
}
}
]
}
]
}
]
}
]
}
}
`
func Test_RuleCount(t *testing.T) {
var cpol kyvernov1.ClusterPolicy
err := json.Unmarshal([]byte(policy), &cpol)
assert.NoError(t, err)
status := cpol.GetStatus()
rules := autogenv1.ComputeRules(&cpol, "")
setRuleCount(rules, status)
assert.Equal(t, status.RuleCount.Validate, 0)
assert.Equal(t, status.RuleCount.Generate, 0)
assert.Equal(t, status.RuleCount.Mutate, 1)
assert.Equal(t, status.RuleCount.VerifyImages, 2)
}
func TestBuildRulesWithOperations(t *testing.T) {
testCases := []struct {
name string
rules sets.Set[ruleEntry]
expectedResult []admissionregistrationv1.RuleWithOperations
}{{
rules: sets.New[ruleEntry](
ruleEntry{"", "v1", "configmaps", "", admissionregistrationv1.NamespacedScope, kyvernov1.Create},
ruleEntry{"", "v1", "pods", "", admissionregistrationv1.NamespacedScope, kyvernov1.Create},
),
expectedResult: []admissionregistrationv1.RuleWithOperations{{
Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create},
Rule: admissionregistrationv1.Rule{
APIGroups: []string{""},
APIVersions: []string{"v1"},
Resources: []string{"configmaps", "pods", "pods/ephemeralcontainers"},
Scope: ptr.To(admissionregistrationv1.NamespacedScope),
},
}},
}, {
rules: sets.New[ruleEntry](
ruleEntry{"", "v1", "configmaps", "", admissionregistrationv1.NamespacedScope, kyvernov1.Create},
ruleEntry{"", "v1", "pods", "", admissionregistrationv1.NamespacedScope, kyvernov1.Create},
ruleEntry{"", "v1", "pods", "", admissionregistrationv1.NamespacedScope, kyvernov1.Update},
),
expectedResult: []admissionregistrationv1.RuleWithOperations{{
Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create},
Rule: admissionregistrationv1.Rule{
APIGroups: []string{""},
APIVersions: []string{"v1"},
Resources: []string{"configmaps"},
Scope: ptr.To(admissionregistrationv1.NamespacedScope),
},
}, {
Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create, admissionregistrationv1.Update},
Rule: admissionregistrationv1.Rule{
APIGroups: []string{""},
APIVersions: []string{"v1"},
Resources: []string{"pods", "pods/ephemeralcontainers"},
Scope: ptr.To(admissionregistrationv1.NamespacedScope),
},
}},
}}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
wh := &webhook{
rules: testCase.rules,
}
result := wh.buildRulesWithOperations()
assert.Equal(t, testCase.expectedResult, result)
})
}
}
func Test_less(t *testing.T) {
tests := []struct {
name string
do func() int
want int
}{{
do: func() int {
return less([]int{0}, []int{0, 0})
},
want: -1,
}, {
do: func() int {
return less([]int{0, 0}, []int{0})
},
want: 1,
}, {
do: func() int {
return less([]int{0}, []int{1})
},
want: -1,
}, {
do: func() int {
return less([]int{1}, []int{0})
},
want: 1,
}, {
do: func() int {
return less([]int{0, 0}, []int{0, 0})
},
want: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.do()
assert.Equal(t, tt.want, got)
})
}
}
func Test_collectResourceDescriptions(t *testing.T) {
tests := []struct {
name string
rule kyvernov1.Rule
defaultOps []kyvernov1.AdmissionOperation
want webhookConfig
}{{
name: "empty",
rule: kyvernov1.Rule{},
defaultOps: allOperations,
want: webhookConfig{},
}, {
name: "match any - default ops",
rule: kyvernov1.Rule{
MatchResources: kyvernov1.MatchResources{
Any: kyvernov1.ResourceFilters{{
ResourceDescription: kyvernov1.ResourceDescription{
Kinds: []string{"ConfigMap"},
},
}},
},
},
defaultOps: []kyvernov1.AdmissionOperation{kyvernov1.Create, kyvernov1.Update},
want: webhookConfig{
"ConfigMap": sets.New(kyvernov1.Create, kyvernov1.Update),
},
}, {
name: "match any - ops",
rule: kyvernov1.Rule{
MatchResources: kyvernov1.MatchResources{
Any: kyvernov1.ResourceFilters{{
ResourceDescription: kyvernov1.ResourceDescription{
Kinds: []string{"ConfigMap"},
Operations: []kyvernov1.AdmissionOperation{kyvernov1.Create, kyvernov1.Update},
},
}},
},
},
defaultOps: allOperations,
want: webhookConfig{
"ConfigMap": sets.New(kyvernov1.Create, kyvernov1.Update),
},
}, {
name: "match any - multiple",
rule: kyvernov1.Rule{
MatchResources: kyvernov1.MatchResources{
Any: kyvernov1.ResourceFilters{{
ResourceDescription: kyvernov1.ResourceDescription{
Kinds: []string{"ConfigMap"},
Operations: []kyvernov1.AdmissionOperation{kyvernov1.Create},
},
}, {
ResourceDescription: kyvernov1.ResourceDescription{
Kinds: []string{
"Secret",
},
Operations: []kyvernov1.AdmissionOperation{kyvernov1.Update},
},
}},
},
},
defaultOps: allOperations,
want: webhookConfig{
"ConfigMap": sets.New(kyvernov1.Create),
"Secret": sets.New(kyvernov1.Update),
},
}, {
name: "match all - default ops",
rule: kyvernov1.Rule{
MatchResources: kyvernov1.MatchResources{
All: kyvernov1.ResourceFilters{{
ResourceDescription: kyvernov1.ResourceDescription{
Kinds: []string{"ConfigMap"},
},
}},
},
},
defaultOps: []kyvernov1.AdmissionOperation{kyvernov1.Create, kyvernov1.Update},
want: webhookConfig{
"ConfigMap": sets.New(kyvernov1.Create, kyvernov1.Update),
},
}, {
name: "match any - ops",
rule: kyvernov1.Rule{
MatchResources: kyvernov1.MatchResources{
All: kyvernov1.ResourceFilters{{
ResourceDescription: kyvernov1.ResourceDescription{
Kinds: []string{"ConfigMap"},
Operations: []kyvernov1.AdmissionOperation{kyvernov1.Create, kyvernov1.Update},
},
}},
},
},
defaultOps: allOperations,
want: webhookConfig{
"ConfigMap": sets.New(kyvernov1.Create, kyvernov1.Update),
},
}, {
name: "match all - multiple",
rule: kyvernov1.Rule{
MatchResources: kyvernov1.MatchResources{
Any: kyvernov1.ResourceFilters{{
ResourceDescription: kyvernov1.ResourceDescription{
Kinds: []string{"ConfigMap"},
Operations: []kyvernov1.AdmissionOperation{kyvernov1.Create},
},
}, {
ResourceDescription: kyvernov1.ResourceDescription{
Kinds: []string{"Secret"},
Operations: []kyvernov1.AdmissionOperation{kyvernov1.Update},
},
}},
},
},
defaultOps: allOperations,
want: webhookConfig{
"ConfigMap": sets.New(kyvernov1.Create),
"Secret": sets.New(kyvernov1.Update),
},
}, {
name: "exclude - no ops",
rule: kyvernov1.Rule{
MatchResources: kyvernov1.MatchResources{
Any: kyvernov1.ResourceFilters{{
ResourceDescription: kyvernov1.ResourceDescription{
Kinds: []string{"ConfigMap"},
Operations: allOperations,
},
}, {
ResourceDescription: kyvernov1.ResourceDescription{
Kinds: []string{"Secret"},
Operations: []kyvernov1.AdmissionOperation{kyvernov1.Update},
},
}},
},
ExcludeResources: &kyvernov1.MatchResources{
Any: kyvernov1.ResourceFilters{{
ResourceDescription: kyvernov1.ResourceDescription{
Kinds: []string{"ConfigMap"},
},
}},
},
},
defaultOps: []kyvernov1.AdmissionOperation{kyvernov1.Create, kyvernov1.Update},
want: webhookConfig{
"ConfigMap": sets.New[kyvernov1.AdmissionOperation](),
"Secret": sets.New(kyvernov1.Update),
},
}, {
name: "exclude - ops",
rule: kyvernov1.Rule{
MatchResources: kyvernov1.MatchResources{
Any: kyvernov1.ResourceFilters{{
ResourceDescription: kyvernov1.ResourceDescription{
Kinds: []string{"ConfigMap"},
Operations: allOperations,
},
}, {
ResourceDescription: kyvernov1.ResourceDescription{
Kinds: []string{"Secret"},
Operations: []kyvernov1.AdmissionOperation{kyvernov1.Update},
},
}},
},
ExcludeResources: &kyvernov1.MatchResources{
Any: kyvernov1.ResourceFilters{{
ResourceDescription: kyvernov1.ResourceDescription{
Kinds: []string{"ConfigMap"},
Operations: []kyvernov1.AdmissionOperation{kyvernov1.Connect},
},
}},
},
},
defaultOps: []kyvernov1.AdmissionOperation{kyvernov1.Create, kyvernov1.Update},
want: webhookConfig{
"ConfigMap": sets.New(kyvernov1.Create, kyvernov1.Update, kyvernov1.Delete),
"Secret": sets.New(kyvernov1.Update),
},
}, {
name: "exclude - with annotations",
rule: kyvernov1.Rule{
MatchResources: kyvernov1.MatchResources{
Any: kyvernov1.ResourceFilters{{
ResourceDescription: kyvernov1.ResourceDescription{
Kinds: []string{"ConfigMap"},
Operations: allOperations,
},
}, {
ResourceDescription: kyvernov1.ResourceDescription{
Kinds: []string{"Secret"},
Operations: []kyvernov1.AdmissionOperation{kyvernov1.Update},
},
}},
},
ExcludeResources: &kyvernov1.MatchResources{
Any: kyvernov1.ResourceFilters{{
ResourceDescription: kyvernov1.ResourceDescription{
Kinds: []string{"ConfigMap"},
Annotations: map[string]string{
"foo": "bar",
},
Operations: []kyvernov1.AdmissionOperation{kyvernov1.Connect},
},
}},
},
},
defaultOps: []kyvernov1.AdmissionOperation{kyvernov1.Create, kyvernov1.Update},
want: webhookConfig{
"ConfigMap": sets.New(allOperations...),
"Secret": sets.New(kyvernov1.Update),
},
}, {
name: "exclude - with name",
rule: kyvernov1.Rule{
MatchResources: kyvernov1.MatchResources{
Any: kyvernov1.ResourceFilters{{
ResourceDescription: kyvernov1.ResourceDescription{
Kinds: []string{"ConfigMap"},
Operations: allOperations,
},
}, {
ResourceDescription: kyvernov1.ResourceDescription{
Kinds: []string{"Secret"},
Operations: []kyvernov1.AdmissionOperation{kyvernov1.Update},
},
}},
},
ExcludeResources: &kyvernov1.MatchResources{
Any: kyvernov1.ResourceFilters{{
ResourceDescription: kyvernov1.ResourceDescription{
Kinds: []string{"ConfigMap"},
Name: "foo",
Operations: []kyvernov1.AdmissionOperation{kyvernov1.Connect},
},
}},
},
},
defaultOps: []kyvernov1.AdmissionOperation{kyvernov1.Create, kyvernov1.Update},
want: webhookConfig{
"ConfigMap": sets.New(allOperations...),
"Secret": sets.New(kyvernov1.Update),
},
}, {
name: "exclude - with names",
rule: kyvernov1.Rule{
MatchResources: kyvernov1.MatchResources{
Any: kyvernov1.ResourceFilters{{
ResourceDescription: kyvernov1.ResourceDescription{
Kinds: []string{"ConfigMap"},
Operations: allOperations,
},
}, {
ResourceDescription: kyvernov1.ResourceDescription{
Kinds: []string{"Secret"},
Operations: []kyvernov1.AdmissionOperation{kyvernov1.Update},
},
}},
},
ExcludeResources: &kyvernov1.MatchResources{
Any: kyvernov1.ResourceFilters{{
ResourceDescription: kyvernov1.ResourceDescription{
Kinds: []string{"ConfigMap"},
Names: []string{"foo"},
Operations: []kyvernov1.AdmissionOperation{kyvernov1.Connect},
},
}},
},
},
defaultOps: []kyvernov1.AdmissionOperation{kyvernov1.Create, kyvernov1.Update},
want: webhookConfig{
"ConfigMap": sets.New(allOperations...),
"Secret": sets.New(kyvernov1.Update),
},
}, {
name: "exclude - with namespaces",
rule: kyvernov1.Rule{
MatchResources: kyvernov1.MatchResources{
Any: kyvernov1.ResourceFilters{{
ResourceDescription: kyvernov1.ResourceDescription{
Kinds: []string{"ConfigMap"},
Operations: allOperations,
},
}, {
ResourceDescription: kyvernov1.ResourceDescription{
Kinds: []string{"Secret"},
Operations: []kyvernov1.AdmissionOperation{kyvernov1.Update},
},
}},
},
ExcludeResources: &kyvernov1.MatchResources{
Any: kyvernov1.ResourceFilters{{
ResourceDescription: kyvernov1.ResourceDescription{
Kinds: []string{"ConfigMap"},
Namespaces: []string{"foo"},
Operations: []kyvernov1.AdmissionOperation{kyvernov1.Connect},
},
}},
},
},
defaultOps: []kyvernov1.AdmissionOperation{kyvernov1.Create, kyvernov1.Update},
want: webhookConfig{
"ConfigMap": sets.New(allOperations...),
"Secret": sets.New(kyvernov1.Update),
},
}, {
name: "exclude - with selector",
rule: kyvernov1.Rule{
MatchResources: kyvernov1.MatchResources{
Any: kyvernov1.ResourceFilters{{
ResourceDescription: kyvernov1.ResourceDescription{
Kinds: []string{"ConfigMap"},
Operations: allOperations,
},
}, {
ResourceDescription: kyvernov1.ResourceDescription{
Kinds: []string{"Secret"},
Operations: []kyvernov1.AdmissionOperation{kyvernov1.Update},
},
}},
},
ExcludeResources: &kyvernov1.MatchResources{
Any: kyvernov1.ResourceFilters{{
ResourceDescription: kyvernov1.ResourceDescription{
Kinds: []string{"ConfigMap"},
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"foo": "bar",
},
},
Operations: []kyvernov1.AdmissionOperation{kyvernov1.Connect},
},
}},
},
},
defaultOps: []kyvernov1.AdmissionOperation{kyvernov1.Create, kyvernov1.Update},
want: webhookConfig{
"ConfigMap": sets.New(allOperations...),
"Secret": sets.New(kyvernov1.Update),
},
}, {
name: "exclude - with ns selector",
rule: kyvernov1.Rule{
MatchResources: kyvernov1.MatchResources{
Any: kyvernov1.ResourceFilters{{
ResourceDescription: kyvernov1.ResourceDescription{
Kinds: []string{"ConfigMap"},
Operations: allOperations,
},
}, {
ResourceDescription: kyvernov1.ResourceDescription{
Kinds: []string{"Secret"},
Operations: []kyvernov1.AdmissionOperation{kyvernov1.Update},
},
}},
},
ExcludeResources: &kyvernov1.MatchResources{
Any: kyvernov1.ResourceFilters{{
ResourceDescription: kyvernov1.ResourceDescription{
Kinds: []string{"ConfigMap"},
NamespaceSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"foo": "bar",
},
},
Operations: []kyvernov1.AdmissionOperation{kyvernov1.Connect},
},
}},
},
},
defaultOps: []kyvernov1.AdmissionOperation{kyvernov1.Create, kyvernov1.Update},
want: webhookConfig{
"ConfigMap": sets.New(allOperations...),
"Secret": sets.New(kyvernov1.Update),
},
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := collectResourceDescriptions(tt.rule, tt.defaultOps...)
assert.Equal(t, tt.want, got)
})
}
}