Repository: aquasecurity/starboard
Pull request #67 (2020-06-22 12:22:29)

fix(ScannerCLI): kube-bench and kube-hunter do not respect --delete-scan-job flag
Resolves: #63
Resolves: #61

Signed-off-by: Daniel Pacak <pacak.daniel@gmail.com>

codecov[bot]: # [Codecov](https://codecov.io/gh/aquasecurity/starboard/pull/67?src=pr&el=h1) Report
> Merging [#67](https://codecov.io/gh/aquasecurity/starboard/pull/67?src=pr&el=desc) into [master](https://codecov.io/gh/aquasecurity/starboard/commit/e0c30b3d6383139193ba8b037431a12cfcf65eb3&el=desc) will **decrease** coverage by `0.02%`.
> The diff coverage is `0.00%`.

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

```diff
@@            Coverage Diff             @@
##           master      #67      +/-   ##
==========================================
- Coverage   10.77%   10.74%   -0.03%     
==========================================
  Files          23       23              
  Lines         854      856       +2     
==========================================
  Hits           92       92              
- Misses        757      759       +2     
  Partials        5        5              
```


| [Impacted Files](https://codecov.io/gh/aquasecurity/starboard/pull/67?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pkg/kubebench/converter.go](https://codecov.io/gh/aquasecurity/starboard/pull/67/diff?src=pr&el=tree#diff-cGtnL2t1YmViZW5jaC9jb252ZXJ0ZXIuZ28=) | `0.00% <0.00%> (ø)` | |
| [pkg/kubebench/scanner.go](https://codecov.io/gh/aquasecurity/starboard/pull/67/diff?src=pr&el=tree#diff-cGtnL2t1YmViZW5jaC9zY2FubmVyLmdv) | `0.00% <0.00%> (ø)` | |

------

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


Patch:
diff --git a/pkg/find/vulnerabilities/trivy/converter.go b/pkg/find/vulnerabilities/trivy/converter.go
index 0bd5e87..6ed1f21 100644
--- a/pkg/find/vulnerabilities/trivy/converter.go
+++ b/pkg/find/vulnerabilities/trivy/converter.go
@@ -53,11 +53,15 @@ func (c *converter) skippingNoisyOutputReader(input io.Reader) (io.Reader, error
 	if index > 0 {
 		return strings.NewReader(inputAsString[index:]), nil
 	}
+	index = strings.LastIndex(inputAsString, "null")
+	if index > 0 {
+		return strings.NewReader(inputAsString[index:]), nil
+	}
 	return strings.NewReader(inputAsString), nil
 }
 
 func (c *converter) convert(reports []ScanReport) starboard.VulnerabilityReport {
-	var vulnerabilities []starboard.VulnerabilityItem
+	vulnerabilities := make([]starboard.VulnerabilityItem, 0)
 
 	for _, report := range reports {
 		for _, sr := range report.Vulnerabilities {
diff --git a/pkg/kubebench/converter.go b/pkg/kubebench/converter.go
index 475b434..955dd3d 100644
--- a/pkg/kubebench/converter.go
+++ b/pkg/kubebench/converter.go
@@ -22,7 +22,7 @@ func (c *converter) Convert(reader io.Reader) (report starboard.CISKubeBenchOutp
 		Scanner: starboard.Scanner{
 			Name:    "kube-bench",
 			Vendor:  "Aqua Security",
-			Version: "latest",
+			Version: kubeBenchVersion,
 		},
 		Sections: []starboard.CISKubeBenchSection{},
 	}
diff --git a/pkg/kubebench/scanner.go b/pkg/kubebench/scanner.go
index 979177f..b896ea6 100644
--- a/pkg/kubebench/scanner.go
+++ b/pkg/kubebench/scanner.go
@@ -23,8 +23,12 @@ import (
 )
 
 const (
-	kubeBenchContainerName  = "kube-bench"
-	kubeBenchContainerImage = "aquasec/kube-bench:latest"
+	kubeBenchVersion       = "0.3.0"
+	kubeBenchContainerName = "kube-bench"
+)
+
+var (
+	kubeBenchContainerImage = fmt.Sprintf("aquasec/kube-bench:%s", kubeBenchVersion)
 )
 
 type Scanner struct {
@@ -46,26 +50,30 @@ func NewScanner(opts kube.ScannerOpts, clientset kubernetes.Interface) *Scanner
 
 func (s *Scanner) Scan(ctx context.Context) (report starboard.CISKubeBenchOutput, node *core.Node, err error) {
 	// 1. Prepare descriptor for the Kubernetes Job which will run kube-bench
-	kubeBenchJob := s.prepareKubeBenchJob()
+	job := s.prepareKubeBenchJob()
 
 	// 2. Run the prepared Job and wait for its completion or failure
-	err = runner.New().Run(ctx, kube.NewRunnableJob(s.clientset, kubeBenchJob))
+	err = runner.New().Run(ctx, kube.NewRunnableJob(s.clientset, job))
 	if err != nil {
 		err = fmt.Errorf("running kube-bench job: %w", err)
 		return
 	}
 
 	defer func() {
+		if !s.opts.DeleteScanJob {
+			klog.V(3).Infof("Skipping scan job deletion: %s/%s", job.Namespace, job.Name)
+			return
+		}
 		// 6. Delete the kube-bench Job
-		klog.V(3).Infof("Deleting job: %s/%s", kubeBenchJob.Namespace, kubeBenchJob.Name)
+		klog.V(3).Infof("Deleting job: %s/%s", job.Namespace, job.Name)
 		background := meta.DeletePropagationBackground
-		_ = s.clientset.BatchV1().Jobs(kubeBenchJob.Namespace).Delete(ctx, kubeBenchJob.Name, meta.DeleteOptions{
+		_ = s.clientset.BatchV1().Jobs(job.Namespace).Delete(ctx, job.Name, meta.DeleteOptions{
 			PropagationPolicy: &background,
 		})
 	}()
 
 	// 3. Get the Pod controlled by the kube-bench Job
-	kubeBenchPod, err := s.pods.GetPodByJob(ctx, kubeBenchJob)
+	kubeBenchPod, err := s.pods.GetPodByJob(ctx, job)
 	if err != nil {
 		err = fmt.Errorf("getting kube-bench pod: %w", err)
 		return
@@ -73,7 +81,7 @@ func (s *Scanner) Scan(ctx context.Context) (report starboard.CISKubeBenchOutput
 
 	// 4. Get kube-bench JSON output from the kube-bench Pod
 	klog.V(3).Infof("Getting logs for %s container in job: %s/%s", kubeBenchContainerName,
-		kubeBenchJob.Namespace, kubeBenchJob.Name)
+		job.Namespace, job.Name)
 	logsReader, err := s.pods.GetPodLogs(ctx, kubeBenchPod, kubeBenchContainerName)
 	if err != nil {
 		err = fmt.Errorf("getting logs: %w", err)
@@ -162,7 +170,7 @@ func (s *Scanner) prepareKubeBenchJob() *batch.Job {
 						{
 							Name:                     kubeBenchContainerName,
 							Image:                    kubeBenchContainerImage,
-							ImagePullPolicy:          core.PullAlways,
+							ImagePullPolicy:          core.PullIfNotPresent,
 							TerminationMessagePolicy: core.TerminationMessageFallbackToLogsOnError,
 							Command:                  []string{"kube-bench"},
 							Args:                     []string{"--json"},
diff --git a/pkg/kubehunter/model.go b/pkg/kubehunter/model.go
index e3053d6..a7c11d5 100644
--- a/pkg/kubehunter/model.go
+++ b/pkg/kubehunter/model.go
@@ -10,7 +10,7 @@ func OutputFrom(reader io.Reader) (report sec.KubeHunterOutput, err error) {
 	report.Scanner = sec.Scanner{
 		Name:    "kube-hunter",
 		Vendor:  "Aqua Security",
-		Version: "latest",
+		Version: kubeHunterVersion,
 	}
 	err = json.NewDecoder(reader).Decode(&report)
 	return
diff --git a/pkg/kubehunter/scanner.go b/pkg/kubehunter/scanner.go
index 943814c..3ab03d3 100644
--- a/pkg/kubehunter/scanner.go
+++ b/pkg/kubehunter/scanner.go
@@ -22,8 +22,12 @@ import (
 )
 
 const (
-	kubeHunterContainerName  = "kube-hunter"
-	kubeHunterContainerImage = "aquasec/kube-hunter:latest"
+	kubeHunterVersion       = "0.3.1"
+	kubeHunterContainerName = "kube-hunter"
+)
+
+var (
+	kubeHunterContainerImage = fmt.Sprintf("aquasec/kube-hunter:%s", kubeHunterVersion)
 )
 
 type Scanner struct {
@@ -43,28 +47,32 @@ func NewScanner(opts kube.ScannerOpts, clientset kubernetes.Interface) *Scanner
 
 func (s *Scanner) Scan(ctx context.Context) (report starboard.KubeHunterOutput, err error) {
 	// 1. Prepare descriptor for the Kubernetes Job which will run kube-hunter
-	kubeHunterJob := s.prepareKubeHunterJob()
+	job := s.prepareKubeHunterJob()
 
 	// 2. Run the prepared Job and wait for its completion or failure
-	err = runner.New().Run(ctx, kube.NewRunnableJob(s.clientset, kubeHunterJob))
+	err = runner.New().Run(ctx, kube.NewRunnableJob(s.clientset, job))
 	if err != nil {
 		err = fmt.Errorf("running kube-hunter job: %w", err)
 		return
 	}
 
 	defer func() {
+		if !s.opts.DeleteScanJob {
+			klog.V(3).Infof("Skipping scan job deletion: %s/%s", job.Namespace, job.Name)
+			return
+		}
 		// 5. Delete the kube-hunter Job
-		klog.V(3).Infof("Deleting job: %s/%s", kubeHunterJob.Namespace, kubeHunterJob.Name)
+		klog.V(3).Infof("Deleting job: %s/%s", job.Namespace, job.Name)
 		background := meta.DeletePropagationBackground
-		_ = s.clientset.BatchV1().Jobs(kubeHunterJob.Namespace).Delete(ctx, kubeHunterJob.Name, meta.DeleteOptions{
+		_ = s.clientset.BatchV1().Jobs(job.Namespace).Delete(ctx, job.Name, meta.DeleteOptions{
 			PropagationPolicy: &background,
 		})
 	}()
 
 	// 3. Get kube-hunter JSON output from the kube-hunter Pod
 	klog.V(3).Infof("Getting logs for %s container in job: %s/%s", kubeHunterContainerName,
-		kubeHunterJob.Namespace, kubeHunterJob.Name)
-	logsReader, err := s.pods.GetContainerLogsByJob(ctx, kubeHunterJob, kubeHunterContainerName)
+		job.Namespace, job.Name)
+	logsReader, err := s.pods.GetContainerLogsByJob(ctx, job, kubeHunterContainerName)
 	if err != nil {
 		err = fmt.Errorf("getting logs: %w", err)
 		return
@@ -109,7 +117,7 @@ func (s *Scanner) prepareKubeHunterJob() *batch.Job {
 						{
 							Name:                     kubeHunterContainerName,
 							Image:                    kubeHunterContainerImage,
-							ImagePullPolicy:          core.PullAlways,
+							ImagePullPolicy:          core.PullIfNotPresent,
 							TerminationMessagePolicy: core.TerminationMessageFallbackToLogsOnError,
 							Command:                  []string{"python", "kube-hunter.py"},
 							Args:                     []string{"--pod", "--report", "json", "--log", "warn"},


Test patch:
diff --git a/pkg/find/vulnerabilities/trivy/converter_test.go b/pkg/find/vulnerabilities/trivy/converter_test.go
index a5c62c8..996340d 100644
--- a/pkg/find/vulnerabilities/trivy/converter_test.go
+++ b/pkg/find/vulnerabilities/trivy/converter_test.go
@@ -102,6 +102,28 @@ func TestConverter_Convert(t *testing.T) {
 			expectedError:  nil,
 			expectedReport: sampleReport,
 		},
+		{
+			name: "Should convert vulnerability report in JSON format when OS is not detected",
+			input: `2020-06-21T23:10:15.162+0200	WARN	OS is not detected and vulnerabilities in OS packages are not detected.
+null`,
+			expectedError: nil,
+			expectedReport: starboard.VulnerabilityReport{
+				Scanner: starboard.Scanner{
+					Name:    "Trivy",
+					Vendor:  "Aqua Security",
+					Version: "0.9.1",
+				},
+				Summary: starboard.VulnerabilitySummary{
+					CriticalCount: 0,
+					HighCount:     0,
+					MediumCount:   0,
+					LowCount:      0,
+					NoneCount:     0,
+					UnknownCount:  0,
+				},
+				Vulnerabilities: []starboard.VulnerabilityItem{},
+			},
+		},
 	}
 
 	for _, tc := range testCases {