From 44af35d6e461e0668744d200379da9938151de97 Mon Sep 17 00:00:00 2001 From: shivkumar dudhani Date: Thu, 12 Sep 2019 17:11:55 -0700 Subject: [PATCH] support wild cards for namespaces in rule resource description --- documentation/writing-policies.md | 9 +- pkg/engine/utils.go | 166 +----------------- pkg/policy/existing.go | 4 +- pkg/utils/json.go | 4 +- pkg/utils/util.go | 22 ++- pkg/utils/util_test.go | 39 +++- pkg/webhooks/mutation.go | 2 +- pkg/webhooks/policyvalidation.go | 2 +- pkg/webhooks/validation.go | 2 +- .../scenario_validate_host_network_port.yaml | 2 +- 10 files changed, 70 insertions(+), 182 deletions(-) diff --git a/documentation/writing-policies.md b/documentation/writing-policies.md index 7de08701fc..f3ca21957b 100644 --- a/documentation/writing-policies.md +++ b/documentation/writing-policies.md @@ -24,9 +24,9 @@ spec : - Deployment - StatefulSet name: "mongo*" # Optional, a resource name is optional. Name supports wildcards * and ? - namespaces: # Optional, list of namespaces - - devtest2 - - devtest1 + namespaces: # Optional, list of namespaces. Supports wilcards * and ? + - "dev*" + - test selector: # Optional, a resource selector is optional. Selector values support wildcards * and ? matchLabels: app: mongodb @@ -39,7 +39,8 @@ spec : - Daemonsets name: "*" namespaces: - - devtest2 + - prod + - "kube*" selector: matchLabels: app: mongodb diff --git a/pkg/engine/utils.go b/pkg/engine/utils.go index 68903bf27d..10b35ecfef 100644 --- a/pkg/engine/utils.go +++ b/pkg/engine/utils.go @@ -18,36 +18,6 @@ import ( "k8s.io/apimachinery/pkg/labels" ) -// //EngineResponse provides the response to the application of a policy rule set on a resource -// type EngineResponse struct { -// // JSON patches for mutation rules -// Patches [][]byte -// // Resource patched with the policy changes -// PatchedResource unstructured.Unstructured -// // Rule details -// RuleInfos []info.RuleInfo -// // PolicyS -// EngineStats -// } - -// type EngineResponseNew struct { -// // error while processing engine action -// Err error -// // Resource patched with the engine action changes -// PatchedResource unstructured.Unstructured -// // Policy Response -// PolicyRespone PolicyResponse -// } - -// type PolicyResponse struct { -// // policy name -// Policy string -// // resource details -// Resource kyverno.ResourceSpec -// } - -// type PolicyStatus - //EngineStats stores in the statistics for a single application of resource type EngineStats struct { // average time required to process the policy rules on a resource @@ -56,136 +26,6 @@ type EngineStats struct { RulesAppliedCount int } -// //ListResourcesThatApplyToPolicy returns list of resources that are filtered by policy rules -// func ListResourcesThatApplyToPolicy(client *client.Client, policy *kyverno.Policy, filterK8Resources []utils.K8Resource) map[string]resourceInfo { -// // key uid -// resourceMap := map[string]resourceInfo{} -// for _, rule := range policy.Spec.Rules { -// // Match -// for _, k := range rule.MatchResources.Kinds { -// namespaces := []string{} -// if k == "Namespace" { -// namespaces = []string{""} -// } else { -// if rule.MatchResources.Namespace != "" { -// // if namespace is specified then we add the namespace -// namespaces = append(namespaces, rule.MatchResources.Namespace) -// } else { -// // no namespace specified, refer to all namespaces -// namespaces = getAllNamespaces(client) -// } - -// // Check if exclude namespace is not clashing -// namespaces = excludeNamespaces(namespaces, rule.ExcludeResources.Namespace) -// } - -// // If kind is namespace then namespace is "", override -// // Get resources in the namespace -// for _, ns := range namespaces { -// rMap := getResourcesPerNamespace(k, client, ns, rule, filterK8Resources) -// mergeresources(resourceMap, rMap) -// } -// } -// } -// return resourceMap -// } - -// func getResourcesPerNamespace(kind string, client *client.Client, namespace string, rule kyverno.Rule, filterK8Resources []utils.K8Resource) map[string]resourceInfo { -// resourceMap := map[string]resourceInfo{} -// // List resources -// list, err := client.ListResource(kind, namespace, rule.MatchResources.Selector) -// if err != nil { -// glog.Errorf("unable to list resource for %s with label selector %s", kind, rule.MatchResources.Selector.String()) -// return nil -// } -// var selector labels.Selector -// // exclude label selector -// if rule.ExcludeResources.Selector != nil { -// selector, err = v1helper.LabelSelectorAsSelector(rule.ExcludeResources.Selector) -// if err != nil { -// glog.Error(err) -// } -// } -// for _, res := range list.Items { -// // exclude label selectors -// if selector != nil { -// set := labels.Set(res.GetLabels()) -// if selector.Matches(set) { -// // if matches -// continue -// } -// } -// var name string -// // match -// // name -// // wild card matching -// name = rule.MatchResources.Name -// if name != "" { -// // if does not match then we skip -// if !wildcard.Match(name, res.GetName()) { -// continue -// } -// } -// // exclude -// // name -// // wild card matching -// name = rule.ExcludeResources.Name -// if name != "nil" { -// // if matches then we skip -// if wildcard.Match(name, res.GetName()) { -// continue -// } -// } -// gvk := res.GroupVersionKind() - -// ri := resourceInfo{Resource: res, Gvk: &metav1.GroupVersionKind{Group: gvk.Group, -// Version: gvk.Version, -// Kind: gvk.Kind}} -// // Skip the filtered resources -// if utils.SkipFilteredResources(gvk.Kind, res.GetNamespace(), res.GetName(), filterK8Resources) { -// continue -// } - -// resourceMap[string(res.GetUID())] = ri -// } -// return resourceMap -// } - -// // merge b into a map -// func mergeresources(a, b map[string]resourceInfo) { -// for k, v := range b { -// a[k] = v -// } -// } - -// func getAllNamespaces(client *client.Client) []string { -// namespaces := []string{} -// // get all namespaces -// nsList, err := client.ListResource("Namespace", "", nil) -// if err != nil { -// glog.Error(err) -// return namespaces -// } -// for _, ns := range nsList.Items { -// namespaces = append(namespaces, ns.GetName()) -// } -// return namespaces -// } - -// func excludeNamespaces(namespaces []string, excludeNs string) []string { -// if excludeNs == "" { -// return namespaces -// } -// filteredNamespaces := []string{} -// for _, n := range namespaces { -// if n == excludeNs { -// continue -// } -// filteredNamespaces = append(filteredNamespaces, n) -// } -// return filteredNamespaces -// } - //MatchesResourceDescription checks if the resource matches resource desription of the rule or not func MatchesResourceDescription(resource unstructured.Unstructured, rule kyverno.Rule) bool { matches := rule.MatchResources.ResourceDescription @@ -207,8 +47,8 @@ func MatchesResourceDescription(resource unstructured.Unstructured, rule kyverno } // Matches - // check if the resource namespace is defined in the list of namespaces for inclusion - if len(matches.Namespaces) > 0 && !utils.Contains(matches.Namespaces, namespace) { + // check if the resource namespace is defined in the list of namespace pattern + if len(matches.Namespaces) > 0 && !utils.ContainsNamepace(matches.Namespaces, namespace) { return false } @@ -238,7 +78,7 @@ func MatchesResourceDescription(resource unstructured.Unstructured, rule kyverno if len(exclude.Namespaces) == 0 { return NotEvaluate } - if utils.Contains(exclude.Namespaces, namespace) { + if utils.ContainsNamepace(exclude.Namespaces, namespace) { return Skip } return Process diff --git a/pkg/policy/existing.go b/pkg/policy/existing.go index 468b6de442..1dd5b6ebe7 100644 --- a/pkg/policy/existing.go +++ b/pkg/policy/existing.go @@ -141,7 +141,7 @@ func excludeResources(included map[string]unstructured.Unstructured, exclude kyv if len(exclude.Namespaces) == 0 { return NotEvaluate } - if utils.Contains(exclude.Namespaces, namespace) { + if utils.ContainsNamepace(exclude.Namespaces, namespace) { return Skip } return Process @@ -266,7 +266,7 @@ func excludeNamespaces(namespaces, excludeNs []string) []string { } filteredNamespaces := []string{} for _, n := range namespaces { - if utils.Contains(excludeNs, n) { + if utils.ContainsNamepace(excludeNs, n) { continue } filteredNamespaces = append(filteredNamespaces, n) diff --git a/pkg/utils/json.go b/pkg/utils/json.go index 5542d2275c..651e86c049 100644 --- a/pkg/utils/json.go +++ b/pkg/utils/json.go @@ -91,7 +91,7 @@ func subsetMap(a, b map[string]interface{}) bool { return true } -func contains(a interface{}, b []interface{}) bool { +func containsInt(a interface{}, b []interface{}) bool { switch typed := a.(type) { case bool: for _, bv := range b { @@ -181,7 +181,7 @@ func subsetSlice(a, b []interface{}) bool { } for _, av := range a { - if !contains(av, b) { + if !containsInt(av, b) { return false } } diff --git a/pkg/utils/util.go b/pkg/utils/util.go index 52cdc69ef1..bb56120384 100644 --- a/pkg/utils/util.go +++ b/pkg/utils/util.go @@ -22,15 +22,33 @@ type K8Resource struct { } //Contains Check if strint is contained in a list of string -func Contains(list []string, element string) bool { +func contains(list []string, element string, fn func(string, string) bool) bool { for _, e := range list { - if e == element { + if fn(e, element) { return true } } return false } +//ContainsNamepace check if namespace satisfies any list of pattern(regex) +func ContainsNamepace(patterns []string, ns string) bool { + return contains(patterns, ns, compareNamespaces) +} + +//ContainsString check if the string is contains in a list +func ContainsString(list []string, element string) bool { + return contains(list, element, compareString) +} + +func compareNamespaces(pattern, ns string) bool { + return wildcard.Match(pattern, ns) +} + +func compareString(str, name string) bool { + return str == name +} + //SkipFilteredResourcesReq checks if request is to be skipped based on filtered kinds func SkipFilteredResourcesReq(request *v1beta1.AdmissionRequest, filterK8Resources []K8Resource) bool { kind := request.Kind.Kind diff --git a/pkg/utils/util_test.go b/pkg/utils/util_test.go index 42d0dd3157..c424e7024c 100644 --- a/pkg/utils/util_test.go +++ b/pkg/utils/util_test.go @@ -9,32 +9,61 @@ import ( func Test_allEmpty(t *testing.T) { var list []string var element string - res := Contains(list, element) + res := ContainsString(list, element) assert.Assert(t, res == false) } func Test_emptyList(t *testing.T) { var list []string element := "foo" - res := Contains(list, element) + res := ContainsString(list, element) assert.Assert(t, res == false) } func Test_emptyElement(t *testing.T) { list := []string{"foo", "bar"} var element string - res := Contains(list, element) + res := ContainsString(list, element) assert.Assert(t, res == false) } func Test_emptyElementInList(t *testing.T) { list := []string{"foo", "bar", ""} var element string - res := Contains(list, element) + res := ContainsString(list, element) assert.Assert(t, res == true) list = []string{"foo", "bar", "bar"} element = "bar" - res = Contains(list, element) + res = ContainsString(list, element) assert.Assert(t, res == true) } + +func Test_containsNs(t *testing.T) { + var patterns []string + var res bool + patterns = []string{"*"} + res = ContainsNamepace(patterns, "default") + assert.Assert(t, res == true) + + patterns = []string{"*", "default"} + res = ContainsNamepace(patterns, "default") + assert.Assert(t, res == true) + + patterns = []string{"default2", "default"} + res = ContainsNamepace(patterns, "default1") + assert.Assert(t, res == false) + + patterns = []string{"d*"} + res = ContainsNamepace(patterns, "default") + assert.Assert(t, res == true) + + patterns = []string{"d*"} + res = ContainsNamepace(patterns, "test") + assert.Assert(t, res == false) + + patterns = []string{} + res = ContainsNamepace(patterns, "test") + assert.Assert(t, res == false) + +} diff --git a/pkg/webhooks/mutation.go b/pkg/webhooks/mutation.go index 643e462a18..74e95a7ae5 100644 --- a/pkg/webhooks/mutation.go +++ b/pkg/webhooks/mutation.go @@ -74,7 +74,7 @@ func (ws *WebhookServer) HandleMutation(request *v1beta1.AdmissionRequest) (bool for _, policy := range policies { // check if policy has a rule for the admission request kind - if !utils.Contains(getApplicableKindsForPolicy(policy), request.Kind.Kind) { + if !utils.ContainsString(getApplicableKindsForPolicy(policy), request.Kind.Kind) { continue } diff --git a/pkg/webhooks/policyvalidation.go b/pkg/webhooks/policyvalidation.go index 6a047a9c5d..db24df03e0 100644 --- a/pkg/webhooks/policyvalidation.go +++ b/pkg/webhooks/policyvalidation.go @@ -94,7 +94,7 @@ func (ws *WebhookServer) validateUniqueRuleName(policy *kyverno.ClusterPolicy) * var ruleNames []string for _, rule := range policy.Spec.Rules { - if utils.Contains(ruleNames, rule.Name) { + if utils.ContainsString(ruleNames, rule.Name) { msg := fmt.Sprintf(`The policy "%s" is invalid: duplicate rule name: "%s"`, policy.Name, rule.Name) glog.Errorln(msg) diff --git a/pkg/webhooks/validation.go b/pkg/webhooks/validation.go index c880ef3808..059139812c 100644 --- a/pkg/webhooks/validation.go +++ b/pkg/webhooks/validation.go @@ -79,7 +79,7 @@ func (ws *WebhookServer) HandleValidation(request *v1beta1.AdmissionRequest, pat var engineResponses []engine.EngineResponseNew for _, policy := range policies { - if !utils.Contains(getApplicableKindsForPolicy(policy), request.Kind.Kind) { + if !utils.ContainsString(getApplicableKindsForPolicy(policy), request.Kind.Kind) { continue } diff --git a/test/scenarios/test/scenario_validate_host_network_port.yaml b/test/scenarios/test/scenario_validate_host_network_port.yaml index da6ec8c9be..22d77bc75d 100644 --- a/test/scenarios/test/scenario_validate_host_network_port.yaml +++ b/test/scenarios/test/scenario_validate_host_network_port.yaml @@ -14,5 +14,5 @@ expected: rules: - name: validate-host-network-port type: Validation - message: Validation rule 'validate-host-network-port' failed at '/spec/containers/0/ports/0/hostPort/' for resource Pod//nginx-host-network. Host network and port are not allowed + message: "Validation rule 'validate-host-network-port' failed at '/spec/containers/0/ports/0/hostPort/' for resource Pod//nginx-host-network. Host network and port are not allowed" success: false \ No newline at end of file