From fac7a15d7d69a23a2687ea77e974ee675a1b352c Mon Sep 17 00:00:00 2001 From: shravan Date: Fri, 20 Mar 2020 20:23:34 +0530 Subject: [PATCH 01/14] 753 prototype changes --- pkg/policy/validate.go | 97 +++++++++++++++++++++++++++++++++ pkg/policy/validate_test.go | 106 ++++++++++++++++++++++++++++++++++++ 2 files changed, 203 insertions(+) diff --git a/pkg/policy/validate.go b/pkg/policy/validate.go index 4789687afa..038fe0b5e8 100644 --- a/pkg/policy/validate.go +++ b/pkg/policy/validate.go @@ -1,6 +1,7 @@ package policy import ( + "encoding/json" "errors" "fmt" "reflect" @@ -52,6 +53,11 @@ func Validate(p kyverno.ClusterPolicy) error { // as there are more than 1 operation in rule, not need to evaluate it further return fmt.Errorf("path: spec.rules[%d]: %v", i, err) } + + if err := validateMatchExcludeConflict(rule); err != nil { + return fmt.Errorf("path: spec.rules[%d]: %v", i, err) + } + // Operation Validation // Mutation if rule.HasMutate() { @@ -89,6 +95,97 @@ func Validate(p kyverno.ClusterPolicy) error { return nil } +func validateMatchExcludeConflict(rule kyverno.Rule) error { + + excludeRoles := make(map[string]bool) + for _, role := range rule.ExcludeResources.UserInfo.Roles { + excludeRoles[role] = true + } + + excludeClusterRoles := make(map[string]bool) + for _, clusterRoles := range rule.ExcludeResources.UserInfo.ClusterRoles { + excludeClusterRoles[clusterRoles] = true + } + + excludeSubjects := make(map[string]bool) + for _, subject := range rule.ExcludeResources.UserInfo.Subjects { + subjectRaw, _ := json.Marshal(subject) + excludeSubjects[string(subjectRaw)] = true + } + + excludeKinds := make(map[string]bool) + for _, kind := range rule.ExcludeResources.ResourceDescription.Kinds { + excludeKinds[kind] = true + } + + excludeNamespaces := make(map[string]bool) + for _, namespace := range rule.ExcludeResources.ResourceDescription.Namespaces { + excludeNamespaces[namespace] = true + } + + excludeMatchExpressions := make(map[string]bool) + if rule.ExcludeResources.ResourceDescription.Selector != nil { + for _, matchExpression := range rule.ExcludeResources.ResourceDescription.Selector.MatchExpressions { + matchExpressionRaw, _ := json.Marshal(matchExpression) + excludeMatchExpressions[string(matchExpressionRaw)] = true + } + } + + for _, role := range rule.MatchResources.UserInfo.Roles { + if excludeRoles[role] { + return errors.New(fmt.Sprintf("excluding role '%v' while also matching it - please remove from both match and exclude", role)) + } + } + + for _, clusterRole := range rule.MatchResources.UserInfo.ClusterRoles { + if excludeClusterRoles[clusterRole] { + return errors.New(fmt.Sprintf("excluding cluster role '%v' while also matching it - please remove from both match and exclude", clusterRole)) + } + } + + for _, subject := range rule.MatchResources.UserInfo.Subjects { + subjectRaw, _ := json.Marshal(subject) + if excludeSubjects[string(subjectRaw)] { + return errors.New(fmt.Sprintf("excluding subject '%v' while also matching it - please remove from both match and exclude", string(subjectRaw))) + } + } + + if rule.MatchResources.ResourceDescription.Name != "" { + if rule.MatchResources.ResourceDescription.Name == rule.ExcludeResources.ResourceDescription.Name { + return errors.New(fmt.Sprintf("excluding resource name '%v' while also matching it - please remove from both match and exclude", rule.MatchResources.ResourceDescription.Name)) + } + } + + for _, namespace := range rule.MatchResources.ResourceDescription.Namespaces { + if excludeNamespaces[namespace] { + return errors.New(fmt.Sprintf("excluding resource namespace '%v' while also matching it - please remove from both match and exclude", namespace)) + } + } + + for _, kind := range rule.MatchResources.ResourceDescription.Kinds { + if excludeKinds[kind] { + return errors.New(fmt.Sprintf("excluding resource kind '%v' while also matching it - please remove from both match and exclude", kind)) + } + } + + if rule.MatchResources.ResourceDescription.Selector != nil && rule.ExcludeResources.ResourceDescription.Selector != nil { + for _, matchExpression := range rule.MatchResources.ResourceDescription.Selector.MatchExpressions { + matchExpressionRaw, _ := json.Marshal(matchExpression) + if excludeMatchExpressions[string(matchExpressionRaw)] { + return errors.New(fmt.Sprintf("excluding resource match expression '%v' while also matching it - please remove from both match and exclude", string(matchExpressionRaw))) + } + } + + for label, value := range rule.MatchResources.ResourceDescription.Selector.MatchLabels { + if rule.ExcludeResources.ResourceDescription.Selector.MatchLabels[label] == value { + return errors.New(fmt.Sprintf("excluding resource label '%v' while also matching it - please remove from both match and exclude", label)) + } + } + } + + return nil +} + func ruleOnlyDealsWithResourceMetaData(rule kyverno.Rule) bool { overlayMap, _ := rule.Mutation.Overlay.(map[string]interface{}) for k := range overlayMap { diff --git a/pkg/policy/validate_test.go b/pkg/policy/validate_test.go index 387c87707b..9f91ab8025 100644 --- a/pkg/policy/validate_test.go +++ b/pkg/policy/validate_test.go @@ -1541,3 +1541,109 @@ func Test_ruleOnlyDealsWithResourceMetaData(t *testing.T) { } } } + +func Test_validateMatchExcludeConflict(t *testing.T) { + testcases := []struct { + description string + rule []byte + expectedError bool + }{ + { + description: "Testing cluster roles - fail", + rule: []byte(`{"name":"set-image-pull-policy-2","match":{"resources":{"kinds":["Pod","Namespace"],"name":"something","namespaces":["something","something1"],"selector":{"matchLabels":{"memory":"high"},"matchExpressions":[{"key":"tier","operator":"In","values":["database"]}]}},"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"},{"name":"something1","kind":"something1","Namespace":"something1","apiGroup":"something1"}],"clusterroles":["something","something1"],"roles":["something","something1"]},"exclude":{"clusterroles":["something"]}}`), + expectedError: true, + }, + { + description: "Testing cluster roles - pass", + rule: []byte(`{"name":"set-image-pull-policy-2","match":{"resources":{"kinds":["Pod","Namespace"],"name":"something","namespaces":["something","something1"],"selector":{"matchLabels":{"memory":"high"},"matchExpressions":[{"key":"tier","operator":"In","values":["database"]}]}},"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"},{"name":"something1","kind":"something1","Namespace":"something1","apiGroup":"something1"}],"clusterroles":["something","something1"],"roles":["something","something1"]},"exclude":{"clusterroles":["something2"]}}`), + expectedError: false, + }, + { + description: "Testing roles - fail", + rule: []byte(`{"name":"set-image-pull-policy-2","match":{"resources":{"kinds":["Pod","Namespace"],"name":"something","namespaces":["something","something1"],"selector":{"matchLabels":{"memory":"high"},"matchExpressions":[{"key":"tier","operator":"In","values":["database"]}]}},"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"},{"name":"something1","kind":"something1","Namespace":"something1","apiGroup":"something1"}],"clusterroles":["something","something1"],"roles":["something","something1"]},"exclude":{"roles":["something"]}}`), + expectedError: true, + }, + { + description: "Testing roles - pass", + rule: []byte(`{"name":"set-image-pull-policy-2","match":{"resources":{"kinds":["Pod","Namespace"],"name":"something","namespaces":["something","something1"],"selector":{"matchLabels":{"memory":"high"},"matchExpressions":[{"key":"tier","operator":"In","values":["database"]}]}},"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"},{"name":"something1","kind":"something1","Namespace":"something1","apiGroup":"something1"}],"clusterroles":["something","something1"],"roles":["something","something1"]},"exclude":{"roles":["something2"]}}`), + expectedError: false, + }, + { + description: "Testing subjects - fail", + rule: []byte(`{"name":"set-image-pull-policy-2","match":{"resources":{"kinds":["Pod","Namespace"],"name":"something","namespaces":["something","something1"],"selector":{"matchLabels":{"memory":"high"},"matchExpressions":[{"key":"tier","operator":"In","values":["database"]}]}},"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"},{"name":"something1","kind":"something1","Namespace":"something1","apiGroup":"something1"}],"clusterroles":["something","something1"],"roles":["something","something1"]},"exclude":{"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"}]}}`), + expectedError: true, + }, + { + description: "Testing subjects - pass", + rule: []byte(`{"name":"set-image-pull-policy-2","match":{"resources":{"kinds":["Pod","Namespace"],"name":"something","namespaces":["something","something1"],"selector":{"matchLabels":{"memory":"high"},"matchExpressions":[{"key":"tier","operator":"In","values":["database"]}]}},"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"},{"name":"something1","kind":"something1","Namespace":"something1","apiGroup":"something1"}],"clusterroles":["something","something1"],"roles":["something","something1"]},"exclude":{"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something1"}]}}`), + expectedError: false, + }, + { + description: "Testing resource kind - fail", + rule: []byte(`{"name":"set-image-pull-policy-2","match":{"resources":{"kinds":["Pod","Namespace"],"name":"something","namespaces":["something","something1"],"selector":{"matchLabels":{"memory":"high"},"matchExpressions":[{"key":"tier","operator":"In","values":["database"]}]}},"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"},{"name":"something1","kind":"something1","Namespace":"something1","apiGroup":"something1"}],"clusterroles":["something","something1"],"roles":["something","something1"]},"exclude":{"resources":{"kinds":["Pod","Namespace1"]}}}`), + expectedError: true, + }, + { + description: "Testing resource kind - pass", + rule: []byte(`{"name":"set-image-pull-policy-2","match":{"resources":{"kinds":["Pod","Namespace"],"name":"something","namespaces":["something","something1"],"selector":{"matchLabels":{"memory":"high"},"matchExpressions":[{"key":"tier","operator":"In","values":["database"]}]}},"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"},{"name":"something1","kind":"something1","Namespace":"something1","apiGroup":"something1"}],"clusterroles":["something","something1"],"roles":["something","something1"]},"exclude":{"resources":{"kinds":["Pod1","Namespace1"]}}}`), + expectedError: false, + }, + { + description: "Testing resource name - fail", + rule: []byte(`{"name":"set-image-pull-policy-2","match":{"resources":{"kinds":["Pod","Namespace"],"name":"something","namespaces":["something","something1"],"selector":{"matchLabels":{"memory":"high"},"matchExpressions":[{"key":"tier","operator":"In","values":["database"]}]}},"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"},{"name":"something1","kind":"something1","Namespace":"something1","apiGroup":"something1"}],"clusterroles":["something","something1"],"roles":["something","something1"]},"exclude":{"resources":{"name":"something"}}}`), + expectedError: true, + }, + { + description: "Testing resource name - pass", + rule: []byte(`{"name":"set-image-pull-policy-2","match":{"resources":{"kinds":["Pod","Namespace"],"name":"something","namespaces":["something","something1"],"selector":{"matchLabels":{"memory":"high"},"matchExpressions":[{"key":"tier","operator":"In","values":["database"]}]}},"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"},{"name":"something1","kind":"something1","Namespace":"something1","apiGroup":"something1"}],"clusterroles":["something","something1"],"roles":["something","something1"]},"exclude":{"resources":{"name":"something1"}}}`), + expectedError: false, + }, + { + description: "Testing resource namespace - fail", + rule: []byte(`{"name":"set-image-pull-policy-2","match":{"resources":{"kinds":["Pod","Namespace"],"name":"something","namespaces":["something","something1"],"selector":{"matchLabels":{"memory":"high"},"matchExpressions":[{"key":"tier","operator":"In","values":["database"]}]}},"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"},{"name":"something1","kind":"something1","Namespace":"something1","apiGroup":"something1"}],"clusterroles":["something","something1"],"roles":["something","something1"]},"exclude":{"resources":{"namespaces":["something2","something1"]}}}`), + expectedError: true, + }, + { + description: "Testing resource namespace - pass", + rule: []byte(`{"name":"set-image-pull-policy-2","match":{"resources":{"kinds":["Pod","Namespace"],"name":"something","namespaces":["something","something1"],"selector":{"matchLabels":{"memory":"high"},"matchExpressions":[{"key":"tier","operator":"In","values":["database"]}]}},"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"},{"name":"something1","kind":"something1","Namespace":"something1","apiGroup":"something1"}],"clusterroles":["something","something1"],"roles":["something","something1"]},"exclude":{"resources":{"namespaces":["something2","something3"]}}}`), + expectedError: false, + }, + { + description: "Testing resource selector label - fail", + rule: []byte(`{"name":"set-image-pull-policy-2","match":{"resources":{"kinds":["Pod","Namespace"],"name":"something","namespaces":["something","something1"],"selector":{"matchLabels":{"memory":"high"},"matchExpressions":[{"key":"tier","operator":"In","values":["database"]}]}},"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"},{"name":"something1","kind":"something1","Namespace":"something1","apiGroup":"something1"}],"clusterroles":["something","something1"],"roles":["something","something1"]},"exclude":{"resources":{"selector":{"matchLabels":{"memory":"high"}}}}}`), + expectedError: true, + }, + { + description: "Testing resource selector label - pass", + rule: []byte(`{"name":"set-image-pull-policy-2","match":{"resources":{"kinds":["Pod","Namespace"],"name":"something","namespaces":["something","something1"],"selector":{"matchLabels":{"memory":"high"},"matchExpressions":[{"key":"tier","operator":"In","values":["database"]}]}},"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"},{"name":"something1","kind":"something1","Namespace":"something1","apiGroup":"something1"}],"clusterroles":["something","something1"],"roles":["something","something1"]},"exclude":{"resources":{"selector":{"matchLabels":{"memory":"high1"}}}}}`), + expectedError: false, + }, + { + description: "Testing resource selector match expression - fail", + rule: []byte(`{"name":"set-image-pull-policy-2","match":{"resources":{"kinds":["Pod","Namespace"],"name":"something","namespaces":["something","something1"],"selector":{"matchLabels":{"memory":"high"},"matchExpressions":[{"key":"tier","operator":"In","values":["database"]}]}},"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"},{"name":"something1","kind":"something1","Namespace":"something1","apiGroup":"something1"}],"clusterroles":["something","something1"],"roles":["something","something1"]},"exclude":{"resources":{"selector":{"matchExpressions":[{"key":"tier1","operator":"In","values":["database"]},{"key":"tier","operator":"In","values":["database"]}]}}}}`), + expectedError: true, + }, + { + description: "Testing resource selector match expression - pass", + rule: []byte(`{"name":"set-image-pull-policy-2","match":{"resources":{"kinds":["Pod","Namespace"],"name":"something","namespaces":["something","something1"],"selector":{"matchLabels":{"memory":"high"},"matchExpressions":[{"key":"tier","operator":"In","values":["database"]}]}},"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"},{"name":"something1","kind":"something1","Namespace":"something1","apiGroup":"something1"}],"clusterroles":["something","something1"],"roles":["something","something1"]},"exclude":{"resources":{"selector":{"matchExpressions":[{"key":"tier1","operator":"In","values":["database"]},{"key":"tier2","operator":"In","values":["database"]}]}}}}`), + expectedError: false, + }, + } + + for i, testcase := range testcases { + var rule kyverno.Rule + _ = json.Unmarshal(testcase.rule, &rule) + err := validateMatchExcludeConflict(rule) + + var gotError bool + if err != nil { + gotError = true + } else { + gotError = false + } + + if gotError != testcase.expectedError { + t.Errorf("Testcase [%d] failed - description - %v", i+1, testcase.description) + } + } +} From dfbf247ad8424bf555415a26ccbf8b63a3012c60 Mon Sep 17 00:00:00 2001 From: shravan Date: Fri, 20 Mar 2020 20:35:26 +0530 Subject: [PATCH 02/14] 753 circle ci fixes --- pkg/policy/validate.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pkg/policy/validate.go b/pkg/policy/validate.go index 038fe0b5e8..fd6acfdfb8 100644 --- a/pkg/policy/validate.go +++ b/pkg/policy/validate.go @@ -133,38 +133,38 @@ func validateMatchExcludeConflict(rule kyverno.Rule) error { for _, role := range rule.MatchResources.UserInfo.Roles { if excludeRoles[role] { - return errors.New(fmt.Sprintf("excluding role '%v' while also matching it - please remove from both match and exclude", role)) + return fmt.Errorf("excluding role '%v' while also matching it - please remove from both match and exclude", role) } } for _, clusterRole := range rule.MatchResources.UserInfo.ClusterRoles { if excludeClusterRoles[clusterRole] { - return errors.New(fmt.Sprintf("excluding cluster role '%v' while also matching it - please remove from both match and exclude", clusterRole)) + return fmt.Errorf("excluding cluster role '%v' while also matching it - please remove from both match and exclude", clusterRole) } } for _, subject := range rule.MatchResources.UserInfo.Subjects { subjectRaw, _ := json.Marshal(subject) if excludeSubjects[string(subjectRaw)] { - return errors.New(fmt.Sprintf("excluding subject '%v' while also matching it - please remove from both match and exclude", string(subjectRaw))) + return fmt.Errorf("excluding subject '%v' while also matching it - please remove from both match and exclude", string(subjectRaw)) } } if rule.MatchResources.ResourceDescription.Name != "" { if rule.MatchResources.ResourceDescription.Name == rule.ExcludeResources.ResourceDescription.Name { - return errors.New(fmt.Sprintf("excluding resource name '%v' while also matching it - please remove from both match and exclude", rule.MatchResources.ResourceDescription.Name)) + return fmt.Errorf("excluding resource name '%v' while also matching it - please remove from both match and exclude", rule.MatchResources.ResourceDescription.Name) } } for _, namespace := range rule.MatchResources.ResourceDescription.Namespaces { if excludeNamespaces[namespace] { - return errors.New(fmt.Sprintf("excluding resource namespace '%v' while also matching it - please remove from both match and exclude", namespace)) + return fmt.Errorf("excluding resource namespace '%v' while also matching it - please remove from both match and exclude", namespace) } } for _, kind := range rule.MatchResources.ResourceDescription.Kinds { if excludeKinds[kind] { - return errors.New(fmt.Sprintf("excluding resource kind '%v' while also matching it - please remove from both match and exclude", kind)) + return fmt.Errorf("excluding resource kind '%v' while also matching it - please remove from both match and exclude", kind) } } @@ -172,13 +172,13 @@ func validateMatchExcludeConflict(rule kyverno.Rule) error { for _, matchExpression := range rule.MatchResources.ResourceDescription.Selector.MatchExpressions { matchExpressionRaw, _ := json.Marshal(matchExpression) if excludeMatchExpressions[string(matchExpressionRaw)] { - return errors.New(fmt.Sprintf("excluding resource match expression '%v' while also matching it - please remove from both match and exclude", string(matchExpressionRaw))) + return fmt.Errorf("excluding resource match expression '%v' while also matching it - please remove from both match and exclude", string(matchExpressionRaw)) } } for label, value := range rule.MatchResources.ResourceDescription.Selector.MatchLabels { if rule.ExcludeResources.ResourceDescription.Selector.MatchLabels[label] == value { - return errors.New(fmt.Sprintf("excluding resource label '%v' while also matching it - please remove from both match and exclude", label)) + return fmt.Errorf("excluding resource label '%v' while also matching it - please remove from both match and exclude", label) } } } From 93205ecbbf09fb832adc71eb64ebbcb487e9c869 Mon Sep 17 00:00:00 2001 From: shravan Date: Tue, 24 Mar 2020 08:45:44 +0530 Subject: [PATCH 03/14] 753 dummy commit --- pkg/policy/validate_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/policy/validate_test.go b/pkg/policy/validate_test.go index 9f91ab8025..810a1054fa 100644 --- a/pkg/policy/validate_test.go +++ b/pkg/policy/validate_test.go @@ -1628,6 +1628,11 @@ func Test_validateMatchExcludeConflict(t *testing.T) { rule: []byte(`{"name":"set-image-pull-policy-2","match":{"resources":{"kinds":["Pod","Namespace"],"name":"something","namespaces":["something","something1"],"selector":{"matchLabels":{"memory":"high"},"matchExpressions":[{"key":"tier","operator":"In","values":["database"]}]}},"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"},{"name":"something1","kind":"something1","Namespace":"something1","apiGroup":"something1"}],"clusterroles":["something","something1"],"roles":["something","something1"]},"exclude":{"resources":{"selector":{"matchExpressions":[{"key":"tier1","operator":"In","values":["database"]},{"key":"tier2","operator":"In","values":["database"]}]}}}}`), expectedError: false, }, + //{ + // description: "Testing resource selector match expression - pass", + // rule: []byte(`{"name":"set-image-pull-policy-2","match":{"resources":{"kinds":["Pod","Namespace"],"name":"something","namespaces":["something","something1"],"selector":{"matchLabels":{"memory":"high"},"matchExpressions":[{"key":"tier","operator":"In","values":["database"]}]}},"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"},{"name":"something1","kind":"something1","Namespace":"something1","apiGroup":"something1"}],"clusterroles":["something","something1"],"roles":["something","something1"]},"exclude":{"resources":{"selector":{"matchExpressions":[{"key":"tier1","operator":"In","values":["database"]},{"key":"tier2","operator":"In","values":["database"]}]}}}}`), + // expectedError: false, + //}, } for i, testcase := range testcases { From 80cda4668b5dee98c31ef494bc1a7227c1cf7a31 Mon Sep 17 00:00:00 2001 From: shravan Date: Tue, 24 Mar 2020 08:50:07 +0530 Subject: [PATCH 04/14] 753 dummy commit --- pkg/policy/validate_test.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pkg/policy/validate_test.go b/pkg/policy/validate_test.go index 810a1054fa..9f91ab8025 100644 --- a/pkg/policy/validate_test.go +++ b/pkg/policy/validate_test.go @@ -1628,11 +1628,6 @@ func Test_validateMatchExcludeConflict(t *testing.T) { rule: []byte(`{"name":"set-image-pull-policy-2","match":{"resources":{"kinds":["Pod","Namespace"],"name":"something","namespaces":["something","something1"],"selector":{"matchLabels":{"memory":"high"},"matchExpressions":[{"key":"tier","operator":"In","values":["database"]}]}},"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"},{"name":"something1","kind":"something1","Namespace":"something1","apiGroup":"something1"}],"clusterroles":["something","something1"],"roles":["something","something1"]},"exclude":{"resources":{"selector":{"matchExpressions":[{"key":"tier1","operator":"In","values":["database"]},{"key":"tier2","operator":"In","values":["database"]}]}}}}`), expectedError: false, }, - //{ - // description: "Testing resource selector match expression - pass", - // rule: []byte(`{"name":"set-image-pull-policy-2","match":{"resources":{"kinds":["Pod","Namespace"],"name":"something","namespaces":["something","something1"],"selector":{"matchLabels":{"memory":"high"},"matchExpressions":[{"key":"tier","operator":"In","values":["database"]}]}},"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"},{"name":"something1","kind":"something1","Namespace":"something1","apiGroup":"something1"}],"clusterroles":["something","something1"],"roles":["something","something1"]},"exclude":{"resources":{"selector":{"matchExpressions":[{"key":"tier1","operator":"In","values":["database"]},{"key":"tier2","operator":"In","values":["database"]}]}}}}`), - // expectedError: false, - //}, } for i, testcase := range testcases { From b2ae45c856454962133ccb5429e1bbad8bbb9cfd Mon Sep 17 00:00:00 2001 From: shravan Date: Fri, 3 Apr 2020 10:30:52 +0530 Subject: [PATCH 05/14] 777 tested prototype --- pkg/kyverno/apply/command.go | 41 +++++++++++++----------------------- pkg/kyverno/apply/helper.go | 37 -------------------------------- 2 files changed, 15 insertions(+), 63 deletions(-) delete mode 100644 pkg/kyverno/apply/helper.go diff --git a/pkg/kyverno/apply/command.go b/pkg/kyverno/apply/command.go index 75ee885986..daea727a61 100644 --- a/pkg/kyverno/apply/command.go +++ b/pkg/kyverno/apply/command.go @@ -7,6 +7,9 @@ import ( "os" "path/filepath" "regexp" + "time" + + client "github.com/nirmata/kyverno/pkg/dclient" "github.com/nirmata/kyverno/pkg/utils" @@ -18,8 +21,6 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/client-go/discovery" - "k8s.io/apimachinery/pkg/util/yaml" "github.com/nirmata/kyverno/pkg/engine" @@ -87,15 +88,19 @@ func Command() *cobra.Command { } } - var dClient discovery.CachedDiscoveryInterface + var dClient *client.Client if cluster { - dClient, err = kubernetesConfig.ToDiscoveryClient() + restConfig, err := kubernetesConfig.ToRESTConfig() if err != nil { - return sanitizedError.New(fmt.Errorf("Issues with kubernetes Config").Error()) + return err + } + dClient, err = client.NewClient(restConfig, 10*time.Second, make(chan struct{}), log.Log) + if err != nil { + return err } } - resources, err := getResources(policies, resourcePaths, dClient, openAPIController) + resources, err := getResources(policies, resourcePaths, dClient) if err != nil { return sanitizedError.New(fmt.Errorf("Issues fetching resources").Error()) } @@ -123,7 +128,7 @@ func Command() *cobra.Command { return cmd } -func getResources(policies []*v1.ClusterPolicy, resourcePaths []string, dClient discovery.CachedDiscoveryInterface, openAPIController *openapi.Controller) ([]*unstructured.Unstructured, error) { +func getResources(policies []*v1.ClusterPolicy, resourcePaths []string, dClient *client.Client) ([]*unstructured.Unstructured, error) { var resources []*unstructured.Unstructured var err error @@ -142,7 +147,7 @@ func getResources(policies []*v1.ClusterPolicy, resourcePaths []string, dClient resourceTypes = append(resourceTypes, kind) } - resources, err = getResourcesOfTypeFromCluster(resourceTypes, dClient, openAPIController) + resources, err = getResourcesOfTypeFromCluster(resourceTypes, dClient) if err != nil { return nil, err } @@ -160,27 +165,11 @@ func getResources(policies []*v1.ClusterPolicy, resourcePaths []string, dClient return resources, nil } -func getResourcesOfTypeFromCluster(resourceTypes []string, dClient discovery.CachedDiscoveryInterface, openAPIController *openapi.Controller) ([]*unstructured.Unstructured, error) { +func getResourcesOfTypeFromCluster(resourceTypes []string, dClient *client.Client) ([]*unstructured.Unstructured, error) { var resources []*unstructured.Unstructured for _, kind := range resourceTypes { - // TODO use lister interface - endpoint, err := getListEndpointForKind(kind, openAPIController) - if err != nil { - return nil, err - } - - listObjectRaw, err := dClient.RESTClient().Get().RequestURI(endpoint).Do().Raw() - if err != nil { - return nil, err - } - - listObject, err := engineutils.ConvertToUnstructured(listObjectRaw) - if err != nil { - return nil, err - } - - resourceList, err := listObject.ToList() + resourceList, err := dClient.ListResource(kind, "", nil) if err != nil { return nil, err } diff --git a/pkg/kyverno/apply/helper.go b/pkg/kyverno/apply/helper.go deleted file mode 100644 index 4ad4b1b6d3..0000000000 --- a/pkg/kyverno/apply/helper.go +++ /dev/null @@ -1,37 +0,0 @@ -package apply - -import ( - "fmt" - "strings" - - "github.com/nirmata/kyverno/pkg/openapi" -) - -func getListEndpointForKind(kind string, openAPIController *openapi.Controller) (string, error) { - - definitionName := openAPIController.GetDefinitionNameFromKind(kind) - definitionNameWithoutPrefix := strings.Replace(definitionName, "io.k8s.", "", -1) - - parts := strings.Split(definitionNameWithoutPrefix, ".") - definitionPrefix := strings.Join(parts[:len(parts)-1], ".") - - defPrefixToApiPrefix := map[string]string{ - "api.core.v1": "/api/v1", - "api.apps.v1": "/apis/apps/v1", - "api.batch.v1": "/apis/batch/v1", - "api.admissionregistration.v1": "/apis/admissionregistration.k8s.io/v1", - "kube-aggregator.pkg.apis.apiregistration.v1": "/apis/apiregistration.k8s.io/v1", - "apiextensions-apiserver.pkg.apis.apiextensions.v1": "/apis/apiextensions.k8s.io/v1", - "api.autoscaling.v1": "/apis/autoscaling/v1/", - "api.storage.v1": "/apis/storage.k8s.io/v1", - "api.coordination.v1": "/apis/coordination.k8s.io/v1", - "api.scheduling.v1": "/apis/scheduling.k8s.io/v1", - "api.rbac.v1": "/apis/rbac.authorization.k8s.io/v1", - } - - if defPrefixToApiPrefix[definitionPrefix] == "" { - return "", fmt.Errorf("Unsupported resource type %v", kind) - } - - return defPrefixToApiPrefix[definitionPrefix] + "/" + strings.ToLower(kind) + "s", nil -} From 0b2aa90444b41baf005897f88d1b5d5957b2b3de Mon Sep 17 00:00:00 2001 From: shravan Date: Sat, 4 Apr 2020 12:46:51 +0530 Subject: [PATCH 06/14] 753 new req save commit --- pkg/policy/validate.go | 78 +++++++++++++++++++++++-------------- pkg/policy/validate_test.go | 2 +- 2 files changed, 49 insertions(+), 31 deletions(-) diff --git a/pkg/policy/validate.go b/pkg/policy/validate.go index fd6acfdfb8..334787959b 100644 --- a/pkg/policy/validate.go +++ b/pkg/policy/validate.go @@ -9,6 +9,8 @@ import ( "strconv" "strings" + "github.com/minio/minio/pkg/wildcard" + "github.com/nirmata/kyverno/pkg/openapi" kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1" @@ -54,8 +56,8 @@ func Validate(p kyverno.ClusterPolicy) error { return fmt.Errorf("path: spec.rules[%d]: %v", i, err) } - if err := validateMatchExcludeConflict(rule); err != nil { - return fmt.Errorf("path: spec.rules[%d]: %v", i, err) + if doesMatchAndExcludeConflict(rule) { + return fmt.Errorf("path: spec.rules[%d]: rule is matching an empty set", i) } // Operation Validation @@ -95,7 +97,9 @@ func Validate(p kyverno.ClusterPolicy) error { return nil } -func validateMatchExcludeConflict(rule kyverno.Rule) error { +// doesMatchAndExcludeConflict checks if the resultant +// of match and exclude block is not an empty set +func doesMatchAndExcludeConflict(rule kyverno.Rule) bool { excludeRoles := make(map[string]bool) for _, role := range rule.ExcludeResources.UserInfo.Roles { @@ -131,59 +135,73 @@ func validateMatchExcludeConflict(rule kyverno.Rule) error { } } - for _, role := range rule.MatchResources.UserInfo.Roles { - if excludeRoles[role] { - return fmt.Errorf("excluding role '%v' while also matching it - please remove from both match and exclude", role) + if len(excludeRoles) > 0 { + for _, role := range rule.MatchResources.UserInfo.Roles { + if !excludeRoles[role] { + return false + } } } - for _, clusterRole := range rule.MatchResources.UserInfo.ClusterRoles { - if excludeClusterRoles[clusterRole] { - return fmt.Errorf("excluding cluster role '%v' while also matching it - please remove from both match and exclude", clusterRole) + if len(excludeClusterRoles) > 0 { + for _, clusterRole := range rule.MatchResources.UserInfo.ClusterRoles { + if !excludeClusterRoles[clusterRole] { + return false + } } } - for _, subject := range rule.MatchResources.UserInfo.Subjects { - subjectRaw, _ := json.Marshal(subject) - if excludeSubjects[string(subjectRaw)] { - return fmt.Errorf("excluding subject '%v' while also matching it - please remove from both match and exclude", string(subjectRaw)) + if len(excludeSubjects) > 0 { + for _, subject := range rule.MatchResources.UserInfo.Subjects { + subjectRaw, _ := json.Marshal(subject) + if !excludeSubjects[string(subjectRaw)] { + return false + } } } - if rule.MatchResources.ResourceDescription.Name != "" { - if rule.MatchResources.ResourceDescription.Name == rule.ExcludeResources.ResourceDescription.Name { - return fmt.Errorf("excluding resource name '%v' while also matching it - please remove from both match and exclude", rule.MatchResources.ResourceDescription.Name) + if rule.ExcludeResources.ResourceDescription.Name != "" { + if !wildcard.Match(rule.ExcludeResources.ResourceDescription.Name, rule.MatchResources.ResourceDescription.Name) { + return false } } - for _, namespace := range rule.MatchResources.ResourceDescription.Namespaces { - if excludeNamespaces[namespace] { - return fmt.Errorf("excluding resource namespace '%v' while also matching it - please remove from both match and exclude", namespace) + if len(excludeNamespaces) > 1 { + for _, namespace := range rule.MatchResources.ResourceDescription.Namespaces { + if !excludeNamespaces[namespace] { + return false + } } } - for _, kind := range rule.MatchResources.ResourceDescription.Kinds { - if excludeKinds[kind] { - return fmt.Errorf("excluding resource kind '%v' while also matching it - please remove from both match and exclude", kind) + if len(excludeKinds) > 1 { + for _, kind := range rule.MatchResources.ResourceDescription.Kinds { + if !excludeKinds[kind] { + return false + } } } if rule.MatchResources.ResourceDescription.Selector != nil && rule.ExcludeResources.ResourceDescription.Selector != nil { - for _, matchExpression := range rule.MatchResources.ResourceDescription.Selector.MatchExpressions { - matchExpressionRaw, _ := json.Marshal(matchExpression) - if excludeMatchExpressions[string(matchExpressionRaw)] { - return fmt.Errorf("excluding resource match expression '%v' while also matching it - please remove from both match and exclude", string(matchExpressionRaw)) + if len(excludeMatchExpressions) > 1 { + for _, matchExpression := range rule.MatchResources.ResourceDescription.Selector.MatchExpressions { + matchExpressionRaw, _ := json.Marshal(matchExpression) + if excludeMatchExpressions[string(matchExpressionRaw)] { + return false + } } } - for label, value := range rule.MatchResources.ResourceDescription.Selector.MatchLabels { - if rule.ExcludeResources.ResourceDescription.Selector.MatchLabels[label] == value { - return fmt.Errorf("excluding resource label '%v' while also matching it - please remove from both match and exclude", label) + if len(rule.ExcludeResources.ResourceDescription.Selector.MatchLabels) > 1 { + for label, value := range rule.MatchResources.ResourceDescription.Selector.MatchLabels { + if rule.ExcludeResources.ResourceDescription.Selector.MatchLabels[label] != value { + return false + } } } } - return nil + return true } func ruleOnlyDealsWithResourceMetaData(rule kyverno.Rule) bool { diff --git a/pkg/policy/validate_test.go b/pkg/policy/validate_test.go index 9f91ab8025..787908a76c 100644 --- a/pkg/policy/validate_test.go +++ b/pkg/policy/validate_test.go @@ -1633,7 +1633,7 @@ func Test_validateMatchExcludeConflict(t *testing.T) { for i, testcase := range testcases { var rule kyverno.Rule _ = json.Unmarshal(testcase.rule, &rule) - err := validateMatchExcludeConflict(rule) + err := doesMatchAndExcludeConflict(rule) var gotError bool if err != nil { From fde5e5490fba441c2562535a2ffc88c18184960d Mon Sep 17 00:00:00 2001 From: shravan Date: Sat, 4 Apr 2020 14:49:50 +0530 Subject: [PATCH 07/14] 753 testcase and fixes --- pkg/policy/validate.go | 15 ++--- pkg/policy/validate_test.go | 109 +++++++++++++----------------------- 2 files changed, 43 insertions(+), 81 deletions(-) diff --git a/pkg/policy/validate.go b/pkg/policy/validate.go index 334787959b..b5549c60ad 100644 --- a/pkg/policy/validate.go +++ b/pkg/policy/validate.go @@ -40,11 +40,6 @@ func Validate(p kyverno.ClusterPolicy) error { } for i, rule := range p.Spec.Rules { - // only one type of rule is allowed per rule - if err := validateRuleType(rule); err != nil { - return fmt.Errorf("path: spec.rules[%d]: %v", i, err) - } - // validate resource description if path, err := validateResources(rule); err != nil { return fmt.Errorf("path: spec.rules[%d].%s: %v", i, path, err) @@ -166,7 +161,7 @@ func doesMatchAndExcludeConflict(rule kyverno.Rule) bool { } } - if len(excludeNamespaces) > 1 { + if len(excludeNamespaces) > 0 { for _, namespace := range rule.MatchResources.ResourceDescription.Namespaces { if !excludeNamespaces[namespace] { return false @@ -174,7 +169,7 @@ func doesMatchAndExcludeConflict(rule kyverno.Rule) bool { } } - if len(excludeKinds) > 1 { + if len(excludeKinds) > 0 { for _, kind := range rule.MatchResources.ResourceDescription.Kinds { if !excludeKinds[kind] { return false @@ -183,16 +178,16 @@ func doesMatchAndExcludeConflict(rule kyverno.Rule) bool { } if rule.MatchResources.ResourceDescription.Selector != nil && rule.ExcludeResources.ResourceDescription.Selector != nil { - if len(excludeMatchExpressions) > 1 { + if len(excludeMatchExpressions) > 0 { for _, matchExpression := range rule.MatchResources.ResourceDescription.Selector.MatchExpressions { matchExpressionRaw, _ := json.Marshal(matchExpression) - if excludeMatchExpressions[string(matchExpressionRaw)] { + if !excludeMatchExpressions[string(matchExpressionRaw)] { return false } } } - if len(rule.ExcludeResources.ResourceDescription.Selector.MatchLabels) > 1 { + if len(rule.ExcludeResources.ResourceDescription.Selector.MatchLabels) > 0 { for label, value := range rule.MatchResources.ResourceDescription.Selector.MatchLabels { if rule.ExcludeResources.ResourceDescription.Selector.MatchLabels[label] != value { return false diff --git a/pkg/policy/validate_test.go b/pkg/policy/validate_test.go index 787908a76c..3bff390944 100644 --- a/pkg/policy/validate_test.go +++ b/pkg/policy/validate_test.go @@ -1542,107 +1542,74 @@ func Test_ruleOnlyDealsWithResourceMetaData(t *testing.T) { } } -func Test_validateMatchExcludeConflict(t *testing.T) { +func Test_doesMatchExcludeConflict(t *testing.T) { testcases := []struct { - description string - rule []byte - expectedError bool + description string + rule []byte + expectedOutput bool }{ { - description: "Testing cluster roles - fail", - rule: []byte(`{"name":"set-image-pull-policy-2","match":{"resources":{"kinds":["Pod","Namespace"],"name":"something","namespaces":["something","something1"],"selector":{"matchLabels":{"memory":"high"},"matchExpressions":[{"key":"tier","operator":"In","values":["database"]}]}},"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"},{"name":"something1","kind":"something1","Namespace":"something1","apiGroup":"something1"}],"clusterroles":["something","something1"],"roles":["something","something1"]},"exclude":{"clusterroles":["something"]}}`), - expectedError: true, + description: "Same match and exclude", + rule: []byte(`{"name":"set-image-pull-policy-2","match":{"resources":{"kinds":["Pod","Namespace"],"name":"something","namespaces":["something","something1"],"selector":{"matchLabels":{"memory":"high"},"matchExpressions":[{"key":"tier","operator":"In","values":["database"]}]}},"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"},{"name":"something1","kind":"something1","Namespace":"something1","apiGroup":"something1"}],"clusterroles":["something","something1"],"roles":["something","something1"]},"exclude":{"resources":{"kinds":["Pod","Namespace"],"name":"something","namespaces":["something","something1"],"selector":{"matchLabels":{"memory":"high"},"matchExpressions":[{"key":"tier","operator":"In","values":["database"]}]}},"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"},{"name":"something1","kind":"something1","Namespace":"something1","apiGroup":"something1"}],"clusterroles":["something","something1"],"roles":["something","something1"]}}`), + expectedOutput: true, }, { - description: "Testing cluster roles - pass", - rule: []byte(`{"name":"set-image-pull-policy-2","match":{"resources":{"kinds":["Pod","Namespace"],"name":"something","namespaces":["something","something1"],"selector":{"matchLabels":{"memory":"high"},"matchExpressions":[{"key":"tier","operator":"In","values":["database"]}]}},"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"},{"name":"something1","kind":"something1","Namespace":"something1","apiGroup":"something1"}],"clusterroles":["something","something1"],"roles":["something","something1"]},"exclude":{"clusterroles":["something2"]}}`), - expectedError: false, + description: "Failed to exclude kind", + rule: []byte(`{"name":"set-image-pull-policy-2","match":{"resources":{"kinds":["Pod","Namespace"],"name":"something","namespaces":["something","something1"],"selector":{"matchLabels":{"memory":"high"},"matchExpressions":[{"key":"tier","operator":"In","values":["database"]}]}},"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"},{"name":"something1","kind":"something1","Namespace":"something1","apiGroup":"something1"}],"clusterroles":["something","something1"],"roles":["something","something1"]},"exclude":{"resources":{"kinds":["Namespace"],"name":"something","namespaces":["something","something1"],"selector":{"matchLabels":{"memory":"high"},"matchExpressions":[{"key":"tier","operator":"In","values":["database"]}]}},"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"},{"name":"something1","kind":"something1","Namespace":"something1","apiGroup":"something1"}],"clusterroles":["something","something1"],"roles":["something","something1"]}}`), + expectedOutput: false, }, { - description: "Testing roles - fail", - rule: []byte(`{"name":"set-image-pull-policy-2","match":{"resources":{"kinds":["Pod","Namespace"],"name":"something","namespaces":["something","something1"],"selector":{"matchLabels":{"memory":"high"},"matchExpressions":[{"key":"tier","operator":"In","values":["database"]}]}},"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"},{"name":"something1","kind":"something1","Namespace":"something1","apiGroup":"something1"}],"clusterroles":["something","something1"],"roles":["something","something1"]},"exclude":{"roles":["something"]}}`), - expectedError: true, + description: "Failed to exclude name", + rule: []byte(`{"name":"set-image-pull-policy-2","match":{"resources":{"kinds":["Pod","Namespace"],"name":"something","namespaces":["something","something1"],"selector":{"matchLabels":{"memory":"high"},"matchExpressions":[{"key":"tier","operator":"In","values":["database"]}]}},"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"},{"name":"something1","kind":"something1","Namespace":"something1","apiGroup":"something1"}],"clusterroles":["something","something1"],"roles":["something","something1"]},"exclude":{"resources":{"kinds":["Pod","Namespace"],"name":"something-*","namespaces":["something","something1"],"selector":{"matchLabels":{"memory":"high"},"matchExpressions":[{"key":"tier","operator":"In","values":["database"]}]}},"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"},{"name":"something1","kind":"something1","Namespace":"something1","apiGroup":"something1"}],"clusterroles":["something","something1"],"roles":["something","something1"]}}`), + expectedOutput: false, }, { - description: "Testing roles - pass", - rule: []byte(`{"name":"set-image-pull-policy-2","match":{"resources":{"kinds":["Pod","Namespace"],"name":"something","namespaces":["something","something1"],"selector":{"matchLabels":{"memory":"high"},"matchExpressions":[{"key":"tier","operator":"In","values":["database"]}]}},"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"},{"name":"something1","kind":"something1","Namespace":"something1","apiGroup":"something1"}],"clusterroles":["something","something1"],"roles":["something","something1"]},"exclude":{"roles":["something2"]}}`), - expectedError: false, + description: "Failed to exclude namespace", + rule: []byte(`{"name":"set-image-pull-policy-2","match":{"resources":{"kinds":["Pod","Namespace"],"name":"something","namespaces":["something","something1"],"selector":{"matchLabels":{"memory":"high"},"matchExpressions":[{"key":"tier","operator":"In","values":["database"]}]}},"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"},{"name":"something1","kind":"something1","Namespace":"something1","apiGroup":"something1"}],"clusterroles":["something","something1"],"roles":["something","something1"]},"exclude":{"resources":{"kinds":["Pod","Namespace"],"name":"something","namespaces":["something3","something1"],"selector":{"matchLabels":{"memory":"high"},"matchExpressions":[{"key":"tier","operator":"In","values":["database"]}]}},"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"},{"name":"something1","kind":"something1","Namespace":"something1","apiGroup":"something1"}],"clusterroles":["something","something1"],"roles":["something","something1"]}}`), + expectedOutput: false, }, { - description: "Testing subjects - fail", - rule: []byte(`{"name":"set-image-pull-policy-2","match":{"resources":{"kinds":["Pod","Namespace"],"name":"something","namespaces":["something","something1"],"selector":{"matchLabels":{"memory":"high"},"matchExpressions":[{"key":"tier","operator":"In","values":["database"]}]}},"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"},{"name":"something1","kind":"something1","Namespace":"something1","apiGroup":"something1"}],"clusterroles":["something","something1"],"roles":["something","something1"]},"exclude":{"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"}]}}`), - expectedError: true, + description: "Failed to exclude labels", + rule: []byte(`{"name":"set-image-pull-policy-2","match":{"resources":{"kinds":["Pod","Namespace"],"name":"something","namespaces":["something","something1"],"selector":{"matchLabels":{"memory":"high"},"matchExpressions":[{"key":"tier","operator":"In","values":["database"]}]}},"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"},{"name":"something1","kind":"something1","Namespace":"something1","apiGroup":"something1"}],"clusterroles":["something","something1"],"roles":["something","something1"]},"exclude":{"resources":{"kinds":["Pod","Namespace"],"name":"something","namespaces":["something","something1"],"selector":{"matchLabels":{"memory":"higha"},"matchExpressions":[{"key":"tier","operator":"In","values":["database"]}]}},"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"},{"name":"something1","kind":"something1","Namespace":"something1","apiGroup":"something1"}],"clusterroles":["something","something1"],"roles":["something","something1"]}}`), + expectedOutput: false, }, { - description: "Testing subjects - pass", - rule: []byte(`{"name":"set-image-pull-policy-2","match":{"resources":{"kinds":["Pod","Namespace"],"name":"something","namespaces":["something","something1"],"selector":{"matchLabels":{"memory":"high"},"matchExpressions":[{"key":"tier","operator":"In","values":["database"]}]}},"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"},{"name":"something1","kind":"something1","Namespace":"something1","apiGroup":"something1"}],"clusterroles":["something","something1"],"roles":["something","something1"]},"exclude":{"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something1"}]}}`), - expectedError: false, + description: "Failed to exclude expression", + rule: []byte(`{"name":"set-image-pull-policy-2","match":{"resources":{"kinds":["Pod","Namespace"],"name":"something","namespaces":["something","something1"],"selector":{"matchLabels":{"memory":"high"},"matchExpressions":[{"key":"tier","operator":"In","values":["database"]}]}},"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"},{"name":"something1","kind":"something1","Namespace":"something1","apiGroup":"something1"}],"clusterroles":["something","something1"],"roles":["something","something1"]},"exclude":{"resources":{"kinds":["Pod","Namespace"],"name":"something","namespaces":["something","something1"],"selector":{"matchLabels":{"memory":"high"},"matchExpressions":[{"key":"tier","operator":"In","values":["databases"]}]}},"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"},{"name":"something1","kind":"something1","Namespace":"something1","apiGroup":"something1"}],"clusterroles":["something","something1"],"roles":["something","something1"]}}`), + expectedOutput: false, }, { - description: "Testing resource kind - fail", - rule: []byte(`{"name":"set-image-pull-policy-2","match":{"resources":{"kinds":["Pod","Namespace"],"name":"something","namespaces":["something","something1"],"selector":{"matchLabels":{"memory":"high"},"matchExpressions":[{"key":"tier","operator":"In","values":["database"]}]}},"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"},{"name":"something1","kind":"something1","Namespace":"something1","apiGroup":"something1"}],"clusterroles":["something","something1"],"roles":["something","something1"]},"exclude":{"resources":{"kinds":["Pod","Namespace1"]}}}`), - expectedError: true, + description: "Failed to exclude subjects", + rule: []byte(`{"name":"set-image-pull-policy-2","match":{"resources":{"kinds":["Pod","Namespace"],"name":"something","namespaces":["something","something1"],"selector":{"matchLabels":{"memory":"high"},"matchExpressions":[{"key":"tier","operator":"In","values":["database"]}]}},"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"},{"name":"something1","kind":"something1","Namespace":"something1","apiGroup":"something1"}],"clusterroles":["something","something1"],"roles":["something","something1"]},"exclude":{"resources":{"kinds":["Pod","Namespace"],"name":"something","namespaces":["something","something1"],"selector":{"matchLabels":{"memory":"high"},"matchExpressions":[{"key":"tier","operator":"In","values":["database"]}]}},"subjects":[{"name":"something2","kind":"something","Namespace":"something","apiGroup":"something"},{"name":"something1","kind":"something1","Namespace":"something1","apiGroup":"something1"}],"clusterroles":["something","something1"],"roles":["something","something1"]}}`), + expectedOutput: false, }, { - description: "Testing resource kind - pass", - rule: []byte(`{"name":"set-image-pull-policy-2","match":{"resources":{"kinds":["Pod","Namespace"],"name":"something","namespaces":["something","something1"],"selector":{"matchLabels":{"memory":"high"},"matchExpressions":[{"key":"tier","operator":"In","values":["database"]}]}},"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"},{"name":"something1","kind":"something1","Namespace":"something1","apiGroup":"something1"}],"clusterroles":["something","something1"],"roles":["something","something1"]},"exclude":{"resources":{"kinds":["Pod1","Namespace1"]}}}`), - expectedError: false, + description: "Failed to exclude clusterroles", + rule: []byte(`{"name":"set-image-pull-policy-2","match":{"resources":{"kinds":["Pod","Namespace"],"name":"something","namespaces":["something","something1"],"selector":{"matchLabels":{"memory":"high"},"matchExpressions":[{"key":"tier","operator":"In","values":["database"]}]}},"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"},{"name":"something1","kind":"something1","Namespace":"something1","apiGroup":"something1"}],"clusterroles":["something","something1"],"roles":["something","something1"]},"exclude":{"resources":{"kinds":["Pod","Namespace"],"name":"something","namespaces":["something","something1"],"selector":{"matchLabels":{"memory":"high"},"matchExpressions":[{"key":"tier","operator":"In","values":["database"]}]}},"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"},{"name":"something1","kind":"something1","Namespace":"something1","apiGroup":"something1"}],"clusterroles":["something3","something1"],"roles":["something","something1"]}}`), + expectedOutput: false, }, { - description: "Testing resource name - fail", - rule: []byte(`{"name":"set-image-pull-policy-2","match":{"resources":{"kinds":["Pod","Namespace"],"name":"something","namespaces":["something","something1"],"selector":{"matchLabels":{"memory":"high"},"matchExpressions":[{"key":"tier","operator":"In","values":["database"]}]}},"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"},{"name":"something1","kind":"something1","Namespace":"something1","apiGroup":"something1"}],"clusterroles":["something","something1"],"roles":["something","something1"]},"exclude":{"resources":{"name":"something"}}}`), - expectedError: true, + description: "Failed to exclude roles", + rule: []byte(`{"name":"set-image-pull-policy-2","match":{"resources":{"kinds":["Pod","Namespace"],"name":"something","namespaces":["something","something1"],"selector":{"matchLabels":{"memory":"high"},"matchExpressions":[{"key":"tier","operator":"In","values":["database"]}]}},"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"},{"name":"something1","kind":"something1","Namespace":"something1","apiGroup":"something1"}],"clusterroles":["something","something1"],"roles":["something","something1"]},"exclude":{"resources":{"kinds":["Pod","Namespace"],"name":"something","namespaces":["something","something1"],"selector":{"matchLabels":{"memory":"high"},"matchExpressions":[{"key":"tier","operator":"In","values":["database"]}]}},"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"},{"name":"something1","kind":"something1","Namespace":"something1","apiGroup":"something1"}],"clusterroles":["something","something1"],"roles":["something3","something1"]}}`), + expectedOutput: false, }, { - description: "Testing resource name - pass", - rule: []byte(`{"name":"set-image-pull-policy-2","match":{"resources":{"kinds":["Pod","Namespace"],"name":"something","namespaces":["something","something1"],"selector":{"matchLabels":{"memory":"high"},"matchExpressions":[{"key":"tier","operator":"In","values":["database"]}]}},"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"},{"name":"something1","kind":"something1","Namespace":"something1","apiGroup":"something1"}],"clusterroles":["something","something1"],"roles":["something","something1"]},"exclude":{"resources":{"name":"something1"}}}`), - expectedError: false, + description: "simple", + rule: []byte(`{"name":"set-image-pull-policy-2","match":{"resources":{"kinds":["Pod","Namespace"],"name":"something","namespaces":["something","something1"]}},"exclude":{"resources":{"kinds":["Pod","Namespace","Job"],"name":"some*","namespaces":["something","something1","something2"]}}}`), + expectedOutput: true, }, { - description: "Testing resource namespace - fail", - rule: []byte(`{"name":"set-image-pull-policy-2","match":{"resources":{"kinds":["Pod","Namespace"],"name":"something","namespaces":["something","something1"],"selector":{"matchLabels":{"memory":"high"},"matchExpressions":[{"key":"tier","operator":"In","values":["database"]}]}},"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"},{"name":"something1","kind":"something1","Namespace":"something1","apiGroup":"something1"}],"clusterroles":["something","something1"],"roles":["something","something1"]},"exclude":{"resources":{"namespaces":["something2","something1"]}}}`), - expectedError: true, - }, - { - description: "Testing resource namespace - pass", - rule: []byte(`{"name":"set-image-pull-policy-2","match":{"resources":{"kinds":["Pod","Namespace"],"name":"something","namespaces":["something","something1"],"selector":{"matchLabels":{"memory":"high"},"matchExpressions":[{"key":"tier","operator":"In","values":["database"]}]}},"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"},{"name":"something1","kind":"something1","Namespace":"something1","apiGroup":"something1"}],"clusterroles":["something","something1"],"roles":["something","something1"]},"exclude":{"resources":{"namespaces":["something2","something3"]}}}`), - expectedError: false, - }, - { - description: "Testing resource selector label - fail", - rule: []byte(`{"name":"set-image-pull-policy-2","match":{"resources":{"kinds":["Pod","Namespace"],"name":"something","namespaces":["something","something1"],"selector":{"matchLabels":{"memory":"high"},"matchExpressions":[{"key":"tier","operator":"In","values":["database"]}]}},"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"},{"name":"something1","kind":"something1","Namespace":"something1","apiGroup":"something1"}],"clusterroles":["something","something1"],"roles":["something","something1"]},"exclude":{"resources":{"selector":{"matchLabels":{"memory":"high"}}}}}`), - expectedError: true, - }, - { - description: "Testing resource selector label - pass", - rule: []byte(`{"name":"set-image-pull-policy-2","match":{"resources":{"kinds":["Pod","Namespace"],"name":"something","namespaces":["something","something1"],"selector":{"matchLabels":{"memory":"high"},"matchExpressions":[{"key":"tier","operator":"In","values":["database"]}]}},"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"},{"name":"something1","kind":"something1","Namespace":"something1","apiGroup":"something1"}],"clusterroles":["something","something1"],"roles":["something","something1"]},"exclude":{"resources":{"selector":{"matchLabels":{"memory":"high1"}}}}}`), - expectedError: false, - }, - { - description: "Testing resource selector match expression - fail", - rule: []byte(`{"name":"set-image-pull-policy-2","match":{"resources":{"kinds":["Pod","Namespace"],"name":"something","namespaces":["something","something1"],"selector":{"matchLabels":{"memory":"high"},"matchExpressions":[{"key":"tier","operator":"In","values":["database"]}]}},"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"},{"name":"something1","kind":"something1","Namespace":"something1","apiGroup":"something1"}],"clusterroles":["something","something1"],"roles":["something","something1"]},"exclude":{"resources":{"selector":{"matchExpressions":[{"key":"tier1","operator":"In","values":["database"]},{"key":"tier","operator":"In","values":["database"]}]}}}}`), - expectedError: true, - }, - { - description: "Testing resource selector match expression - pass", - rule: []byte(`{"name":"set-image-pull-policy-2","match":{"resources":{"kinds":["Pod","Namespace"],"name":"something","namespaces":["something","something1"],"selector":{"matchLabels":{"memory":"high"},"matchExpressions":[{"key":"tier","operator":"In","values":["database"]}]}},"subjects":[{"name":"something","kind":"something","Namespace":"something","apiGroup":"something"},{"name":"something1","kind":"something1","Namespace":"something1","apiGroup":"something1"}],"clusterroles":["something","something1"],"roles":["something","something1"]},"exclude":{"resources":{"selector":{"matchExpressions":[{"key":"tier1","operator":"In","values":["database"]},{"key":"tier2","operator":"In","values":["database"]}]}}}}`), - expectedError: false, + description: "simple - fail", + rule: []byte(`{"name":"set-image-pull-policy-2","match":{"resources":{"kinds":["Pod","Namespace"],"name":"somxething","namespaces":["something","something1"]}},"exclude":{"resources":{"kinds":["Pod","Namespace","Job"],"name":"some*","namespaces":["something","something1","something2"]}}}`), + expectedOutput: false, }, } for i, testcase := range testcases { var rule kyverno.Rule _ = json.Unmarshal(testcase.rule, &rule) - err := doesMatchAndExcludeConflict(rule) - var gotError bool - if err != nil { - gotError = true - } else { - gotError = false - } - - if gotError != testcase.expectedError { + if doesMatchAndExcludeConflict(rule) != testcase.expectedOutput { t.Errorf("Testcase [%d] failed - description - %v", i+1, testcase.description) } } From d4baf44fd9f042926be488712f072db0c15cb28b Mon Sep 17 00:00:00 2001 From: shravan Date: Sat, 4 Apr 2020 16:18:36 +0530 Subject: [PATCH 08/14] 753 practical test fixes --- pkg/policy/validate.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pkg/policy/validate.go b/pkg/policy/validate.go index a972fea0b2..04741669ea 100644 --- a/pkg/policy/validate.go +++ b/pkg/policy/validate.go @@ -56,7 +56,7 @@ func Validate(policyRaw []byte, client *dclient.Client, mock bool, openAPIContro } if doesMatchAndExcludeConflict(rule) { - return fmt.Errorf("path: spec.rules[%d]: rule is matching an empty set", i) + return fmt.Errorf("path: spec.rules[%v]: rule is matching an empty set", rule.Name) } // validate rule actions @@ -94,6 +94,14 @@ func Validate(policyRaw []byte, client *dclient.Client, mock bool, openAPIContro // of match and exclude block is not an empty set func doesMatchAndExcludeConflict(rule kyverno.Rule) bool { + if reflect.DeepEqual(rule.MatchResources, kyverno.MatchResources{}) { + return true + } + + if reflect.DeepEqual(rule.ExcludeResources, kyverno.ExcludeResources{}) { + return false + } + excludeRoles := make(map[string]bool) for _, role := range rule.ExcludeResources.UserInfo.Roles { excludeRoles[role] = true From 2599b29a0997afcd45bedef69efadd6239950c86 Mon Sep 17 00:00:00 2001 From: shravan Date: Sat, 4 Apr 2020 17:13:29 +0530 Subject: [PATCH 09/14] 753 final fixes --- pkg/webhooks/policymutation.go | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/pkg/webhooks/policymutation.go b/pkg/webhooks/policymutation.go index dea2f87fff..134a558328 100644 --- a/pkg/webhooks/policymutation.go +++ b/pkg/webhooks/policymutation.go @@ -302,10 +302,10 @@ func generateRuleForControllers(rule kyverno.Rule, controllers string, log logr. } // overwrite Kinds by pod controllers defined in the annotation - controllerRule.MatchResources.Kinds = strings.Split(controllers, ",") + controllerRule.MatchResources.Kinds = replacePodWithPodControllers(controllerRule.MatchResources.Kinds, strings.Split(controllers, ",")) if len(exclude.Kinds) != 0 { controllerRule.ExcludeResources = exclude.DeepCopy() - controllerRule.ExcludeResources.Kinds = strings.Split(controllers, ",") + controllerRule.ExcludeResources.Kinds = replacePodWithPodControllers(controllerRule.ExcludeResources.Kinds, strings.Split(controllers, ",")) } if rule.Mutation.Overlay != nil { @@ -395,3 +395,16 @@ func defaultPodControllerAnnotation(ann map[string]string) ([]byte, error) { } return patchByte, nil } + +func replacePodWithPodControllers(kinds []string, controllers []string) []string { + + var kindsWithoutPod []string + for _, kind := range kinds { + if strings.ToLower(kind) == "pod" { + continue + } + kindsWithoutPod = append(kindsWithoutPod, kind) + } + + return append(kindsWithoutPod, controllers...) +} From d43456b681a4872b19bca334314b3be74e79975c Mon Sep 17 00:00:00 2001 From: shravan Date: Sat, 4 Apr 2020 21:06:55 +0530 Subject: [PATCH 10/14] 753_avoiding_duplicate_vals --- pkg/webhooks/policymutation.go | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/pkg/webhooks/policymutation.go b/pkg/webhooks/policymutation.go index 134a558328..7dfef34588 100644 --- a/pkg/webhooks/policymutation.go +++ b/pkg/webhooks/policymutation.go @@ -398,13 +398,22 @@ func defaultPodControllerAnnotation(ann map[string]string) ([]byte, error) { func replacePodWithPodControllers(kinds []string, controllers []string) []string { - var kindsWithoutPod []string + kindMap := make(map[string]bool) + for _, kind := range kinds { - if strings.ToLower(kind) == "pod" { - continue - } - kindsWithoutPod = append(kindsWithoutPod, kind) + kindMap[kind] = true } - return append(kindsWithoutPod, controllers...) + delete(kindMap, "Pod") + + for _, controller := range controllers { + kindMap[controller] = true + } + + output := make([]string, 0, len(kindMap)) + for kind := range kindMap { + output = append(output, kind) + } + + return output } From ed45dc12c09207713f483d31bc6da714f063171a Mon Sep 17 00:00:00 2001 From: shravan Date: Sat, 4 Apr 2020 22:39:21 +0530 Subject: [PATCH 11/14] 775 working prototype --- pkg/engine/mutation.go | 38 +++++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/pkg/engine/mutation.go b/pkg/engine/mutation.go index b147f5b490..0449698768 100644 --- a/pkg/engine/mutation.go +++ b/pkg/engine/mutation.go @@ -1,6 +1,7 @@ package engine import ( + "encoding/json" "reflect" "strings" "time" @@ -101,18 +102,19 @@ func Mutate(policyContext PolicyContext) (resp response.EngineResponse) { if reflect.DeepEqual(policyContext.AdmissionInfo, kyverno.RequestInfo{}) { continue } + } - if strings.Contains(PodControllers, resource.GetKind()) { + if strings.Contains(PodControllers, resource.GetKind()) { + if !patchedResourceHasPodControllerAnnotation(patchedResource) { var ruleResponse response.RuleResponse - ruleResponse, patchedResource = mutate.ProcessOverlay(logger, rule.Name, podTemplateRule, patchedResource) + ruleResponse, patchedResource = mutate.ProcessOverlay(logger, "podControllerAnnotation", podTemplateRule.Mutation.Overlay, patchedResource) if !ruleResponse.Success { logger.Info("failed to insert annotation for podTemplate", "error", ruleResponse.Message) - continue - } - - if ruleResponse.Success && ruleResponse.Patches != nil { - logger.V(2).Info("inserted annotation for podTemplate") - resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, ruleResponse) + } else { + if ruleResponse.Success && ruleResponse.Patches != nil { + logger.V(2).Info("inserted annotation for podTemplate") + resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, ruleResponse) + } } } } @@ -120,6 +122,24 @@ func Mutate(policyContext PolicyContext) (resp response.EngineResponse) { resp.PatchedResource = patchedResource return resp } + +func patchedResourceHasPodControllerAnnotation(resource unstructured.Unstructured) bool { + var podController struct { + Spec struct { + Template struct { + Metadata struct { + Annotations map[string]interface{} `json:"annotations"` + } `json:"metadata"` + } `json:"template"` + } `json:"spec"` + } + + resourceRaw, _ := json.Marshal(resource.Object) + json.Unmarshal(resourceRaw, &podController) + + _, ok := podController.Spec.Template.Metadata.Annotations[PodTemplateAnnotation] + return ok +} func incrementAppliedRuleCount(resp *response.EngineResponse) { resp.PolicyResponse.RulesAppliedCount++ } @@ -150,7 +170,7 @@ var podTemplateRule = kyverno.Rule{ "template": map[string]interface{}{ "metadata": map[string]interface{}{ "annotations": map[string]interface{}{ - "+(pod-policies.kyverno.io/autogen-applied)": "true", + "+(" + PodTemplateAnnotation + ")": "true", }, }, }, From 23d2a215dc8c45a9185a61c8514b4e65f4b00bba Mon Sep 17 00:00:00 2001 From: shravan Date: Sat, 4 Apr 2020 22:50:05 +0530 Subject: [PATCH 12/14] 753 fixing tests --- pkg/webhooks/policymutation.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/webhooks/policymutation.go b/pkg/webhooks/policymutation.go index 7dfef34588..7211306adf 100644 --- a/pkg/webhooks/policymutation.go +++ b/pkg/webhooks/policymutation.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "reflect" + "sort" "strconv" "strings" @@ -415,5 +416,9 @@ func replacePodWithPodControllers(kinds []string, controllers []string) []string output = append(output, kind) } + sort.Slice(output, func(i, j int) bool { + return output[i] < output[j] + }) + return output } From ad3fcb500feffe8c37e735235cfa51378eb8791a Mon Sep 17 00:00:00 2001 From: shravan Date: Sat, 4 Apr 2020 22:52:53 +0530 Subject: [PATCH 13/14] 775 circle ci fixes --- pkg/engine/mutation.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/engine/mutation.go b/pkg/engine/mutation.go index 0449698768..8f76efca9f 100644 --- a/pkg/engine/mutation.go +++ b/pkg/engine/mutation.go @@ -135,7 +135,7 @@ func patchedResourceHasPodControllerAnnotation(resource unstructured.Unstructure } resourceRaw, _ := json.Marshal(resource.Object) - json.Unmarshal(resourceRaw, &podController) + _ = json.Unmarshal(resourceRaw, &podController) _, ok := podController.Spec.Template.Metadata.Annotations[PodTemplateAnnotation] return ok From 09b3172b8cf9b3e52b57c9ce0ee04fc3319463db Mon Sep 17 00:00:00 2001 From: shravan Date: Sat, 11 Apr 2020 22:11:28 +0530 Subject: [PATCH 14/14] 753 reverting autogen rule changes --- pkg/webhooks/policymutation.go | 31 ++----------------------------- 1 file changed, 2 insertions(+), 29 deletions(-) diff --git a/pkg/webhooks/policymutation.go b/pkg/webhooks/policymutation.go index 7211306adf..dea2f87fff 100644 --- a/pkg/webhooks/policymutation.go +++ b/pkg/webhooks/policymutation.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" "reflect" - "sort" "strconv" "strings" @@ -303,10 +302,10 @@ func generateRuleForControllers(rule kyverno.Rule, controllers string, log logr. } // overwrite Kinds by pod controllers defined in the annotation - controllerRule.MatchResources.Kinds = replacePodWithPodControllers(controllerRule.MatchResources.Kinds, strings.Split(controllers, ",")) + controllerRule.MatchResources.Kinds = strings.Split(controllers, ",") if len(exclude.Kinds) != 0 { controllerRule.ExcludeResources = exclude.DeepCopy() - controllerRule.ExcludeResources.Kinds = replacePodWithPodControllers(controllerRule.ExcludeResources.Kinds, strings.Split(controllers, ",")) + controllerRule.ExcludeResources.Kinds = strings.Split(controllers, ",") } if rule.Mutation.Overlay != nil { @@ -396,29 +395,3 @@ func defaultPodControllerAnnotation(ann map[string]string) ([]byte, error) { } return patchByte, nil } - -func replacePodWithPodControllers(kinds []string, controllers []string) []string { - - kindMap := make(map[string]bool) - - for _, kind := range kinds { - kindMap[kind] = true - } - - delete(kindMap, "Pod") - - for _, controller := range controllers { - kindMap[controller] = true - } - - output := make([]string, 0, len(kindMap)) - for kind := range kindMap { - output = append(output, kind) - } - - sort.Slice(output, func(i, j int) bool { - return output[i] < output[j] - }) - - return output -}