Repository: aquasecurity/starboard
Pull request #304 (2021-01-05 22:41:02)

feat: Bump up kube-hunter to v0.4.0
Introduce kube-hunter.imageRef config param.

Signed-off-by: Daniel Pacak <pacak.daniel@gmail.com>

codecov[bot]: # [Codecov](https://codecov.io/gh/aquasecurity/starboard/pull/304?src=pr&el=h1) Report
> Merging [#304](https://codecov.io/gh/aquasecurity/starboard/pull/304?src=pr&el=desc) (d004402) into [main](https://codecov.io/gh/aquasecurity/starboard/commit/f0444e694dc8223819b4f0f70db76258471a751e?el=desc) (f0444e6) will **decrease** coverage by `14.97%`.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/aquasecurity/starboard/pull/304/graphs/tree.svg?width=650&height=150&src=pr&token=qqTGKAJ0Wb)](https://codecov.io/gh/aquasecurity/starboard/pull/304?src=pr&el=tree)

```diff
@@             Coverage Diff             @@
##             main     #304       +/-   ##
===========================================
- Coverage   62.68%   47.71%   -14.98%     
===========================================
  Files          57       12       -45     
  Lines        2731      918     -1813     
===========================================
- Hits         1712      438     -1274     
+ Misses        799      414      -385     
+ Partials      220       66      -154     
```


| [Impacted Files](https://codecov.io/gh/aquasecurity/starboard/pull/304?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pkg/vulnerabilityreport/writer.go](https://codecov.io/gh/aquasecurity/starboard/pull/304/diff?src=pr&el=tree#diff-cGtnL3Z1bG5lcmFiaWxpdHlyZXBvcnQvd3JpdGVyLmdv) | `0.00% <0.00%> (-84.38%)` | :arrow_down: |
| [pkg/vulnerabilityreport/scanner.go](https://codecov.io/gh/aquasecurity/starboard/pull/304/diff?src=pr&el=tree#diff-cGtnL3Z1bG5lcmFiaWxpdHlyZXBvcnQvc2Nhbm5lci5nbw==) | `0.00% <0.00%> (-73.47%)` | :arrow_down: |
| [pkg/trivy/scanner.go](https://codecov.io/gh/aquasecurity/starboard/pull/304/diff?src=pr&el=tree#diff-cGtnL3RyaXZ5L3NjYW5uZXIuZ28=) | `44.81% <0.00%> (-28.15%)` | :arrow_down: |
| [pkg/vulnerabilityreport/store.go](https://codecov.io/gh/aquasecurity/starboard/pull/304/diff?src=pr&el=tree#diff-cGtnL3Z1bG5lcmFiaWxpdHlyZXBvcnQvc3RvcmUuZ28=) | `65.51% <0.00%> (-17.25%)` | :arrow_down: |
| [pkg/trivy/converter.go](https://codecov.io/gh/aquasecurity/starboard/pull/304/diff?src=pr&el=tree#diff-cGtnL3RyaXZ5L2NvbnZlcnRlci5nbw==) | `81.25% <0.00%> (-9.38%)` | :arrow_down: |
| [pkg/cmd/init.go](https://codecov.io/gh/aquasecurity/starboard/pull/304/diff?src=pr&el=tree#diff-cGtnL2NtZC9pbml0Lmdv) | | |
| [pkg/polaris/converter.go](https://codecov.io/gh/aquasecurity/starboard/pull/304/diff?src=pr&el=tree#diff-cGtnL3BvbGFyaXMvY29udmVydGVyLmdv) | | |
| [pkg/cmd/commands.go](https://codecov.io/gh/aquasecurity/starboard/pull/304/diff?src=pr&el=tree#diff-cGtnL2NtZC9jb21tYW5kcy5nbw==) | | |
| [pkg/ext/id\_generator.go](https://codecov.io/gh/aquasecurity/starboard/pull/304/diff?src=pr&el=tree#diff-cGtnL2V4dC9pZF9nZW5lcmF0b3IuZ28=) | | |
| [pkg/cmd/root.go](https://codecov.io/gh/aquasecurity/starboard/pull/304/diff?src=pr&el=tree#diff-cGtnL2NtZC9yb290Lmdv) | | |
| ... and [35 more](https://codecov.io/gh/aquasecurity/starboard/pull/304/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/aquasecurity/starboard/pull/304?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/starboard/pull/304?src=pr&el=footer). Last update [45d1f63...d004402](https://codecov.io/gh/aquasecurity/starboard/pull/304?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/docs/configuration.md b/docs/configuration.md
index 784549d..ed69ec9 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -64,6 +64,8 @@ values.
 | `trivy.serverTokenHeader`      | `Trivy-Token`                                          | The name of the HTTP header to send the authentication token to Trivy server. Only application in `ClientServer` mode when `trivy.serverToken` is specified. |
 | `aqua.imageRef`                | `docker.io/aquasec/scanner:5.3`                        | Aqua scanner image reference. The tag determines the version of the `scanner` binary executable and it must be compatible with version of Aqua console. |
 | `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 |
 | `polaris.config.yaml`          | [Check the default value here][default-polaris-config] | Polaris configuration file |
 
 | SECRET KEY                  | DESCRIPTION |
diff --git a/mkdocs.yml b/mkdocs.yml
index 21ff969..18b37ad 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -17,13 +17,13 @@ nav:
           - From Source (Linux, macOS): cli/installation/source.md
       - Getting Started: cli/getting-started.md
   - Starboard Operator:
-    - Overview: operator/index.md
-    - Installation:
-        - kubectl: operator/installation/kubectl.md
-        - Helm: operator/installation/helm.md
-        - Operator Lifecycle Manager: operator/installation/olm.md
-    - Getting Started: operator/getting-started.md
-    - Configuration: operator/configuration.md
+      - Overview: operator/index.md
+      - Installation:
+          - kubectl: operator/installation/kubectl.md
+          - Helm: operator/installation/helm.md
+          - Operator Lifecycle Manager: operator/installation/olm.md
+      - Getting Started: operator/getting-started.md
+      - Configuration: operator/configuration.md
   - Configuration: configuration.md
   - Integrations:
       - Vulnerability Scanners: integrations/vulnerability-scanners.md
diff --git a/pkg/cmd/scan_kubehunter.go b/pkg/cmd/scan_kubehunter.go
index ea90f21..a8f146f 100644
--- a/pkg/cmd/scan_kubehunter.go
+++ b/pkg/cmd/scan_kubehunter.go
@@ -36,11 +36,11 @@ const (
 func ScanKubeHunterReports(cf *genericclioptions.ConfigFlags) func(cmd *cobra.Command, args []string) (err error) {
 	return func(cmd *cobra.Command, args []string) error {
 		ctx := context.Background()
-		config, err := cf.ToRESTConfig()
+		kubernetesConfig, err := cf.ToRESTConfig()
 		if err != nil {
 			return err
 		}
-		kubernetesClientset, err := kubernetes.NewForConfig(config)
+		kubernetesClientset, err := kubernetes.NewForConfig(kubernetesConfig)
 		if err != nil {
 			return err
 		}
@@ -48,11 +48,16 @@ func ScanKubeHunterReports(cf *genericclioptions.ConfigFlags) func(cmd *cobra.Co
 		if err != nil {
 			return err
 		}
-		report, err := kubehunter.NewScanner(starboard.NewScheme(), kubernetesClientset, opts).Scan(ctx)
+		config, err := starboard.NewConfigManager(kubernetesClientset, starboard.NamespaceName).Read(ctx)
 		if err != nil {
 			return err
 		}
-		starboardClientset, err := starboardapi.NewForConfig(config)
+
+		report, err := kubehunter.NewScanner(starboard.NewScheme(), config, kubernetesClientset, opts).Scan(ctx)
+		if err != nil {
+			return err
+		}
+		starboardClientset, err := starboardapi.NewForConfig(kubernetesConfig)
 		if err != nil {
 			return err
 		}
diff --git a/pkg/kubebench/scanner.go b/pkg/kubebench/scanner.go
index 4f9957d..e2ad7b3 100644
--- a/pkg/kubebench/scanner.go
+++ b/pkg/kubebench/scanner.go
@@ -60,7 +60,7 @@ func (s *Scanner) Scan(ctx context.Context, node corev1.Node) (report v1alpha1.C
 	// 1. Prepare descriptor for the Kubernetes Job which will run kube-bench
 	job, err := s.prepareKubeBenchJob(node)
 	if err != nil {
-		return report, nil
+		return report, err
 	}
 
 	// 2. Run the prepared Job and wait for its completion or failure
diff --git a/pkg/kubehunter/model.go b/pkg/kubehunter/model.go
index 8b11501..b354ba3 100644
--- a/pkg/kubehunter/model.go
+++ b/pkg/kubehunter/model.go
@@ -5,9 +5,9 @@ import (
 	"io"
 	"time"
 
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-
 	sec "github.com/aquasecurity/starboard/pkg/apis/aquasecurity/v1alpha1"
+	"github.com/aquasecurity/starboard/pkg/starboard"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
 func toSummary(vulnerabilities []sec.KubeHunterVulnerability) (summary sec.KubeHunterSummary) {
@@ -26,11 +26,20 @@ func toSummary(vulnerabilities []sec.KubeHunterVulnerability) (summary sec.KubeH
 	return
 }
 
-func OutputFrom(reader io.Reader) (report sec.KubeHunterOutput, err error) {
+func OutputFrom(config Config, reader io.Reader) (report sec.KubeHunterOutput, err error) {
+	imageRef, err := config.GetKubeHunterImageRef()
+	if err != nil {
+		return report, err
+	}
+	version, err := starboard.GetVersionFromImageRef(imageRef)
+	if err != nil {
+		return sec.KubeHunterOutput{}, err
+	}
+
 	report.Scanner = sec.Scanner{
 		Name:    "kube-hunter",
 		Vendor:  "Aqua Security",
-		Version: kubeHunterVersion,
+		Version: version,
 	}
 	report.UpdateTimestamp = metav1.NewTime(time.Now())
 	err = json.NewDecoder(reader).Decode(&report)
diff --git a/pkg/kubehunter/scanner.go b/pkg/kubehunter/scanner.go
index 1b029b4..bb42faf 100644
--- a/pkg/kubehunter/scanner.go
+++ b/pkg/kubehunter/scanner.go
@@ -5,12 +5,12 @@ import (
 	"fmt"
 
 	"github.com/aquasecurity/starboard/pkg/apis/aquasecurity/v1alpha1"
+	"github.com/aquasecurity/starboard/pkg/ext"
 	"github.com/aquasecurity/starboard/pkg/kube"
 	"github.com/aquasecurity/starboard/pkg/kube/pod"
 	"github.com/aquasecurity/starboard/pkg/runner"
 	"github.com/aquasecurity/starboard/pkg/scanners"
 	"github.com/aquasecurity/starboard/pkg/starboard"
-	"github.com/google/uuid"
 	batchv1 "k8s.io/api/batch/v1"
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/api/resource"
@@ -22,44 +22,49 @@ import (
 )
 
 const (
-	kubeHunterVersion       = "0.3.1"
 	kubeHunterContainerName = "kube-hunter"
 )
 
-var (
-	kubeHunterContainerImage = fmt.Sprintf("aquasec/kube-hunter:%s", kubeHunterVersion)
-)
+type Config interface {
+	GetKubeHunterImageRef() (string, error)
+}
 
 type Scanner struct {
 	scheme    *runtime.Scheme
+	config    Config
 	clientset kubernetes.Interface
-
+	ext.IDGenerator
 	opts kube.ScannerOpts
 	pods *pod.Manager
 }
 
 func NewScanner(
 	scheme *runtime.Scheme,
+	config Config,
 	clientset kubernetes.Interface,
 	opts kube.ScannerOpts,
 ) *Scanner {
 	return &Scanner{
-		scheme:    scheme,
-		opts:      opts,
-		clientset: clientset,
-		pods:      pod.NewPodManager(clientset),
+		scheme:      scheme,
+		config:      config,
+		clientset:   clientset,
+		IDGenerator: ext.NewGoogleUUIDGenerator(),
+		opts:        opts,
+		pods:        pod.NewPodManager(clientset),
 	}
 }
 
-func (s *Scanner) Scan(ctx context.Context) (report v1alpha1.KubeHunterOutput, err error) {
+func (s *Scanner) Scan(ctx context.Context) (v1alpha1.KubeHunterOutput, error) {
 	// 1. Prepare descriptor for the Kubernetes Job which will run kube-hunter
-	job := s.prepareKubeHunterJob()
+	job, err := s.prepareKubeHunterJob()
+	if err != nil {
+		return v1alpha1.KubeHunterOutput{}, err
+	}
 
 	// 2. Run the prepared Job and wait for its completion or failure
 	err = runner.New().Run(ctx, kube.NewRunnableJob(s.scheme, s.clientset, job))
 	if err != nil {
-		err = fmt.Errorf("running kube-hunter job: %w", err)
-		return
+		return v1alpha1.KubeHunterOutput{}, fmt.Errorf("running kube-hunter job: %w", err)
 	}
 
 	defer func() {
@@ -80,42 +85,31 @@ func (s *Scanner) Scan(ctx context.Context) (report v1alpha1.KubeHunterOutput, e
 		job.Namespace, job.Name)
 	logsReader, err := s.pods.GetContainerLogsByJob(ctx, job, kubeHunterContainerName)
 	if err != nil {
-		err = fmt.Errorf("getting logs: %w", err)
-		return
+		return v1alpha1.KubeHunterOutput{}, fmt.Errorf("getting logs: %w", err)
 	}
 	defer func() {
 		_ = logsReader.Close()
 	}()
 
 	// 4. Parse the KubeHuberOutput from the logs Reader
-	report, err = OutputFrom(logsReader)
-	if err != nil {
-		err = fmt.Errorf("parsing kube hunter report: %w", err)
-		return
-	}
-
-	return
+	return OutputFrom(s.config, logsReader)
 }
 
-func (s *Scanner) prepareKubeHunterJob() *batchv1.Job {
+func (s *Scanner) prepareKubeHunterJob() (*batchv1.Job, error) {
+	imageRef, err := s.config.GetKubeHunterImageRef()
+	if err != nil {
+		return nil, err
+	}
 	return &batchv1.Job{
 		ObjectMeta: metav1.ObjectMeta{
-			Name:      uuid.New().String(),
+			Name:      s.GenerateID(),
 			Namespace: starboard.NamespaceName,
-			Labels: map[string]string{
-				"app": "kube-hunter",
-			},
 		},
 		Spec: batchv1.JobSpec{
 			BackoffLimit:          pointer.Int32Ptr(0),
 			Completions:           pointer.Int32Ptr(1),
 			ActiveDeadlineSeconds: scanners.GetActiveDeadlineSeconds(s.opts.ScanJobTimeout),
 			Template: corev1.PodTemplateSpec{
-				ObjectMeta: metav1.ObjectMeta{
-					Labels: map[string]string{
-						"app": "kube-hunter",
-					},
-				},
 				Spec: corev1.PodSpec{
 					ServiceAccountName: starboard.ServiceAccountName,
 					RestartPolicy:      corev1.RestartPolicyNever,
@@ -123,10 +117,9 @@ func (s *Scanner) prepareKubeHunterJob() *batchv1.Job {
 					Containers: []corev1.Container{
 						{
 							Name:                     kubeHunterContainerName,
-							Image:                    kubeHunterContainerImage,
+							Image:                    imageRef,
 							ImagePullPolicy:          corev1.PullIfNotPresent,
 							TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError,
-							Command:                  []string{"python", "kube-hunter.py"},
 							Args:                     []string{"--pod", "--report", "json", "--log", "warn"},
 							Resources: corev1.ResourceRequirements{
 								Limits: corev1.ResourceList{
@@ -143,5 +136,5 @@ func (s *Scanner) prepareKubeHunterJob() *batchv1.Job {
 				},
 			},
 		},
-	}
+	}, nil
 }
diff --git a/pkg/starboard/config.go b/pkg/starboard/config.go
index 18c482f..6acbae4 100644
--- a/pkg/starboard/config.go
+++ b/pkg/starboard/config.go
@@ -266,8 +266,9 @@ func GetDefaultConfig() ConfigData {
 
 		"aqua.imageRef": "docker.io/aquasec/scanner:5.3",
 
-		"kube-bench.imageRef": "docker.io/aquasec/kube-bench:0.4.0",
-		"polaris.config.yaml": polarisConfigYAML,
+		"kube-bench.imageRef":  "docker.io/aquasec/kube-bench:0.4.0",
+		"kube-hunter.imageRef": "docker.io/aquasec/kube-hunter:0.4.0",
+		"polaris.config.yaml":  polarisConfigYAML,
 	}
 }
 
@@ -319,11 +320,14 @@ func (c ConfigData) GetAquaImageRef() (string, error) {
 	return c.getRequiredProperty("aqua.imageRef")
 }
 
-// GetKubeBenchImageRef returns Docker image of kube-bench scanner.
 func (c ConfigData) GetKubeBenchImageRef() (string, error) {
 	return c.getRequiredProperty("kube-bench.imageRef")
 }
 
+func (c ConfigData) GetKubeHunterImageRef() (string, error) {
+	return c.getRequiredProperty("kube-hunter.imageRef")
+}
+
 func (c ConfigData) getRequiredProperty(key string) (string, error) {
 	var ok bool
 	var value string


Test patch:
diff --git a/itest/starboard/starboard_cli_test.go b/itest/starboard/starboard_cli_test.go
index 6f9c270..f6cf872 100644
--- a/itest/starboard/starboard_cli_test.go
+++ b/itest/starboard/starboard_cli_test.go
@@ -986,12 +986,13 @@ var _ = Describe("Starboard CLI", func() {
 		})
 	})
 
-	PDescribe("Command run kube-hunter", 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
 		It("should run kube-hunter", func() {
 			err := cmd.Run(versionInfo, []string{
 				"starboard",
-				"kube-hunter",
+				"scan",
+				"kubehunterreports",
 				"-v", starboardCLILogLevel,
 			}, GinkgoWriter, GinkgoWriter)
 			Expect(err).ToNot(HaveOccurred())
diff --git a/pkg/starboard/config_test.go b/pkg/starboard/config_test.go
index 367777f..bb12148 100644
--- a/pkg/starboard/config_test.go
+++ b/pkg/starboard/config_test.go
@@ -116,6 +116,40 @@ func TestConfigData_GetKubeBenchImageRef(t *testing.T) {
 	}
 }
 
+func TestConfigData_GetKubeHunterImageRef(t *testing.T) {
+	testCases := []struct {
+		name             string
+		configData       starboard.ConfigData
+		expectedError    string
+		expectedImageRef string
+	}{
+		{
+			name:          "Should return error",
+			configData:    starboard.ConfigData{},
+			expectedError: "property kube-hunter.imageRef not set",
+		},
+		{
+			name: "Should return image reference from config data",
+			configData: starboard.ConfigData{
+				"kube-hunter.imageRef": "gcr.io/aquasecurity/kube-hunter:0.4.0",
+			},
+			expectedImageRef: "gcr.io/aquasecurity/kube-hunter:0.4.0",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			imageRef, err := tc.configData.GetKubeHunterImageRef()
+			if tc.expectedError != "" {
+				require.EqualError(t, err, tc.expectedError)
+			} else {
+				require.NoError(t, err)
+				assert.Equal(t, tc.expectedImageRef, imageRef)
+			}
+		})
+	}
+}
+
 func TestConfigData_GetVulnerabilityReportsScanner(t *testing.T) {
 	testCases := []struct {
 		name            string