Repository: FairwindsOps/polaris
Pull request #421 (2020-09-28 18:33:54)

Implement namespace support for exceptions
Add namespace support for exceptions for shared clusters.

CLAassistant: [![CLA assistant check](https://cla-assistant.io/pull/badge/not_signed)](https://cla-assistant.io/FairwindsOps/polaris?pullRequest=421) <br/>Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our [Contributor License Agreement](https://cla-assistant.io/FairwindsOps/polaris?pullRequest=421) before we can accept your contribution.<br/><sub>You have signed the CLA already but the status is still pending? Let us [recheck](https://cla-assistant.io/check/FairwindsOps/polaris?pullRequest=421) it.</sub>
rbren: Can you also add documentation here: https://github.com/FairwindsOps/polaris/blob/master/docs/usage.md#exemptions
mblaschke: Done
marcemv90: With this new feature is it possible to ignore completely a namespace? Because I would like to get rid of the kube-system namespace in the dashboard.
baderbuddy: @marcemv90 this should allow all controllers within a namespace to be exempted (since it matches on prefix you could add a controller-name of `""`) but you'd have to specify the rules to exempt still

Patch:
diff --git a/docs/usage.md b/docs/usage.md
index d1d293e0..61ff76e8 100644
--- a/docs/usage.md
+++ b/docs/usage.md
@@ -71,9 +71,16 @@ To exempt a controller from all checks via annotations, use the annotation `pola
 kubectl annotate deployment my-deployment polaris.fairwinds.com/exempt=true
 ```
 
-To exempt a controller via the config, you have to specify a list of controller names and a list of rules, e.g.
+To exempt a controller via the config, you have to specify a namespace (optional), a list of controller names and a list of rules, e.g.
 ```yaml
 exemptions:
+  # exception valid for kube-system namespace
+  - namespace: kube-system
+    controllerNames:
+      - dns-controller
+    rules:
+      - hostNetworkSet
+  # exception valid in all namespaces
   - controllerNames:
       - dns-controller
     rules:
diff --git a/examples/config-full.yaml b/examples/config-full.yaml
index 19d01d07..c4553d0d 100644
--- a/examples/config-full.yaml
+++ b/examples/config-full.yaml
@@ -35,6 +35,12 @@ exemptions:
     rules:
     - hostNetworkSet
     - hostPortSet
+  - namespace: kube-system
+    controllerNames:
+      - my-network-controller
+    rules:
+      - hostNetworkSet
+      - hostPortSet
 
 customChecks:
   resourceLimits:
diff --git a/examples/config.yaml b/examples/config.yaml
index cd3b1dba..403fa355 100644
--- a/examples/config.yaml
+++ b/examples/config.yaml
@@ -27,7 +27,8 @@ checks:
   insecureCapabilities: warning
 
 exemptions:
-  - controllerNames:
+  - namespace: kube-system
+    controllerNames:
       - kube-apiserver
       - kube-proxy
       - kube-scheduler
diff --git a/pkg/config/config.go b/pkg/config/config.go
index e697926c..0137f849 100644
--- a/pkg/config/config.go
+++ b/pkg/config/config.go
@@ -40,6 +40,7 @@ type Configuration struct {
 type Exemption struct {
 	Rules           []string `json:"rules"`
 	ControllerNames []string `json:"controllerNames"`
+	Namespace       string `json:"namespace"`
 }
 
 var configBox = (*packr.Box)(nil)
diff --git a/pkg/config/exemptions.go b/pkg/config/exemptions.go
index 31711797..2f413525 100644
--- a/pkg/config/exemptions.go
+++ b/pkg/config/exemptions.go
@@ -5,7 +5,7 @@ import (
 )
 
 // IsActionable determines whether a check is actionable given the current configuration
-func (conf Configuration) IsActionable(ruleID, controllerName string) bool {
+func (conf Configuration) IsActionable(ruleID, namespace, controllerName string) bool {
 	if severity, ok := conf.Checks[ruleID]; !ok || !severity.IsActionable() {
 		return false
 	}
@@ -14,10 +14,15 @@ func (conf Configuration) IsActionable(ruleID, controllerName string) bool {
 	}
 
 	for _, example := range conf.Exemptions {
+		if example.Namespace != "" && example.Namespace != namespace {
+			continue
+		}
+
 		for _, rule := range example.Rules {
 			if rule != ruleID {
 				continue
 			}
+
 			for _, controller := range example.ControllerNames {
 				if strings.HasPrefix(controllerName, controller) {
 					return false
diff --git a/pkg/config/schema.go b/pkg/config/schema.go
index 255741b1..0ef8b055 100644
--- a/pkg/config/schema.go
+++ b/pkg/config/schema.go
@@ -152,7 +152,7 @@ func (check SchemaCheck) CheckObject(obj interface{}) (bool, error) {
 }
 
 // IsActionable decides if this check applies to a particular target
-func (check SchemaCheck) IsActionable(target TargetKind, controllerType string, isInit bool) bool {
+func (check SchemaCheck) IsActionable(target TargetKind, namespace, controllerType string, isInit bool) bool {
 	if check.Target != target {
 		return false
 	}
diff --git a/pkg/validator/schema.go b/pkg/validator/schema.go
index bc18d67b..17c38e46 100644
--- a/pkg/validator/schema.go
+++ b/pkg/validator/schema.go
@@ -86,10 +86,10 @@ func resolveCheck(conf *config.Configuration, checkID string, controller kube.Ge
 	if !ok {
 		return nil, fmt.Errorf("Check %s not found", checkID)
 	}
-	if !conf.IsActionable(check.ID, controller.ObjectMeta.GetName()) {
+	if !conf.IsActionable(check.ID, controller.ObjectMeta.GetNamespace(), controller.ObjectMeta.GetName()) {
 		return nil, nil
 	}
-	if !check.IsActionable(target, controller.Kind, isInitContainer) {
+	if !check.IsActionable(target, controller.ObjectMeta.GetNamespace(), controller.Kind, isInitContainer) {
 		return nil, nil
 	}
 	return &check, nil


Test patch:
diff --git a/pkg/config/exemptions_test.go b/pkg/config/exemptions_test.go
index ebffc12c..cead8aab 100644
--- a/pkg/config/exemptions_test.go
+++ b/pkg/config/exemptions_test.go
@@ -39,10 +39,19 @@ exemptions:
       - test
 `
 
+var confNamespaceTest = `
+checks:
+  ANY: warning
+exemptions:
+  - namespace: kube-system
+    controllerNames:
+      - test
+`
+
 func TestInclusiveExemption(t *testing.T) {
 	parsedConf, _ := Parse([]byte(confExemptTest))
-	applicable := parsedConf.IsActionable("ANY", "test")
-	applicableOtherController := parsedConf.IsActionable("ANY", "other")
+	applicable := parsedConf.IsActionable("ANY", "test", "test")
+	applicableOtherController := parsedConf.IsActionable("ANY","test",  "other")
 
 	assert.False(t, applicable, "Expected all checks to be exempted when their controller is specified.")
 	assert.True(t, applicableOtherController, "Expected checks to only be exempted when their controller is specified.")
@@ -50,13 +59,22 @@ func TestInclusiveExemption(t *testing.T) {
 
 func TestIndividualRuleException(t *testing.T) {
 	parsedConf, _ := Parse([]byte(confExemptRuleTest))
-	applicable := parsedConf.IsActionable("ANY", "test")
-	applicableOtherRule := parsedConf.IsActionable("OTHER", "test")
-	applicableOtherRuleOtherController := parsedConf.IsActionable("OTHER", "other")
-	applicableRuleOtherController := parsedConf.IsActionable("ANY", "other")
+	applicable := parsedConf.IsActionable("ANY", "test", "test")
+	applicableOtherRule := parsedConf.IsActionable("OTHER","test",  "test")
+	applicableOtherRuleOtherController := parsedConf.IsActionable("OTHER","test",  "other")
+	applicableRuleOtherController := parsedConf.IsActionable("ANY","test",  "other")
 
 	assert.False(t, applicable, "Expected all checks to be exempted when their controller and rule are specified.")
 	assert.True(t, applicableOtherRule, "Expected checks to only be exempted when their controller and rule are specified.")
 	assert.True(t, applicableOtherRuleOtherController, "Expected checks to only be exempted when their controller and rule are specified.")
 	assert.True(t, applicableRuleOtherController, "Expected checks to only be exempted when their controller and rule are specified.")
 }
+
+func TestNamespaceExemption(t *testing.T) {
+	parsedConf, _ := Parse([]byte(confNamespaceTest))
+	applicable := parsedConf.IsActionable("ANY", "kube-system", "test")
+	applicableOtherController := parsedConf.IsActionable("ANY","default",  "test")
+
+	assert.False(t, applicable, "Expected all checks to be exempted when their namespace and controller is specified.")
+	assert.True(t, applicableOtherController, "Expected checks to only be exempted when their namespace and controller is specified.")
+}
\ No newline at end of file