Repository: aquasecurity/starboard (go)

Issue:
Figure out why integration test for kube-hunter command is failing on GitHub actions runner
We have an integration test which runs kube-hunter command. However this tests fails when it's run on GitHub actions runner. It seems to be working fine in dev environment with local KIND cluster though.

Pull request:
Add kubeHunter.quick configuration parameter
Fixes #165.

This PR adds a `kubeHunter.quick` key to starboard's configuration settings. When the `quick` key is set to `"true"`, `kube-hunter` is called with the `--quick` flag, which enables quick subnet scanning (/24). By default, `quick` scanning is disabled.

Setting this key to `"true"` in the integration test for `starboard` allows the test for `kube-hunter` to run in a reasonable amount of time. Indeed, without the `--quick` flag, `kube-hunter` will attempt to scan an entire `/16` CIDR, which takes a lot of time.

I used `"false"` and `"true"` (the strings `"false"` and `"true"`, not the booleans `false` and `true`) as possible values for the `kubeHunter.quick` config key, because currently the `ConfigData` type only allows `string` values:

```go
// ConfigData holds Starboard configuration settings as a set
// of key-value pairs.
type ConfigData map[string]string
```

However, YAML has booleans, so this may seem a little weird to users. Indeed, attempting to use `true` instead of `"true"` will cause a weird error: `Invalid value: "map[...": unrecognized type: string`.

One approach to circumvent this issue would be to use the strings "no" and "yes" instead.

Let me know what you think.

Patch:
diff --git a/deploy/init/03-starboard.cm.yaml b/deploy/init/03-starboard.cm.yaml
index 95ad462..a6b7131 100644
--- a/deploy/init/03-starboard.cm.yaml
+++ b/deploy/init/03-starboard.cm.yaml
@@ -12,6 +12,7 @@ data:
   trivy.serverURL: http://trivy-server.trivy-server:4954
   kube-bench.imageRef: docker.io/aquasec/kube-bench:0.4.0
   kube-hunter.imageRef: docker.io/aquasec/kube-hunter:0.4.0
+  kube-hunter.quick: "false"
   polaris.imageRef: quay.io/fairwinds/polaris:3.0
   polaris.config.yaml: |
     checks:
diff --git a/docs/settings.md b/docs/settings.md
index 60f74c6..372891c 100644
--- a/docs/settings.md
+++ b/docs/settings.md
@@ -66,6 +66,7 @@ The following tables list available configuration settings with their default va
 | `aqua.serverURL`               | N/A                                                    | The endpoint URL of Aqua management console |
 | `kube-bench.imageRef`          | `docker.io/aquasec/kube-bench:0.4.0`                   | kube-bench image reference |
 | `kube-hunter.imageRef`         | `docker.io/aquasec/kube-hunter:0.4.0`                  | kube-hunter image reference |
+| `kube-hunter.quick`            | `"false"`                                              | Whether to use kube-hunter's "quick" scanning mode (subnet 24). Set to `"true"` to enable. |
 | `polaris.imageRef`             | `quay.io/fairwinds/polaris:3.0`                        | Polaris image reference |
 | `polaris.config.yaml`          | [Check the default value here][default-polaris-config] | Polaris configuration file |
 
diff --git a/pkg/kubehunter/scanner.go b/pkg/kubehunter/scanner.go
index f64d27c..697e48e 100644
--- a/pkg/kubehunter/scanner.go
+++ b/pkg/kubehunter/scanner.go
@@ -27,6 +27,7 @@ const (
 
 type Config interface {
 	GetKubeHunterImageRef() (string, error)
+	GetKubeHunterQuick() (bool, error)
 }
 
 type Scanner struct {
@@ -100,6 +101,14 @@ func (s *Scanner) prepareKubeHunterJob() (*batchv1.Job, error) {
 	if err != nil {
 		return nil, err
 	}
+	kubeHunterArgs := []string{"--pod", "--report", "json", "--log", "warn"}
+	quick, err := s.config.GetKubeHunterQuick()
+	if err != nil {
+		return nil, err
+	}
+	if quick {
+		kubeHunterArgs = append(kubeHunterArgs, "--quick")
+	}
 	return &batchv1.Job{
 		ObjectMeta: metav1.ObjectMeta{
 			Name:      s.GenerateID(),
@@ -121,7 +130,7 @@ func (s *Scanner) prepareKubeHunterJob() (*batchv1.Job, error) {
 							Image:                    imageRef,
 							ImagePullPolicy:          corev1.PullIfNotPresent,
 							TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError,
-							Args:                     []string{"--pod", "--report", "json", "--log", "warn"},
+							Args:                     kubeHunterArgs,
 							Resources: corev1.ResourceRequirements{
 								Limits: corev1.ResourceList{
 									corev1.ResourceCPU:    resource.MustParse("300m"),
diff --git a/pkg/starboard/config.go b/pkg/starboard/config.go
index b508873..ce40abb 100644
--- a/pkg/starboard/config.go
+++ b/pkg/starboard/config.go
@@ -267,6 +267,7 @@ func GetDefaultConfig() ConfigData {
 
 		"kube-bench.imageRef":  "docker.io/aquasec/kube-bench:0.4.0",
 		"kube-hunter.imageRef": "docker.io/aquasec/kube-hunter:0.4.0",
+		"kube-hunter.quick":    "false",
 
 		"polaris.imageRef":    "quay.io/fairwinds/polaris:3.0",
 		"polaris.config.yaml": polarisConfigYAML,
@@ -329,6 +330,17 @@ func (c ConfigData) GetKubeHunterImageRef() (string, error) {
 	return c.getRequiredProperty("kube-hunter.imageRef")
 }
 
+func (c ConfigData) GetKubeHunterQuick() (bool, error) {
+	val, err := c.getRequiredProperty("kube-hunter.quick")
+	if err != nil {
+		return false, err
+	}
+	if val != "false" && val != "true" {
+		return false, fmt.Errorf("property kube-hunter.quick must be either \"false\" or \"true\", got %q", val)
+	}
+	return val == "true", nil
+}
+
 func (c ConfigData) GetPolarisImageRef() (string, error) {
 	return c.getRequiredProperty("polaris.imageRef")
 }


Test patch:
diff --git a/itest/starboard/starboard_cli_test.go b/itest/starboard/starboard_cli_test.go
index 6049588..1e0bae6 100644
--- a/itest/starboard/starboard_cli_test.go
+++ b/itest/starboard/starboard_cli_test.go
@@ -66,7 +66,6 @@ var _ = Describe("Starboard CLI", func() {
 			"-v",
 			starboardCLILogLevel,
 		}, GinkgoWriter, GinkgoWriter)
-
 		Expect(err).ToNot(HaveOccurred())
 	})
 
@@ -987,8 +986,22 @@ var _ = Describe("Starboard CLI", func() {
 		})
 	})
 
-	PDescribe("Command scan kubehunterreports", func() {
-		// FIXME Figure out why kube-hunter is failing on GitHub actions runner, whereas it's fine with local KIND cluster
+	Describe("Command scan kubehunterreports", func() {
+		BeforeEach(func() {
+			cm, err := kubernetesClientset.CoreV1().ConfigMaps(starboard.NamespaceName).
+				Get(context.TODO(), starboard.ConfigMapName, metav1.GetOptions{})
+			Expect(err).ToNot(HaveOccurred())
+
+			// Need to use kube-hunter quick scanning mode (subnet 24), otherwise
+			// when running the test in Azure (e.g., in a GitHub actions runner)
+			// kube-hunter may attempt to scan a large CIDR (subnet 16), which takes a long
+			// time and isn't necessary for the purposes of the test.
+			cm.Data["kube-hunter.quick"] = "true"
+			_, err = kubernetesClientset.CoreV1().ConfigMaps(starboard.NamespaceName).
+				Update(context.TODO(), cm, metav1.UpdateOptions{})
+			Expect(err).ToNot(HaveOccurred())
+		})
+
 		It("should run kube-hunter", func() {
 			err := cmd.Run(versionInfo, []string{
 				"starboard",
diff --git a/pkg/starboard/config_test.go b/pkg/starboard/config_test.go
index bb12148..fb54915 100644
--- a/pkg/starboard/config_test.go
+++ b/pkg/starboard/config_test.go
@@ -150,6 +150,52 @@ func TestConfigData_GetKubeHunterImageRef(t *testing.T) {
 	}
 }
 
+func TestConfigData_GetKubeHunterQuick(t *testing.T) {
+	testCases := []struct {
+		name          string
+		configData    starboard.ConfigData
+		expectedError string
+		expectedQuick bool
+	}{
+		{
+			name:          "Should return error",
+			configData:    starboard.ConfigData{},
+			expectedError: "property kube-hunter.quick not set",
+		}, {
+			name: "Should return error when when quick is set to something other than \"false\" or \"true\" in config data",
+			configData: starboard.ConfigData{
+				"kube-hunter.quick": "not-a-boolean",
+			},
+			expectedError: "property kube-hunter.quick must be either \"false\" or \"true\", got \"not-a-boolean\"",
+		}, {
+			name: "Should return false when quick is set to \"false\" in config data",
+			configData: starboard.ConfigData{
+				"kube-hunter.quick": "false",
+			},
+			expectedQuick: false,
+		},
+		{
+			name: "Should return true when quick is set to \"true\" in config data",
+			configData: starboard.ConfigData{
+				"kube-hunter.quick": "true",
+			},
+			expectedQuick: true,
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			quick, err := tc.configData.GetKubeHunterQuick()
+			if tc.expectedError != "" {
+				require.EqualError(t, err, tc.expectedError)
+			} else {
+				require.NoError(t, err)
+				assert.Equal(t, tc.expectedQuick, quick)
+			}
+		})
+	}
+}
+
 func TestConfigData_GetVulnerabilityReportsScanner(t *testing.T) {
 	testCases := []struct {
 		name            string