mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-14 19:58:45 +00:00
fix: process the matched resources only for mutate existing policies (#10164)
* fix: process the matched resources only for mutate existing policies Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com> * fix lint issue Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com> * chore: add unit tests Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com> --------- Signed-off-by: Mariam Fahmy <mariam.fahmy@nirmata.com> Co-authored-by: shuting <shuting@nirmata.com>
This commit is contained in:
parent
5534ac335a
commit
59ff771ae8
5 changed files with 210 additions and 140 deletions
|
@ -40,7 +40,7 @@ func (pc *policyController) handleGenerateForExisting(policy kyvernov1.PolicyInt
|
||||||
var errors []error
|
var errors []error
|
||||||
for _, rule := range policy.GetSpec().Rules {
|
for _, rule := range policy.GetSpec().Rules {
|
||||||
ruleType := kyvernov1beta1.Generate
|
ruleType := kyvernov1beta1.Generate
|
||||||
triggers := generateTriggers(pc.client, rule, pc.log)
|
triggers := getTriggers(pc.client, rule, policy.IsNamespaced(), policy.GetNamespace(), pc.log)
|
||||||
for _, trigger := range triggers {
|
for _, trigger := range triggers {
|
||||||
ur := newUR(policy, common.ResourceSpecFromUnstructured(*trigger), rule.Name, ruleType, false)
|
ur := newUR(policy, common.ResourceSpecFromUnstructured(*trigger), rule.Name, ruleType, false)
|
||||||
skip, err := pc.handleUpdateRequest(ur, trigger, rule, policy)
|
skip, err := pc.handleUpdateRequest(ur, trigger, rule, policy)
|
||||||
|
|
|
@ -18,7 +18,7 @@ func (pc *policyController) handleMutate(policyKey string, policy kyvernov1.Poli
|
||||||
var ruleType kyvernov1beta1.RequestType
|
var ruleType kyvernov1beta1.RequestType
|
||||||
if rule.HasMutateExisting() {
|
if rule.HasMutateExisting() {
|
||||||
ruleType = kyvernov1beta1.Mutate
|
ruleType = kyvernov1beta1.Mutate
|
||||||
triggers := generateTriggers(pc.client, rule, pc.log)
|
triggers := getTriggers(pc.client, rule, policy.IsNamespaced(), policy.GetNamespace(), pc.log)
|
||||||
for _, trigger := range triggers {
|
for _, trigger := range triggers {
|
||||||
murs := pc.listMutateURs(policyKey, trigger)
|
murs := pc.listMutateURs(policyKey, trigger)
|
||||||
if murs != nil {
|
if murs != nil {
|
||||||
|
|
|
@ -424,18 +424,58 @@ func (pc *policyController) handleUpdateRequest(ur *kyvernov1beta1.UpdateRequest
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateTriggers(client dclient.Interface, rule kyvernov1.Rule, log logr.Logger) []*unstructured.Unstructured {
|
func getTriggers(client dclient.Interface, rule kyvernov1.Rule, isNamespacedPolicy bool, policyNamespace string, log logr.Logger) []*unstructured.Unstructured {
|
||||||
list := &unstructured.UnstructuredList{}
|
var resources []*unstructured.Unstructured
|
||||||
|
|
||||||
kinds := fetchUniqueKinds(rule)
|
appendResources := func(match kyvernov1.ResourceDescription) {
|
||||||
|
resources = append(resources, getResources(client, policyNamespace, isNamespacedPolicy, match, log)...)
|
||||||
|
}
|
||||||
|
|
||||||
for _, kind := range kinds {
|
if !rule.MatchResources.ResourceDescription.IsEmpty() {
|
||||||
mlist, err := client.ListResource(context.TODO(), "", kind, "", rule.MatchResources.Selector)
|
appendResources(rule.MatchResources.ResourceDescription)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, any := range rule.MatchResources.Any {
|
||||||
|
appendResources(any.ResourceDescription)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, all := range rule.MatchResources.All {
|
||||||
|
appendResources(all.ResourceDescription)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resources
|
||||||
|
}
|
||||||
|
|
||||||
|
func getResources(client dclient.Interface, policyNs string, isNamespacedPolicy bool, match kyvernov1.ResourceDescription, log logr.Logger) []*unstructured.Unstructured {
|
||||||
|
var items []*unstructured.Unstructured
|
||||||
|
|
||||||
|
for _, kind := range match.Kinds {
|
||||||
|
group, version, kind, _ := kubeutils.ParseKindSelector(kind)
|
||||||
|
|
||||||
|
namespace := ""
|
||||||
|
if isNamespacedPolicy {
|
||||||
|
namespace = policyNs
|
||||||
|
}
|
||||||
|
|
||||||
|
groupVersion := ""
|
||||||
|
if group != "*" && version != "*" {
|
||||||
|
groupVersion = group + "/" + version
|
||||||
|
} else if version != "*" {
|
||||||
|
groupVersion = version
|
||||||
|
}
|
||||||
|
|
||||||
|
resources, err := client.ListResource(context.TODO(), groupVersion, kind, namespace, match.Selector)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err, "failed to list matched resource")
|
log.Error(err, "failed to list matched resource")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
list.Items = append(list.Items, mlist.Items...)
|
|
||||||
|
for i, res := range resources.Items {
|
||||||
|
if !resourceMatches(match, res, isNamespacedPolicy) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
items = append(items, &resources.Items[i])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return convertlist(list.Items)
|
return items
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,29 +3,28 @@ package policy
|
||||||
import (
|
import (
|
||||||
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
|
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
"k8s.io/apimachinery/pkg/util/sets"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func fetchUniqueKinds(rule kyvernov1.Rule) []string {
|
func resourceMatches(match kyvernov1.ResourceDescription, res unstructured.Unstructured, isNamespacedPolicy bool) bool {
|
||||||
kinds := sets.New(rule.MatchResources.Kinds...)
|
if match.Name != "" && res.GetName() != match.Name {
|
||||||
|
return false
|
||||||
for _, any := range rule.MatchResources.Any {
|
|
||||||
kinds.Insert(any.Kinds...)
|
|
||||||
}
|
}
|
||||||
|
if len(match.Names) > 0 && !contains(match.Names, res.GetName()) {
|
||||||
for _, all := range rule.MatchResources.All {
|
return false
|
||||||
kinds.Insert(all.Kinds...)
|
|
||||||
}
|
}
|
||||||
|
if !isNamespacedPolicy && len(match.Namespaces) > 0 && !contains(match.Namespaces, res.GetNamespace()) {
|
||||||
return kinds.UnsortedList()
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertlist(ulists []unstructured.Unstructured) []*unstructured.Unstructured {
|
func contains(slice []string, item string) bool {
|
||||||
result := make([]*unstructured.Unstructured, 0, len(ulists))
|
for _, s := range slice {
|
||||||
for _, list := range ulists {
|
if s == item {
|
||||||
result = append(result, list.DeepCopy())
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return result
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func castPolicy(p interface{}) kyvernov1.PolicyInterface {
|
func castPolicy(p interface{}) kyvernov1.PolicyInterface {
|
||||||
|
|
|
@ -1,148 +1,179 @@
|
||||||
package policy
|
package policy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
kyverno "github.com/kyverno/kyverno/api/kyverno/v1"
|
kyverno "github.com/kyverno/kyverno/api/kyverno/v1"
|
||||||
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
|
|
||||||
"gotest.tools/assert"
|
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_fetchUniqueKinds(t *testing.T) {
|
func Test_resourceMatches(t *testing.T) {
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
rule kyverno.Rule
|
match kyverno.ResourceDescription
|
||||||
want []string
|
res unstructured.Unstructured
|
||||||
|
isNamespacedPolicy bool
|
||||||
|
want bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "Unique MatchResource kinds",
|
name: "Matching resource based on its name",
|
||||||
rule: kyverno.Rule{
|
match: kyverno.ResourceDescription{
|
||||||
MatchResources: kyverno.MatchResources{
|
Kinds: []string{"Pod"},
|
||||||
ResourceDescription: kyverno.ResourceDescription{
|
Names: []string{"my-pod", "test-pod"},
|
||||||
Kinds: []string{"kind1", "kind2"},
|
},
|
||||||
|
res: unstructured.Unstructured{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "Pod",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "my-pod",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: []string{"kind1", "kind2"},
|
isNamespacedPolicy: false,
|
||||||
},
|
want: true,
|
||||||
|
|
||||||
{
|
|
||||||
name: "Any with same kind are valid",
|
|
||||||
rule: kyverno.Rule{
|
|
||||||
MatchResources: kyverno.MatchResources{
|
|
||||||
Any: []kyverno.ResourceFilter{
|
|
||||||
{
|
|
||||||
ResourceDescription: kyverno.ResourceDescription{
|
|
||||||
Kinds: []string{"kind1", "kind2"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ResourceDescription: kyverno.ResourceDescription{
|
|
||||||
Kinds: []string{"kind1", "kind3"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
want: []string{"kind1", "kind2", "kind3"},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Match with All and Any kind",
|
name: "Non-matching resource based on its name",
|
||||||
rule: kyverno.Rule{
|
match: kyverno.ResourceDescription{
|
||||||
MatchResources: kyverno.MatchResources{
|
Kinds: []string{"Pod"},
|
||||||
All: []kyverno.ResourceFilter{
|
Names: []string{"test-pod", "test-pod-1"},
|
||||||
{
|
},
|
||||||
ResourceDescription: kyverno.ResourceDescription{
|
res: unstructured.Unstructured{
|
||||||
Kinds: []string{"kind1"},
|
Object: map[string]interface{}{
|
||||||
},
|
"apiVersion": "v1",
|
||||||
},
|
"kind": "Pod",
|
||||||
},
|
"metadata": map[string]interface{}{
|
||||||
Any: []kyverno.ResourceFilter{
|
"name": "my-pod",
|
||||||
{
|
|
||||||
ResourceDescription: kyverno.ResourceDescription{
|
|
||||||
Kinds: []string{"kind1", "kind2"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: []string{"kind1", "kind2"},
|
isNamespacedPolicy: false,
|
||||||
|
want: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Match with different All and Any kind",
|
name: "Matching resource based on its namespace",
|
||||||
rule: kyverno.Rule{
|
match: kyverno.ResourceDescription{
|
||||||
MatchResources: kyverno.MatchResources{
|
Namespaces: []string{"test-ns"},
|
||||||
All: []kyverno.ResourceFilter{
|
Kinds: []string{"Pod"},
|
||||||
{
|
},
|
||||||
ResourceDescription: kyverno.ResourceDescription{
|
res: unstructured.Unstructured{
|
||||||
Kinds: []string{"kind4", "kind5"},
|
Object: map[string]interface{}{
|
||||||
},
|
"apiVersion": "v1",
|
||||||
},
|
"kind": "Pod",
|
||||||
},
|
"metadata": map[string]interface{}{
|
||||||
Any: []kyverno.ResourceFilter{
|
"name": "my-pod",
|
||||||
{
|
"namespace": "test-ns",
|
||||||
ResourceDescription: kyverno.ResourceDescription{
|
|
||||||
Kinds: []string{"kind1", "kind2"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: []string{"kind1", "kind2", "kind4", "kind5"},
|
isNamespacedPolicy: false,
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Non-matching resource based on its namespace",
|
||||||
|
match: kyverno.ResourceDescription{
|
||||||
|
Namespaces: []string{"test-ns"},
|
||||||
|
Kinds: []string{"Pod"},
|
||||||
|
},
|
||||||
|
res: unstructured.Unstructured{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "Pod",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "my-pod",
|
||||||
|
"namespace": "default",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
isNamespacedPolicy: false,
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Matching resource with a namespaced policy",
|
||||||
|
match: kyverno.ResourceDescription{
|
||||||
|
Namespaces: []string{"test-ns"},
|
||||||
|
Kinds: []string{"Pod"},
|
||||||
|
},
|
||||||
|
res: unstructured.Unstructured{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "Pod",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "my-pod",
|
||||||
|
"namespace": "default",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
isNamespacedPolicy: true,
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Matching resource based on its name and namespace",
|
||||||
|
match: kyverno.ResourceDescription{
|
||||||
|
Namespaces: []string{"test-ns"},
|
||||||
|
Kinds: []string{"Pod"},
|
||||||
|
Names: []string{"my-pod"},
|
||||||
|
},
|
||||||
|
res: unstructured.Unstructured{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "Pod",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "my-pod",
|
||||||
|
"namespace": "test-ns",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
isNamespacedPolicy: false,
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Non-matching resource based on its name and namespace",
|
||||||
|
match: kyverno.ResourceDescription{
|
||||||
|
Namespaces: []string{"test-ns"},
|
||||||
|
Kinds: []string{"Pod"},
|
||||||
|
Names: []string{"my-pod"},
|
||||||
|
},
|
||||||
|
res: unstructured.Unstructured{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "Pod",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "my-pod",
|
||||||
|
"namespace": "default",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
isNamespacedPolicy: false,
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Non-matching resource based on its name and namespace",
|
||||||
|
match: kyverno.ResourceDescription{
|
||||||
|
Namespaces: []string{"test-ns"},
|
||||||
|
Kinds: []string{"Pod"},
|
||||||
|
Names: []string{"test-pod-1", "test-pod-2"},
|
||||||
|
},
|
||||||
|
res: unstructured.Unstructured{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "Pod",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "my-pod",
|
||||||
|
"namespace": "test-ns",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
isNamespacedPolicy: false,
|
||||||
|
want: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
kinds := fetchUniqueKinds(tt.rule)
|
if got := resourceMatches(tt.match, tt.res, tt.isNamespacedPolicy); got != tt.want {
|
||||||
for _, want := range tt.want {
|
t.Errorf("resourceMatches() = %v, want %v", got, tt.want)
|
||||||
if !kubeutils.ContainsKind(kinds, want) {
|
|
||||||
assert.Error(t, fmt.Errorf("%s fails, expected %s", tt.name, want), "")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_convertlist(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
ulists []unstructured.Unstructured
|
|
||||||
want []*unstructured.Unstructured
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Convert list",
|
|
||||||
ulists: []unstructured.Unstructured{
|
|
||||||
{
|
|
||||||
Object: map[string]interface{}{
|
|
||||||
"kind": "kind1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Object: map[string]interface{}{
|
|
||||||
"namespace": "ns-1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
want: []*unstructured.Unstructured{
|
|
||||||
{
|
|
||||||
Object: map[string]interface{}{
|
|
||||||
"kind": "kind1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Object: map[string]interface{}{
|
|
||||||
"namespace": "ns-1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
assert.DeepEqual(t, convertlist(tt.ulists), tt.want)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue