Repository: aquasecurity/kube-bench
Pull request #751 (2020-10-21 19:11:05)

Allow for groups and checks to be skipped via command line parameter
This adds the feature specified in the PR #564 

Currently this is how the workflow works:
- User can add --skip followed by any number of IDS 
- For each id before running checks it will see if it belongs to the current group or check.
- If it belongs to the current group all child checks are skipped, if it belongs to the current check only that current check is skipped.
- If skip is specified in the yaml or the command line parameter it will be skipped.

Although the original PR did have a comment specifying that 1.2.* for example could be good I wasn't sure if that was wanted so if any maintainer could let me know what they think of the current implementation and I can go from there, thanks!

codecov[bot]: # [Codecov](https://codecov.io/gh/aquasecurity/kube-bench/pull/751?src=pr&el=h1) Report
> Merging [#751](https://codecov.io/gh/aquasecurity/kube-bench/pull/751?src=pr&el=desc) into [master](https://codecov.io/gh/aquasecurity/kube-bench/commit/9474472194546103a41ed153f65dc7facd2458f4?el=desc) will **increase** coverage by `0.20%`.
> The diff coverage is `50.00%`.

[![Impacted file tree graph](https://codecov.io/gh/aquasecurity/kube-bench/pull/751/graphs/tree.svg?width=650&height=150&src=pr&token=MVaDEmdgi9)](https://codecov.io/gh/aquasecurity/kube-bench/pull/751?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #751      +/-   ##
==========================================
+ Coverage   63.89%   64.10%   +0.20%     
==========================================
  Files          12       12              
  Lines        1047     1053       +6     
==========================================
+ Hits          669      675       +6     
- Misses        339      340       +1     
+ Partials       39       38       -1     
```


| [Impacted Files](https://codecov.io/gh/aquasecurity/kube-bench/pull/751?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [cmd/common.go](https://codecov.io/gh/aquasecurity/kube-bench/pull/751/diff?src=pr&el=tree#diff-Y21kL2NvbW1vbi5nbw==) | `39.63% <0.00%> (-0.55%)` | :arrow_down: |
| [check/controls.go](https://codecov.io/gh/aquasecurity/kube-bench/pull/751/diff?src=pr&el=tree#diff-Y2hlY2svY29udHJvbHMuZ28=) | `79.54% <100.00%> (+3.96%)` | :arrow_up: |
| [cmd/root.go](https://codecov.io/gh/aquasecurity/kube-bench/pull/751/diff?src=pr&el=tree#diff-Y21kL3Jvb3QuZ28=) | `40.86% <100.00%> (+0.64%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/aquasecurity/kube-bench/pull/751?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/aquasecurity/kube-bench/pull/751?src=pr&el=footer). Last update [9474472...8dd39cb](https://codecov.io/gh/aquasecurity/kube-bench/pull/751?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/README.md b/README.md
index 8d2a782..926ee11 100644
--- a/README.md
+++ b/README.md
@@ -298,7 +298,7 @@ Alternatively, you can manually download and extract the kube-bench binary:
 ```
 curl -L https://github.com/aquasecurity/kube-bench/releases/download/v0.3.1/kube-bench_0.3.1_linux_amd64.tar.gz -o kube-bench_0.3.1_linux_amd64.tar.gz
 
-tar -xvf kube-bench_0.3.0_linux_amd64.tar.gz
+tar -xvf kube-bench_0.3.1_linux_amd64.tar.gz
 ```
 
 You can then run kube-bench directly:
diff --git a/cfg/config.yaml b/cfg/config.yaml
index b31cab1..52360fb 100644
--- a/cfg/config.yaml
+++ b/cfg/config.yaml
@@ -48,8 +48,7 @@ master:
     defaultconf: /etc/kubernetes/manifests/kube-scheduler.yaml
     kubeconfig:
       - /etc/kubernetes/scheduler.conf
-    defaultkubeconfig:
-      - /etc/kubernetes/scheduler.conf
+    defaultkubeconfig: /etc/kubernetes/scheduler.conf
 
   controllermanager:
     bins:
@@ -67,8 +66,7 @@ master:
     defaultconf: /etc/kubernetes/manifests/kube-controller-manager.yaml
     kubeconfig:
       - /etc/kubernetes/controller-manager.conf
-    defaultkubeconfig:
-      - /etc/kubernetes/controller-manager.conf
+    defaultkubeconfig: /etc/kubernetes/controller-manager.conf
 
   etcd:
     optional: true
diff --git a/cfg/eks-1.0/policies.yaml b/cfg/eks-1.0/policies.yaml
index 98dbe28..51f25a0 100644
--- a/cfg/eks-1.0/policies.yaml
+++ b/cfg/eks-1.0/policies.yaml
@@ -131,7 +131,7 @@ groups:
         text: "Minimize the admission of containers with capabilities assigned (Not Scored)"
         type: "manual"
         remediation: |
-          Review the use of capabilites in applications runnning on your cluster. Where a namespace
+          Review the use of capabilities in applications running on your cluster. Where a namespace
           contains applications which do not require any Linux capabities to operate consider adding
           a PSP which forbids the admission of containers which do not drop all capabilities.
         scored: false
diff --git a/check/controls.go b/check/controls.go
index 992b78d..d6f6721 100644
--- a/check/controls.go
+++ b/check/controls.go
@@ -75,7 +75,7 @@ func NewControls(t NodeType, in []byte) (*Controls, error) {
 }
 
 // RunChecks runs the checks with the given Runner. Only checks for which the filter Predicate returns `true` will run.
-func (controls *Controls) RunChecks(runner Runner, filter Predicate) Summary {
+func (controls *Controls) RunChecks(runner Runner, filter Predicate, skipIdMap map[string]bool) Summary {
 	var g []*Group
 	m := make(map[string]*Group)
 	controls.Summary.Pass, controls.Summary.Fail, controls.Summary.Warn, controls.Info = 0, 0, 0, 0
@@ -87,8 +87,10 @@ func (controls *Controls) RunChecks(runner Runner, filter Predicate) Summary {
 				continue
 			}
 
-			// propagate skip type to check if set at the group level.
-			if group.Skip {
+			_, groupSkippedViaCmd := skipIdMap[group.ID]
+			_, checkSkippedViaCmd := skipIdMap[check.ID]
+
+			if group.Skip || groupSkippedViaCmd || checkSkippedViaCmd  {
 				check.Type = SKIP
 			}
 
diff --git a/cmd/common.go b/cmd/common.go
index 49a4e2d..9e33098 100644
--- a/cmd/common.go
+++ b/cmd/common.go
@@ -32,7 +32,6 @@ import (
 
 // NewRunFilter constructs a Predicate based on FilterOpts which determines whether tested Checks should be run or not.
 func NewRunFilter(opts FilterOpts) (check.Predicate, error) {
-
 	if opts.CheckList != "" && opts.GroupList != "" {
 		return nil, fmt.Errorf("group option and check option can't be used together")
 	}
@@ -118,10 +117,20 @@ func runChecks(nodetype check.NodeType, testYamlFile string) {
 		exitWithError(fmt.Errorf("error setting up run filter: %v", err))
 	}
 
-	controls.RunChecks(runner, filter)
+	controls.RunChecks(runner, filter, parseSkipIds(skipIds))
 	controlsCollection = append(controlsCollection, controls)
 }
 
+func parseSkipIds(skipIds string) map[string]bool {
+	var skipIdMap =  make(map[string]bool, 0)
+	if skipIds != "" {
+		for _, id := range strings.Split(skipIds, ",") {
+			skipIdMap[strings.Trim(id, " ")] = true
+		}
+	}
+	return skipIdMap
+}
+
 // colorPrint outputs the state in a specific colour, along with a message string
 func colorPrint(state check.State, s string) {
 	colors[state].Printf("[%s] ", state)
diff --git a/cmd/root.go b/cmd/root.go
index 432d326..41334a7 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -51,6 +51,7 @@ var (
 	noResults           bool
 	noSummary           bool
 	noRemediations      bool
+	skipIds             string
 	filterOpts          FilterOpts
 	includeTestOutput   bool
 	outputFile          string
@@ -153,6 +154,7 @@ func init() {
 	RootCmd.PersistentFlags().BoolVar(&pgSQL, "pgsql", false, "Save the results to PostgreSQL")
 	RootCmd.PersistentFlags().BoolVar(&filterOpts.Scored, "scored", true, "Run the scored CIS checks")
 	RootCmd.PersistentFlags().BoolVar(&filterOpts.Unscored, "unscored", true, "Run the unscored CIS checks")
+	RootCmd.PersistentFlags().StringVar(&skipIds, "skip", "", "List of comma separated values of checks to be skipped")
 	RootCmd.PersistentFlags().BoolVar(&includeTestOutput, "include-test-output", false, "Prints the actual result when test fails")
 	RootCmd.PersistentFlags().StringVar(&outputFile, "outputfile", "", "Writes the JSON results to output file")
 


Test patch:
diff --git a/check/controls_test.go b/check/controls_test.go
index 0d4cee6..34a76b8 100644
--- a/check/controls_test.go
+++ b/check/controls_test.go
@@ -69,7 +69,6 @@ func TestYamlFiles(t *testing.T) {
 }
 
 func TestNewControls(t *testing.T) {
-
 	t.Run("Should return error when node type is not specified", func(t *testing.T) {
 		// given
 		in := []byte(`
@@ -95,9 +94,48 @@ groups:
 
 }
 
+func TestControls_RunChecks_SkippedCmd(t *testing.T) {
+	t.Run("Should skip checks and groups specified by skipMap", func(t *testing.T) {
+		// given
+		normalRunner := &defaultRunner{}
+		// and
+		in := []byte(`
+---
+type: "master"
+groups:
+- id: G1
+  checks:
+  - id: G1/C1
+  - id: G1/C2
+  - id: G1/C3
+- id: G2
+  checks:
+  - id: G2/C1
+  - id: G2/C2
+`)
+		controls, err := NewControls(MASTER, in)
+		assert.NoError(t, err)
+
+		var allChecks Predicate = func(group *Group, c *Check) bool {
+			return true
+		}
+
+		skipMap := make(map[string]bool, 0)
+		skipMap["G1"] = true
+		skipMap["G2/C1"] = true
+		skipMap["G2/C2"] = true
+		controls.RunChecks(normalRunner, allChecks, skipMap)
+
+		G1 := controls.Groups[0]
+		assertEqualGroupSummary(t, 0, 0, 3, 0, G1)
+
+		G2 := controls.Groups[1]
+		assertEqualGroupSummary(t, 0, 0, 2, 0, G2)
+	})
+}
 
 func TestControls_RunChecks_Skipped(t *testing.T) {
-	t.Run("Should run checks matching the filter and update summaries", func(t *testing.T) {
+	t.Run("Should skip checks where the parent group is marked as skip", func(t *testing.T) {
 		// given
 		normalRunner := &defaultRunner{}
 		// and
@@ -116,12 +154,12 @@ groups:
 		var allChecks Predicate = func(group *Group, c *Check) bool {
 			return true
 		}
-		controls.RunChecks(normalRunner, allChecks)
+		emptySkipList := make(map[string]bool, 0)
+		controls.RunChecks(normalRunner, allChecks, emptySkipList)
 
 		G1 := controls.Groups[0]
 		assertEqualGroupSummary(t, 0, 0, 1, 0, G1)
 	})
-
 }
 
 func TestControls_RunChecks(t *testing.T) {
@@ -162,8 +200,9 @@ groups:
 		var runAll Predicate = func(group *Group, c *Check) bool {
 			return true
 		}
+		var emptySkipList = make(map[string]bool, 0)
 		// when
-		controls.RunChecks(runner, runAll)
+		controls.RunChecks(runner, runAll, emptySkipList)
 		// then
 		assert.Equal(t, 2, len(controls.Groups))
 		// and
diff --git a/cmd/common_test.go b/cmd/common_test.go
index 15f4164..774c4aa 100644
--- a/cmd/common_test.go
+++ b/cmd/common_test.go
@@ -30,6 +30,18 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
+func TestParseSkipIds(t *testing.T) {
+	skipMap := parseSkipIds("4.12,4.13,5")
+	_, fourTwelveExists := skipMap["4.12"]
+	_, fourThirteenExists := skipMap["4.13"]
+	_, fiveExists := skipMap["5"]
+	_, other := skipMap["G1"]
+	assert.True(t, fourThirteenExists)
+	assert.True(t, fourTwelveExists)
+	assert.True(t, fiveExists)
+	assert.False(t, other)
+}
+
 func TestNewRunFilter(t *testing.T) {
 
 	type TestCase struct {