Repository: aquasecurity/starboard
Pull request #494 (2021-04-12 19:51:07)

feat: Read Polaris config from the `starboard-polairs-config` ConfigMap
Resolves https://github.com/aquasecurity/starboard/issues/481

codecov[bot]: # [Codecov](https://codecov.io/gh/aquasecurity/starboard/pull/494?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aquasecurity) Report
> Merging [#494](https://codecov.io/gh/aquasecurity/starboard/pull/494?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aquasecurity) (493db04) into [main](https://codecov.io/gh/aquasecurity/starboard/commit/802cfa709ac03670332f1f597776b11022571472?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aquasecurity) (802cfa7) will **decrease** coverage by `34.15%`.
> The diff coverage is `66.66%`.

[![Impacted file tree graph](https://codecov.io/gh/aquasecurity/starboard/pull/494/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/494?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aquasecurity)

```diff
@@             Coverage Diff             @@
##             main     #494       +/-   ##
===========================================
- Coverage   70.25%   36.10%   -34.16%     
===========================================
  Files          62       61        -1     
  Lines        3994     3886      -108     
===========================================
- Hits         2806     1403     -1403     
- Misses        892     2401     +1509     
+ Partials      296       82      -214     
```


| [Impacted Files](https://codecov.io/gh/aquasecurity/starboard/pull/494?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aquasecurity) | Coverage Δ | |
|---|---|---|
| [pkg/plugin/factory.go](https://codecov.io/gh/aquasecurity/starboard/pull/494/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%> (-71.43%)` | :arrow_down: |
| [pkg/plugin/polaris/plugin.go](https://codecov.io/gh/aquasecurity/starboard/pull/494/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aquasecurity#diff-cGtnL3BsdWdpbi9wb2xhcmlzL3BsdWdpbi5nbw==) | `88.73% <50.00%> (-4.03%)` | :arrow_down: |
| [pkg/starboard/config.go](https://codecov.io/gh/aquasecurity/starboard/pull/494/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aquasecurity#diff-cGtnL3N0YXJib2FyZC9jb25maWcuZ28=) | `70.58% <77.77%> (+0.51%)` | :arrow_up: |
| [pkg/cmd/get.go](https://codecov.io/gh/aquasecurity/starboard/pull/494/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aquasecurity#diff-cGtnL2NtZC9nZXQuZ28=) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [pkg/cmd/find.go](https://codecov.io/gh/aquasecurity/starboard/pull/494/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aquasecurity#diff-cGtnL2NtZC9maW5kLmdv) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [pkg/cmd/root.go](https://codecov.io/gh/aquasecurity/starboard/pull/494/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aquasecurity#diff-cGtnL2NtZC9yb290Lmdv) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [pkg/cmd/scan.go](https://codecov.io/gh/aquasecurity/starboard/pull/494/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aquasecurity#diff-cGtnL2NtZC9zY2FuLmdv) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [pkg/cmd/polaris.go](https://codecov.io/gh/aquasecurity/starboard/pull/494/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aquasecurity#diff-cGtnL2NtZC9wb2xhcmlzLmdv) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [pkg/cmd/version.go](https://codecov.io/gh/aquasecurity/starboard/pull/494/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aquasecurity#diff-cGtnL2NtZC92ZXJzaW9uLmdv) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [pkg/cmd/kube\_bench.go](https://codecov.io/gh/aquasecurity/starboard/pull/494/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aquasecurity#diff-cGtnL2NtZC9rdWJlX2JlbmNoLmdv) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| ... and [36 more](https://codecov.io/gh/aquasecurity/starboard/pull/494/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/494?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/494?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aquasecurity). Last update [802cfa7...493db04](https://codecov.io/gh/aquasecurity/starboard/pull/494?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/deploy/helm/templates/config.yaml b/deploy/helm/templates/config.yaml
index 3e8d2dd..f2cfef4 100644
--- a/deploy/helm/templates/config.yaml
+++ b/deploy/helm/templates/config.yaml
@@ -27,8 +27,6 @@ data:
   configAuditReports.scanner: {{ .Values.operator.configAuditReportsPlugin | quote }}
   {{- if eq .Values.operator.configAuditReportsPlugin "Polaris" }}
   polaris.imageRef: "{{ .Values.polaris.imageRef }}"
-  polaris.config.yaml: |
-    {{- toYaml .Values.polaris.config | nindent 4 }}
   {{- end }}
   {{- if eq .Values.operator.configAuditReportsPlugin "Conftest" }}
   conftest.imageRef: {{ .Values.conftest.imageRef | quote }}
@@ -43,3 +41,15 @@ metadata:
   labels:
     {{- include "starboard-operator.labels" . | nindent 4 }}
 {{- end }}
+{{- if eq .Values.operator.configAuditReportsPlugin "Polaris" }}
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: starboard-polaris-config
+  labels:
+  {{- include "starboard-operator.labels" . | nindent 4 }}
+data:
+  polaris.config.yaml: |
+  {{- toYaml .Values.polaris.config | nindent 4 }}
+{{- end }}
diff --git a/deploy/static/05-starboard-operator.config.yaml b/deploy/static/05-starboard-operator.config.yaml
index b2f805b..73ac9d0 100644
--- a/deploy/static/05-starboard-operator.config.yaml
+++ b/deploy/static/05-starboard-operator.config.yaml
@@ -12,6 +12,15 @@ data:
   trivy.mode: Standalone
   trivy.serverURL: http://trivy-server.trivy-server:4954
   polaris.imageRef: quay.io/fairwinds/polaris:3.2
+  conftest.imageRef: openpolicyagent/conftest:v0.23.0
+  kube-bench.imageRef: docker.io/aquasec/kube-bench:0.5.0
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: starboard-polaris-config
+  namespace: starboard-operator
+data:
   polaris.config.yaml: |
     checks:
       # reliability
@@ -196,8 +205,6 @@ data:
         - kube-hunter
         rules:
         - runAsRootAllowed
-  conftest.imageRef: openpolicyagent/conftest:v0.23.0
-  kube-bench.imageRef: docker.io/aquasec/kube-bench:0.5.0
 ---
 apiVersion: v1
 kind: Secret
diff --git a/docs/integrations/vulnerability-scanners/trivy.md b/docs/integrations/vulnerability-scanners/trivy.md
index e04e177..a67260e 100644
--- a/docs/integrations/vulnerability-scanners/trivy.md
+++ b/docs/integrations/vulnerability-scanners/trivy.md
@@ -1,14 +1,14 @@
 ## Standalone
 
 The default configuration settings enable Trivy `vulnerabilityReports.scanner` in [`Standalone`][trivy-standalone]
-`trivy.mode`. Even though it doesn't require any additional setup, it's the least efficient method. Each pod created
-by a scan job has the init container that downloads the Trivy vulnerabilities database from the GitHub releases page
-and stores it in the local file system of an [emptyDir][emptyDir-volume] volume. This volume is then shared with the main
-containers that perform the actual scanning. Finally, the pod is deleted along with the emptyDir volume.
+`trivy.mode`. Even though it doesn't require any additional setup, it's the least efficient method. Each Pod created
+by a scan Job has the init container that downloads the Trivy vulnerabilities database from the GitHub releases page
+and stores it in the local file system of an [emptyDir][emptyDir-volume] volume. This volume is then shared with
+containers that perform the actual scanning. Finally, the Pod is deleted along with the emptyDir volume.
 
 ![](./../../images/design/trivy-standalone.png)
 
-The number of containers defined by a scan job equals the number of containers defined by the scanned Kubernetes
+The number of containers defined by a scan Job equals the number of containers defined by the scanned Kubernetes
 workload, so the cache in this mode is useful only if the workload defines multiple containers.
 
 Beyond that, frequent downloads from GitHub might lead to a [rate limiting][gh-rate-limiting] problem. The limits are
@@ -30,10 +30,6 @@ EOF
 )"
 ```
 
-[trivy-standalone]: https://github.com/aquasecurity/trivy#standalone
-[emptyDir-volume]: https://kubernetes.io/docs/concepts/storage/volumes/#emptydir
-[gh-rate-limiting]: https://docs.github.com/en/free-pro-team@latest/rest/overview/resources-in-the-rest-api#rate-limiting
-
 ## ClientServer
 
 You can connect Starboard to an external Trivy server by changing the default `trivy.mode` from
@@ -80,4 +76,7 @@ EOF
 
 ![](./../../images/design/trivy-clientserver.png)
 
-[trivy-clientserver]: https://github.com/aquasecurity/trivy#client--server
+[trivy-standalone]: https://aquasecurity.github.io/trivy/latest/modes/standalone/
+[emptyDir-volume]: https://kubernetes.io/docs/concepts/storage/volumes/#emptydir
+[gh-rate-limiting]: https://docs.github.com/en/free-pro-team@latest/rest/overview/resources-in-the-rest-api#rate-limiting
+[trivy-clientserver]: https://aquasecurity.github.io/trivy/latest/modes/client-server/
diff --git a/docs/operator/installation/olm.md b/docs/operator/installation/olm.md
index 91545d8..c6255f0 100644
--- a/docs/operator/installation/olm.md
+++ b/docs/operator/installation/olm.md
@@ -75,7 +75,7 @@ configure it to watch the `default` namespaces:
    ```console
    $ kubectl get clusterserviceversions -n starboard-operator
    NAME                        DISPLAY              VERSION   REPLACES                    PHASE
-   starboard-operator.{{ var.tag }}   Starboard Operator   {{ var.build.version }}     starboard-operator.{{ var.tag_prev }}   Succeeded
+   starboard-operator.{{ var.tag }}  Starboard Operator   {{ var.build.version }}    starboard-operator.{{ var.tag_prev }}   Succeeded
    ```
    If the above command succeeds and the ClusterServiceVersion has transitioned from `Installing` to `Succeeded` phase
    you will also find the operator's Deployment in the same namespace where the Subscription is:
diff --git a/pkg/plugin/polaris/plugin.go b/pkg/plugin/polaris/plugin.go
index fc6cb57..af84953 100644
--- a/pkg/plugin/polaris/plugin.go
+++ b/pkg/plugin/polaris/plugin.go
@@ -40,9 +40,12 @@ func NewPlugin(clock ext.Clock, config Config) configauditreport.Plugin {
 	}
 }
 
-func (p *plugin) GetConfigHash(_ starboard.PluginContext) (string, error) {
-	// TODO Compute config hash based on Polaris config
-	return kube.ComputeHash("TODO"), nil
+func (p *plugin) GetConfigHash(ctx starboard.PluginContext) (string, error) {
+	cm, err := ctx.GetConfig()
+	if err != nil {
+		return "", err
+	}
+	return kube.ComputeHash(cm.Data), nil
 }
 
 func (p *plugin) GetScanJobSpec(ctx starboard.PluginContext, obj client.Object) (corev1.PodSpec, []*corev1.Secret, error) {
@@ -63,7 +66,7 @@ func (p *plugin) GetScanJobSpec(ctx starboard.PluginContext, obj client.Object)
 				VolumeSource: corev1.VolumeSource{
 					ConfigMap: &corev1.ConfigMapVolumeSource{
 						LocalObjectReference: corev1.LocalObjectReference{
-							Name: starboard.ConfigMapName,
+							Name: starboard.GetPluginConfigMapName(ctx.GetName()),
 						},
 					},
 				},
diff --git a/pkg/starboard/config.go b/pkg/starboard/config.go
index 6630510..4abc863 100644
--- a/pkg/starboard/config.go
+++ b/pkg/starboard/config.go
@@ -277,13 +277,19 @@ func GetDefaultConfig() ConfigData {
 		"kube-hunter.imageRef": "docker.io/aquasec/kube-hunter:0.4.1",
 		"kube-hunter.quick":    "false",
 
-		"polaris.imageRef":    "quay.io/fairwinds/polaris:3.2",
-		"polaris.config.yaml": polarisConfigYAML,
+		"polaris.imageRef": "quay.io/fairwinds/polaris:3.2",
 
 		"conftest.imageRef": "openpolicyagent/conftest:v0.23.0",
 	}
 }
 
+// GetDefaultPolarisConfig returns the default Polaris configuration.
+func GetDefaultPolarisConfig() ConfigData {
+	return map[string]string{
+		"polaris.config.yaml": polarisConfigYAML,
+	}
+}
+
 func (c ConfigData) GetVulnerabilityReportsScanner() (Scanner, error) {
 	var ok bool
 	var value string
@@ -433,6 +439,21 @@ func (c *configManager) EnsureDefault(ctx context.Context) error {
 		return err
 	}
 
+	polarisCm := &corev1.ConfigMap{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: c.namespace,
+			Name:      GetPluginConfigMapName(string(Polaris)),
+			Labels: labels.Set{
+				"app.kubernetes.io/managed-by": "starboard",
+			},
+		},
+		Data: GetDefaultPolarisConfig(),
+	}
+	_, err = c.client.CoreV1().ConfigMaps(c.namespace).Create(ctx, polarisCm, metav1.CreateOptions{})
+	if err != nil && !apierrors.IsAlreadyExists(err) {
+		return err
+	}
+
 	secret := &corev1.Secret{
 		ObjectMeta: metav1.ObjectMeta{
 			Namespace: c.namespace,
@@ -477,6 +498,10 @@ func (c *configManager) Delete(ctx context.Context) error {
 	if err != nil && !apierrors.IsNotFound(err) {
 		return err
 	}
+	err = c.client.CoreV1().ConfigMaps(c.namespace).Delete(ctx, GetPluginConfigMapName(string(Polaris)), metav1.DeleteOptions{})
+	if err != nil && !apierrors.IsNotFound(err) {
+		return err
+	}
 	err = c.client.CoreV1().Secrets(c.namespace).Delete(ctx, SecretName, metav1.DeleteOptions{})
 	if err != nil && !apierrors.IsNotFound(err) {
 		return err


Test patch:
diff --git a/docs/integrations/config-checkers/conftest.md b/docs/integrations/config-checkers/conftest.md
index d282890..3d641a0 100644
--- a/docs/integrations/config-checkers/conftest.md
+++ b/docs/integrations/config-checkers/conftest.md
@@ -58,10 +58,10 @@ As an example, let's create the `starboard-conftest-config` ConfigMap with [file
 
 ```
 kubectl create configmap starboard-conftest-config -n <starboard_namespace> \
---from-file=conftest.policy.kubernetes.rego=kubernetes/lib/kubernetes.rego \
---from-file=conftest.policy.utils.rego=kubernetes/lib/utils.rego \
---from-file=conftest.policy.file_system_not_read_only.rego=kubernetes/policies/general/file_system_not_read_only.rego \
---from-file=conftest.policy.uses_image_tag_latest.rego=kubernetes/policies/general/uses_image_tag_latest.rego
+  --from-file=conftest.policy.kubernetes.rego=kubernetes/lib/kubernetes.rego \
+  --from-file=conftest.policy.utils.rego=kubernetes/lib/utils.rego \
+  --from-file=conftest.policy.file_system_not_read_only.rego=kubernetes/policies/general/file_system_not_read_only.rego \
+  --from-file=conftest.policy.uses_image_tag_latest.rego=kubernetes/policies/general/uses_image_tag_latest.rego
 ```
 
 To test this setup out with Starboard CLI you can create the `nginx` Deployment with the latest `nginx` image and check
diff --git a/itest/starboard/starboard_cli_test.go b/itest/starboard/starboard_cli_test.go
index 6e287fd..4eb13c5 100644
--- a/itest/starboard/starboard_cli_test.go
+++ b/itest/starboard/starboard_cli_test.go
@@ -129,6 +129,15 @@ var _ = Describe("Starboard CLI", func() {
 			Expect(err).ToNot(HaveOccurred())
 			Expect(cm.Data).To(BeEquivalentTo(starboard.GetDefaultConfig()))
 
+			var polarisCm corev1.ConfigMap
+			err = kubeClient.Get(context.TODO(), types.NamespacedName{
+				Name:      starboard.GetPluginConfigMapName(string(starboard.Polaris)),
+				Namespace: starboard.NamespaceName,
+			}, &polarisCm)
+
+			Expect(err).ToNot(HaveOccurred())
+			Expect(polarisCm.Data).To(BeEquivalentTo(starboard.GetDefaultPolarisConfig()))
+
 			var secret corev1.Secret
 			err = kubeClient.Get(context.TODO(), types.NamespacedName{
 				Name:      starboard.SecretName,
diff --git a/pkg/plugin/polaris/plugin_test.go b/pkg/plugin/polaris/plugin_test.go
index 5c306d1..bddc07b 100644
--- a/pkg/plugin/polaris/plugin_test.go
+++ b/pkg/plugin/polaris/plugin_test.go
@@ -16,6 +16,7 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/utils/pointer"
 	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/client/fake"
 )
 
 var (
@@ -58,7 +59,7 @@ func TestPlugin_GetScanJobSpec(t *testing.T) {
 						VolumeSource: corev1.VolumeSource{
 							ConfigMap: &corev1.ConfigMapVolumeSource{
 								LocalObjectReference: corev1.LocalObjectReference{
-									Name: starboard.ConfigMapName,
+									Name: starboard.GetPluginConfigMapName(string(starboard.Polaris)),
 								},
 							},
 						},
@@ -117,6 +118,7 @@ func TestPlugin_GetScanJobSpec(t *testing.T) {
 			g := gomega.NewGomegaWithT(t)
 
 			pluginContext := starboard.NewPluginContext().
+				WithName(string(starboard.Polaris)).
 				WithNamespace(starboard.NamespaceName).
 				WithServiceAccountName(starboard.ServiceAccountName).
 				Build()
@@ -190,3 +192,64 @@ func TestPlugin_ParseConfigAuditReportData(t *testing.T) {
 		Category: "Resources",
 	}))
 }
+
+func TestPlugin_GetConfigHash(t *testing.T) {
+
+	newPluginContextWithConfigData := func(data map[string]string) starboard.PluginContext {
+		return starboard.NewPluginContext().
+			WithName(string(starboard.Polaris)).
+			WithNamespace("starboard-ns").
+			WithClient(fake.NewClientBuilder().
+				WithObjects(&corev1.ConfigMap{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "starboard-polaris-config",
+						Namespace: "starboard-ns",
+					},
+					Data: data,
+				}).
+				Build()).
+			Build()
+	}
+
+	t.Run("Should return different hash for different config data", func(t *testing.T) {
+		g := gomega.NewGomegaWithT(t)
+
+		pluginContext1 := newPluginContextWithConfigData(map[string]string{
+			"foo":   "bar",
+			"brown": "fox",
+		})
+		pluginContext2 := newPluginContextWithConfigData(map[string]string{
+			"brown": "fox",
+			"foo":   "baz",
+		})
+
+		plugin := polaris.NewPlugin(fixedClock, starboard.ConfigData{})
+		hash1, err := plugin.GetConfigHash(pluginContext1)
+		g.Expect(err).ToNot(gomega.HaveOccurred())
+
+		hash2, err := plugin.GetConfigHash(pluginContext2)
+		g.Expect(err).ToNot(gomega.HaveOccurred())
+		g.Expect(hash1).ToNot(gomega.Equal(hash2))
+	})
+
+	t.Run("Should return the same hash for the same config data", func(t *testing.T) {
+		g := gomega.NewGomegaWithT(t)
+
+		pluginContext1 := newPluginContextWithConfigData(map[string]string{
+			"foo":   "bar",
+			"brown": "fox",
+		})
+		pluginContext2 := newPluginContextWithConfigData(map[string]string{
+			"brown": "fox",
+			"foo":   "bar",
+		})
+
+		plugin := polaris.NewPlugin(fixedClock, starboard.ConfigData{})
+		hash1, err := plugin.GetConfigHash(pluginContext1)
+		g.Expect(err).ToNot(gomega.HaveOccurred())
+
+		hash2, err := plugin.GetConfigHash(pluginContext2)
+		g.Expect(err).ToNot(gomega.HaveOccurred())
+		g.Expect(hash1).To(gomega.Equal(hash2))
+	})
+}