Repository: aquasecurity/starboard
Pull request #644 (2021-07-23 22:13:34)

feat: Audit configuration of other K8s objects
## TODO

- [x] Services
- [x] ConfigMaps
- [x] Roles
- [x] RoleBindings
- [x] ClusterRoles
- [x] ClusterRoleBindings
- [x] CustomResourceDefinitions

Signed-off-by: Daniel Pacak <pacak.daniel@gmail.com>

codecov[bot]: # [Codecov](https://codecov.io/gh/aquasecurity/starboard/pull/644?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aquasecurity) Report
> Merging [#644](https://codecov.io/gh/aquasecurity/starboard/pull/644?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aquasecurity) (1a5c131) into [main](https://codecov.io/gh/aquasecurity/starboard/commit/134b2f5e860d8f813e070775414905008d2afaae?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aquasecurity) (134b2f5) will **decrease** coverage by `22.81%`.
> The diff coverage is `6.45%`.

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

```diff
@@             Coverage Diff             @@
##             main     #644       +/-   ##
===========================================
- Coverage   68.34%   45.53%   -22.82%     
===========================================
  Files          58       41       -17     
  Lines        4511     3683      -828     
===========================================
- Hits         3083     1677     -1406     
- Misses       1062     1900      +838     
+ Partials      366      106      -260     
```


| [Impacted Files](https://codecov.io/gh/aquasecurity/starboard/pull/644?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aquasecurity) | Coverage Δ | |
|---|---|---|
| [pkg/kube/object.go](https://codecov.io/gh/aquasecurity/starboard/pull/644/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aquasecurity#diff-cGtnL2t1YmUvb2JqZWN0Lmdv) | `40.41% <0.00%> (-30.91%)` | :arrow_down: |
| [pkg/operator/controller/configauditreport.go](https://codecov.io/gh/aquasecurity/starboard/pull/644/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%> (-65.75%)` | :arrow_down: |
| [pkg/configauditreport/builder.go](https://codecov.io/gh/aquasecurity/starboard/pull/644/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aquasecurity#diff-cGtnL2NvbmZpZ2F1ZGl0cmVwb3J0L2J1aWxkZXIuZ28=) | `78.78% <100.00%> (-7.22%)` | :arrow_down: |
| [pkg/starboard/config.go](https://codecov.io/gh/aquasecurity/starboard/pull/644/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aquasecurity#diff-cGtnL3N0YXJib2FyZC9jb25maWcuZ28=) | `70.28% <100.00%> (+0.21%)` | :arrow_up: |
| [pkg/kubehunter/scanner.go](https://codecov.io/gh/aquasecurity/starboard/pull/644/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aquasecurity#diff-cGtnL2t1YmVodW50ZXIvc2Nhbm5lci5nbw==) | `0.00% <0.00%> (-80.51%)` | :arrow_down: |
| [pkg/plugin/factory.go](https://codecov.io/gh/aquasecurity/starboard/pull/644/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%> (-80.49%)` | :arrow_down: |
| [pkg/configauditreport/scanner.go](https://codecov.io/gh/aquasecurity/starboard/pull/644/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aquasecurity#diff-cGtnL2NvbmZpZ2F1ZGl0cmVwb3J0L3NjYW5uZXIuZ28=) | `0.00% <0.00%> (-70.77%)` | :arrow_down: |
| [pkg/vulnerabilityreport/scanner.go](https://codecov.io/gh/aquasecurity/starboard/pull/644/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aquasecurity#diff-cGtnL3Z1bG5lcmFiaWxpdHlyZXBvcnQvc2Nhbm5lci5nbw==) | `0.00% <0.00%> (-70.54%)` | :arrow_down: |
| [pkg/operator/controller/vulnerabilityreport.go](https://codecov.io/gh/aquasecurity/starboard/pull/644/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%> (-65.17%)` | :arrow_down: |
| [pkg/kube/runnable\_job.go](https://codecov.io/gh/aquasecurity/starboard/pull/644/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aquasecurity#diff-cGtnL2t1YmUvcnVubmFibGVfam9iLmdv) | `0.00% <0.00%> (-64.39%)` | :arrow_down: |
| ... and [32 more](https://codecov.io/gh/aquasecurity/starboard/pull/644/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/644?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/644?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aquasecurity). Last update [134b2f5...1a5c131](https://codecov.io/gh/aquasecurity/starboard/pull/644?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/rbac.yaml b/deploy/helm/templates/rbac.yaml
index 10ebe82..15c415f 100644
--- a/deploy/helm/templates/rbac.yaml
+++ b/deploy/helm/templates/rbac.yaml
@@ -33,6 +33,7 @@ rules:
       - pods
       - pods/log
       - replicationcontrollers
+      - services
     verbs:
       - get
       - list
@@ -40,7 +41,7 @@ rules:
   - apiGroups:
       - ""
     resources:
-      - "nodes"
+      - nodes
     verbs:
       - get
       - list
@@ -71,7 +72,7 @@ rules:
       - create
   - apiGroups:
       - apps
-    resources: # resources that own pods are inspected
+    resources:
       - replicasets
       - statefulsets
       - daemonsets
@@ -82,19 +83,36 @@ rules:
   - apiGroups:
       - batch
     resources:
+      - jobs
       - cronjobs
     verbs:
       - get
       - list
       - watch
   - apiGroups:
-      - batch
-    resources: # jobs are inspected and directly used by the starboard-operator
-      - jobs
+      - rbac.authorization.k8s.io
+    resources:
+      - roles
+      - rolebindings
+      - clusterroles
+      - clusterrolebindings
+    verbs:
+      - get
+      - list
+      - watch
+  - apiGroups:
+      - apiextensions.k8s.io
+    resources:
+      - customresourcedefinitions
     verbs:
       - get
       - list
       - watch
+  - apiGroups:
+      - batch
+    resources:
+      - jobs
+    verbs:
       - create
       - delete
   - apiGroups:
@@ -102,6 +120,7 @@ rules:
     resources:
       - vulnerabilityreports
       - configauditreports
+      - clusterconfigauditreports
       - ciskubebenchreports
     verbs:
       - get
diff --git a/deploy/static/03-starboard-operator.clusterrole.yaml b/deploy/static/03-starboard-operator.clusterrole.yaml
index ad5a9bf..935b7b8 100644
--- a/deploy/static/03-starboard-operator.clusterrole.yaml
+++ b/deploy/static/03-starboard-operator.clusterrole.yaml
@@ -11,6 +11,7 @@ rules:
       - pods
       - pods/log
       - replicationcontrollers
+      - services
     verbs:
       - get
       - list
@@ -18,7 +19,7 @@ rules:
   - apiGroups:
       - ""
     resources:
-      - "nodes"
+      - nodes
     verbs:
       - get
       - list
@@ -60,19 +61,36 @@ rules:
   - apiGroups:
       - batch
     resources:
+      - jobs
       - cronjobs
     verbs:
       - get
       - list
       - watch
   - apiGroups:
-      - batch
+      - rbac.authorization.k8s.io
     resources:
-      - jobs
+      - roles
+      - rolebindings
+      - clusterroles
+      - clusterrolebindings
+    verbs:
+      - get
+      - list
+      - watch
+  - apiGroups:
+      - apiextensions.k8s.io
+    resources:
+      - customresourcedefinitions
     verbs:
       - get
       - list
       - watch
+  - apiGroups:
+      - batch
+    resources:
+      - jobs
+    verbs:
       - create
       - delete
   - apiGroups:
@@ -80,6 +98,7 @@ rules:
     resources:
       - vulnerabilityreports
       - configauditreports
+      - clusterconfigauditreports
       - ciskubebenchreports
     verbs:
       - get
diff --git a/docs/crds/configaudit-report.md b/docs/crds/configaudit-report.md
index ba15ac0..3f24693 100644
--- a/docs/crds/configaudit-report.md
+++ b/docs/crds/configaudit-report.md
@@ -22,7 +22,7 @@ metadata:
     starboard.resource.name: nginx-6d4cf56db6
     starboard.resource.namespace: default
     plugin-config-hash: 7f65d98b75
-    pod-spec-hash: 7cb64cb677
+    resource-spec-hash: 7cb64cb677
   uid: d5cf8847-c96d-4534-beb9-514a34230302
   ownerReferences:
     - apiVersion: apps/v1
diff --git a/docs/crds/vulnerability-report.md b/docs/crds/vulnerability-report.md
index d485b4c..31860c8 100644
--- a/docs/crds/vulnerability-report.md
+++ b/docs/crds/vulnerability-report.md
@@ -20,7 +20,7 @@ metadata:
     starboard.resource.kind: ReplicaSet
     starboard.resource.name: nginx-6d4cf56db6
     starboard.resource.namespace: default
-    pod-spec-hash: 7cb64cb677
+    resource-spec-hash: 7cb64cb677
   uid: 8aa1a7cb-a319-4b93-850d-5a67827dfbbf
   ownerReferences:
     - apiVersion: apps/v1
diff --git a/pkg/cmd/scan_configauditreports.go b/pkg/cmd/scan_configauditreports.go
index 4b1e6d9..c29f64a 100644
--- a/pkg/cmd/scan_configauditreports.go
+++ b/pkg/cmd/scan_configauditreports.go
@@ -73,11 +73,11 @@ func ScanConfigAuditReports(buildInfo starboard.BuildInfo, cf *genericclioptions
 			return err
 		}
 		scanner := configauditreport.NewScanner(kubeClientset, kubeClient, plugin, pluginContext, config, opts)
-		report, err := scanner.Scan(ctx, workload)
+		reportBuilder, err := scanner.Scan(ctx, workload)
 		if err != nil {
 			return err
 		}
 		writer := configauditreport.NewReadWriter(kubeClient)
-		return writer.WriteReport(ctx, report)
+		return reportBuilder.Write(ctx, writer)
 	}
 }
diff --git a/pkg/configauditreport/builder.go b/pkg/configauditreport/builder.go
index 0a308b7..a5897b3 100644
--- a/pkg/configauditreport/builder.go
+++ b/pkg/configauditreport/builder.go
@@ -1,6 +1,7 @@
 package configauditreport
 
 import (
+	"context"
 	"fmt"
 	"strings"
 	"time"
@@ -12,18 +13,19 @@ import (
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/util/validation"
 	"k8s.io/utils/pointer"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
 )
 
 type ScanJobBuilder struct {
-	plugin             Plugin
-	pluginContext      starboard.PluginContext
-	timeout            time.Duration
-	object             client.Object
-	tolerations        []corev1.Toleration
-	scanJobAnnotations map[string]string
+	plugin        Plugin
+	pluginContext starboard.PluginContext
+	timeout       time.Duration
+	object        client.Object
+	tolerations   []corev1.Toleration
+	annotations   map[string]string
 }
 
 func NewScanJob() *ScanJobBuilder {
@@ -55,8 +57,8 @@ func (s *ScanJobBuilder) WithTolerations(tolerations []corev1.Toleration) *ScanJ
 	return s
 }
 
-func (s *ScanJobBuilder) WithScanJobAnnotations(scanJobAnnotations map[string]string) *ScanJobBuilder {
-	s.scanJobAnnotations = scanJobAnnotations
+func (s *ScanJobBuilder) WithAnnotations(annotations map[string]string) *ScanJobBuilder {
+	s.annotations = annotations
 	return s
 }
 
@@ -66,44 +68,31 @@ func (s *ScanJobBuilder) Get() (*batchv1.Job, []*corev1.Secret, error) {
 		return nil, nil, err
 	}
 
-	podSpec, err := kube.GetPodSpec(s.object)
+	resourceSpecHash, err := kube.ComputeSpecHash(s.object)
 	if err != nil {
 		return nil, nil, err
 	}
 
 	jobSpec.Tolerations = append(jobSpec.Tolerations, s.tolerations...)
 
-	podSpecHash := kube.ComputeHash(podSpec)
-
 	pluginConfigHash, err := s.plugin.GetConfigHash(s.pluginContext)
 	if err != nil {
 		return nil, nil, err
 	}
 
 	labels := map[string]string{
-		starboard.LabelResourceKind:             s.object.GetObjectKind().GroupVersionKind().Kind,
-		starboard.LabelResourceName:             s.object.GetName(),
-		starboard.LabelResourceNamespace:        s.object.GetNamespace(),
-		starboard.LabelPodSpecHash:              podSpecHash,
+		starboard.LabelResourceSpecHash:         resourceSpecHash,
 		starboard.LabelPluginConfigHash:         pluginConfigHash,
 		starboard.LabelConfigAuditReportScanner: s.pluginContext.GetName(),
 		starboard.LabelK8SAppManagedBy:          starboard.AppStarboard,
 	}
 
-	for _, secret := range secrets {
-		if secret.Labels == nil {
-			secret.Labels = make(map[string]string)
-		}
-		for k, v := range labels {
-			secret.Labels[k] = v
-		}
-	}
-
-	return &batchv1.Job{
+	job := &batchv1.Job{
 		ObjectMeta: metav1.ObjectMeta{
-			Name:      GetScanJobName(s.object),
-			Namespace: s.pluginContext.GetNamespace(),
-			Labels:    labels,
+			Name:        GetScanJobName(s.object),
+			Namespace:   s.pluginContext.GetNamespace(),
+			Labels:      labels,
+			Annotations: s.annotations,
 		},
 		Spec: batchv1.JobSpec{
 			BackoffLimit:          pointer.Int32Ptr(0),
@@ -112,12 +101,34 @@ func (s *ScanJobBuilder) Get() (*batchv1.Job, []*corev1.Secret, error) {
 			Template: corev1.PodTemplateSpec{
 				ObjectMeta: metav1.ObjectMeta{
 					Labels:      labels,
-					Annotations: s.scanJobAnnotations,
+					Annotations: s.annotations,
 				},
 				Spec: jobSpec,
 			},
 		},
-	}, secrets, nil
+	}
+
+	err = kube.ObjectToObjectMetadata(s.object, &job.ObjectMeta)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	err = kube.ObjectToObjectMetadata(s.object, &job.Spec.Template.ObjectMeta)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	for _, secret := range secrets {
+		if secret.Labels == nil {
+			secret.Labels = make(map[string]string)
+		}
+		for k, v := range labels {
+			secret.Labels[k] = v
+		}
+		err = kube.ObjectToObjectMetadata(s.object, &secret.ObjectMeta)
+	}
+
+	return job, secrets, nil
 }
 
 func GetScanJobName(obj client.Object) string {
@@ -130,8 +141,8 @@ func GetScanJobName(obj client.Object) string {
 
 type ReportBuilder struct {
 	scheme           *runtime.Scheme
-	controller       metav1.Object
-	podSpecHash      string
+	controller       client.Object
+	resourceSpecHash string
 	pluginConfigHash string
 	data             v1alpha1.ConfigAuditReportData
 }
@@ -142,13 +153,13 @@ func NewReportBuilder(scheme *runtime.Scheme) *ReportBuilder {
 	}
 }
 
-func (b *ReportBuilder) Controller(controller metav1.Object) *ReportBuilder {
+func (b *ReportBuilder) Controller(controller client.Object) *ReportBuilder {
 	b.controller = controller
 	return b
 }
 
-func (b *ReportBuilder) PodSpecHash(hash string) *ReportBuilder {
-	b.podSpecHash = hash
+func (b *ReportBuilder) ResourceSpecHash(hash string) *ReportBuilder {
+	b.resourceSpecHash = hash
 	return b
 }
 
@@ -162,48 +173,73 @@ func (b *ReportBuilder) Data(data v1alpha1.ConfigAuditReportData) *ReportBuilder
 	return b
 }
 
-func (b *ReportBuilder) reportName() (string, error) {
-	kind, err := kube.KindForObject(b.controller, b.scheme)
-	if err != nil {
-		return "", err
+func (b *ReportBuilder) reportName() string {
+	kind := b.controller.GetObjectKind().GroupVersionKind().Kind
+	name := b.controller.GetName()
+	reportName := fmt.Sprintf("%s-%s", strings.ToLower(kind), name)
+	if len(validation.IsValidLabelValue(reportName)) == 0 {
+		return reportName
 	}
-	return fmt.Sprintf("%s-%s", strings.ToLower(kind),
-		b.controller.GetName()), nil
+	return fmt.Sprintf("%s-%s", strings.ToLower(kind), kube.ComputeHash(name))
 }
 
-func (b *ReportBuilder) Get() (v1alpha1.ConfigAuditReport, error) {
-	kind, err := kube.KindForObject(b.controller, b.scheme)
-	if err != nil {
-		return v1alpha1.ConfigAuditReport{}, fmt.Errorf("getting kind for object: %w", err)
+func (b *ReportBuilder) GetClusterReport() (v1alpha1.ClusterConfigAuditReport, error) {
+	labels := make(map[string]string)
+	if b.resourceSpecHash != "" {
+		labels[starboard.LabelResourceSpecHash] = b.resourceSpecHash
 	}
-
-	labels := map[string]string{
-		starboard.LabelResourceKind:      kind,
-		starboard.LabelResourceName:      b.controller.GetName(),
-		starboard.LabelResourceNamespace: b.controller.GetNamespace(),
+	if b.pluginConfigHash != "" {
+		labels[starboard.LabelPluginConfigHash] = b.pluginConfigHash
 	}
 
-	if b.podSpecHash != "" {
-		labels[starboard.LabelPodSpecHash] = b.podSpecHash
+	report := v1alpha1.ClusterConfigAuditReport{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:   b.reportName(),
+			Labels: labels,
+		},
+		Report: b.data,
 	}
+	err := kube.ObjectToObjectMetadata(b.controller, &report.ObjectMeta)
+	if err != nil {
+		return v1alpha1.ClusterConfigAuditReport{}, err
+	}
+	err = controllerutil.SetControllerReference(b.controller, &report, b.scheme)
+	if err != nil {
+		return v1alpha1.ClusterConfigAuditReport{}, fmt.Errorf("setting controller reference: %w", err)
+	}
+	// The OwnerReferencesPermissionsEnforcement admission controller protects the
+	// access to metadata.ownerReferences[x].blockOwnerDeletion of an object, so
+	// that only users with "update" permission to the finalizers subresource of the
+	// referenced owner can change it.
+	// We set metadata.ownerReferences[x].blockOwnerDeletion to false so that
+	// additional RBAC permissions are not required when the OwnerReferencesPermissionsEnforcement
+	// is enabled.
+	// See https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#ownerreferencespermissionenforcement
+	report.OwnerReferences[0].BlockOwnerDeletion = pointer.BoolPtr(false)
+	return report, nil
+}
 
+func (b *ReportBuilder) GetReport() (v1alpha1.ConfigAuditReport, error) {
+	labels := make(map[string]string)
+	if b.resourceSpecHash != "" {
+		labels[starboard.LabelResourceSpecHash] = b.resourceSpecHash
+	}
 	if b.pluginConfigHash != "" {
 		labels[starboard.LabelPluginConfigHash] = b.pluginConfigHash
 	}
 
-	reportName, err := b.reportName()
-	if err != nil {
-		return v1alpha1.ConfigAuditReport{}, err
-	}
-
 	report := v1alpha1.ConfigAuditReport{
 		ObjectMeta: metav1.ObjectMeta{
-			Name:      reportName,
+			Name:      b.reportName(),
 			Namespace: b.controller.GetNamespace(),
 			Labels:    labels,
 		},
 		Report: b.data,
 	}
+	err := kube.ObjectToObjectMetadata(b.controller, &report.ObjectMeta)
+	if err != nil {
+		return v1alpha1.ConfigAuditReport{}, err
+	}
 	err = controllerutil.SetControllerReference(b.controller, &report, b.scheme)
 	if err != nil {
 		return v1alpha1.ConfigAuditReport{}, fmt.Errorf("setting controller reference: %w", err)
@@ -219,3 +255,19 @@ func (b *ReportBuilder) Get() (v1alpha1.ConfigAuditReport, error) {
 	report.OwnerReferences[0].BlockOwnerDeletion = pointer.BoolPtr(false)
 	return report, nil
 }
+
+func (b *ReportBuilder) Write(ctx context.Context, writer Writer) error {
+	if kube.IsClusterScopedKind(b.controller.GetObjectKind().GroupVersionKind().Kind) {
+		report, err := b.GetClusterReport()
+		if err != nil {
+			return err
+		}
+		return writer.WriteClusterReport(ctx, report)
+	} else {
+		report, err := b.GetReport()
+		if err != nil {
+			return err
+		}
+		return writer.WriteReport(ctx, report)
+	}
+}
diff --git a/pkg/configauditreport/io.go b/pkg/configauditreport/io.go
index 1bf2cb6..d4dd092 100644
--- a/pkg/configauditreport/io.go
+++ b/pkg/configauditreport/io.go
@@ -6,7 +6,6 @@ import (
 
 	"github.com/aquasecurity/starboard/pkg/apis/aquasecurity/v1alpha1"
 	"github.com/aquasecurity/starboard/pkg/kube"
-	"github.com/aquasecurity/starboard/pkg/starboard"
 	"k8s.io/apimachinery/pkg/api/errors"
 	"k8s.io/apimachinery/pkg/types"
 	"sigs.k8s.io/controller-runtime/pkg/client"
@@ -107,11 +106,9 @@ func (r *readWriter) WriteClusterReport(ctx context.Context, report v1alpha1.Clu
 func (r *readWriter) FindReportByOwner(ctx context.Context, owner kube.Object) (*v1alpha1.ConfigAuditReport, error) {
 	var list v1alpha1.ConfigAuditReportList
 
-	err := r.List(ctx, &list, client.MatchingLabels{
-		starboard.LabelResourceKind:      string(owner.Kind),
-		starboard.LabelResourceNamespace: owner.Namespace,
-		starboard.LabelResourceName:      owner.Name,
-	}, client.InNamespace(owner.Namespace))
+	labels := client.MatchingLabels(kube.PartialObjectToLabels(owner))
+
+	err := r.List(ctx, &list, labels, client.InNamespace(owner.Namespace))
 	if err != nil {
 		return nil, err
 	}
@@ -152,10 +149,9 @@ func (r *readWriter) FindReportByOwnerInHierarchy(ctx context.Context, owner kub
 func (r *readWriter) FindClusterReportByOwner(ctx context.Context, owner kube.Object) (*v1alpha1.ClusterConfigAuditReport, error) {
 	var list v1alpha1.ClusterConfigAuditReportList
 
-	err := r.List(ctx, &list, client.MatchingLabels{
-		starboard.LabelResourceKind: string(owner.Kind),
-		starboard.LabelResourceName: owner.Name,
-	})
+	labels := client.MatchingLabels(kube.PartialObjectToLabels(owner))
+
+	err := r.List(ctx, &list, labels)
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/configauditreport/scanner.go b/pkg/configauditreport/scanner.go
index 4b630b1..9fadb76 100644
--- a/pkg/configauditreport/scanner.go
+++ b/pkg/configauditreport/scanner.go
@@ -4,7 +4,6 @@ import (
 	"context"
 	"fmt"
 
-	"github.com/aquasecurity/starboard/pkg/apis/aquasecurity/v1alpha1"
 	"github.com/aquasecurity/starboard/pkg/kube"
 	"github.com/aquasecurity/starboard/pkg/runner"
 	"github.com/aquasecurity/starboard/pkg/starboard"
@@ -46,20 +45,20 @@ func NewScanner(
 	}
 }
 
-func (s *Scanner) Scan(ctx context.Context, workload kube.Object) (v1alpha1.ConfigAuditReport, error) {
-	klog.V(3).Infof("Getting Pod template for workload: %v", workload)
-	scanJobTolerations, err := s.config.GetScanJobTolerations()
+func (s *Scanner) Scan(ctx context.Context, obj kube.Object) (*ReportBuilder, error) {
+	owner, err := s.objectResolver.GetObjectFromPartialObject(ctx, obj)
 	if err != nil {
-		return v1alpha1.ConfigAuditReport{}, err
+		return nil, err
 	}
 
-	scanJobAnnotations, err := s.config.GetScanJobAnnotations()
+	scanJobTolerations, err := s.config.GetScanJobTolerations()
 	if err != nil {
-		return v1alpha1.ConfigAuditReport{}, err
+		return nil, fmt.Errorf("getting scan job tolerations: %w", err)
 	}
-	owner, err := s.objectResolver.GetObjectFromPartialObject(ctx, workload)
+
+	scanJobAnnotations, err := s.config.GetScanJobAnnotations()
 	if err != nil {
-		return v1alpha1.ConfigAuditReport{}, err
+		return nil, fmt.Errorf("getting scan job annotations: %w", err)
 	}
 
 	klog.V(3).Infof("Scanning with options: %+v", s.opts)
@@ -69,15 +68,15 @@ func (s *Scanner) Scan(ctx context.Context, workload kube.Object) (v1alpha1.Conf
 		WithTimeout(s.opts.ScanJobTimeout).
 		WithObject(owner).
 		WithTolerations(scanJobTolerations).
-		WithScanJobAnnotations(scanJobAnnotations).
+		WithAnnotations(scanJobAnnotations).
 		Get()
 	if err != nil {
-		return v1alpha1.ConfigAuditReport{}, err
+		return nil, fmt.Errorf("constructing scan job: %w", err)
 	}
 
 	err = runner.New().Run(ctx, kube.NewRunnableJob(s.scheme, s.clientset, job, secrets...))
 	if err != nil {
-		return v1alpha1.ConfigAuditReport{}, fmt.Errorf("running scan job: %w", err)
+		return nil, fmt.Errorf("running scan job: %w", err)
 	}
 
 	defer func() {
@@ -94,11 +93,10 @@ func (s *Scanner) Scan(ctx context.Context, workload kube.Object) (v1alpha1.Conf
 
 	containerName := s.plugin.GetContainerName()
 
-	klog.V(3).Infof("Getting logs for %s container in job: %s/%s", containerName,
-		job.Namespace, job.Name)
+	klog.V(3).Infof("Getting logs for %s container in job: %s/%s", containerName, job.Namespace, job.Name)
 	logsStream, err := s.logsReader.GetLogsByJobAndContainerName(ctx, job, containerName)
 	if err != nil {
-		return v1alpha1.ConfigAuditReport{}, fmt.Errorf("getting logs: %w", err)
+		return nil, fmt.Errorf("getting logs: %w", err)
 	}
 
 	result, err := s.plugin.ParseConfigAuditReportData(s.pluginContext, logsStream)
@@ -106,19 +104,18 @@ func (s *Scanner) Scan(ctx context.Context, workload kube.Object) (v1alpha1.Conf
 		_ = logsStream.Close()
 	}()
 
-	podSpecHash, ok := job.Labels[starboard.LabelPodSpecHash]
+	resourceSpecHash, ok := job.Labels[starboard.LabelResourceSpecHash]
 	if !ok {
-		return v1alpha1.ConfigAuditReport{}, fmt.Errorf("expected label %s not set", starboard.LabelPodSpecHash)
+		return nil, fmt.Errorf("expected label %s not set", starboard.LabelResourceSpecHash)
 	}
 	pluginConfigHash, ok := job.Labels[starboard.LabelPluginConfigHash]
 	if !ok {
-		return v1alpha1.ConfigAuditReport{}, fmt.Errorf("expected label %s not set", starboard.LabelPluginConfigHash)
+		return nil, fmt.Errorf("expected label %s not set", starboard.LabelPluginConfigHash)
 	}
 
 	return NewReportBuilder(s.scheme).
 		Controller(owner).
-		PodSpecHash(podSpecHash).
+		ResourceSpecHash(resourceSpecHash).
 		PluginConfigHash(pluginConfigHash).
-		Data(result).
-		Get()
+		Data(result), nil
 }
diff --git a/pkg/kube/object.go b/pkg/kube/object.go
index aca64d7..92537a9 100644
--- a/pkg/kube/object.go
+++ b/pkg/kube/object.go
@@ -11,12 +11,15 @@ import (
 	batchv1 "k8s.io/api/batch/v1"
 	batchv1beta1 "k8s.io/api/batch/v1beta1"
 	corev1 "k8s.io/api/core/v1"
+	rbacv1 "k8s.io/api/rbac/v1"
+	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
 	"k8s.io/apimachinery/pkg/api/meta"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/labels"
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/runtime/schema"
 	"k8s.io/apimachinery/pkg/types"
+	"k8s.io/apimachinery/pkg/util/validation"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
 )
@@ -48,6 +51,14 @@ const (
 	KindDaemonSet             Kind = "DaemonSet"
 	KindCronJob               Kind = "CronJob"
 	KindJob                   Kind = "Job"
+	KindService               Kind = "Service"
+	KindConfigMap             Kind = "ConfigMap"
+	KindRole                  Kind = "Role"
+	KindRoleBinding           Kind = "RoleBinding"
+
+	KindClusterRole              Kind = "ClusterRole"
+	KindClusterRoleBindings      Kind = "ClusterRoleBinding"
+	KindCustomResourceDefinition Kind = "CustomResourceDefinition"
 )
 
 // IsBuiltInWorkload returns true if the specified v1.OwnerReference
@@ -61,6 +72,67 @@ func IsBuiltInWorkload(controller *metav1.OwnerReference) bool {
 			controller.Kind == string(KindJob))
 }
 
+func IsClusterScopedKind(k string) bool {
+	switch k {
+	case string(KindClusterRole), string(KindClusterRoleBindings), string(KindCustomResourceDefinition):
+		return true
+	default:
+		return false
+	}
+}
+
+func PartialObjectToLabels(obj Object) map[string]string {
+	labels := map[string]string{
+		starboard.LabelResourceKind:      string(obj.Kind),
+		starboard.LabelResourceNamespace: obj.Namespace,
+	}
+	if len(validation.IsValidLabelValue(obj.Name)) == 0 {
+		labels[starboard.LabelResourceName] = obj.Name
+	} else {
+		labels[starboard.LabelResourceNameHash] = ComputeHash(obj.Name)
+	}
+	return labels
+}
+
+func ObjectToObjectMetadata(obj client.Object, meta *metav1.ObjectMeta) error {
+	if meta.Labels == nil {
+		meta.Labels = make(map[string]string)
+	}
+	meta.Labels[starboard.LabelResourceKind] = obj.GetObjectKind().GroupVersionKind().Kind
+	meta.Labels[starboard.LabelResourceNamespace] = obj.GetNamespace()
+	if len(validation.IsValidLabelValue(obj.GetName())) == 0 {
+		meta.Labels[starboard.LabelResourceName] = obj.GetName()
+	} else {
+		meta.Labels[starboard.LabelResourceNameHash] = ComputeHash(obj.GetName())
+		if meta.Annotations == nil {
+			meta.Annotations = make(map[string]string)
+		}
+		meta.Annotations[starboard.LabelResourceName] = obj.GetName()
+	}
+	return nil
+}
+
+func PartialObjectFromObjectMetadata(objectMeta metav1.ObjectMeta) (Object, error) {
+	if _, found := objectMeta.Labels[starboard.LabelResourceKind]; !found {
+		return Object{}, fmt.Errorf("required label does not exist: %s", starboard.LabelResourceKind)
+	}
+	var objname string
+	if _, found := objectMeta.Labels[starboard.LabelResourceName]; !found {
+		if _, found := objectMeta.Annotations[starboard.LabelResourceName]; found {
+			objname = objectMeta.Annotations[starboard.LabelResourceName]
+		} else {
+			return Object{}, fmt.Errorf("required label does not exist: %s", starboard.LabelResourceName)
+		}
+	} else {
+		objname = objectMeta.Labels[starboard.LabelResourceName]
+	}
+	return Object{
+		Kind:      Kind(objectMeta.Labels[starboard.LabelResourceKind]),
+		Name:      objname,
+		Namespace: objectMeta.Labels[starboard.LabelResourceNamespace],
+	}, nil
+}
+
 func ObjectFromLabelsSet(set labels.Set) (Object, error) {
 	if !set.Has(starboard.LabelResourceKind) {
 		return Object{}, fmt.Errorf("required label does not exist: %s", starboard.LabelResourceKind)
@@ -128,6 +200,37 @@ func GetPartialObjectFromKindAndNamespacedName(kind Kind, name types.NamespacedN
 	}
 }
 
+// ComputeSpecHash computes hash of the specified K8s client.Object.
+// The hash is used to indicate whether the client.Object should be
+// rescanned or not by adding it as the starboard.LabelResourceSpecHash
+// label to an instance of a security report.
+func ComputeSpecHash(obj client.Object) (string, error) {
+	switch t := obj.(type) {
+	case *corev1.Pod, *appsv1.Deployment, *appsv1.ReplicaSet, *corev1.ReplicationController, *appsv1.StatefulSet, *appsv1.DaemonSet, *batchv1beta1.CronJob, *batchv1.Job:
+		spec, err := GetPodSpec(obj)
+		if err != nil {
+			return "", err
+		}
+		return ComputeHash(spec), nil
+	case *corev1.Service:
+		return ComputeHash(obj), nil
+	case *corev1.ConfigMap:
+		return ComputeHash(obj), nil
+	case *rbacv1.Role:
+		return ComputeHash(obj), nil
+	case *rbacv1.RoleBinding:
+		return ComputeHash(obj), nil
+	case *rbacv1.ClusterRole:
+		return ComputeHash(obj), nil
+	case *rbacv1.ClusterRoleBinding:
+		return ComputeHash(obj), nil
+	case *apiextensionsv1.CustomResourceDefinition:
+		return ComputeHash(obj), nil
+	default:
+		return "", fmt.Errorf("computing spec hash of unsupported object: %T", t)
+	}
+}
+
 // GetPodSpec returns v1.PodSpec from the specified Kubernetes
 // client.Object. Returns error if the given client.Object
 // is not a Kubernetes workload.
@@ -177,6 +280,20 @@ func (o *ObjectResolver) GetObjectFromPartialObject(ctx context.Context, workloa
 		obj = &batchv1beta1.CronJob{}
 	case KindJob:
 		obj = &batchv1.Job{}
+	case KindService:
+		obj = &corev1.Service{}
+	case KindConfigMap:
+		obj = &corev1.ConfigMap{}
+	case KindRole:
+		obj = &rbacv1.Role{}
+	case KindRoleBinding:
+		obj = &rbacv1.RoleBinding{}
+	case KindClusterRole:
+		obj = &rbacv1.ClusterRole{}
+	case KindClusterRoleBindings:
+		obj = &rbacv1.ClusterRoleBinding{}
+	case KindCustomResourceDefinition:
+		obj = &apiextensionsv1.CustomResourceDefinition{}
 	default:
 		return nil, fmt.Errorf("unknown kind: %s", workload.Kind)
 	}
diff --git a/pkg/operator/controller/configauditreport.go b/pkg/operator/controller/configauditreport.go
index c510503..deb83b3 100644
--- a/pkg/operator/controller/configauditreport.go
+++ b/pkg/operator/controller/configauditreport.go
@@ -16,6 +16,8 @@ import (
 	batchv1 "k8s.io/api/batch/v1"
 	batchv1beta1 "k8s.io/api/batch/v1beta1"
 	corev1 "k8s.io/api/core/v1"
+	rbacv1 "k8s.io/api/rbac/v1"
+	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
 	"k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	ctrl "sigs.k8s.io/controller-runtime"
@@ -44,7 +46,7 @@ func (r *ConfigAuditReportReconciler) SetupWithManager(mgr ctrl.Manager) error {
 		return err
 	}
 
-	workloads := []struct {
+	resources := []struct {
 		kind       kube.Kind
 		forObject  client.Object
 		ownsObject client.Object
@@ -56,19 +58,46 @@ func (r *ConfigAuditReportReconciler) SetupWithManager(mgr ctrl.Manager) error {
 		{kind: kube.KindDaemonSet, forObject: &appsv1.DaemonSet{}, ownsObject: &v1alpha1.ConfigAuditReport{}},
 		{kind: kube.KindCronJob, forObject: &batchv1beta1.CronJob{}, ownsObject: &v1alpha1.ConfigAuditReport{}},
 		{kind: kube.KindJob, forObject: &batchv1.Job{}, ownsObject: &v1alpha1.ConfigAuditReport{}},
+		{kind: kube.KindService, forObject: &corev1.Service{}, ownsObject: &v1alpha1.ConfigAuditReport{}},
+		{kind: kube.KindConfigMap, forObject: &corev1.ConfigMap{}, ownsObject: &v1alpha1.ConfigAuditReport{}},
+		{kind: kube.KindRole, forObject: &rbacv1.Role{}, ownsObject: &v1alpha1.ConfigAuditReport{}},
+		{kind: kube.KindRoleBinding, forObject: &rbacv1.RoleBinding{}, ownsObject: &v1alpha1.ConfigAuditReport{}},
 	}
 
-	for _, workload := range workloads {
+	clusterResources := []struct {
+		kind       kube.Kind
+		forObject  client.Object
+		ownsObject client.Object
+	}{
+		{kind: kube.KindClusterRole, forObject: &rbacv1.ClusterRole{}, ownsObject: &v1alpha1.ClusterConfigAuditReport{}},
+		{kind: kube.KindClusterRoleBindings, forObject: &rbacv1.ClusterRoleBinding{}, ownsObject: &v1alpha1.ClusterConfigAuditReport{}},
+		{kind: kube.KindCustomResourceDefinition, forObject: &apiextensionsv1.CustomResourceDefinition{}, ownsObject: &v1alpha1.ClusterConfigAuditReport{}},
+	}
+
+	for _, resource := range resources {
 		err = ctrl.NewControllerManagedBy(mgr).
-			For(workload.forObject, builder.WithPredicates(
+			For(resource.forObject, builder.WithPredicates(
 				Not(ManagedByStarboardOperator),
 				Not(IsBeingTerminated),
 				installModePredicate,
 			)).
-			Owns(workload.ownsObject).
-			Complete(r.reconcileWorkload(workload.kind))
+			Owns(resource.ownsObject).
+			Complete(r.reconcileResource(resource.kind))
 		if err != nil {
-			return err
+			return fmt.Errorf("constructing controller for %s: %w", resource.kind, err)
+		}
+	}
+
+	for _, resource := range clusterResources {
+		err = ctrl.NewControllerManagedBy(mgr).
+			For(resource.forObject, builder.WithPredicates(
+				Not(ManagedByStarboardOperator),
+				Not(IsBeingTerminated),
+			)).
+			Owns(resource.ownsObject).
+			Complete(r.reconcileResource(resource.kind))
+		if err != nil {
+			return fmt.Errorf("constructing controller for %s: %w", resource.kind, err)
 		}
 	}
 
@@ -82,54 +111,54 @@ func (r *ConfigAuditReportReconciler) SetupWithManager(mgr ctrl.Manager) error {
 		Complete(r.reconcileJobs())
 }
 
-func (r *ConfigAuditReportReconciler) reconcileWorkload(workloadKind kube.Kind) reconcile.Func {
+func (r *ConfigAuditReportReconciler) reconcileResource(resourceKind kube.Kind) reconcile.Func {
 	return func(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
-		log := r.Logger.WithValues("kind", workloadKind, "name", req.NamespacedName)
+		log := r.Logger.WithValues("kind", resourceKind, "name", req.NamespacedName)
 
-		workloadPartial := kube.GetPartialObjectFromKindAndNamespacedName(workloadKind, req.NamespacedName)
+		resourcePartial := kube.GetPartialObjectFromKindAndNamespacedName(resourceKind, req.NamespacedName)
 
-		log.V(1).Info("Getting workload from cache")
-		workloadObj, err := r.GetObjectFromPartialObject(ctx, workloadPartial)
+		log.V(1).Info("Getting resource from cache")
+		resource, err := r.GetObjectFromPartialObject(ctx, resourcePartial)
 		if err != nil {
 			if errors.IsNotFound(err) {
-				log.V(1).Info("Ignoring cached workload that must have been deleted")
+				log.V(1).Info("Ignoring cached resource that must have been deleted")
 				return ctrl.Result{}, nil
 			}
-			return ctrl.Result{}, fmt.Errorf("getting %s from cache: %w", workloadKind, err)
+			return ctrl.Result{}, fmt.Errorf("getting %s from cache: %w", resourceKind, err)
 		}
 
-		// Skip processing if it's a Pod controlled by a built-in K8s workload.
-		if workloadKind == kube.KindPod {
-			controller := metav1.GetControllerOf(workloadObj)
+		// Skip processing if a resource is a Pod controlled by a built-in K8s workload.
+		if resourceKind == kube.KindPod {
+			controller := metav1.GetControllerOf(resource)
 			if kube.IsBuiltInWorkload(controller) {
 				log.V(1).Info("Ignoring managed pod", "controllerKind", controller.Kind, "controllerName", controller.Name)
 				return ctrl.Result{}, nil
 			}
 		}
 
-		// Skip processing if it's a Job controlled by CronJob.
-		if workloadKind == kube.KindJob {
-			controller := metav1.GetControllerOf(workloadObj)
+		// Skip processing if a resource is a Job controlled by CronJob.
+		if resourceKind == kube.KindJob {
+			controller := metav1.GetControllerOf(resource)
 			if controller != nil && controller.Kind == string(kube.KindCronJob) {
 				log.V(1).Info("Ignoring managed job", "controllerKind", controller.Kind, "controllerName", controller.Name)
 				return ctrl.Result{}, nil
 			}
 		}
 
-		podSpec, err := kube.GetPodSpec(workloadObj)
+		resourceSpecHash, err := kube.ComputeSpecHash(resource)
 		if err != nil {
-			return ctrl.Result{}, err
+			return ctrl.Result{}, fmt.Errorf("computing spec hash: %w", err)
 		}
-		podSpecHash := kube.ComputeHash(podSpec)
+
 		pluginConfigHash, err := r.Plugin.GetConfigHash(r.PluginContext)
 		if err != nil {
-			return ctrl.Result{}, err
+			return ctrl.Result{}, fmt.Errorf("computing plugin config hash: %w", err)
 		}
 
-		log = log.WithValues("podSpecHash", podSpecHash, "pluginConfigHash", pluginConfigHash)
+		log = log.WithValues("resourceSpecHash", resourceSpecHash, "pluginConfigHash", pluginConfigHash)
 
 		log.V(1).Info("Checking whether configuration audit report exists")
-		hasReport, err := r.hasReport(ctx, workloadPartial, podSpecHash, pluginConfigHash)
+		hasReport, err := r.hasReport(ctx, resourcePartial, resourceSpecHash, pluginConfigHash)
 		if err != nil {
 			return ctrl.Result{}, err
 		}
@@ -140,7 +169,7 @@ func (r *ConfigAuditReportReconciler) reconcileWorkload(workloadKind kube.Kind)
 		}
 
 		log.V(1).Info("Checking whether configuration audit has been scheduled")
-		_, job, err := r.hasActiveScanJob(ctx, workloadObj, podSpecHash)
+		_, job, err := r.hasActiveScanJob(ctx, resource, resourceSpecHash)
 		if err != nil {
 			return ctrl.Result{}, err
 		}
@@ -163,30 +192,30 @@ func (r *ConfigAuditReportReconciler) reconcileWorkload(workloadKind kube.Kind)
 
 		scanJobTolerations, err := r.ConfigData.GetScanJobTolerations()
 		if err != nil {
-			return ctrl.Result{}, err
+			return ctrl.Result{}, fmt.Errorf("getting scan job tolerations: %w", err)
 		}
 
 		scanJobAnnotations, err := r.ConfigData.GetScanJobAnnotations()
 		if err != nil {
-			return ctrl.Result{}, err
+			return ctrl.Result{}, fmt.Errorf("getting scan job annotations: %w", err)
 		}
 
 		job, secrets, err := configauditreport.NewScanJob().
 			WithPlugin(r.Plugin).
 			WithPluginContext(r.PluginContext).
 			WithTimeout(r.Config.ScanJobTimeout).
-			WithObject(workloadObj).
+			WithObject(resource).
 			WithTolerations(scanJobTolerations).
-			WithScanJobAnnotations(scanJobAnnotations).
+			WithAnnotations(scanJobAnnotations).
 			Get()
 		if err != nil {
-			return ctrl.Result{}, err
+			return ctrl.Result{}, fmt.Errorf("constructing scan job: %w", err)
 		}
 
 		for _, secret := range secrets {
 			err := r.Client.Create(ctx, secret)
 			if err != nil {
-				if !errors.IsAlreadyExists(err) {
+				if errors.IsAlreadyExists(err) {
 					log.V(1).Info("Secret already exists", "secretName", secret.Name)
 					return ctrl.Result{}, nil
 				}
@@ -221,13 +250,28 @@ func (r *ConfigAuditReportReconciler) reconcileWorkload(workloadKind kube.Kind)
 }
 
 func (r *ConfigAuditReportReconciler) hasReport(ctx context.Context, owner kube.Object, podSpecHash string, pluginConfigHash string) (bool, error) {
+	if kube.IsClusterScopedKind(string(owner.Kind)) {
+		return r.hasClusterReport(ctx, owner, podSpecHash, pluginConfigHash)
+	}
 	// TODO FindByOwner should accept optional label selector to further narrow down search results
 	report, err := r.ReadWriter.FindReportByOwner(ctx, owner)
 	if err != nil {
 		return false, err
 	}
 	if report != nil {
-		return report.Labels[starboard.LabelPodSpecHash] == podSpecHash &&
+		return report.Labels[starboard.LabelResourceSpecHash] == podSpecHash &&
+			report.Labels[starboard.LabelPluginConfigHash] == pluginConfigHash, nil
+	}
+	return false, nil
+}
+
+func (r *ConfigAuditReportReconciler) hasClusterReport(ctx context.Context, owner kube.Object, podSpecHash string, pluginConfigHash string) (bool, error) {
+	report, err := r.ReadWriter.FindClusterReportByOwner(ctx, owner)
+	if err != nil {
+		return false, err
+	}
+	if report != nil {
+		return report.Labels[starboard.LabelResourceSpecHash] == podSpecHash &&
 			report.Labels[starboard.LabelPluginConfigHash] == pluginConfigHash, nil
 	}
 	return false, nil
@@ -243,7 +287,7 @@ func (r *ConfigAuditReportReconciler) hasActiveScanJob(ctx context.Context, obj
 		}
 		return false, nil, fmt.Errorf("getting pod from cache: %w", err)
 	}
-	if job.Labels[starboard.LabelPodSpecHash] == hash {
+	if job.Labels[starboard.LabelResourceSpecHash] == hash {
 		return true, job, nil
 	}
 	return false, nil, nil
@@ -286,7 +330,7 @@ func (r *ConfigAuditReportReconciler) reconcileJobs() reconcile.Func {
 func (r *ConfigAuditReportReconciler) processCompleteScanJob(ctx context.Context, job *batchv1.Job) error {
 	log := r.Logger.WithValues("job", fmt.Sprintf("%s/%s", job.Namespace, job.Name))
 
-	owner, err := kube.ObjectFromLabelsSet(job.Labels)
+	owner, err := kube.PartialObjectFromObjectMetadata(job.ObjectMeta)
 	if err != nil {
 		return fmt.Errorf("getting workload from scan job labels set: %w", err)
 	}
@@ -302,16 +346,16 @@ func (r *ConfigAuditReportReconciler) processCompleteScanJob(ctx context.Context
 		return fmt.Errorf("getting object from partial object: %w", err)
 	}
 
-	podSpecHash, ok := job.Labels[starboard.LabelPodSpecHash]
+	resourceSpecHash, ok := job.Labels[starboard.LabelResourceSpecHash]
 	if !ok {
-		return fmt.Errorf("expected label %s not set", starboard.LabelPodSpecHash)
+		return fmt.Errorf("expected label %s not set", starboard.LabelResourceSpecHash)
 	}
 	pluginConfigHash, ok := job.Labels[starboard.LabelPluginConfigHash]
 	if !ok {
 		return fmt.Errorf("expected label %s not set", starboard.LabelPluginConfigHash)
 	}
 
-	hasReport, err := r.hasReport(ctx, owner, podSpecHash, pluginConfigHash)
+	hasReport, err := r.hasReport(ctx, owner, resourceSpecHash, pluginConfigHash)
 	if err != nil {
 		return err
 	}
@@ -332,20 +376,16 @@ func (r *ConfigAuditReportReconciler) processCompleteScanJob(ctx context.Context
 		_ = logsStream.Close()
 	}()
 
-	report, err := configauditreport.NewReportBuilder(r.Client.Scheme()).
+	reportBuilder := configauditreport.NewReportBuilder(r.Client.Scheme()).
 		Controller(ownerObj).
-		PodSpecHash(podSpecHash).
+		ResourceSpecHash(resourceSpecHash).
 		PluginConfigHash(pluginConfigHash).
-		Data(result).
-		Get()
+		Data(result)
+	err = reportBuilder.Write(ctx, r.ReadWriter)
 	if err != nil {
 		return err
 	}
 
-	err = r.ReadWriter.WriteReport(ctx, report)
-	if err != nil {
-		return err
-	}
 	log.V(1).Info("Deleting complete scan job", "owner", owner)
 	return r.deleteJob(ctx, job)
 }
diff --git a/pkg/operator/controller/plugins_config.go b/pkg/operator/controller/plugins_config.go
index 88e7fa9..6203b89 100644
--- a/pkg/operator/controller/plugins_config.go
+++ b/pkg/operator/controller/plugins_config.go
@@ -52,12 +52,12 @@ func (r *PluginsConfigReconciler) Reconcile(ctx context.Context, req ctrl.Reques
 
 	configHash, err := r.Plugin.GetConfigHash(r.PluginContext)
 	if err != nil {
-		return ctrl.Result{}, err
+		return ctrl.Result{}, fmt.Errorf("getting config hash: %w", err)
 	}
 
 	labelSelector, err := labels.Parse(fmt.Sprintf("%s != %s", starboard.LabelPluginConfigHash, configHash))
 	if err != nil {
-		return ctrl.Result{}, err
+		return ctrl.Result{}, fmt.Errorf("parsing label selector: %w", err)
 	}
 
 	var reportList v1alpha1.ConfigAuditReportList
@@ -70,20 +70,52 @@ func (r *PluginsConfigReconciler) Reconcile(ctx context.Context, req ctrl.Reques
 
 	log.V(1).Info("Listing ConfigAuditReports",
 		"reportsCount", len(reportList.Items),
-		"batchDeleteLimit", r.Config.BatchDeleteLimit)
+		"batchDeleteLimit", r.Config.BatchDeleteLimit,
+		"labelSelector", labelSelector.String())
 
 	for i := 0; i < ext.MinInt(r.Config.BatchDeleteLimit, len(reportList.Items)); i++ {
 		report := reportList.Items[i]
 		log.V(1).Info("Deleting ConfigAuditReport", "report", report.Namespace+"/"+report.Name)
 		err := r.Client.Delete(ctx, &report)
 		if err != nil {
-			return ctrl.Result{}, err
+			if !errors.IsNotFound(err) {
+				return ctrl.Result{}, fmt.Errorf("deleting ConfigAuditReport: %w", err)
+			}
 		}
 	}
 	if len(reportList.Items)-r.Config.BatchDeleteLimit > 0 {
-		// TODO Calculate RequeueAfter based on average scan duration?
+		log.V(1).Info("Requeuing reconciliation key", "requeueAfter", r.Config.BatchDeleteDelay)
 		return ctrl.Result{RequeueAfter: r.Config.BatchDeleteDelay}, nil
 	}
 
+	var clusterReportList v1alpha1.ClusterConfigAuditReportList
+	err = r.Client.List(ctx, &clusterReportList,
+		client.Limit(r.Config.BatchDeleteLimit+1),
+		client.MatchingLabelsSelector{Selector: labelSelector})
+	if err != nil {
+		return ctrl.Result{}, fmt.Errorf("listing reports: %w", err)
+	}
+
+	log.V(1).Info("Listing ClusterConfigAuditReports",
+		"reportsCount", len(clusterReportList.Items),
+		"batchDeleteLimit", r.Config.BatchDeleteLimit,
+		"labelSelector", labelSelector)
+
+	for i := 0; i < ext.MinInt(r.Config.BatchDeleteLimit, len(clusterReportList.Items)); i++ {
+		report := clusterReportList.Items[i]
+		log.V(1).Info("Deleting ClusterConfigAuditReport", "report", report.Name)
+		err := r.Client.Delete(ctx, &report)
+		if err != nil {
+			if !errors.IsNotFound(err) {
+				return ctrl.Result{}, fmt.Errorf("deleting ClusterConfigAuditReport: %w", err)
+			}
+		}
+	}
+	if len(clusterReportList.Items)-r.Config.BatchDeleteLimit > 0 {
+		log.V(1).Info("Requeuing reconciliation key", "requeueAfter", r.Config.BatchDeleteDelay)
+		return ctrl.Result{RequeueAfter: r.Config.BatchDeleteDelay}, nil
+	}
+
+	log.V(1).Info("Finished reconciling key", "labelSelector", labelSelector)
 	return ctrl.Result{}, nil
 }
diff --git a/pkg/operator/controller/vulnerabilityreport.go b/pkg/operator/controller/vulnerabilityreport.go
index 9d601ac..03e3bd4 100644
--- a/pkg/operator/controller/vulnerabilityreport.go
+++ b/pkg/operator/controller/vulnerabilityreport.go
@@ -176,7 +176,7 @@ func (r *VulnerabilityReportReconciler) hasVulnerabilityReports(ctx context.Cont
 	actual := map[string]bool{}
 	for _, report := range list {
 		if containerName, ok := report.Labels[starboard.LabelContainerName]; ok {
-			if hash == report.Labels[starboard.LabelPodSpecHash] {
+			if hash == report.Labels[starboard.LabelResourceSpecHash] {
 				actual[containerName] = true
 			}
 		}
@@ -200,7 +200,7 @@ func (r *VulnerabilityReportReconciler) hasActiveScanJob(ctx context.Context, ow
 		}
 		return false, nil, fmt.Errorf("getting job from cache: %w", err)
 	}
-	if job.Labels[starboard.LabelPodSpecHash] == hash {
+	if job.Labels[starboard.LabelResourceSpecHash] == hash {
 		return true, job, nil
 	}
 	return false, nil, nil
@@ -247,7 +247,7 @@ func (r *VulnerabilityReportReconciler) submitScanJob(ctx context.Context, spec
 		starboard.LabelResourceKind:               owner.GetObjectKind().GroupVersionKind().Kind,
 		starboard.LabelResourceName:               owner.GetName(),
 		starboard.LabelResourceNamespace:          owner.GetNamespace(),
-		starboard.LabelPodSpecHash:                hash,
+		starboard.LabelResourceSpecHash:           hash,
 		starboard.LabelK8SAppManagedBy:            starboard.AppStarboard,
 		starboard.LabelVulnerabilityReportScanner: "true",
 	}
@@ -357,9 +357,9 @@ func (r *VulnerabilityReportReconciler) processCompleteScanJob(ctx context.Conte
 		return fmt.Errorf("getting container images: %w", err)
 	}
 
-	hash, ok := job.Labels[starboard.LabelPodSpecHash]
+	hash, ok := job.Labels[starboard.LabelResourceSpecHash]
 	if !ok {
-		return fmt.Errorf("expected label %s not set", starboard.LabelPodSpecHash)
+		return fmt.Errorf("expected label %s not set", starboard.LabelResourceSpecHash)
 	}
 
 	log.V(1).Info("Resolving workload properties",
diff --git a/pkg/starboard/config.go b/pkg/starboard/config.go
index 3b22239..fedb218 100644
--- a/pkg/starboard/config.go
+++ b/pkg/starboard/config.go
@@ -13,6 +13,8 @@ import (
 	batchv1beta1 "k8s.io/api/batch/v1beta1"
 	coordinationv1 "k8s.io/api/coordination/v1"
 	corev1 "k8s.io/api/core/v1"
+	rbacv1 "k8s.io/api/rbac/v1"
+	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/labels"
@@ -26,8 +28,10 @@ func NewScheme() *runtime.Scheme {
 	_ = appsv1.AddToScheme(scheme)
 	_ = batchv1.AddToScheme(scheme)
 	_ = batchv1beta1.AddToScheme(scheme)
+	_ = rbacv1.AddToScheme(scheme)
 	_ = v1alpha1.AddToScheme(scheme)
 	_ = coordinationv1.AddToScheme(scheme)
+	_ = apiextensionsv1.AddToScheme(scheme)
 	return scheme
 }
 
diff --git a/pkg/starboard/constants.go b/pkg/starboard/constants.go
index 5dfdc18..5b03236 100644
--- a/pkg/starboard/constants.go
+++ b/pkg/starboard/constants.go
@@ -21,9 +21,10 @@ const (
 const (
 	LabelResourceKind      = "starboard.resource.kind"
 	LabelResourceName      = "starboard.resource.name"
+	LabelResourceNameHash  = "starboard.resource.name-hash"
 	LabelResourceNamespace = "starboard.resource.namespace"
 	LabelContainerName     = "starboard.container.name"
-	LabelPodSpecHash       = "pod-spec-hash"
+	LabelResourceSpecHash  = "resource-spec-hash"
 	LabelPluginConfigHash  = "plugin-config-hash"
 
 	LabelConfigAuditReportScanner   = "configAuditReport.scanner"
diff --git a/pkg/vulnerabilityreport/builder.go b/pkg/vulnerabilityreport/builder.go
index 3c932d4..829c8a5 100644
--- a/pkg/vulnerabilityreport/builder.go
+++ b/pkg/vulnerabilityreport/builder.go
@@ -79,7 +79,7 @@ func (b *ReportBuilder) Get() (v1alpha1.VulnerabilityReport, error) {
 	}
 
 	if b.hash != "" {
-		labels[starboard.LabelPodSpecHash] = b.hash
+		labels[starboard.LabelResourceSpecHash] = b.hash
 	}
 
 	reportName, err := b.reportName()
diff --git a/pkg/vulnerabilityreport/scanner.go b/pkg/vulnerabilityreport/scanner.go
index 9c8a729..081d3ed 100644
--- a/pkg/vulnerabilityreport/scanner.go
+++ b/pkg/vulnerabilityreport/scanner.go
@@ -148,7 +148,7 @@ func (s *Scanner) prepareScanJob(workload client.Object, spec corev1.PodSpec, cr
 		starboard.LabelResourceKind:               workload.GetObjectKind().GroupVersionKind().Kind,
 		starboard.LabelResourceName:               workload.GetName(),
 		starboard.LabelResourceNamespace:          workload.GetNamespace(),
-		starboard.LabelPodSpecHash:                podSpecHash,
+		starboard.LabelResourceSpecHash:           podSpecHash,
 		starboard.LabelK8SAppManagedBy:            starboard.AppStarboard,
 		starboard.LabelVulnerabilityReportScanner: "true",
 	}
@@ -190,9 +190,9 @@ func (s *Scanner) getVulnerabilityReportsByScanJob(ctx context.Context, job *bat
 		return nil, fmt.Errorf("getting container images: %w", err)
 	}
 
-	podSpecHash, ok := job.Labels[starboard.LabelPodSpecHash]
+	podSpecHash, ok := job.Labels[starboard.LabelResourceSpecHash]
 	if !ok {
-		return nil, fmt.Errorf("expected label %s not set", starboard.LabelPodSpecHash)
+		return nil, fmt.Errorf("expected label %s not set", starboard.LabelResourceSpecHash)
 	}
 
 	for containerName, containerImage := range containerImages {


Test patch:
diff --git a/itest/starboard-operator/behavior/behavior.go b/itest/starboard-operator/behavior/behavior.go
index a0b485e..e7c39f0 100644
--- a/itest/starboard-operator/behavior/behavior.go
+++ b/itest/starboard-operator/behavior/behavior.go
@@ -13,6 +13,7 @@ import (
 	batchv1beta1 "k8s.io/api/batch/v1beta1"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/util/intstr"
 	"k8s.io/apimachinery/pkg/util/rand"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 )
@@ -415,6 +416,45 @@ func ConfigurationCheckerBehavior(inputs *Inputs) func() {
 		// TODO Add scenario for StatefulSet
 
 		// TODO Add scenario for DaemonSet
+
+		Context("When Service is created", func() {
+			var ctx context.Context
+			var svc *corev1.Service
+
+			BeforeEach(func() {
+				ctx = context.Background()
+				svc = &corev1.Service{
+					ObjectMeta: metav1.ObjectMeta{
+						Namespace: inputs.PrimaryNamespace,
+						Name:      "nginx-" + rand.String(5),
+					},
+					Spec: corev1.ServiceSpec{
+						Selector: map[string]string{
+							"app": "nginx",
+						},
+						Ports: []corev1.ServicePort{
+							{
+								Port:       80,
+								TargetPort: intstr.FromInt(80),
+								Protocol:   corev1.ProtocolTCP,
+							},
+						},
+					},
+				}
+				err := inputs.Create(ctx, svc)
+				Expect(err).ToNot(HaveOccurred())
+			})
+
+			It("Should create ConfigAuditReport", func() {
+				Eventually(inputs.HasConfigAuditReportOwnedBy(svc), inputs.AssertTimeout).Should(BeTrue())
+			})
+
+			AfterEach(func() {
+				err := inputs.Delete(ctx, svc)
+				Expect(err).ToNot(HaveOccurred())
+			})
+
+		})
 	}
 }
 
diff --git a/pkg/configauditreport/builder_test.go b/pkg/configauditreport/builder_test.go
index ec96a87..f06105a 100644
--- a/pkg/configauditreport/builder_test.go
+++ b/pkg/configauditreport/builder_test.go
@@ -13,6 +13,7 @@ import (
 	appsv1 "k8s.io/api/apps/v1"
 	batchv1 "k8s.io/api/batch/v1"
 	corev1 "k8s.io/api/core/v1"
+	rbacv1 "k8s.io/api/rbac/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/client-go/kubernetes/scheme"
 	"k8s.io/utils/pointer"
@@ -20,44 +21,97 @@ import (
 )
 
 func TestReportBuilder(t *testing.T) {
-	g := NewGomegaWithT(t)
 
-	report, err := configauditreport.NewReportBuilder(scheme.Scheme).
-		Controller(&appsv1.ReplicaSet{
+	t.Run("Should build report for namespaced resource", func(t *testing.T) {
+		g := NewGomegaWithT(t)
+
+		report, err := configauditreport.NewReportBuilder(scheme.Scheme).
+			Controller(&appsv1.ReplicaSet{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       "ReplicaSet",
+					APIVersion: "apps/v1",
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "some-owner",
+					Namespace: "qa",
+				},
+			}).
+			ResourceSpecHash("xyz").
+			PluginConfigHash("nop").
+			Data(v1alpha1.ConfigAuditReportData{}).
+			GetReport()
+
+		g.Expect(err).ToNot(HaveOccurred())
+		g.Expect(report).To(Equal(v1alpha1.ConfigAuditReport{
 			ObjectMeta: metav1.ObjectMeta{
-				Name:      "some-owner",
+				Name:      "replicaset-some-owner",
 				Namespace: "qa",
-			},
-		}).
-		PodSpecHash("xyz").
-		PluginConfigHash("nop").
-		Data(v1alpha1.ConfigAuditReportData{}).
-		Get()
-
-	g.Expect(err).ToNot(HaveOccurred())
-	g.Expect(report).To(Equal(v1alpha1.ConfigAuditReport{
-		ObjectMeta: metav1.ObjectMeta{
-			Name:      "replicaset-some-owner",
-			Namespace: "qa",
-			OwnerReferences: []metav1.OwnerReference{
-				{
-					APIVersion:         "apps/v1",
-					Kind:               "ReplicaSet",
-					Name:               "some-owner",
-					Controller:         pointer.BoolPtr(true),
-					BlockOwnerDeletion: pointer.BoolPtr(false),
+				OwnerReferences: []metav1.OwnerReference{
+					{
+						APIVersion:         "apps/v1",
+						Kind:               "ReplicaSet",
+						Name:               "some-owner",
+						Controller:         pointer.BoolPtr(true),
+						BlockOwnerDeletion: pointer.BoolPtr(false),
+					},
+				},
+				Labels: map[string]string{
+					starboard.LabelResourceKind:      "ReplicaSet",
+					starboard.LabelResourceName:      "some-owner",
+					starboard.LabelResourceNamespace: "qa",
+					starboard.LabelResourceSpecHash:  "xyz",
+					starboard.LabelPluginConfigHash:  "nop",
 				},
 			},
-			Labels: map[string]string{
-				starboard.LabelResourceKind:      "ReplicaSet",
-				starboard.LabelResourceName:      "some-owner",
-				starboard.LabelResourceNamespace: "qa",
-				starboard.LabelPodSpecHash:       "xyz",
-				starboard.LabelPluginConfigHash:  "nop",
+			Report: v1alpha1.ConfigAuditReportData{},
+		}))
+	})
+
+	t.Run("Should build report for cluster scoped resource", func(t *testing.T) {
+		g := NewGomegaWithT(t)
+
+		report, err := configauditreport.NewReportBuilder(scheme.Scheme).
+			Controller(&rbacv1.ClusterRole{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       "ClusterRole",
+					APIVersion: "rbac.authorization.k8s.io/v1",
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "system:controller:node-controller",
+				},
+			}).
+			ResourceSpecHash("xyz").
+			PluginConfigHash("nop").
+			Data(v1alpha1.ConfigAuditReportData{}).
+			GetClusterReport()
+
+		g.Expect(err).ToNot(HaveOccurred())
+		g.Expect(report).To(Equal(v1alpha1.ClusterConfigAuditReport{
+			ObjectMeta: metav1.ObjectMeta{
+				Name: "clusterrole-6f69bb5b79",
+				OwnerReferences: []metav1.OwnerReference{
+					{
+						APIVersion:         "rbac.authorization.k8s.io/v1",
+						Kind:               "ClusterRole",
+						Name:               "system:controller:node-controller",
+						Controller:         pointer.BoolPtr(true),
+						BlockOwnerDeletion: pointer.BoolPtr(false),
+					},
+				},
+				Labels: map[string]string{
+					starboard.LabelResourceKind:      "ClusterRole",
+					starboard.LabelResourceNameHash:  "6f69bb5b79",
+					starboard.LabelResourceNamespace: "",
+					starboard.LabelResourceSpecHash:  "xyz",
+					starboard.LabelPluginConfigHash:  "nop",
+				},
+				Annotations: map[string]string{
+					starboard.LabelResourceName: "system:controller:node-controller",
+				},
 			},
-		},
-		Report: v1alpha1.ConfigAuditReportData{},
-	}))
+			Report: v1alpha1.ConfigAuditReportData{},
+		}))
+	})
 }
 
 type testPlugin struct {
@@ -85,62 +139,131 @@ func (p *testPlugin) GetConfigHash(_ starboard.PluginContext) (string, error) {
 }
 
 func TestScanJobBuilder(t *testing.T) {
-	g := NewGomegaWithT(t)
-	job, _, err := configauditreport.NewScanJob().
-		WithPlugin(&testPlugin{
-			configHash: "hash-test",
-		}).
-		WithPluginContext(starboard.NewPluginContext().
-			WithName("plugin-test").
-			WithNamespace("starboard-ns").
-			WithServiceAccountName("starboard-sa").
-			Get()).
-		WithTimeout(3 * time.Second).
-		WithObject(&appsv1.ReplicaSet{
-			TypeMeta: metav1.TypeMeta{
-				Kind:       "ReplicaSet",
-				APIVersion: "apps/v1",
-			},
+
+	t.Run("Should build scan job for resource with simple name", func(t *testing.T) {
+		g := NewGomegaWithT(t)
+		job, _, err := configauditreport.NewScanJob().
+			WithPlugin(&testPlugin{
+				configHash: "hash-test",
+			}).
+			WithPluginContext(starboard.NewPluginContext().
+				WithName("plugin-test").
+				WithNamespace("starboard-ns").
+				WithServiceAccountName("starboard-sa").
+				Get()).
+			WithTimeout(3 * time.Second).
+			WithObject(&appsv1.ReplicaSet{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       "ReplicaSet",
+					APIVersion: "apps/v1",
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "nginx-6799fc88d8",
+					Namespace: "prod-ns",
+				},
+			}).
+			Get()
+		g.Expect(err).ToNot(HaveOccurred())
+		g.Expect(job).NotTo(BeNil())
+		g.Expect(job).To(Equal(&batchv1.Job{
 			ObjectMeta: metav1.ObjectMeta{
-				Name:      "nginx-6799fc88d8",
-				Namespace: "prod-ns",
+				Name:      "scan-configauditreport-fcc9884cb",
+				Namespace: "starboard-ns",
+				Labels: map[string]string{
+					starboard.LabelResourceSpecHash:         "58b8989656",
+					starboard.LabelPluginConfigHash:         "hash-test",
+					starboard.LabelConfigAuditReportScanner: "plugin-test",
+					starboard.LabelK8SAppManagedBy:          "starboard",
+					starboard.LabelResourceKind:             "ReplicaSet",
+					starboard.LabelResourceName:             "nginx-6799fc88d8",
+					starboard.LabelResourceNamespace:        "prod-ns",
+				},
 			},
-		}).
-		Get()
-	g.Expect(err).ToNot(HaveOccurred())
-	g.Expect(job).NotTo(BeNil())
-	g.Expect(job).To(Equal(&batchv1.Job{
-		ObjectMeta: metav1.ObjectMeta{
-			Name:      "scan-configauditreport-fcc9884cb",
-			Namespace: "starboard-ns",
-			Labels: map[string]string{
-				starboard.LabelPodSpecHash:              "58b8989656",
-				starboard.LabelPluginConfigHash:         "hash-test",
-				starboard.LabelConfigAuditReportScanner: "plugin-test",
-				starboard.LabelK8SAppManagedBy:          "starboard",
-				starboard.LabelResourceKind:             "ReplicaSet",
-				starboard.LabelResourceName:             "nginx-6799fc88d8",
-				starboard.LabelResourceNamespace:        "prod-ns",
+			Spec: batchv1.JobSpec{
+				BackoffLimit:          pointer.Int32Ptr(0),
+				Completions:           pointer.Int32Ptr(1),
+				ActiveDeadlineSeconds: pointer.Int64Ptr(3),
+				Template: corev1.PodTemplateSpec{
+					ObjectMeta: metav1.ObjectMeta{
+						Labels: map[string]string{
+							starboard.LabelResourceSpecHash:         "58b8989656",
+							starboard.LabelPluginConfigHash:         "hash-test",
+							starboard.LabelConfigAuditReportScanner: "plugin-test",
+							starboard.LabelK8SAppManagedBy:          "starboard",
+							starboard.LabelResourceKind:             "ReplicaSet",
+							starboard.LabelResourceName:             "nginx-6799fc88d8",
+							starboard.LabelResourceNamespace:        "prod-ns",
+						},
+					},
+					Spec: corev1.PodSpec{},
+				},
 			},
-		},
-		Spec: batchv1.JobSpec{
-			BackoffLimit:          pointer.Int32Ptr(0),
-			Completions:           pointer.Int32Ptr(1),
-			ActiveDeadlineSeconds: pointer.Int64Ptr(3),
-			Template: corev1.PodTemplateSpec{
+		}))
+	})
+
+	t.Run("Should build scan job for resource with special name", func(t *testing.T) {
+		g := NewGomegaWithT(t)
+		job, _, err := configauditreport.NewScanJob().
+			WithPlugin(&testPlugin{
+				configHash: "hash-test",
+			}).
+			WithPluginContext(starboard.NewPluginContext().
+				WithName("plugin-test").
+				WithNamespace("starboard-ns").
+				WithServiceAccountName("starboard-sa").
+				Get()).
+			WithTimeout(3 * time.Second).
+			WithObject(&rbacv1.ClusterRole{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       "ClusterRole",
+					APIVersion: "rbac.authorization.k8s.io/v1",
+				},
 				ObjectMeta: metav1.ObjectMeta{
-					Labels: map[string]string{
-						starboard.LabelPodSpecHash:              "58b8989656",
-						starboard.LabelPluginConfigHash:         "hash-test",
-						starboard.LabelConfigAuditReportScanner: "plugin-test",
-						starboard.LabelK8SAppManagedBy:          "starboard",
-						starboard.LabelResourceKind:             "ReplicaSet",
-						starboard.LabelResourceName:             "nginx-6799fc88d8",
-						starboard.LabelResourceNamespace:        "prod-ns",
+					Name: "system:controller:node-controller",
+				},
+			}).
+			Get()
+		g.Expect(err).ToNot(HaveOccurred())
+		g.Expect(job).NotTo(BeNil())
+		g.Expect(job).To(Equal(&batchv1.Job{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "scan-configauditreport-58ddcc9dcb",
+				Namespace: "starboard-ns",
+				Labels: map[string]string{
+					starboard.LabelResourceSpecHash:         "7c48697ccf",
+					starboard.LabelPluginConfigHash:         "hash-test",
+					starboard.LabelConfigAuditReportScanner: "plugin-test",
+					starboard.LabelK8SAppManagedBy:          "starboard",
+					starboard.LabelResourceKind:             "ClusterRole",
+					starboard.LabelResourceNameHash:         "6f69bb5b79",
+					starboard.LabelResourceNamespace:        "",
+				},
+				Annotations: map[string]string{
+					starboard.LabelResourceName: "system:controller:node-controller",
+				},
+			},
+			Spec: batchv1.JobSpec{
+				BackoffLimit:          pointer.Int32Ptr(0),
+				Completions:           pointer.Int32Ptr(1),
+				ActiveDeadlineSeconds: pointer.Int64Ptr(3),
+				Template: corev1.PodTemplateSpec{
+					ObjectMeta: metav1.ObjectMeta{
+						Labels: map[string]string{
+							starboard.LabelResourceSpecHash:         "7c48697ccf",
+							starboard.LabelPluginConfigHash:         "hash-test",
+							starboard.LabelConfigAuditReportScanner: "plugin-test",
+							starboard.LabelK8SAppManagedBy:          "starboard",
+							starboard.LabelResourceKind:             "ClusterRole",
+							starboard.LabelResourceNameHash:         "6f69bb5b79",
+							starboard.LabelResourceNamespace:        "",
+						},
+						Annotations: map[string]string{
+							starboard.LabelResourceName: "system:controller:node-controller",
+						},
 					},
+					Spec: corev1.PodSpec{},
 				},
-				Spec: corev1.PodSpec{},
 			},
-		},
-	}))
+		}))
+	})
 }
diff --git a/pkg/configauditreport/io_test.go b/pkg/configauditreport/io_test.go
index 2a5f44b..0e23b54 100644
--- a/pkg/configauditreport/io_test.go
+++ b/pkg/configauditreport/io_test.go
@@ -79,7 +79,7 @@ func TestReadWriter(t *testing.T) {
 					starboard.LabelResourceKind:      "Deployment",
 					starboard.LabelResourceName:      "app",
 					starboard.LabelResourceNamespace: "qa",
-					starboard.LabelPodSpecHash:       "h1",
+					starboard.LabelResourceSpecHash:  "h1",
 				},
 			},
 			Report: v1alpha1.ConfigAuditReportData{
@@ -98,7 +98,7 @@ func TestReadWriter(t *testing.T) {
 					starboard.LabelResourceKind:      "Deployment",
 					starboard.LabelResourceName:      "app",
 					starboard.LabelResourceNamespace: "qa",
-					starboard.LabelPodSpecHash:       "h2",
+					starboard.LabelResourceSpecHash:  "h2",
 				},
 			},
 			Report: v1alpha1.ConfigAuditReportData{
@@ -126,7 +126,7 @@ func TestReadWriter(t *testing.T) {
 					starboard.LabelResourceKind:      "Deployment",
 					starboard.LabelResourceName:      "app",
 					starboard.LabelResourceNamespace: "qa",
-					starboard.LabelPodSpecHash:       "h2",
+					starboard.LabelResourceSpecHash:  "h2",
 				},
 				ResourceVersion: "1",
 			},
@@ -139,8 +139,41 @@ func TestReadWriter(t *testing.T) {
 		}, found)
 	})
 
-	t.Run("Should find ConfigAuditReport", func(t *testing.T) {
-		client := fake.NewClientBuilder().WithScheme(kubernetesScheme).WithObjects(&v1alpha1.ConfigAuditReport{
+	t.Run("Should find ConfigAuditReport by owner", func(t *testing.T) {
+		client := fake.NewClientBuilder().WithScheme(kubernetesScheme).WithObjects(
+			&v1alpha1.ConfigAuditReport{
+				ObjectMeta: metav1.ObjectMeta{
+					Namespace:       "my-namespace",
+					Name:            "deployment-my-deploy-my",
+					ResourceVersion: "0",
+					Labels: map[string]string{
+						starboard.LabelResourceKind:      string(kube.KindDeployment),
+						starboard.LabelResourceName:      "my-deploy",
+						starboard.LabelResourceNamespace: "my-namespace",
+					},
+				},
+				Report: v1alpha1.ConfigAuditReportData{},
+			}, &v1alpha1.ConfigAuditReport{
+				ObjectMeta: metav1.ObjectMeta{
+					Namespace: "my-namespace",
+					Name:      "my-sts",
+					Labels: map[string]string{
+						starboard.LabelResourceKind:      string(kube.KindStatefulSet),
+						starboard.LabelResourceName:      "my-sts",
+						starboard.LabelResourceNamespace: "my-namespace",
+					},
+				},
+				Report: v1alpha1.ConfigAuditReportData{},
+			}).Build()
+
+		readWriter := configauditreport.NewReadWriter(client)
+		found, err := readWriter.FindReportByOwner(context.TODO(), kube.Object{
+			Kind:      kube.KindDeployment,
+			Name:      "my-deploy",
+			Namespace: "my-namespace",
+		})
+		require.NoError(t, err)
+		assert.Equal(t, &v1alpha1.ConfigAuditReport{
 			ObjectMeta: metav1.ObjectMeta{
 				Namespace:       "my-namespace",
 				Name:            "deployment-my-deploy-my",
@@ -152,35 +185,62 @@ func TestReadWriter(t *testing.T) {
 				},
 			},
 			Report: v1alpha1.ConfigAuditReportData{},
-		}, &v1alpha1.ConfigAuditReport{
-			ObjectMeta: metav1.ObjectMeta{
-				Namespace: "my-namespace",
-				Name:      "my-sts",
-				Labels: map[string]string{
-					starboard.LabelResourceKind:      string(kube.KindStatefulSet),
-					starboard.LabelResourceName:      "my-sts",
-					starboard.LabelResourceNamespace: "my-namespace",
+		}, found)
+	})
+
+	t.Run("Should find ConfigAuditReport by owner with special name", func(t *testing.T) {
+		client := fake.NewClientBuilder().WithScheme(kubernetesScheme).WithObjects(
+			&v1alpha1.ConfigAuditReport{
+				ObjectMeta: metav1.ObjectMeta{
+					Namespace:       "kube-system",
+					Name:            "role-79f88497",
+					ResourceVersion: "0",
+					Labels: map[string]string{
+						starboard.LabelResourceKind:      "Role",
+						starboard.LabelResourceNameHash:  "79f88497",
+						starboard.LabelResourceNamespace: "kube-system",
+					},
+					Annotations: map[string]string{
+						starboard.LabelResourceName: "system:controller:cloud-provider",
+					},
 				},
-			},
-			Report: v1alpha1.ConfigAuditReportData{},
-		}).Build()
+				Report: v1alpha1.ConfigAuditReportData{},
+			}, &v1alpha1.ConfigAuditReport{
+				ObjectMeta: metav1.ObjectMeta{
+					Namespace:       "kube-system",
+					Name:            "role-868458b9d6",
+					ResourceVersion: "0",
+					Labels: map[string]string{
+						starboard.LabelResourceKind:      "Role",
+						starboard.LabelResourceNameHash:  "868458b9d6",
+						starboard.LabelResourceNamespace: "kube-system",
+					},
+					Annotations: map[string]string{
+						starboard.LabelResourceName: "system:controller:token-cleaner",
+					},
+				},
+				Report: v1alpha1.ConfigAuditReportData{},
+			}).Build()
 
 		readWriter := configauditreport.NewReadWriter(client)
 		found, err := readWriter.FindReportByOwner(context.TODO(), kube.Object{
-			Kind:      kube.KindDeployment,
-			Name:      "my-deploy",
-			Namespace: "my-namespace",
+			Kind:      kube.KindRole,
+			Name:      "system:controller:token-cleaner",
+			Namespace: "kube-system",
 		})
 		require.NoError(t, err)
 		assert.Equal(t, &v1alpha1.ConfigAuditReport{
 			ObjectMeta: metav1.ObjectMeta{
-				Namespace:       "my-namespace",
-				Name:            "deployment-my-deploy-my",
+				Namespace:       "kube-system",
+				Name:            "role-868458b9d6",
 				ResourceVersion: "0",
 				Labels: map[string]string{
-					starboard.LabelResourceKind:      string(kube.KindDeployment),
-					starboard.LabelResourceName:      "my-deploy",
-					starboard.LabelResourceNamespace: "my-namespace",
+					starboard.LabelResourceKind:      "Role",
+					starboard.LabelResourceNameHash:  "868458b9d6",
+					starboard.LabelResourceNamespace: "kube-system",
+				},
+				Annotations: map[string]string{
+					starboard.LabelResourceName: "system:controller:token-cleaner",
 				},
 			},
 			Report: v1alpha1.ConfigAuditReportData{},
@@ -242,9 +302,9 @@ func TestReadWriter(t *testing.T) {
 						Name:            "clusterrole-admin",
 						ResourceVersion: "0",
 						Labels: map[string]string{
-							starboard.LabelResourceKind: "ClusterRole",
-							starboard.LabelResourceName: "admin",
-							starboard.LabelPodSpecHash:  "h1",
+							starboard.LabelResourceKind:     "ClusterRole",
+							starboard.LabelResourceName:     "admin",
+							starboard.LabelResourceSpecHash: "h1",
 						},
 					},
 					Report: v1alpha1.ConfigAuditReportData{
@@ -260,9 +320,9 @@ func TestReadWriter(t *testing.T) {
 			ObjectMeta: metav1.ObjectMeta{
 				Name: "clusterrole-admin",
 				Labels: map[string]string{
-					starboard.LabelResourceKind: "ClusterRole",
-					starboard.LabelResourceName: "admin",
-					starboard.LabelPodSpecHash:  "h2",
+					starboard.LabelResourceKind:     "ClusterRole",
+					starboard.LabelResourceName:     "admin",
+					starboard.LabelResourceSpecHash: "h2",
 				},
 			},
 			Report: v1alpha1.ConfigAuditReportData{
@@ -286,9 +346,9 @@ func TestReadWriter(t *testing.T) {
 			ObjectMeta: metav1.ObjectMeta{
 				Name: "clusterrole-admin",
 				Labels: map[string]string{
-					starboard.LabelResourceKind: "ClusterRole",
-					starboard.LabelResourceName: "admin",
-					starboard.LabelPodSpecHash:  "h2",
+					starboard.LabelResourceKind:     "ClusterRole",
+					starboard.LabelResourceName:     "admin",
+					starboard.LabelResourceSpecHash: "h2",
 				},
 				ResourceVersion: "1",
 			},
@@ -310,8 +370,9 @@ func TestReadWriter(t *testing.T) {
 						Name:            "clusterrole-viewer",
 						ResourceVersion: "1",
 						Labels: map[string]string{
-							starboard.LabelResourceKind: "ClusterRole",
-							starboard.LabelResourceName: "viewer",
+							starboard.LabelResourceKind:      "ClusterRole",
+							starboard.LabelResourceName:      "viewer",
+							starboard.LabelResourceNamespace: "",
 						},
 					},
 					Report: v1alpha1.ConfigAuditReportData{},
@@ -321,8 +382,9 @@ func TestReadWriter(t *testing.T) {
 						Name:            "clusterrole-editor",
 						ResourceVersion: "1",
 						Labels: map[string]string{
-							starboard.LabelResourceKind: "ClusterRole",
-							starboard.LabelResourceName: "editor",
+							starboard.LabelResourceKind:      "ClusterRole",
+							starboard.LabelResourceName:      "editor",
+							starboard.LabelResourceNamespace: "",
 						},
 					},
 					Report: v1alpha1.ConfigAuditReportData{},
@@ -340,8 +402,9 @@ func TestReadWriter(t *testing.T) {
 				Name:            "clusterrole-editor",
 				ResourceVersion: "1",
 				Labels: map[string]string{
-					starboard.LabelResourceKind: "ClusterRole",
-					starboard.LabelResourceName: "editor",
+					starboard.LabelResourceKind:      "ClusterRole",
+					starboard.LabelResourceName:      "editor",
+					starboard.LabelResourceNamespace: "",
 				},
 			},
 			Report: v1alpha1.ConfigAuditReportData{},
diff --git a/pkg/kube/object_test.go b/pkg/kube/object_test.go
index 9c471b9..2380e30 100644
--- a/pkg/kube/object_test.go
+++ b/pkg/kube/object_test.go
@@ -491,3 +491,162 @@ func TestObjectResolver_GetRelatedReplicasetName(t *testing.T) {
 	})
 
 }
+
+func TestIsClusterScopedKind(t *testing.T) {
+	testCases := []struct {
+		kind string
+		want bool
+	}{
+		{
+			kind: "Role",
+			want: false,
+		},
+		{
+			kind: "RoleBinding",
+			want: false,
+		},
+		{
+			kind: "ClusterRole",
+			want: true,
+		},
+		{
+			kind: "ClusterRoleBinding",
+			want: true,
+		},
+		{
+			kind: "CustomResourceDefinition",
+			want: true,
+		},
+		{
+			kind: "Pod",
+			want: false,
+		},
+	}
+	for _, tt := range testCases {
+		t.Run(fmt.Sprintf("Should return %t when controller kind is %s", tt.want, tt.kind), func(t *testing.T) {
+			assert.Equal(t, tt.want, kube.IsClusterScopedKind(tt.kind))
+		})
+	}
+}
+
+func TestPartialObjectFromObjectMetadata(t *testing.T) {
+	testCases := []struct {
+		name          string
+		object        metav1.ObjectMeta
+		expected      kube.Object
+		expectedError string
+	}{
+		{
+			name: "Test Role",
+			object: metav1.ObjectMeta{
+				Labels: map[string]string{
+					starboard.LabelResourceKind:      "Role",
+					starboard.LabelResourceNamespace: "kube-system",
+					starboard.LabelResourceNameHash:  kube.ComputeHash("system:admin"),
+				},
+				Annotations: map[string]string{
+					starboard.LabelResourceName: "system:admin",
+				},
+			},
+			expected: kube.Object{Kind: kube.KindRole, Name: "system:admin", Namespace: "kube-system"},
+		},
+		{
+			name: "Test RoleBinding",
+			object: metav1.ObjectMeta{
+				Labels: map[string]string{
+					starboard.LabelResourceKind:      "RoleBinding",
+					starboard.LabelResourceNamespace: "kube-system",
+					starboard.LabelResourceNameHash:  kube.ComputeHash("system:admin:binding"),
+				},
+				Annotations: map[string]string{
+					starboard.LabelResourceName: "system:admin:binding",
+				},
+			},
+			expected: kube.Object{Kind: kube.KindRoleBinding, Name: "system:admin:binding", Namespace: "kube-system"},
+		},
+		{
+			name: "Kind ClusterRole",
+			object: metav1.ObjectMeta{
+				Labels: map[string]string{
+					starboard.LabelResourceKind:      "ClusterRole",
+					starboard.LabelResourceNamespace: "",
+					starboard.LabelResourceNameHash:  kube.ComputeHash("system:netnode"),
+				},
+				Annotations: map[string]string{
+					starboard.LabelResourceName: "system:netnode",
+				},
+			},
+			expected: kube.Object{Kind: kube.KindClusterRole, Name: "system:netnode"},
+		},
+		{
+			name: "Kind ClusterRoleBinding",
+			object: metav1.ObjectMeta{
+				Labels: map[string]string{
+					starboard.LabelResourceKind:      "ClusterRoleBinding",
+					starboard.LabelResourceNamespace: "",
+					starboard.LabelResourceNameHash:  kube.ComputeHash("system:netnode:binding"),
+				},
+				Annotations: map[string]string{
+					starboard.LabelResourceName: "system:netnode:binding",
+				},
+			},
+			expected: kube.Object{Kind: kube.KindClusterRoleBindings, Name: "system:netnode:binding"},
+		},
+		{
+			name: "Kind Pod",
+			object: metav1.ObjectMeta{
+				Labels: map[string]string{
+					starboard.LabelResourceKind:      "Pod",
+					starboard.LabelResourceNamespace: "default",
+					starboard.LabelResourceName:      "nginx-pod",
+				},
+			},
+			expected: kube.Object{Kind: kube.KindPod, Name: "nginx-pod", Namespace: "default"},
+		},
+		{
+			name: "Kind Deployment",
+			object: metav1.ObjectMeta{
+				Labels: map[string]string{
+					starboard.LabelResourceKind:      "Deployment",
+					starboard.LabelResourceNamespace: "default",
+					starboard.LabelResourceName:      "nginx-deployment",
+				},
+			},
+			expected: kube.Object{Kind: kube.KindDeployment, Name: "nginx-deployment", Namespace: "default"},
+		},
+		{
+			name: "Kind DaemonSet",
+			object: metav1.ObjectMeta{
+				Labels: map[string]string{
+					starboard.LabelResourceKind:      "DaemonSet",
+					starboard.LabelResourceNamespace: "default",
+					starboard.LabelResourceName:      "nginx-ds",
+				},
+			},
+			expected: kube.Object{Kind: kube.KindDaemonSet, Name: "nginx-ds", Namespace: "default"},
+		},
+		{
+			name: fmt.Sprintf("Should return error when %s label is missing", starboard.LabelResourceKind),
+			object: metav1.ObjectMeta{
+				Labels: map[string]string{
+					starboard.LabelResourceName:      "nginx",
+					starboard.LabelResourceNamespace: "default",
+				},
+			},
+			expectedError: "required label does not exist: starboard.resource.kind",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			actual, err := kube.PartialObjectFromObjectMetadata(tc.object)
+			if tc.expectedError == "" {
+				require.NoError(t, err)
+				assert.Equal(t, tc.expected, actual)
+			} else {
+				require.EqualError(t, err, tc.expectedError)
+			}
+		})
+	}
+
+}
diff --git a/pkg/plugin/trivy/plugin_test.go b/pkg/plugin/trivy/plugin_test.go
index 80c7fd3..b4d4210 100644
--- a/pkg/plugin/trivy/plugin_test.go
+++ b/pkg/plugin/trivy/plugin_test.go
@@ -1155,9 +1155,9 @@ CVE-2019-1543`,
 		{
 			name: "ClientServer mode without insecure registry",
 			config: map[string]string{
-				"trivy.imageRef":                 "docker.io/aquasec/trivy:0.14.0",
-				"trivy.mode":                     string(trivy.ClientServer),
-				"trivy.serverURL":                "http://trivy.trivy:4954",
+				"trivy.imageRef":                  "docker.io/aquasec/trivy:0.14.0",
+				"trivy.mode":                      string(trivy.ClientServer),
+				"trivy.serverURL":                 "http://trivy.trivy:4954",
 				"trivy.resources.requests.cpu":    "100m",
 				"trivy.resources.requests.memory": "100M",
 				"trivy.resources.limits.cpu":      "500m",
@@ -1335,10 +1335,10 @@ CVE-2019-1543`,
 				"trivy.mode":                         string(trivy.ClientServer),
 				"trivy.serverURL":                    "http://trivy.trivy:4954",
 				"trivy.insecureRegistry.pocRegistry": "poc.myregistry.harbor.com.pl",
-				"trivy.resources.requests.cpu":        "100m",
-				"trivy.resources.requests.memory":     "100M",
-				"trivy.resources.limits.cpu":          "500m",
-				"trivy.resources.limits.memory":       "500M",
+				"trivy.resources.requests.cpu":       "100m",
+				"trivy.resources.requests.memory":    "100M",
+				"trivy.resources.limits.cpu":         "500m",
+				"trivy.resources.limits.memory":      "500M",
 			},
 			workloadSpec: corev1.PodSpec{
 				Containers: []corev1.Container{
diff --git a/pkg/vulnerabilityreport/builder_test.go b/pkg/vulnerabilityreport/builder_test.go
index 681d11c..89b35f0 100644
--- a/pkg/vulnerabilityreport/builder_test.go
+++ b/pkg/vulnerabilityreport/builder_test.go
@@ -46,7 +46,7 @@ func TestBuilder(t *testing.T) {
 				starboard.LabelResourceName:      "some-owner",
 				starboard.LabelResourceNamespace: "qa",
 				starboard.LabelContainerName:     "my-container",
-				starboard.LabelPodSpecHash:       "xyz",
+				starboard.LabelResourceSpecHash:  "xyz",
 			},
 		},
 		Report: v1alpha1.VulnerabilityReportData{},
diff --git a/pkg/vulnerabilityreport/io_test.go b/pkg/vulnerabilityreport/io_test.go
index bc33376..a6430ae 100644
--- a/pkg/vulnerabilityreport/io_test.go
+++ b/pkg/vulnerabilityreport/io_test.go
@@ -28,11 +28,11 @@ func TestNewReadWriter(t *testing.T) {
 					Name:      "deployment-app1-container1",
 					Namespace: "qa",
 					Labels: map[string]string{
-						"starboard.resource.kind":      "Deployment",
-						"starboard.resource.name":      "app1",
-						"starboard.resource.namespace": "qa",
-						"starboard.container.name":     "container1",
-						"pod-spec-hash":                "h1",
+						starboard.LabelResourceKind:      "Deployment",
+						starboard.LabelResourceName:      "app1",
+						starboard.LabelResourceNamespace: "qa",
+						starboard.LabelContainerName:     "container1",
+						starboard.LabelResourceSpecHash:  "h1",
 					},
 				},
 			},
@@ -41,11 +41,11 @@ func TestNewReadWriter(t *testing.T) {
 					Name:      "deployment-app1-container2",
 					Namespace: "qa",
 					Labels: map[string]string{
-						"starboard.resource.kind":      "Deployment",
-						"starboard.resource.name":      "app1",
-						"starboard.resource.namespace": "qa",
-						"starboard.container.name":     "container2",
-						"pod-spec-hash":                "h1",
+						starboard.LabelResourceKind:      "Deployment",
+						starboard.LabelResourceName:      "app1",
+						starboard.LabelResourceNamespace: "qa",
+						starboard.LabelContainerName:     "container2",
+						starboard.LabelResourceSpecHash:  "h1",
 					},
 				},
 			},
@@ -68,7 +68,7 @@ func TestNewReadWriter(t *testing.T) {
 						starboard.LabelResourceName:      "app1",
 						starboard.LabelResourceNamespace: "qa",
 						starboard.LabelContainerName:     "container1",
-						starboard.LabelPodSpecHash:       "h1",
+						starboard.LabelResourceSpecHash:  "h1",
 					},
 					ResourceVersion: "1",
 				},
@@ -82,7 +82,7 @@ func TestNewReadWriter(t *testing.T) {
 						starboard.LabelResourceName:      "app1",
 						starboard.LabelResourceNamespace: "qa",
 						starboard.LabelContainerName:     "container2",
-						starboard.LabelPodSpecHash:       "h1",
+						starboard.LabelResourceSpecHash:  "h1",
 					},
 					ResourceVersion: "1",
 				},
@@ -98,11 +98,11 @@ func TestNewReadWriter(t *testing.T) {
 					Namespace:       "qa",
 					ResourceVersion: "0",
 					Labels: map[string]string{
-						"starboard.resource.kind":      "Deployment",
-						"starboard.resource.name":      "app1",
-						"starboard.resource.namespace": "qa",
-						"starboard.container.name":     "container1",
-						"pod-spec-hash":                "h1",
+						starboard.LabelResourceKind:      "Deployment",
+						starboard.LabelResourceName:      "app1",
+						starboard.LabelResourceNamespace: "qa",
+						starboard.LabelContainerName:     "container1",
+						starboard.LabelResourceSpecHash:  "h1",
 					},
 				},
 			},
@@ -112,11 +112,11 @@ func TestNewReadWriter(t *testing.T) {
 					Namespace:       "qa",
 					ResourceVersion: "0",
 					Labels: map[string]string{
-						"starboard.resource.kind":      "Deployment",
-						"starboard.resource.name":      "app1",
-						"starboard.resource.namespace": "qa",
-						"starboard.container.name":     "container2",
-						"pod-spec-hash":                "h2",
+						starboard.LabelResourceKind:      "Deployment",
+						starboard.LabelResourceName:      "app1",
+						starboard.LabelResourceNamespace: "qa",
+						starboard.LabelContainerName:     "container2",
+						starboard.LabelResourceSpecHash:  "h2",
 					},
 				},
 			}).Build()
@@ -127,11 +127,11 @@ func TestNewReadWriter(t *testing.T) {
 					Name:      "deployment-app1-container1",
 					Namespace: "qa",
 					Labels: map[string]string{
-						"starboard.resource.kind":      "Deployment",
-						"starboard.resource.name":      "app1",
-						"starboard.resource.namespace": "qa",
-						"starboard.container.name":     "container1",
-						"pod-spec-hash":                "h2",
+						starboard.LabelResourceKind:      "Deployment",
+						starboard.LabelResourceName:      "app1",
+						starboard.LabelResourceNamespace: "qa",
+						starboard.LabelContainerName:     "container1",
+						starboard.LabelResourceSpecHash:  "h2",
 					},
 				},
 			},
@@ -140,11 +140,11 @@ func TestNewReadWriter(t *testing.T) {
 					Name:      "deployment-app1-container2",
 					Namespace: "qa",
 					Labels: map[string]string{
-						"starboard.resource.kind":      "Deployment",
-						"starboard.resource.name":      "app1",
-						"starboard.resource.namespace": "qa",
-						"starboard.container.name":     "container2",
-						"pod-spec-hash":                "h2",
+						starboard.LabelResourceKind:      "Deployment",
+						starboard.LabelResourceName:      "app1",
+						starboard.LabelResourceNamespace: "qa",
+						starboard.LabelContainerName:     "container2",
+						starboard.LabelResourceSpecHash:  "h2",
 					},
 				},
 			},
@@ -167,11 +167,11 @@ func TestNewReadWriter(t *testing.T) {
 				Name:            "deployment-app1-container1",
 				Namespace:       "qa",
 				Labels: map[string]string{
-					"starboard.resource.kind":      "Deployment",
-					"starboard.resource.name":      "app1",
-					"starboard.resource.namespace": "qa",
-					"starboard.container.name":     "container1",
-					"pod-spec-hash":                "h2",
+					starboard.LabelResourceKind:      "Deployment",
+					starboard.LabelResourceName:      "app1",
+					starboard.LabelResourceNamespace: "qa",
+					starboard.LabelContainerName:     "container1",
+					starboard.LabelResourceSpecHash:  "h2",
 				},
 			},
 		}, found)
@@ -191,11 +191,11 @@ func TestNewReadWriter(t *testing.T) {
 				Name:            "deployment-app1-container2",
 				Namespace:       "qa",
 				Labels: map[string]string{
-					"starboard.resource.kind":      "Deployment",
-					"starboard.resource.name":      "app1",
-					"starboard.resource.namespace": "qa",
-					"starboard.container.name":     "container2",
-					"pod-spec-hash":                "h2",
+					starboard.LabelResourceKind:      "Deployment",
+					starboard.LabelResourceName:      "app1",
+					starboard.LabelResourceNamespace: "qa",
+					starboard.LabelContainerName:     "container2",
+					starboard.LabelResourceSpecHash:  "h2",
 				},
 			},
 		}, found)