Repository: aquasecurity/starboard
Pull request #998 (2022-03-04 15:01:29)

feat: allow config check to contain zero to many messages
Signed-off-by: Daniel Pacak <pacak.daniel@gmail.com>

codecov[bot]: # [Codecov](https://codecov.io/gh/aquasecurity/starboard/pull/998?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aquasecurity) Report
> Merging [#998](https://codecov.io/gh/aquasecurity/starboard/pull/998?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aquasecurity) (d719bc6) into [main](https://codecov.io/gh/aquasecurity/starboard/commit/e7d7a40f2c598b6846a4264ccd9e86b5c07a11e1?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aquasecurity) (e7d7a40) will **decrease** coverage by `10.72%`.
> The diff coverage is `75.00%`.

[![Impacted file tree graph](https://codecov.io/gh/aquasecurity/starboard/pull/998/graphs/tree.svg?width=650&height=150&src=pr&token=qqTGKAJ0Wb&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aquasecurity)](https://codecov.io/gh/aquasecurity/starboard/pull/998?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aquasecurity)

```diff
@@             Coverage Diff             @@
##             main     #998       +/-   ##
===========================================
- Coverage   51.80%   41.07%   -10.73%     
===========================================
  Files          46       45        -1     
  Lines        6859     6676      -183     
===========================================
- Hits         3553     2742      -811     
- Misses       3023     3764      +741     
+ Partials      283      170      -113     
```


| [Impacted Files](https://codecov.io/gh/aquasecurity/starboard/pull/998?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aquasecurity) | Coverage Δ | |
|---|---|---|
| [pkg/plugin/conftest/plugin.go](https://codecov.io/gh/aquasecurity/starboard/pull/998/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aquasecurity#diff-cGtnL3BsdWdpbi9jb25mdGVzdC9wbHVnaW4uZ28=) | `77.18% <50.00%> (-0.68%)` | :arrow_down: |
| [pkg/plugin/polaris/plugin.go](https://codecov.io/gh/aquasecurity/starboard/pull/998/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aquasecurity#diff-cGtnL3BsdWdpbi9wb2xhcmlzL3BsdWdpbi5nbw==) | `78.57% <100.00%> (-1.69%)` | :arrow_down: |
| [pkg/plugin/factory.go](https://codecov.io/gh/aquasecurity/starboard/pull/998/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aquasecurity#diff-cGtnL3BsdWdpbi9mYWN0b3J5Lmdv) | `0.00% <0.00%> (-81.49%)` | :arrow_down: |
| [pkg/operator/controller/configauditreport.go](https://codecov.io/gh/aquasecurity/starboard/pull/998/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aquasecurity#diff-cGtnL29wZXJhdG9yL2NvbnRyb2xsZXIvY29uZmlnYXVkaXRyZXBvcnQuZ28=) | `0.00% <0.00%> (-60.73%)` | :arrow_down: |
| [pkg/operator/controller/vulnerabilityreport.go](https://codecov.io/gh/aquasecurity/starboard/pull/998/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aquasecurity#diff-cGtnL29wZXJhdG9yL2NvbnRyb2xsZXIvdnVsbmVyYWJpbGl0eXJlcG9ydC5nbw==) | `0.00% <0.00%> (-57.32%)` | :arrow_down: |
| [pkg/operator/controller/ciskubebenchreport.go](https://codecov.io/gh/aquasecurity/starboard/pull/998/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aquasecurity#diff-cGtnL29wZXJhdG9yL2NvbnRyb2xsZXIvY2lza3ViZWJlbmNocmVwb3J0Lmdv) | `0.00% <0.00%> (-52.59%)` | :arrow_down: |
| [pkg/operator/controller/plugins\_config.go](https://codecov.io/gh/aquasecurity/starboard/pull/998/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aquasecurity#diff-cGtnL29wZXJhdG9yL2NvbnRyb2xsZXIvcGx1Z2luc19jb25maWcuZ28=) | `0.00% <0.00%> (-50.00%)` | :arrow_down: |
| [pkg/operator/predicate/predicate.go](https://codecov.io/gh/aquasecurity/starboard/pull/998/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aquasecurity#diff-cGtnL29wZXJhdG9yL3ByZWRpY2F0ZS9wcmVkaWNhdGUuZ28=) | `58.90% <0.00%> (-28.77%)` | :arrow_down: |
| [pkg/kubebench/io.go](https://codecov.io/gh/aquasecurity/starboard/pull/998/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aquasecurity#diff-cGtnL2t1YmViZW5jaC9pby5nbw==) | `81.81% <0.00%> (-12.13%)` | :arrow_down: |
| [pkg/configauditreport/builder.go](https://codecov.io/gh/aquasecurity/starboard/pull/998/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aquasecurity#diff-cGtnL2NvbmZpZ2F1ZGl0cmVwb3J0L2J1aWxkZXIuZ28=) | `67.39% <0.00%> (-11.42%)` | :arrow_down: |
| ... and [7 more](https://codecov.io/gh/aquasecurity/starboard/pull/998/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aquasecurity) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/aquasecurity/starboard/pull/998?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aquasecurity).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aquasecurity)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/aquasecurity/starboard/pull/998?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aquasecurity). Last update [e7d7a40...d719bc6](https://codecov.io/gh/aquasecurity/starboard/pull/998?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aquasecurity). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aquasecurity).


Patch:
diff --git a/docs/crds/configaudit-report.md b/docs/crds/configaudit-report.md
index 3f24693..ed68cd0 100644
--- a/docs/crds/configaudit-report.md
+++ b/docs/crds/configaudit-report.md
@@ -36,49 +36,49 @@ report:
   scanner:
     name: Polaris
     vendor: Fairwinds Ops
-    version: '3.2'
+    version: '4.2'
   summary:
-    dangerCount: 0
-    passCount: 3
-    warningCount: 2
+    criticalCount: 2
+    highCount: 0
+    lowCount: 9
+    mediumCount: 0
   checks:
     - category: Security
-      checkID: hostNetworkSet
-      message: Host network is not configured
-      severity: warning
+      checkID: hostPIDSet
+      messages:
+        - Host PID is not configured
+      severity: CRITICAL
       success: true
     - category: Security
-      checkID: dangerousCapabilities
-      message: Container does not have any dangerous capabilities
-      severity: danger
+      checkID: hostIPCSet
+      messages:
+        - Host IPC is not configured
+      severity: CRITICAL
       success: true
-      scope:
-        type: Container
-        value: nginx
     - category: Security
-      checkID: hostPortSet
-      message: Host port is not configured
-      severity: warning
+      checkID: hostNetworkSet
+      messages:
+        - Host network is not configured
+      severity: LOW
       success: true
-      scope:
-        type: Container
-        value: nginx
     - category: Security
-      checkID: insecureCapabilities
-      message: Container should not have insecure capabilities
-      severity: warning
-      success: false
+      checkID: notReadOnlyRootFilesystem
+      messages:
+        - Filesystem should be read only
       scope:
         type: Container
         value: nginx
-    - category: Security
-      checkID: notReadOnlyRootFilesystem
-      message: Filesystem should be read only
-      severity: warning
+      severity: LOW
       success: false
+    - category: Security
+      checkID: privilegeEscalationAllowed
+      messages:
+        - Privilege escalation should not be allowed
       scope:
         type: Container
         value: nginx
+      severity: CRITICAL
+      success: false
 ```
 
 Third party Kubernetes configuration checkers, linters, and sanitizers that are compliant with the ConfigAuditReport
diff --git a/pkg/apis/aquasecurity/v1alpha1/config_audit_types.go b/pkg/apis/aquasecurity/v1alpha1/config_audit_types.go
index 2a52675..c412745 100644
--- a/pkg/apis/aquasecurity/v1alpha1/config_audit_types.go
+++ b/pkg/apis/aquasecurity/v1alpha1/config_audit_types.go
@@ -107,12 +107,12 @@ type CheckScope struct {
 // Check provides the result of conducting a single audit step.
 type Check struct {
 	ID          string   `json:"checkID"`
-	Title       string   `json:"title"`
-	Description string   `json:"description"`
+	Title       string   `json:"title,omitempty"`
+	Description string   `json:"description,omitempty"`
 	Severity    Severity `json:"severity"`
 	Category    string   `json:"category,omitempty"`
 
-	Message string `json:"message,omitempty"`
+	Messages []string `json:"messages,omitempty"`
 
 	// Remediation provides description or links to external resources to remediate failing check.
 	// +optional
diff --git a/pkg/plugin/polaris/plugin.go b/pkg/plugin/polaris/plugin.go
index 7f6f8f9..b63d678 100644
--- a/pkg/plugin/polaris/plugin.go
+++ b/pkg/plugin/polaris/plugin.go
@@ -440,7 +440,7 @@ func (p *plugin) ParseConfigAuditReportData(ctx starboard.PluginContext, logsRea
 		}
 		check := v1alpha1.Check{
 			ID:       pr.ID,
-			Message:  pr.Message,
+			Messages: []string{pr.Message},
 			Success:  pr.Success,
 			Severity: severity,
 			Category: pr.Category,
@@ -458,7 +458,7 @@ func (p *plugin) ParseConfigAuditReportData(ctx starboard.PluginContext, logsRea
 			}
 			containerChecks = append(containerChecks, v1alpha1.Check{
 				ID:       crr.ID,
-				Message:  crr.Message,
+				Messages: []string{crr.Message},
 				Success:  crr.Success,
 				Severity: severity,
 				Category: crr.Category,


Test patch:
diff --git a/pkg/plugin/conftest/plugin.go b/pkg/plugin/conftest/plugin.go
index a933842..558f51f 100644
--- a/pkg/plugin/conftest/plugin.go
+++ b/pkg/plugin/conftest/plugin.go
@@ -378,7 +378,7 @@ func (p *plugin) ParseConfigAuditReportData(ctx starboard.PluginContext, logsRea
 			checks = append(checks, v1alpha1.Check{
 				ID:       p.getPolicyTitleFromResult(warning),
 				Severity: v1alpha1.SeverityLow,
-				Message:  warning.Message,
+				Messages: []string{warning.Message},
 				Category: defaultCheckCategory,
 				Success:  false,
 			})
@@ -389,7 +389,7 @@ func (p *plugin) ParseConfigAuditReportData(ctx starboard.PluginContext, logsRea
 			checks = append(checks, v1alpha1.Check{
 				ID:       p.getPolicyTitleFromResult(failure),
 				Severity: v1alpha1.SeverityCritical,
-				Message:  failure.Message,
+				Messages: []string{failure.Message},
 				Category: defaultCheckCategory,
 				Success:  false,
 			})
diff --git a/pkg/plugin/conftest/plugin_test.go b/pkg/plugin/conftest/plugin_test.go
index bd91581..f0a4068 100644
--- a/pkg/plugin/conftest/plugin_test.go
+++ b/pkg/plugin/conftest/plugin_test.go
@@ -618,8 +618,8 @@ func TestPlugin_ParseConfigAuditReportData(t *testing.T) {
 	// Rego scripts the Check.ID is not unique. For example, for a Pod with multiple
 	// containers the Check.ID will be duplicated for each container, but the
 	// Check.Message will be different.
-	groupChecksByMessage := func(element interface{}) string {
-		return element.(v1alpha1.Check).Message
+	groupChecksByMessages := func(element interface{}) string {
+		return strings.Join(element.(v1alpha1.Check).Messages, ",")
 	}
 
 	g.Expect(err).ToNot(HaveOccurred())
@@ -634,38 +634,38 @@ func TestPlugin_ParseConfigAuditReportData(t *testing.T) {
 			CriticalCount: 6,
 			LowCount:      0,
 		}),
-		"Checks": MatchAllElements(groupChecksByMessage, Elements{
+		"Checks": MatchAllElements(groupChecksByMessages, Elements{
 			"container kubedns of deployment kube-dns in default namespace should add 'ALL' to securityContext.capabilities.drop": Equal(v1alpha1.Check{
 				ID:       "Default capabilities: some containers do not drop all",
-				Message:  "container kubedns of deployment kube-dns in default namespace should add 'ALL' to securityContext.capabilities.drop",
+				Messages: []string{"container kubedns of deployment kube-dns in default namespace should add 'ALL' to securityContext.capabilities.drop"},
 				Success:  false,
 				Severity: v1alpha1.SeverityCritical,
 				Category: "Security",
 			}),
 			"container dnsmasq of deployment kube-dns in default namespace should add 'ALL' to securityContext.capabilities.drop": Equal(v1alpha1.Check{
 				ID:       "Default capabilities: some containers do not drop all",
-				Message:  "container dnsmasq of deployment kube-dns in default namespace should add 'ALL' to securityContext.capabilities.drop",
+				Messages: []string{"container dnsmasq of deployment kube-dns in default namespace should add 'ALL' to securityContext.capabilities.drop"},
 				Success:  false,
 				Severity: v1alpha1.SeverityCritical,
 				Category: "Security",
 			}),
 			"container sidecar of deployment kube-dns in default namespace should add 'ALL' to securityContext.capabilities.drop": Equal(v1alpha1.Check{
 				ID:       "Default capabilities: some containers do not drop all",
-				Message:  "container sidecar of deployment kube-dns in default namespace should add 'ALL' to securityContext.capabilities.drop",
+				Messages: []string{"container sidecar of deployment kube-dns in default namespace should add 'ALL' to securityContext.capabilities.drop"},
 				Success:  false,
 				Severity: v1alpha1.SeverityCritical,
 				Category: "Security",
 			}),
 			"container prometheus-to-sd of deployment kube-dns in default namespace should add 'ALL' to securityContext.capabilities.drop": Equal(v1alpha1.Check{
 				ID:       "Default capabilities: some containers do not drop all",
-				Message:  "container prometheus-to-sd of deployment kube-dns in default namespace should add 'ALL' to securityContext.capabilities.drop",
+				Messages: []string{"container prometheus-to-sd of deployment kube-dns in default namespace should add 'ALL' to securityContext.capabilities.drop"},
 				Success:  false,
 				Severity: v1alpha1.SeverityCritical,
 				Category: "Security",
 			}),
 			"container dnsmasq of deployment kube-dns in default namespace should set securityContext.readOnlyRootFilesystem to true": Equal(v1alpha1.Check{
 				ID:       "Root file system is not read-only",
-				Message:  "container dnsmasq of deployment kube-dns in default namespace should set securityContext.readOnlyRootFilesystem to true",
+				Messages: []string{"container dnsmasq of deployment kube-dns in default namespace should set securityContext.readOnlyRootFilesystem to true"},
 				Success:  false,
 				Severity: v1alpha1.SeverityCritical,
 				Category: "Security",
@@ -675,7 +675,7 @@ func TestPlugin_ParseConfigAuditReportData(t *testing.T) {
 				// in the rule's response, which is then returned as metadata.type
 				// in Conftest output, the parser will fallback to a unique identifier.
 				ID:       "00000000-0000-0000-0000-000000000001",
-				Message:  "container prometheus-to-sd of deployment kube-dns in default namespace should set resources.requests.cpu",
+				Messages: []string{"container prometheus-to-sd of deployment kube-dns in default namespace should set resources.requests.cpu"},
 				Success:  false,
 				Severity: v1alpha1.SeverityCritical,
 				Category: "Security",
@@ -684,38 +684,38 @@ func TestPlugin_ParseConfigAuditReportData(t *testing.T) {
 		// Most Rego scripts do not return structured response object to indicate
 		// container name. Therefore, the ContainerChecks map is empty.
 		"ContainerChecks": Equal(map[string][]v1alpha1.Check{}),
-		"PodChecks": MatchAllElements(groupChecksByMessage, Elements{
+		"PodChecks": MatchAllElements(groupChecksByMessages, Elements{
 			"container kubedns of deployment kube-dns in default namespace should add 'ALL' to securityContext.capabilities.drop": Equal(v1alpha1.Check{
 				ID:       "Default capabilities: some containers do not drop all",
-				Message:  "container kubedns of deployment kube-dns in default namespace should add 'ALL' to securityContext.capabilities.drop",
+				Messages: []string{"container kubedns of deployment kube-dns in default namespace should add 'ALL' to securityContext.capabilities.drop"},
 				Success:  false,
 				Severity: v1alpha1.SeverityCritical,
 				Category: "Security",
 			}),
 			"container dnsmasq of deployment kube-dns in default namespace should add 'ALL' to securityContext.capabilities.drop": Equal(v1alpha1.Check{
 				ID:       "Default capabilities: some containers do not drop all",
-				Message:  "container dnsmasq of deployment kube-dns in default namespace should add 'ALL' to securityContext.capabilities.drop",
+				Messages: []string{"container dnsmasq of deployment kube-dns in default namespace should add 'ALL' to securityContext.capabilities.drop"},
 				Success:  false,
 				Severity: v1alpha1.SeverityCritical,
 				Category: "Security",
 			}),
 			"container sidecar of deployment kube-dns in default namespace should add 'ALL' to securityContext.capabilities.drop": Equal(v1alpha1.Check{
 				ID:       "Default capabilities: some containers do not drop all",
-				Message:  "container sidecar of deployment kube-dns in default namespace should add 'ALL' to securityContext.capabilities.drop",
+				Messages: []string{"container sidecar of deployment kube-dns in default namespace should add 'ALL' to securityContext.capabilities.drop"},
 				Success:  false,
 				Severity: v1alpha1.SeverityCritical,
 				Category: "Security",
 			}),
 			"container prometheus-to-sd of deployment kube-dns in default namespace should add 'ALL' to securityContext.capabilities.drop": Equal(v1alpha1.Check{
 				ID:       "Default capabilities: some containers do not drop all",
-				Message:  "container prometheus-to-sd of deployment kube-dns in default namespace should add 'ALL' to securityContext.capabilities.drop",
+				Messages: []string{"container prometheus-to-sd of deployment kube-dns in default namespace should add 'ALL' to securityContext.capabilities.drop"},
 				Success:  false,
 				Severity: v1alpha1.SeverityCritical,
 				Category: "Security",
 			}),
 			"container dnsmasq of deployment kube-dns in default namespace should set securityContext.readOnlyRootFilesystem to true": Equal(v1alpha1.Check{
 				ID:       "Root file system is not read-only",
-				Message:  "container dnsmasq of deployment kube-dns in default namespace should set securityContext.readOnlyRootFilesystem to true",
+				Messages: []string{"container dnsmasq of deployment kube-dns in default namespace should set securityContext.readOnlyRootFilesystem to true"},
 				Success:  false,
 				Severity: v1alpha1.SeverityCritical,
 				Category: "Security",
@@ -725,7 +725,7 @@ func TestPlugin_ParseConfigAuditReportData(t *testing.T) {
 				// in the rule's response, which is then returned as metadata.type
 				// in Conftest output, the parser will fallback to a unique identifier.
 				ID:       "00000000-0000-0000-0000-000000000001",
-				Message:  "container prometheus-to-sd of deployment kube-dns in default namespace should set resources.requests.cpu",
+				Messages: []string{"container prometheus-to-sd of deployment kube-dns in default namespace should set resources.requests.cpu"},
 				Success:  false,
 				Severity: v1alpha1.SeverityCritical,
 				Category: "Security",
diff --git a/pkg/plugin/polaris/plugin_test.go b/pkg/plugin/polaris/plugin_test.go
index 6e59725..80096bd 100644
--- a/pkg/plugin/polaris/plugin_test.go
+++ b/pkg/plugin/polaris/plugin_test.go
@@ -382,13 +382,13 @@ func TestPlugin_ParseConfigAuditReportData(t *testing.T) {
 	}))
 	g.Expect(result.PodChecks).To(ConsistOf(v1alpha1.Check{
 		ID:       "hostIPCSet",
-		Message:  "Host IPC is not configured",
+		Messages: []string{"Host IPC is not configured"},
 		Success:  false,
 		Severity: v1alpha1.SeverityCritical,
 		Category: "Security",
 	}, v1alpha1.Check{
 		ID:       "hostNetworkSet",
-		Message:  "Host network is not configured",
+		Messages: []string{"Host network is not configured"},
 		Success:  true,
 		Severity: v1alpha1.SeverityLow,
 		Category: "Networking",
@@ -396,7 +396,7 @@ func TestPlugin_ParseConfigAuditReportData(t *testing.T) {
 	g.Expect(result.ContainerChecks).To(HaveLen(1))
 	g.Expect(result.ContainerChecks["db"]).To(ConsistOf(v1alpha1.Check{
 		ID:       "cpuLimitsMissing",
-		Message:  "CPU limits are set",
+		Messages: []string{"CPU limits are set"},
 		Success:  false,
 		Severity: v1alpha1.SeverityLow,
 		Category: "Resources",
@@ -406,7 +406,7 @@ func TestPlugin_ParseConfigAuditReportData(t *testing.T) {
 		},
 	}, v1alpha1.Check{
 		ID:       "cpuRequestsMissing",
-		Message:  "CPU requests are set",
+		Messages: []string{"CPU requests are set"},
 		Success:  true,
 		Severity: v1alpha1.SeverityLow,
 		Category: "Resources",