Repository: aquasecurity/starboard
Pull request #450 (2021-03-23 11:28:03)

fix: Skip reconciling Jobs managed by CronJob
Signed-off-by: Daniel Pacak <pacak.daniel@gmail.com>

codecov[bot]: # [Codecov](https://codecov.io/gh/aquasecurity/starboard/pull/450?src=pr&el=h1) Report
> Merging [#450](https://codecov.io/gh/aquasecurity/starboard/pull/450?src=pr&el=desc) (104537d) into [main](https://codecov.io/gh/aquasecurity/starboard/commit/3504621398bb826e62bec1eacae3218b07de217a?el=desc) (3504621) will **decrease** coverage by `37.69%`.
> The diff coverage is `0.00%`.

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

```diff
@@             Coverage Diff             @@
##             main     #450       +/-   ##
===========================================
- Coverage   68.88%   31.18%   -37.70%     
===========================================
  Files          57       56        -1     
  Lines        3384     3293       -91     
===========================================
- Hits         2331     1027     -1304     
- Misses        775     2198     +1423     
+ Partials      278       68      -210     
```


| [Impacted Files](https://codecov.io/gh/aquasecurity/starboard/pull/450?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pkg/operator/controller/ciskubebenchreport.go](https://codecov.io/gh/aquasecurity/starboard/pull/450/diff?src=pr&el=tree#diff-cGtnL29wZXJhdG9yL2NvbnRyb2xsZXIvY2lza3ViZWJlbmNocmVwb3J0Lmdv) | `0.00% <0.00%> (-66.49%)` | :arrow_down: |
| [pkg/operator/controller/configauditreport.go](https://codecov.io/gh/aquasecurity/starboard/pull/450/diff?src=pr&el=tree#diff-cGtnL29wZXJhdG9yL2NvbnRyb2xsZXIvY29uZmlnYXVkaXRyZXBvcnQuZ28=) | `0.00% <0.00%> (-69.24%)` | :arrow_down: |
| [pkg/operator/controller/vulnerabilityreport.go](https://codecov.io/gh/aquasecurity/starboard/pull/450/diff?src=pr&el=tree#diff-cGtnL29wZXJhdG9yL2NvbnRyb2xsZXIvdnVsbmVyYWJpbGl0eXJlcG9ydC5nbw==) | `0.00% <0.00%> (-63.68%)` | :arrow_down: |
| [pkg/cmd/get.go](https://codecov.io/gh/aquasecurity/starboard/pull/450/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/450/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/450/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/450/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/450/diff?src=pr&el=tree#diff-cGtnL2NtZC9wb2xhcmlzLmdv) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [pkg/cmd/version.go](https://codecov.io/gh/aquasecurity/starboard/pull/450/diff?src=pr&el=tree#diff-cGtnL2NtZC92ZXJzaW9uLmdv) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [pkg/cmd/kube\_bench.go](https://codecov.io/gh/aquasecurity/starboard/pull/450/diff?src=pr&el=tree#diff-cGtnL2NtZC9rdWJlX2JlbmNoLmdv) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| ... and [34 more](https://codecov.io/gh/aquasecurity/starboard/pull/450/diff?src=pr&el=tree-more) | |

------

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


Patch:
diff --git a/deploy/helm/templates/configmap.yaml b/deploy/helm/templates/config.yaml
similarity index 100%
rename from deploy/helm/templates/configmap.yaml
rename to deploy/helm/templates/config.yaml
diff --git a/deploy/helm/templates/rbac.yaml b/deploy/helm/templates/rbac.yaml
index ed1bdf4..aeddc37 100644
--- a/deploy/helm/templates/rbac.yaml
+++ b/deploy/helm/templates/rbac.yaml
@@ -67,6 +67,14 @@ rules:
       - get
       - list
       - watch
+  - apiGroups:
+      - batch
+    resources:
+      - cronjobs
+    verbs:
+      - get
+      - list
+      - watch
   - apiGroups:
       - batch
     resources: # jobs are inspected and directly used by the starboard-operator
diff --git a/deploy/static/03-starboard-operator.clusterrole.yaml b/deploy/static/03-starboard-operator.clusterrole.yaml
index f6ae94d..1808f5b 100644
--- a/deploy/static/03-starboard-operator.clusterrole.yaml
+++ b/deploy/static/03-starboard-operator.clusterrole.yaml
@@ -45,6 +45,14 @@ rules:
       - get
       - list
       - watch
+  - apiGroups:
+      - batch
+    resources:
+      - cronjobs
+    verbs:
+      - get
+      - list
+      - watch
   - apiGroups:
       - batch
     resources:
diff --git a/go.mod b/go.mod
index 8a4a073..a3fa3e5 100644
--- a/go.mod
+++ b/go.mod
@@ -9,8 +9,8 @@ require (
 	github.com/google/go-containerregistry v0.1.1
 	github.com/google/uuid v1.1.1
 	github.com/hashicorp/go-version v1.2.0
-	github.com/onsi/ginkgo v1.15.0
-	github.com/onsi/gomega v1.10.5
+	github.com/onsi/ginkgo v1.15.2
+	github.com/onsi/gomega v1.11.0
 	github.com/spf13/cobra v1.1.1
 	github.com/spf13/pflag v1.0.5
 	github.com/stretchr/testify v1.7.0
diff --git a/go.sum b/go.sum
index 0282e38..5039bf2 100644
--- a/go.sum
+++ b/go.sum
@@ -342,8 +342,9 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU
 github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
 github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
 github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
-github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
 github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
+github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
 github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4=
 github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk=
 github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6/go.mod h1:DbHgvLiFKX1Sh2T1w8Q/h4NAI8MHIpzCdnBUDTXU3I0=
@@ -578,8 +579,9 @@ github.com/nakabonne/nestif v0.3.0/go.mod h1:dI314BppzXjJ4HsCnbo7XzrJHPszZsjnk5w
 github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
-github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
 github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
+github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
+github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
 github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
 github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
 github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@@ -590,8 +592,8 @@ github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+
 github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg=
 github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
 github.com/onsi/ginkgo v1.14.1/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
-github.com/onsi/ginkgo v1.15.0 h1:1V1NfVQR87RtWAgp1lv9JZJ5Jap+XFGKPi00andXGi4=
-github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg=
+github.com/onsi/ginkgo v1.15.2 h1:l77YT15o814C2qVL47NOyjV/6RbaP7kKdrvZnxQ3Org=
+github.com/onsi/ginkgo v1.15.2/go.mod h1:Dd6YFfwBW84ETqqtL0CPyPXillHgY6XhQH3uuCCTr/o=
 github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
 github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
@@ -600,8 +602,8 @@ github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoT
 github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
 github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
 github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
-github.com/onsi/gomega v1.10.5 h1:7n6FEkpFmfCoo2t+YYqXH0evK+a9ICQz0xcAy9dYcaQ=
-github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48=
+github.com/onsi/gomega v1.11.0 h1:+CqWgvj0OZycCaqclBD1pxKHAU+tOkHmQIWvDHq2aug=
+github.com/onsi/gomega v1.11.0/go.mod h1:azGKhqFUon9Vuj0YmTfLSmx0FUwqXYSTl5re8lQLTUg=
 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
 github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
 github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
@@ -1152,8 +1154,9 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
 gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
 gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/pkg/kube/object.go b/pkg/kube/object.go
index 116f4da..c7a7ae7 100644
--- a/pkg/kube/object.go
+++ b/pkg/kube/object.go
@@ -49,6 +49,17 @@ const (
 	KindJob                   Kind = "Job"
 )
 
+// IsBuiltInWorkload returns true if the specified v1.OwnerReference
+// is a built-in Kubernetes workload, false otherwise.
+func IsBuiltInWorkload(controller *metav1.OwnerReference) bool {
+	return controller != nil &&
+		(controller.Kind == string(KindReplicaSet) ||
+			controller.Kind == string(KindReplicationController) ||
+			controller.Kind == string(KindStatefulSet) ||
+			controller.Kind == string(KindDaemonSet) ||
+			controller.Kind == string(KindJob))
+}
+
 func ObjectFromLabelsSet(set labels.Set) (Object, error) {
 	if !set.Has(LabelResourceKind) {
 		return Object{}, fmt.Errorf("required label does not exist: %s", LabelResourceKind)
diff --git a/pkg/operator/controller/ciskubebenchreport.go b/pkg/operator/controller/ciskubebenchreport.go
index f394f00..d726445 100644
--- a/pkg/operator/controller/ciskubebenchreport.go
+++ b/pkg/operator/controller/ciskubebenchreport.go
@@ -118,6 +118,9 @@ func (r *CISKubeBenchReportReconciler) reconcileNodes() reconcile.Func {
 		log.V(1).Info("Scheduling CIS Kubernetes Benchmark checks")
 		err = r.Client.Create(ctx, job)
 		if err != nil {
+			if errors.IsAlreadyExists(err) {
+				return ctrl.Result{}, nil
+			}
 			return ctrl.Result{}, fmt.Errorf("creating job: %w", err)
 		}
 
diff --git a/pkg/operator/controller/configauditreport.go b/pkg/operator/controller/configauditreport.go
index dfbd0ed..ad6fd50 100644
--- a/pkg/operator/controller/configauditreport.go
+++ b/pkg/operator/controller/configauditreport.go
@@ -12,6 +12,7 @@ import (
 	"github.com/go-logr/logr"
 	appsv1 "k8s.io/api/apps/v1"
 	batchv1 "k8s.io/api/batch/v1"
+	batchv1beta1 "k8s.io/api/batch/v1beta1"
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -45,6 +46,7 @@ var (
 		{kind: kube.KindReplicationController, forObject: &corev1.ReplicationController{}, ownsObject: &v1alpha1.ConfigAuditReport{}},
 		{kind: kube.KindStatefulSet, forObject: &appsv1.StatefulSet{}, ownsObject: &v1alpha1.ConfigAuditReport{}},
 		{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{}},
 	}
 )
@@ -98,18 +100,21 @@ func (r *ConfigAuditReportReconciler) reconcileWorkload(workloadKind kube.Kind)
 		// Skip processing if it's a Pod controlled by a standard K8s workload.
 		if workloadKind == kube.KindPod {
 			controller := metav1.GetControllerOf(workloadObj)
-			if controller != nil &&
-				(controller.Kind == string(kube.KindReplicaSet) ||
-					controller.Kind == string(kube.KindReplicationController) ||
-					controller.Kind == string(kube.KindStatefulSet) ||
-					controller.Kind == string(kube.KindDaemonSet) ||
-					controller.Kind == string(kube.KindCronJob) ||
-					controller.Kind == string(kube.KindJob)) {
+			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)
+			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)
 		if err != nil {
 			return ctrl.Result{}, err
@@ -167,15 +172,19 @@ func (r *ConfigAuditReportReconciler) reconcileWorkload(workloadKind kube.Kind)
 		log.V(1).Info("Scheduling configuration audit", "secrets", len(secrets))
 		err = r.Client.Create(ctx, job)
 		if err != nil {
+			if errors.IsAlreadyExists(err) {
+				// TODO Delete secrets that were created in the previous step. Alternatively we can delete them on schedule.
+				return ctrl.Result{}, nil
+			}
 			return ctrl.Result{}, fmt.Errorf("creating job: %w", err)
 		}
 
 		for _, secret := range secrets {
-			err = controllerutil.SetOwnerReference(job, secret, r.Client.Scheme())
+			err := controllerutil.SetOwnerReference(job, secret, r.Client.Scheme())
 			if err != nil {
 				return ctrl.Result{}, fmt.Errorf("setting owner reference: %w", err)
 			}
-			err := r.Client.Update(ctx, secret)
+			err = r.Client.Update(ctx, secret)
 			if err != nil {
 				return ctrl.Result{}, fmt.Errorf("updating secret: %w", err)
 			}
diff --git a/pkg/operator/controller/vulnerabilityreport.go b/pkg/operator/controller/vulnerabilityreport.go
index 4d6ff91..a9902d4 100644
--- a/pkg/operator/controller/vulnerabilityreport.go
+++ b/pkg/operator/controller/vulnerabilityreport.go
@@ -228,6 +228,10 @@ func (r *VulnerabilityReportReconciler) submitScanJob(ctx context.Context, spec
 
 	err = r.Client.Create(ctx, scanJob)
 	if err != nil {
+		if errors.IsAlreadyExists(err) {
+			// TODO Delete secrets that were created in the previous step. Alternatively we can delete them on schedule.
+			return nil
+		}
 		return fmt.Errorf("creating job: %w", err)
 	}
 


Test patch:
diff --git a/itest/starboard-operator/starboard_operator_test.go b/itest/starboard-operator/starboard_operator_test.go
index 043f84c..d73797c 100644
--- a/itest/starboard-operator/starboard_operator_test.go
+++ b/itest/starboard-operator/starboard_operator_test.go
@@ -10,6 +10,8 @@ import (
 	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
 	appsv1 "k8s.io/api/apps/v1"
+	batchv1 "k8s.io/api/batch/v1"
+	batchv1beta1 "k8s.io/api/batch/v1beta1"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/labels"
@@ -123,35 +125,83 @@ var _ = Describe("Starboard Operator", func() {
 
 	// TODO Add a scenario for rolling update, i.e. create Deployment and then update its container image.
 
+	Describe("When CronJob is created", func() {
+		var cronJob *batchv1beta1.CronJob
+
+		BeforeEach(func() {
+			cronJob = &batchv1beta1.CronJob{
+				ObjectMeta: metav1.ObjectMeta{
+					Namespace: namespaceName,
+					Name:      "hello",
+				},
+				Spec: batchv1beta1.CronJobSpec{
+					Schedule: "*/1 * * * *",
+					JobTemplate: batchv1beta1.JobTemplateSpec{
+						Spec: batchv1.JobSpec{
+							Template: corev1.PodTemplateSpec{
+								Spec: corev1.PodSpec{
+									RestartPolicy: corev1.RestartPolicyOnFailure,
+									Containers: []corev1.Container{
+										{
+											Name:  "hello",
+											Image: "busybox",
+											Command: []string{
+												"/bin/sh",
+												"-c",
+												"date; echo Hello from the Kubernetes cluster",
+											},
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			}
+			err := kubeClient.Create(context.Background(), cronJob)
+			Expect(err).ToNot(HaveOccurred())
+		})
+
+		It("Should create VulnerabilityReport and ConfigAuditReport", func() {
+			Eventually(HasConfigAuditReportOwnedBy(cronJob), assertionTimeout).Should(BeTrue())
+			// FIXME(issue: #415): Assign VulnerabilityReports to CronJob instead of Jobs. The PodSpec of a CronJob does not change so there's no point in rescanning individual Jobs.
+			// Eventually(HasVulnerabilityReportOwnedBy(cronJob), assertionTimeout).Should(BeTrue())
+		})
+
+		AfterEach(func() {
+			err := kubeClient.Delete(context.Background(), cronJob)
+			Expect(err).ToNot(HaveOccurred())
+		})
+	})
+
 	Describe("When operator is started", func() {
 
 		It("Should scan all nodes with CIS Kubernetes Benchmark checks", func() {
 			nodeList, err := kubeClientset.CoreV1().Nodes().List(context.Background(), metav1.ListOptions{})
 			Expect(err).ToNot(HaveOccurred())
 			for _, node := range nodeList.Items {
-				Eventually(HasCISKubeBenchReportFor(node), assertionTimeout).Should(BeTrue())
+				Eventually(HasCISKubeBenchReportOwnedBy(node), assertionTimeout).Should(BeTrue())
 			}
 		})
 
 	})
 })
 
-func HasActiveReplicaSet(namespace, name string) func() bool {
-	return func() bool {
+func HasActiveReplicaSet(namespace, name string) func() (bool, error) {
+	return func() (bool, error) {
 		rs, err := GetActiveReplicaSetForDeployment(namespace, name)
 		if err != nil {
-			return false
+			return false, err
 		}
-		return rs != nil
+		return rs != nil, nil
 	}
 }
 
-func HasVulnerabilityReportOwnedBy(obj client.Object) func() bool {
-	return func() bool {
+func HasVulnerabilityReportOwnedBy(obj client.Object) func() (bool, error) {
+	return func() (bool, error) {
 		gvk, err := apiutil.GVKForObject(obj, scheme)
 		if err != nil {
-			// TODO Report error
-			return false
+			return false, err
 		}
 		var reportList v1alpha1.VulnerabilityReportList
 		err = kubeClient.List(context.Background(), &reportList, client.MatchingLabels{
@@ -160,19 +210,17 @@ func HasVulnerabilityReportOwnedBy(obj client.Object) func() bool {
 			kube.LabelResourceNamespace: obj.GetNamespace(),
 		})
 		if err != nil {
-			// TODO Report error
-			return false
+			return false, err
 		}
-		return len(reportList.Items) == 1
+		return len(reportList.Items) == 1, nil
 	}
 }
 
-func HasConfigAuditReportOwnedBy(obj client.Object) func() bool {
-	return func() bool {
+func HasConfigAuditReportOwnedBy(obj client.Object) func() (bool, error) {
+	return func() (bool, error) {
 		gvk, err := apiutil.GVKForObject(obj, scheme)
 		if err != nil {
-			// TODO Report error
-			return false
+			return false, err
 		}
 		var reportsList v1alpha1.ConfigAuditReportList
 		err = kubeClient.List(context.Background(), &reportsList, client.MatchingLabels{
@@ -181,9 +229,9 @@ func HasConfigAuditReportOwnedBy(obj client.Object) func() bool {
 			kube.LabelResourceNamespace: obj.GetNamespace(),
 		})
 		if err != nil {
-			return false
+			return false, err
 		}
-		return len(reportsList.Items) == 1
+		return len(reportsList.Items) == 1, nil
 	}
 }
 
@@ -216,12 +264,12 @@ func GetActiveReplicaSetForDeployment(namespace, name string) (*appsv1.ReplicaSe
 	return nil, nil
 }
 
-func HasCISKubeBenchReportFor(node corev1.Node) func() bool {
-	return func() bool {
+func HasCISKubeBenchReportOwnedBy(node corev1.Node) func() (bool, error) {
+	return func() (bool, error) {
 		report, err := kubeBenchReportReader.FindByOwner(context.Background(), kube.Object{Kind: kube.KindNode, Name: node.Name})
 		if err != nil {
-			return false
+			return false, err
 		}
-		return report != nil
+		return report != nil, nil
 	}
 }
diff --git a/itest/starboard-operator/suite_test.go b/itest/starboard-operator/suite_test.go
index 045c933..38d7c0d 100644
--- a/itest/starboard-operator/suite_test.go
+++ b/itest/starboard-operator/suite_test.go
@@ -4,7 +4,6 @@ import (
 	"path/filepath"
 	"testing"
 
-	"github.com/aquasecurity/starboard/pkg/generated/clientset/versioned"
 	"github.com/aquasecurity/starboard/pkg/kubebench"
 	"github.com/aquasecurity/starboard/pkg/operator"
 	"github.com/aquasecurity/starboard/pkg/operator/etc"
@@ -30,10 +29,9 @@ var (
 )
 
 var (
-	scheme             *runtime.Scheme
-	kubeClientset      kubernetes.Interface
-	kubeClient         client.Client
-	starboardClientset versioned.Interface
+	scheme        *runtime.Scheme
+	kubeClientset kubernetes.Interface
+	kubeClient    client.Client
 )
 
 var (
@@ -64,9 +62,6 @@ var _ = BeforeSuite(func(done Done) {
 	kubeClient, err = client.New(kubeConfig, client.Options{Scheme: scheme})
 	Expect(err).ToNot(HaveOccurred())
 
-	starboardClientset, err = versioned.NewForConfig(kubeConfig)
-	Expect(err).ToNot(HaveOccurred())
-
 	kubeBenchReportReader = kubebench.NewReadWriter(kubeClient)
 
 	testEnv = &envtest.Environment{
diff --git a/itest/starboard/starboard_cli_test.go b/itest/starboard/starboard_cli_test.go
index f51d60a..01cd3eb 100644
--- a/itest/starboard/starboard_cli_test.go
+++ b/itest/starboard/starboard_cli_test.go
@@ -15,11 +15,14 @@ import (
 	. "github.com/onsi/gomega/gbytes"
 	. "github.com/onsi/gomega/gstruct"
 	appsv1 "k8s.io/api/apps/v1"
+	batchv1 "k8s.io/api/batch/v1"
+	batchv1beta1 "k8s.io/api/batch/v1beta1"
 	corev1 "k8s.io/api/core/v1"
 	apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/labels"
 	"k8s.io/utils/pointer"
+	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/yaml"
 )
 
@@ -897,7 +900,7 @@ var _ = Describe("Starboard CLI", func() {
 				Expect(err).ToNot(HaveOccurred())
 			})
 
-			It("should create configaudit resource", func() {
+			It("should create ConfigAuditReport", func() {
 				err := cmd.Run(versionInfo, []string{
 					"starboard",
 					"scan", "configauditreports", "pod/" + podName,
@@ -960,7 +963,7 @@ var _ = Describe("Starboard CLI", func() {
 				Expect(err).ToNot(HaveOccurred())
 			})
 
-			It("should create configaudit resources", func() {
+			It("should create ConfigAuditReport", func() {
 				err := cmd.Run(versionInfo, []string{
 					"starboard",
 					"scan", "configauditreports", "pod/" + podName,
@@ -1012,6 +1015,93 @@ var _ = Describe("Starboard CLI", func() {
 
 		})
 
+		Context("when CronJob is specified as workload", func() {
+
+			var cronJob *batchv1beta1.CronJob
+
+			BeforeEach(func() {
+				cronJob = &batchv1beta1.CronJob{
+					ObjectMeta: metav1.ObjectMeta{
+						Namespace: namespaceItest,
+						Name:      "hello",
+					},
+					Spec: batchv1beta1.CronJobSpec{
+						Schedule: "*/1 * * * *",
+						JobTemplate: batchv1beta1.JobTemplateSpec{
+							Spec: batchv1.JobSpec{
+								Template: corev1.PodTemplateSpec{
+									Spec: corev1.PodSpec{
+										RestartPolicy: corev1.RestartPolicyOnFailure,
+										Containers: []corev1.Container{
+											{
+												Name:  "hello",
+												Image: "busybox",
+												Command: []string{
+													"/bin/sh",
+													"-c",
+													"date; echo Hello from the Kubernetes cluster",
+												},
+											},
+										},
+									},
+								},
+							},
+						},
+					},
+				}
+				err := kubeClient.Create(context.Background(), cronJob)
+				Expect(err).ToNot(HaveOccurred())
+			})
+
+			It("should create ConfigAuditReport", func() {
+				err := cmd.Run(versionInfo, []string{
+					"starboard",
+					"scan", "configauditreports", "cronjob/hello",
+					"--namespace", namespaceItest,
+					"-v", starboardCLILogLevel,
+				}, GinkgoWriter, GinkgoWriter)
+				Expect(err).ToNot(HaveOccurred())
+
+				var reportList v1alpha1.ConfigAuditReportList
+
+				err = kubeClient.List(context.Background(), &reportList, client.MatchingLabels{
+					kube.LabelResourceKind:      string(kube.KindCronJob),
+					kube.LabelResourceName:      cronJob.Name,
+					kube.LabelResourceNamespace: cronJob.Namespace,
+				})
+				Expect(err).ToNot(HaveOccurred())
+
+				Expect(reportList.Items).To(MatchAllElements(resourceNameAsIDFn, Elements{
+					"hello": MatchFields(IgnoreExtras, Fields{
+
+						"ObjectMeta": MatchFields(IgnoreExtras, Fields{
+							"Labels": MatchAllKeys(Keys{
+								kube.LabelResourceKind:      Equal("CronJob"),
+								kube.LabelResourceName:      Equal("hello"),
+								kube.LabelResourceNamespace: Equal(namespaceItest),
+							}),
+							"OwnerReferences": ConsistOf(metav1.OwnerReference{
+								APIVersion:         "batch/v1beta1",
+								Kind:               "CronJob",
+								Name:               "hello",
+								UID:                cronJob.UID,
+								Controller:         pointer.BoolPtr(true),
+								BlockOwnerDeletion: pointer.BoolPtr(true),
+							}),
+						}),
+						"Report": MatchFields(IgnoreExtras, Fields{
+							"Scanner": Equal(polarisScanner),
+						}),
+					}),
+				}))
+
+			})
+
+			AfterEach(func() {
+				err := kubeClient.Delete(context.Background(), cronJob)
+				Expect(err).ToNot(HaveOccurred())
+			})
+		})
 	})
 
 	Describe("Command scan ciskubebenchreports", func() {
diff --git a/itest/starboard/suite_test.go b/itest/starboard/suite_test.go
index 5d81748..aa386f0 100644
--- a/itest/starboard/suite_test.go
+++ b/itest/starboard/suite_test.go
@@ -12,15 +12,19 @@ import (
 	apicorev1 "k8s.io/api/core/v1"
 	apiextensions "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/client-go/kubernetes"
 	corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
 	"k8s.io/client-go/tools/clientcmd"
 	"k8s.io/klog"
+	"sigs.k8s.io/controller-runtime/pkg/client"
 )
 
 const namespaceItest = "starboard-itest"
 
 var (
+	scheme                 *runtime.Scheme
+	kubeClient             client.Client
 	kubernetesClientset    kubernetes.Interface
 	apiextensionsClientset apiextensions.ApiextensionsV1beta1Interface
 	starboardClientset     starboardapi.Interface
@@ -57,6 +61,10 @@ var _ = BeforeSuite(func() {
 	config, err := clientcmd.BuildConfigFromFlags("", os.Getenv("KUBECONFIG"))
 	Expect(err).ToNot(HaveOccurred())
 
+	scheme = starboard.NewScheme()
+	kubeClient, err = client.New(config, client.Options{Scheme: scheme})
+	Expect(err).ToNot(HaveOccurred())
+
 	kubernetesClientset, err = kubernetes.NewForConfig(config)
 	Expect(err).ToNot(HaveOccurred())
 
diff --git a/pkg/kube/object_test.go b/pkg/kube/object_test.go
index 53c1906..0d8ab61 100644
--- a/pkg/kube/object_test.go
+++ b/pkg/kube/object_test.go
@@ -3,6 +3,7 @@ package kube_test
 import (
 	"context"
 	"errors"
+	"fmt"
 	"testing"
 
 	"github.com/aquasecurity/starboard/pkg/kube"
@@ -21,6 +22,43 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/client/fake"
 )
 
+func TestIsBuiltInWorkload(t *testing.T) {
+	testCases := []struct {
+		kind              string
+		isBuiltInWorkload bool
+	}{
+		{
+			kind:              "ReplicationController",
+			isBuiltInWorkload: true,
+		},
+		{
+			kind:              "ReplicaSet",
+			isBuiltInWorkload: true,
+		},
+		{
+			kind:              "StatefulSet",
+			isBuiltInWorkload: true,
+		},
+		{
+			kind:              "DaemonSet",
+			isBuiltInWorkload: true,
+		},
+		{
+			kind:              "Job",
+			isBuiltInWorkload: true,
+		},
+		{
+			kind:              "ArgoCD",
+			isBuiltInWorkload: false,
+		},
+	}
+	for _, tc := range testCases {
+		t.Run(fmt.Sprintf("Should return %t when controller kind is %s", tc.isBuiltInWorkload, tc.kind), func(t *testing.T) {
+			assert.Equal(t, tc.isBuiltInWorkload, kube.IsBuiltInWorkload(&metav1.OwnerReference{Kind: tc.kind}))
+		})
+	}
+}
+
 func TestObjectFromLabelsSet(t *testing.T) {
 	testCases := []struct {
 		name           string