Repository: aquasecurity/starboard
Pull request #474 (2021-04-06 10:12:15)

feat: Add PluginContext for configuration audit scanners
Signed-off-by: Daniel Pacak <pacak.daniel@gmail.com>

codecov[bot]: # [Codecov](https://codecov.io/gh/aquasecurity/starboard/pull/474?src=pr&el=h1) Report
> Merging [#474](https://codecov.io/gh/aquasecurity/starboard/pull/474?src=pr&el=desc) (5edee3d) into [main](https://codecov.io/gh/aquasecurity/starboard/commit/774ee8bc45596b88d5502c23f157a8d143f243c4?el=desc) (774ee8b) will **decrease** coverage by `35.54%`.
> The diff coverage is `3.57%`.

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

```diff
@@             Coverage Diff             @@
##             main     #474       +/-   ##
===========================================
- Coverage   69.39%   33.84%   -35.55%     
===========================================
  Files          59       59               
  Lines        3679     3584       -95     
===========================================
- Hits         2553     1213     -1340     
- Misses        839     2298     +1459     
+ Partials      287       73      -214     
```


| [Impacted Files](https://codecov.io/gh/aquasecurity/starboard/pull/474?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pkg/cmd/scan\_configaudit.go](https://codecov.io/gh/aquasecurity/starboard/pull/474/diff?src=pr&el=tree#diff-cGtnL2NtZC9zY2FuX2NvbmZpZ2F1ZGl0Lmdv) | `0.00% <0.00%> (-55.00%)` | :arrow_down: |
| [pkg/configauditreport/scanner.go](https://codecov.io/gh/aquasecurity/starboard/pull/474/diff?src=pr&el=tree#diff-cGtnL2NvbmZpZ2F1ZGl0cmVwb3J0L3NjYW5uZXIuZ28=) | `0.00% <0.00%> (-81.95%)` | :arrow_down: |
| [pkg/operator/controller/configauditreport.go](https://codecov.io/gh/aquasecurity/starboard/pull/474/diff?src=pr&el=tree#diff-cGtnL29wZXJhdG9yL2NvbnRyb2xsZXIvY29uZmlnYXVkaXRyZXBvcnQuZ28=) | `0.00% <0.00%> (-67.57%)` | :arrow_down: |
| [pkg/starboard/plugin.go](https://codecov.io/gh/aquasecurity/starboard/pull/474/diff?src=pr&el=tree#diff-cGtnL3N0YXJib2FyZC9wbHVnaW4uZ28=) | `0.00% <0.00%> (ø)` | |
| [pkg/plugin/polaris/plugin.go](https://codecov.io/gh/aquasecurity/starboard/pull/474/diff?src=pr&el=tree#diff-cGtnL3BsdWdpbi9wb2xhcmlzL3BsdWdpbi5nbw==) | `91.24% <100.00%> (-1.46%)` | :arrow_down: |
| [pkg/cmd/get.go](https://codecov.io/gh/aquasecurity/starboard/pull/474/diff?src=pr&el=tree#diff-cGtnL2NtZC9nZXQuZ28=) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [pkg/cmd/find.go](https://codecov.io/gh/aquasecurity/starboard/pull/474/diff?src=pr&el=tree#diff-cGtnL2NtZC9maW5kLmdv) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [pkg/cmd/root.go](https://codecov.io/gh/aquasecurity/starboard/pull/474/diff?src=pr&el=tree#diff-cGtnL2NtZC9yb290Lmdv) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [pkg/cmd/scan.go](https://codecov.io/gh/aquasecurity/starboard/pull/474/diff?src=pr&el=tree#diff-cGtnL2NtZC9zY2FuLmdv) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [pkg/cmd/polaris.go](https://codecov.io/gh/aquasecurity/starboard/pull/474/diff?src=pr&el=tree#diff-cGtnL2NtZC9wb2xhcmlzLmdv) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| ... and [37 more](https://codecov.io/gh/aquasecurity/starboard/pull/474/diff?src=pr&el=tree-more) | |

------

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


Patch:
diff --git a/pkg/cmd/scan_configaudit.go b/pkg/cmd/scan_configaudit.go
index 1c3aecd..87bb786 100644
--- a/pkg/cmd/scan_configaudit.go
+++ b/pkg/cmd/scan_configaudit.go
@@ -40,7 +40,7 @@ func ScanConfigAuditReports(buildInfo starboard.BuildInfo, cf *genericclioptions
 		if err != nil {
 			return err
 		}
-		workload, gvk, err := WorkloadFromArgs(mapper, ns, args)
+		workload, _, err := WorkloadFromArgs(mapper, ns, args)
 		if err != nil {
 			return err
 		}
@@ -66,8 +66,12 @@ func ScanConfigAuditReports(buildInfo starboard.BuildInfo, cf *genericclioptions
 		if err != nil {
 			return err
 		}
-		scanner := configauditreport.NewScanner(kubeClientset, kubeClient, opts, instance)
-		report, err := scanner.Scan(ctx, workload, gvk)
+		pluginContext := starboard.NewPluginContext().
+			WithNamespace(starboard.NamespaceName).
+			WithServiceAccountName(starboard.ServiceAccountName).
+			Build()
+		scanner := configauditreport.NewScanner(kubeClientset, kubeClient, opts, instance, pluginContext)
+		report, err := scanner.Scan(ctx, workload)
 		if err != nil {
 			return err
 		}
diff --git a/pkg/configauditreport/plugin.go b/pkg/configauditreport/plugin.go
index 2b0f56f..708f4ca 100644
--- a/pkg/configauditreport/plugin.go
+++ b/pkg/configauditreport/plugin.go
@@ -4,6 +4,7 @@ import (
 	"io"
 
 	"github.com/aquasecurity/starboard/pkg/apis/aquasecurity/v1alpha1"
+	"github.com/aquasecurity/starboard/pkg/starboard"
 	corev1 "k8s.io/api/core/v1"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 )
@@ -18,7 +19,7 @@ type Plugin interface {
 	// it schedules a Kubernetes job to scan the specified workload client.Object.
 	// The plugin might return zero to many v1.Secret objects which will be
 	// created by Starboard and associated with the scan job.
-	GetScanJobSpec(obj client.Object) (corev1.PodSpec, []*corev1.Secret, error)
+	GetScanJobSpec(ctx starboard.PluginContext, obj client.Object) (corev1.PodSpec, []*corev1.Secret, error)
 
 	// ParseConfigAuditReportData is a callback to parse and convert logs of
 	// the container in a pod controlled by the scan job to v1alpha1.ConfigAuditResult.
diff --git a/pkg/configauditreport/scanner.go b/pkg/configauditreport/scanner.go
index a375810..8ea354b 100644
--- a/pkg/configauditreport/scanner.go
+++ b/pkg/configauditreport/scanner.go
@@ -13,7 +13,6 @@ import (
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
-	"k8s.io/apimachinery/pkg/runtime/schema"
 	"k8s.io/client-go/kubernetes"
 	"k8s.io/klog"
 	"k8s.io/utils/pointer"
@@ -27,6 +26,7 @@ type Scanner struct {
 	objectResolver *kube.ObjectResolver
 	logsReader     kube.LogsReader
 	plugin         Plugin
+	pluginContext  starboard.PluginContext
 	ext.IDGenerator
 }
 
@@ -35,19 +35,21 @@ func NewScanner(
 	client client.Client,
 	opts kube.ScannerOpts,
 	plugin Plugin,
+	pluginContext starboard.PluginContext,
 ) *Scanner {
 	return &Scanner{
 		scheme:         client.Scheme(),
 		clientset:      clientset,
 		opts:           opts,
 		plugin:         plugin,
+		pluginContext:  pluginContext,
 		objectResolver: &kube.ObjectResolver{Client: client},
 		logsReader:     kube.NewLogsReader(clientset),
 		IDGenerator:    ext.NewGoogleUUIDGenerator(),
 	}
 }
 
-func (s *Scanner) Scan(ctx context.Context, workload kube.Object, gvk schema.GroupVersionKind) (v1alpha1.ConfigAuditReport, error) {
+func (s *Scanner) Scan(ctx context.Context, workload kube.Object) (v1alpha1.ConfigAuditReport, error) {
 	klog.V(3).Infof("Getting Pod template for workload: %v", workload)
 
 	owner, err := s.objectResolver.GetObjectFromPartialObject(ctx, workload)
@@ -56,7 +58,7 @@ func (s *Scanner) Scan(ctx context.Context, workload kube.Object, gvk schema.Gro
 	}
 
 	klog.V(3).Infof("Scanning with options: %+v", s.opts)
-	job, secrets, err := s.getScanJob(workload, owner)
+	job, secrets, err := s.getScanJob(owner)
 	if err != nil {
 		return v1alpha1.ConfigAuditReport{}, err
 	}
@@ -98,8 +100,8 @@ func (s *Scanner) Scan(ctx context.Context, workload kube.Object, gvk schema.Gro
 		Get()
 }
 
-func (s *Scanner) getScanJob(workload kube.Object, obj client.Object) (*batchv1.Job, []*corev1.Secret, error) {
-	jobSpec, secrets, err := s.plugin.GetScanJobSpec(obj)
+func (s *Scanner) getScanJob(obj client.Object) (*batchv1.Job, []*corev1.Secret, error) {
+	jobSpec, secrets, err := s.plugin.GetScanJobSpec(s.pluginContext, obj)
 	if err != nil {
 		return nil, nil, err
 	}
@@ -108,9 +110,9 @@ func (s *Scanner) getScanJob(workload kube.Object, obj client.Object) (*batchv1.
 			Name:      s.GenerateID(),
 			Namespace: starboard.NamespaceName,
 			Labels: map[string]string{
-				kube.LabelResourceKind:      string(workload.Kind),
-				kube.LabelResourceName:      workload.Name,
-				kube.LabelResourceNamespace: workload.Namespace,
+				kube.LabelResourceKind:      obj.GetObjectKind().GroupVersionKind().Kind,
+				kube.LabelResourceName:      obj.GetName(),
+				kube.LabelResourceNamespace: obj.GetNamespace(),
 			},
 		},
 		Spec: batchv1.JobSpec{
@@ -120,9 +122,9 @@ func (s *Scanner) getScanJob(workload kube.Object, obj client.Object) (*batchv1.
 			Template: corev1.PodTemplateSpec{
 				ObjectMeta: metav1.ObjectMeta{
 					Labels: map[string]string{
-						kube.LabelResourceKind:      string(workload.Kind),
-						kube.LabelResourceName:      workload.Name,
-						kube.LabelResourceNamespace: workload.Namespace,
+						kube.LabelResourceKind:      obj.GetObjectKind().GroupVersionKind().Kind,
+						kube.LabelResourceName:      obj.GetName(),
+						kube.LabelResourceNamespace: obj.GetNamespace(),
 					},
 				},
 				Spec: jobSpec,
diff --git a/pkg/operator/controller/configauditreport.go b/pkg/operator/controller/configauditreport.go
index e501712..4eb7931 100644
--- a/pkg/operator/controller/configauditreport.go
+++ b/pkg/operator/controller/configauditreport.go
@@ -9,6 +9,7 @@ import (
 	"github.com/aquasecurity/starboard/pkg/kube"
 	"github.com/aquasecurity/starboard/pkg/operator/etc"
 	. "github.com/aquasecurity/starboard/pkg/operator/predicate"
+	"github.com/aquasecurity/starboard/pkg/starboard"
 	"github.com/go-logr/logr"
 	appsv1 "k8s.io/api/apps/v1"
 	batchv1 "k8s.io/api/batch/v1"
@@ -32,6 +33,7 @@ type ConfigAuditReportReconciler struct {
 	LimitChecker
 	kube.LogsReader
 	configauditreport.Plugin
+	starboard.PluginContext
 	configauditreport.ReadWriter
 }
 
@@ -226,19 +228,15 @@ func (r *ConfigAuditReportReconciler) getScanJobName(workload kube.Object) strin
 }
 
 func (r *ConfigAuditReportReconciler) getScanJob(workload kube.Object, obj client.Object, hash string) (*batchv1.Job, []*corev1.Secret, error) {
-	jobSpec, secrets, err := r.Plugin.GetScanJobSpec(obj)
+	jobSpec, secrets, err := r.Plugin.GetScanJobSpec(r.PluginContext, obj)
 
 	if err != nil {
 		return nil, nil, err
 	}
 
-	jobSpec.ServiceAccountName = r.Config.ServiceAccount
-
-	jobName := r.getScanJobName(workload)
-
 	return &batchv1.Job{
 		ObjectMeta: metav1.ObjectMeta{
-			Name:      jobName,
+			Name:      r.getScanJobName(workload),
 			Namespace: r.Config.Namespace,
 			Labels: map[string]string{
 				kube.LabelResourceKind:          string(workload.Kind),
diff --git a/pkg/operator/operator.go b/pkg/operator/operator.go
index 31eb70e..8c30412 100644
--- a/pkg/operator/operator.go
+++ b/pkg/operator/operator.go
@@ -163,7 +163,11 @@ func Run(buildInfo starboard.BuildInfo, operatorConfig etc.Config) error {
 			LimitChecker:   limitChecker,
 			LogsReader:     logsReader,
 			Plugin:         configAuditReportPlugin,
-			ReadWriter:     configauditreport.NewReadWriter(mgr.GetClient()),
+			PluginContext: starboard.NewPluginContext().
+				WithNamespace(operatorNamespace).
+				WithServiceAccountName(operatorConfig.ServiceAccount).
+				Build(),
+			ReadWriter: configauditreport.NewReadWriter(mgr.GetClient()),
 		}).SetupWithManager(mgr); err != nil {
 			return fmt.Errorf("unable to setup configauditreport reconciler: %w", err)
 		}
diff --git a/pkg/plugin/polaris/plugin.go b/pkg/plugin/polaris/plugin.go
index 7034005..1dd1c4c 100644
--- a/pkg/plugin/polaris/plugin.go
+++ b/pkg/plugin/polaris/plugin.go
@@ -39,7 +39,7 @@ func NewPlugin(clock ext.Clock, config Config) configauditreport.Plugin {
 	}
 }
 
-func (p *plugin) GetScanJobSpec(obj client.Object) (corev1.PodSpec, []*corev1.Secret, error) {
+func (p *plugin) GetScanJobSpec(ctx starboard.PluginContext, obj client.Object) (corev1.PodSpec, []*corev1.Secret, error) {
 	imageRef, err := p.config.GetPolarisImageRef()
 	if err != nil {
 		return corev1.PodSpec{}, nil, err
@@ -47,7 +47,7 @@ func (p *plugin) GetScanJobSpec(obj client.Object) (corev1.PodSpec, []*corev1.Se
 	sourceName := p.sourceNameFrom(obj)
 
 	return corev1.PodSpec{
-		ServiceAccountName:           starboard.ServiceAccountName,
+		ServiceAccountName:           ctx.GetServiceAccountName(),
 		AutomountServiceAccountToken: pointer.BoolPtr(true),
 		RestartPolicy:                corev1.RestartPolicyNever,
 		Affinity:                     starboard.LinuxNodeAffinity(),
diff --git a/pkg/starboard/doc.go b/pkg/starboard/doc.go
new file mode 100644
index 0000000..541a45e
--- /dev/null
+++ b/pkg/starboard/doc.go
@@ -0,0 +1,2 @@
+// The starboard package provides primitives for working with Starboard toolkit.
+package starboard
diff --git a/pkg/starboard/plugin.go b/pkg/starboard/plugin.go
new file mode 100644
index 0000000..2a9f953
--- /dev/null
+++ b/pkg/starboard/plugin.go
@@ -0,0 +1,50 @@
+package starboard
+
+// PluginContext is plugin's execution context within the Starboard toolkit.
+// The context is used to grant access to other methods so that this plugin
+// can interact with the toolkit.
+type PluginContext interface {
+	// GetNamespace return the name of the K8s Namespace where Starboard creates Jobs
+	// and other helper objects.
+	GetNamespace() string
+	// GetServiceAccountName return the name of the K8s Service Account used to run workloads
+	// created by Starboard.
+	GetServiceAccountName() string
+}
+
+type pluginContext struct {
+	namespace          string
+	serviceAccountName string
+}
+
+func (p *pluginContext) GetNamespace() string {
+	return p.namespace
+}
+
+func (p *pluginContext) GetServiceAccountName() string {
+	return p.serviceAccountName
+}
+
+type PluginContextBuilder struct {
+	ctx *pluginContext
+}
+
+func NewPluginContext() *PluginContextBuilder {
+	return &PluginContextBuilder{
+		ctx: &pluginContext{},
+	}
+}
+
+func (b *PluginContextBuilder) WithNamespace(namespace string) *PluginContextBuilder {
+	b.ctx.namespace = namespace
+	return b
+}
+
+func (b *PluginContextBuilder) WithServiceAccountName(name string) *PluginContextBuilder {
+	b.ctx.serviceAccountName = name
+	return b
+}
+
+func (b *PluginContextBuilder) Build() PluginContext {
+	return b.ctx
+}


Test patch:
diff --git a/pkg/plugin/polaris/plugin_test.go b/pkg/plugin/polaris/plugin_test.go
index 81cbcfa..5d6a6c6 100644
--- a/pkg/plugin/polaris/plugin_test.go
+++ b/pkg/plugin/polaris/plugin_test.go
@@ -117,8 +117,12 @@ func TestPlugin_GetScanJobSpec(t *testing.T) {
 
 	for _, tc := range testCases {
 		t.Run(tc.name, func(t *testing.T) {
+			pluginContext := starboard.NewPluginContext().
+				WithNamespace(starboard.NamespaceName).
+				WithServiceAccountName(starboard.ServiceAccountName).
+				Build()
 			plugin := polaris.NewPlugin(fixedClock, tc.config)
-			jobSpec, secrets, err := plugin.GetScanJobSpec(tc.obj)
+			jobSpec, secrets, err := plugin.GetScanJobSpec(pluginContext, tc.obj)
 			require.NoError(t, err, tc.name)
 			assert.Nil(t, secrets)
 			assert.Equal(t, tc.expectedJobSpec, jobSpec, tc.name)
diff --git a/pkg/starboard/plugin_test.go b/pkg/starboard/plugin_test.go
new file mode 100644
index 0000000..8cb5fc2
--- /dev/null
+++ b/pkg/starboard/plugin_test.go
@@ -0,0 +1,1 @@
+package starboard