From 677b39c195d9d6b12fe6c94d6e50749471f9f59f Mon Sep 17 00:00:00 2001 From: Ilyes Ben Dlala Date: Fri, 12 May 2023 16:41:19 +0200 Subject: [PATCH 01/18] #150 added schedule and cronjob logic to scheduledscan controller Signed-off-by: Ilyes Ben Dlala --- .../apis/execution/v1/scheduledscan_types.go | 6 +++ .../execution/scheduledscan_controller.go | 44 ++++++++++++++++--- 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/operator/apis/execution/v1/scheduledscan_types.go b/operator/apis/execution/v1/scheduledscan_types.go index 97007f324a..1a6b80ac76 100644 --- a/operator/apis/execution/v1/scheduledscan_types.go +++ b/operator/apis/execution/v1/scheduledscan_types.go @@ -18,8 +18,14 @@ type ScheduledScanSpec struct { // Interval describes how often the scan should be repeated // Examples: '12h', '30m' + // +kubebuilder:validation:Optional Interval metav1.Duration `json:"interval"` + // +kubebuilder:validation:MinLength=0 + // +kubebuilder:validation:Optional + // The schedule in Cron format, see https://en.wikipedia.org/wiki/Cron. + Schedule string `json:"schedule"` + // SuccessfulJobsHistoryLimit determines how many past Scans will be kept until the oldest one will be deleted, defaults to 3. When set to 0, Scans will be deleted directly after completion // +kubebuilder:validation:Optional // +kubebuilder:validation:Minimum=0 diff --git a/operator/controllers/execution/scheduledscan_controller.go b/operator/controllers/execution/scheduledscan_controller.go index 83f3756f36..984bb2b7d8 100644 --- a/operator/controllers/execution/scheduledscan_controller.go +++ b/operator/controllers/execution/scheduledscan_controller.go @@ -13,6 +13,7 @@ import ( "time" "github.com/go-logr/logr" + "github.com/robfig/cron" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" @@ -100,11 +101,10 @@ func (r *ScheduledScanReconciler) Reconcile(ctx context.Context, req ctrl.Reques } // Calculate the next schedule - var nextSchedule time.Time - if scheduledScan.Status.LastScheduleTime != nil { - nextSchedule = scheduledScan.Status.LastScheduleTime.Add(scheduledScan.Spec.Interval.Duration) - } else { - nextSchedule = time.Now().Add(-1 * time.Second) + nextSchedule, err := getNextSchedule(scheduledScan, time.Now()) + if err != nil { + log.Error(err, "Unable to calculate next schedule") + return ctrl.Result{}, err } // check if it is time to start the next Scan @@ -162,6 +162,40 @@ func (r *ScheduledScanReconciler) Reconcile(ctx context.Context, req ctrl.Reques return ctrl.Result{RequeueAfter: nextSchedule.Sub(time.Now())}, nil } +func getNextSchedule(scheduledScan executionv1.ScheduledScan, now time.Time) (next time.Time, err error) { + // check if the Cron schedule is set + if scheduledScan.Spec.Schedule != "" { + sched, err := cron.ParseStandard(scheduledScan.Spec.Schedule) + if err != nil { + return time.Time{}, fmt.Errorf("Unparseable schedule %q: %v", scheduledScan.Spec.Schedule, err) + } + + // for optimization purposes, cheat a bit and start from our last observed run time + // we could reconstitute this here, but there's not much point, since we've + // just updated it. + var earliestTime time.Time + if scheduledScan.Status.LastScheduleTime != nil { + earliestTime = scheduledScan.Status.LastScheduleTime.Time + } else { + earliestTime = scheduledScan.ObjectMeta.CreationTimestamp.Time + } + if earliestTime.After(now) { + tmp := sched.Next(now) + return tmp, nil + } + } + if scheduledScan.Spec.Interval.Duration > 0 { + var nextSchedule time.Time + if scheduledScan.Status.LastScheduleTime != nil { + nextSchedule = scheduledScan.Status.LastScheduleTime.Add(scheduledScan.Spec.Interval.Duration) + } else { + nextSchedule = time.Now().Add(-1 * time.Second) + } + return nextSchedule, nil + } + return time.Time{}, fmt.Errorf("No schedule or interval found") +} + // Copy over securecodebox.io annotations from the scheduledScan to the created scan func getAnnotationsForScan(scheduledScan executionv1.ScheduledScan) map[string]string { annotations := map[string]string{} From 609a946ffd0f8adbc36b5bc43617e29bcbb09ced Mon Sep 17 00:00:00 2001 From: Ilyes Ben Dlala Date: Fri, 12 May 2023 16:42:23 +0200 Subject: [PATCH 02/18] #150 WIP Added tests for scheduled scans with cron config Signed-off-by: Ilyes Ben Dlala --- .../execution/scantype_controller_test.go | 6 +-- .../scheduledscan_controller_test.go | 46 ++++++++++++++++++- .../controllers/execution/test_utils_test.go | 27 ++++++++++- 3 files changed, 73 insertions(+), 6 deletions(-) diff --git a/operator/controllers/execution/scantype_controller_test.go b/operator/controllers/execution/scantype_controller_test.go index 22b3ec4a69..d9ca48304e 100644 --- a/operator/controllers/execution/scantype_controller_test.go +++ b/operator/controllers/execution/scantype_controller_test.go @@ -33,7 +33,7 @@ var _ = Describe("ScanType controller", func() { createNamespace(ctx, namespace) createScanType(ctx, namespace) - scheduledScan := createScheduledScan(ctx, namespace, true) + scheduledScan := createScheduledScanWithInterval(ctx, namespace, true) // ensure that the ScheduledScan has been triggered waitForScheduledScanToBeTriggered(ctx, namespace) @@ -74,7 +74,7 @@ var _ = Describe("ScanType controller", func() { createNamespace(ctx, namespace) createScanType(ctx, namespace) - scheduledScan := createScheduledScan(ctx, namespace, true) + scheduledScan := createScheduledScanWithInterval(ctx, namespace, true) // ensure that the ScheduledScan has been triggered waitForScheduledScanToBeTriggered(ctx, namespace) @@ -104,7 +104,7 @@ var _ = Describe("ScanType controller", func() { createNamespace(ctx, namespace) createScanType(ctx, namespace) - scheduledScan := createScheduledScan(ctx, namespace, false) + scheduledScan := createScheduledScanWithInterval(ctx, namespace, false) // ensure that the ScheduledScan has been triggered waitForScheduledScanToBeTriggered(ctx, namespace) diff --git a/operator/controllers/execution/scheduledscan_controller_test.go b/operator/controllers/execution/scheduledscan_controller_test.go index acfd2de029..4a0992a003 100644 --- a/operator/controllers/execution/scheduledscan_controller_test.go +++ b/operator/controllers/execution/scheduledscan_controller_test.go @@ -64,14 +64,14 @@ var _ = Describe("ScheduledScan controller", func() { } }) }) - Context("A Scan is triggred due to a Scheduled Scan", func() { + Context("A Scan is triggred due to a Scheduled Scan with Interval in Spec", func() { It("The ScheduledScan's Finding Summary shoud be updated of with the results of the successful Scan", func() { ctx := context.Background() namespace := "scantype-multiple-scheduled-scan-triggerd-test" createNamespace(ctx, namespace) createScanType(ctx, namespace) - scheduledScan := createScheduledScan(ctx, namespace, true) + scheduledScan := createScheduledScanWithInterval(ctx, namespace, true) var scanlist executionv1.ScanList // ensure that the ScheduledScan has been triggered @@ -104,4 +104,46 @@ var _ = Describe("ScheduledScan controller", func() { Expect(scheduledScan.Status.Findings.FindingCategories).Should(Equal(map[string]uint64{"Open Port": 42})) }) }) + + Context("A Scan is triggred due to a Scheduled Scan with Schedule in Spec", func() { + It("The ScheduledScan's Finding Summary shoud be updated of with the results of the successful Scan", func() { + ctx := context.Background() + namespace := "scantype-multiple-scheduled-scan-triggerd-test-schedule" + + createNamespace(ctx, namespace) + createScanType(ctx, namespace) + scheduledScan := createScheduledScanWithSchedule(ctx, namespace, true) + + var scanlist executionv1.ScanList + // ensure that the ScheduledScan has been triggered + waitForScheduledScanToBeTriggered(ctx, namespace) + k8sClient.List(ctx, &scanlist, client.InNamespace(namespace)) + + Expect(scheduledScan.Spec.Schedule).Should(Equal("* * * * *")) + Expect(scanlist.Items).Should(HaveLen(1)) + + scan := scanlist.Items[0] + scan.Status.State = "Done" + + scan.Status.Findings = executionv1.FindingStats{ + Count: 42, + FindingSeverities: executionv1.FindingSeverities{High: 42}, + FindingCategories: map[string]uint64{"Open Port": 42}, + } + + k8sClient.Status().Update(ctx, &scan) + + Eventually(func() bool { + err := k8sClient.Get(ctx, types.NamespacedName{Name: "test-scan", Namespace: namespace}, &scheduledScan) + if errors.IsNotFound(err) { + panic("ScheduledScan should be present for this check!") + } + return scheduledScan.Status.Findings.Count != 0 + }, timeout, interval).Should(BeTrue()) + + Expect(scheduledScan.Status.Findings.Count).Should(Equal(uint64(42))) + Expect(scheduledScan.Status.Findings.FindingSeverities).Should(Equal(executionv1.FindingSeverities{High: 42})) + Expect(scheduledScan.Status.Findings.FindingCategories).Should(Equal(map[string]uint64{"Open Port": 42})) + }) + }) }) diff --git a/operator/controllers/execution/test_utils_test.go b/operator/controllers/execution/test_utils_test.go index dadce804fa..951bc21289 100644 --- a/operator/controllers/execution/test_utils_test.go +++ b/operator/controllers/execution/test_utils_test.go @@ -63,7 +63,7 @@ func createScanType(ctx context.Context, namespace string) { Expect(k8sClient.Create(ctx, scanType)).Should(Succeed()) } -func createScheduledScan(ctx context.Context, namespace string, retriggerOnScanTypeChange bool) executionv1.ScheduledScan { +func createScheduledScanWithInterval(ctx context.Context, namespace string, retriggerOnScanTypeChange bool) executionv1.ScheduledScan { namespaceLocalResourceMode := executionv1.NamespaceLocal scheduledScan := executionv1.ScheduledScan{ @@ -87,3 +87,28 @@ func createScheduledScan(ctx context.Context, namespace string, retriggerOnScanT return scheduledScan } + +func createScheduledScanWithSchedule(ctx context.Context, namespace string, retriggerOnScanTypeChange bool) executionv1.ScheduledScan { + namespaceLocalResourceMode := executionv1.NamespaceLocal + + scheduledScan := executionv1.ScheduledScan{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-scan", + Namespace: namespace, + }, + Spec: executionv1.ScheduledScanSpec{ + Schedule: "* * * * *", + RetriggerOnScanTypeChange: retriggerOnScanTypeChange, + ScanSpec: &executionv1.ScanSpec{ + ScanType: "nmap", + ResourceMode: &namespaceLocalResourceMode, + Parameters: []string{"scanme.nmap.org"}, + }, + }, + } + Expect(k8sClient.Create(ctx, &scheduledScan)).Should(Succeed()) + + Expect(k8sClient.Get(ctx, types.NamespacedName{Name: "test-scan", Namespace: namespace}, &scheduledScan)).Should(Succeed()) + + return scheduledScan +} From 4b48d061dfecb668bbae4e9efa5ec958e3f7892f Mon Sep 17 00:00:00 2001 From: Ilyes Ben Dlala Date: Fri, 12 May 2023 16:42:57 +0200 Subject: [PATCH 03/18] #150 Added auto-gen files and config Signed-off-by: Ilyes Ben Dlala --- operator/.vscode/launch.json | 12 +++++++++++- operator/.vscode/tasks.json | 11 +++++++++++ .../execution.securecodebox.io_scheduledscans.yaml | 5 ++++- operator/go.mod | 1 + operator/go.sum | 2 ++ 5 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 operator/.vscode/tasks.json diff --git a/operator/.vscode/launch.json b/operator/.vscode/launch.json index df17791049..a9fb1ccd86 100644 --- a/operator/.vscode/launch.json +++ b/operator/.vscode/launch.json @@ -8,7 +8,7 @@ "name": "Start Operator (minio)", "type": "go", "request": "launch", - "mode": "auto", + "mode": "debug", "program": "main.go", "env": { "MINIO_ACCESS_KEY": "minioadmin", @@ -26,6 +26,16 @@ "request": "launch", "mode": "auto", "program": "main.go" + }, + { + "name": "Debug Unit Tests", + "type": "go", + "request": "launch", + "mode": "test", + "program": "${workspaceFolder}/controllers/execution", + "args": ["-test.v"], + //"preLaunchTask": "makefileMagic", + "env": {"KUBEBUILDER_ASSETS": "${workspaceFolder}/testbin/bin"} } ] } \ No newline at end of file diff --git a/operator/.vscode/tasks.json b/operator/.vscode/tasks.json new file mode 100644 index 0000000000..88be450e63 --- /dev/null +++ b/operator/.vscode/tasks.json @@ -0,0 +1,11 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "makefileMagic", + "command": "bash", + "args": ["-c", "source ${workspaceFolder}/testbin/setup-envtest.sh && fetch_envtest_tools ${workspaceFolder}/testbin && setup_envtest_env ${workspaceFolder}/testbin"], + "type": "shell" + }, +] +} \ No newline at end of file diff --git a/operator/config/crd/bases/execution.securecodebox.io_scheduledscans.yaml b/operator/config/crd/bases/execution.securecodebox.io_scheduledscans.yaml index 02adc44843..432ec7ffcc 100644 --- a/operator/config/crd/bases/execution.securecodebox.io_scheduledscans.yaml +++ b/operator/config/crd/bases/execution.securecodebox.io_scheduledscans.yaml @@ -4240,6 +4240,10 @@ spec: type: object type: array type: object + schedule: + description: The schedule in Cron format, see https://en.wikipedia.org/wiki/Cron. + minLength: 0 + type: string successfulJobsHistoryLimit: description: SuccessfulJobsHistoryLimit determines how many past Scans will be kept until the oldest one will be deleted, defaults to 3. @@ -4248,7 +4252,6 @@ spec: minimum: 0 type: integer required: - - interval - scanSpec type: object status: diff --git a/operator/go.mod b/operator/go.mod index 1f2fb67436..226cde4c26 100644 --- a/operator/go.mod +++ b/operator/go.mod @@ -66,6 +66,7 @@ require ( github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.34.0 // indirect github.com/prometheus/procfs v0.7.3 // indirect + github.com/robfig/cron v1.2.0 github.com/rs/xid v1.4.0 // indirect github.com/sirupsen/logrus v1.8.1 // indirect github.com/spf13/pflag v1.0.5 // indirect diff --git a/operator/go.sum b/operator/go.sum index 891febcb87..4dc7ff8780 100644 --- a/operator/go.sum +++ b/operator/go.sum @@ -483,6 +483,8 @@ github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1 github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ= +github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= From 0fe8e473fd32542d44e166435a54ffa8db859eaf Mon Sep 17 00:00:00 2001 From: Ilyes Ben Dlala Date: Tue, 16 May 2023 16:58:20 +0200 Subject: [PATCH 04/18] fix newline in makefile of operator Signed-off-by: Ilyes Ben Dlala --- operator/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/operator/Makefile b/operator/Makefile index aaf0605d2d..b7841cb51f 100644 --- a/operator/Makefile +++ b/operator/Makefile @@ -144,7 +144,7 @@ helm-deploy: --set="image.pullPolicy=IfNotPresent" \ --set="lurker.image.repository=docker.io/$(IMG_NS)/$(LURKER_IMG)" \ --set="lurker.image.tag=$(IMG_TAG)" \ - --set="lurker.image.pullPolicy=IfNotPresent" + --set="lurker.image.pullPolicy=IfNotPresent" \ --set="minio.auth.rootUser = $(MINIO_ROOT_USER)" \ --set="minio.auth.rootPassword = $(MINIO_ROOT_PASSWORD)" From d83c5f07624048a298b292e7cb9db412ef2d14fd Mon Sep 17 00:00:00 2001 From: Ilyes Ben Dlala Date: Tue, 16 May 2023 16:59:17 +0200 Subject: [PATCH 05/18] #180 Added "Schedule" print column to ScheduledScan CRD Signed-off-by: Ilyes Ben Dlala --- operator/apis/execution/v1/scheduledscan_types.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/operator/apis/execution/v1/scheduledscan_types.go b/operator/apis/execution/v1/scheduledscan_types.go index 1a6b80ac76..09b06365a3 100644 --- a/operator/apis/execution/v1/scheduledscan_types.go +++ b/operator/apis/execution/v1/scheduledscan_types.go @@ -21,9 +21,8 @@ type ScheduledScanSpec struct { // +kubebuilder:validation:Optional Interval metav1.Duration `json:"interval"` - // +kubebuilder:validation:MinLength=0 - // +kubebuilder:validation:Optional // The schedule in Cron format, see https://en.wikipedia.org/wiki/Cron. + // +kubebuilder:validation:Optional Schedule string `json:"schedule"` // SuccessfulJobsHistoryLimit determines how many past Scans will be kept until the oldest one will be deleted, defaults to 3. When set to 0, Scans will be deleted directly after completion @@ -65,6 +64,7 @@ type ScheduledScanStatus struct { // +kubebuilder:printcolumn:name="UID",type=string,JSONPath=`.metadata.uid`,description="K8s Resource UID",priority=1 // +kubebuilder:printcolumn:name="Type",type=string,JSONPath=`.spec.scanSpec.scanType`,description="Scan Type" // +kubebuilder:printcolumn:name="Interval",type=string,JSONPath=`.spec.interval`,description="Interval" +// +kubebuilder:printcolumn:name="Schedule",type=string,JSONPath=`.spec.schedule`,description="Schedule" // +kubebuilder:printcolumn:name="Findings",type=string,JSONPath=`.status.findings.count`,description="Total Finding Count" // +kubebuilder:printcolumn:name="Parameters",type=string,JSONPath=`.spec.scanSpec.parameters`,description="Arguments passed to the Scanner",priority=1 From f8e4a1dce7941ed401c463c32cc80b26e6e91e18 Mon Sep 17 00:00:00 2001 From: Ilyes Ben Dlala Date: Tue, 16 May 2023 17:00:06 +0200 Subject: [PATCH 06/18] #180 Regenerated CRD yaml files Signed-off-by: Ilyes Ben Dlala --- .../crd/bases/execution.securecodebox.io_scheduledscans.yaml | 1 - operator/crds/execution.securecodebox.io_scheduledscans.yaml | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/operator/config/crd/bases/execution.securecodebox.io_scheduledscans.yaml b/operator/config/crd/bases/execution.securecodebox.io_scheduledscans.yaml index 432ec7ffcc..466693b4d2 100644 --- a/operator/config/crd/bases/execution.securecodebox.io_scheduledscans.yaml +++ b/operator/config/crd/bases/execution.securecodebox.io_scheduledscans.yaml @@ -4242,7 +4242,6 @@ spec: type: object schedule: description: The schedule in Cron format, see https://en.wikipedia.org/wiki/Cron. - minLength: 0 type: string successfulJobsHistoryLimit: description: SuccessfulJobsHistoryLimit determines how many past Scans diff --git a/operator/crds/execution.securecodebox.io_scheduledscans.yaml b/operator/crds/execution.securecodebox.io_scheduledscans.yaml index d71b360f9a..e1a0992408 100644 --- a/operator/crds/execution.securecodebox.io_scheduledscans.yaml +++ b/operator/crds/execution.securecodebox.io_scheduledscans.yaml @@ -4813,6 +4813,9 @@ spec: type: object type: array type: object + schedule: + description: The schedule in Cron format, see https://en.wikipedia.org/wiki/Cron. + type: string successfulJobsHistoryLimit: description: SuccessfulJobsHistoryLimit determines how many past Scans From b3afdd41dfcca7cdf0dfcd49a9a11633fe0958bd Mon Sep 17 00:00:00 2001 From: Ilyes Ben Dlala Date: Tue, 16 May 2023 17:00:53 +0200 Subject: [PATCH 07/18] Fixed earliest time logic in getNextSchedule for ScheduledScans Signed-off-by: Ilyes Ben Dlala --- operator/controllers/execution/scheduledscan_controller.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/operator/controllers/execution/scheduledscan_controller.go b/operator/controllers/execution/scheduledscan_controller.go index 984bb2b7d8..150dffabc9 100644 --- a/operator/controllers/execution/scheduledscan_controller.go +++ b/operator/controllers/execution/scheduledscan_controller.go @@ -180,9 +180,9 @@ func getNextSchedule(scheduledScan executionv1.ScheduledScan, now time.Time) (ne earliestTime = scheduledScan.ObjectMeta.CreationTimestamp.Time } if earliestTime.After(now) { - tmp := sched.Next(now) - return tmp, nil + return sched.Next(now), nil } + return sched.Next(earliestTime), nil } if scheduledScan.Spec.Interval.Duration > 0 { var nextSchedule time.Time From ba357566c0ac78e5f725777c270e1d5b774ff73c Mon Sep 17 00:00:00 2001 From: Ilyes Ben Dlala Date: Tue, 16 May 2023 17:20:58 +0200 Subject: [PATCH 08/18] #180 Fix to generated scheduledScan yaml file Signed-off-by: Ilyes Ben Dlala --- .../crd/bases/execution.securecodebox.io_scheduledscans.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/operator/config/crd/bases/execution.securecodebox.io_scheduledscans.yaml b/operator/config/crd/bases/execution.securecodebox.io_scheduledscans.yaml index 466693b4d2..71a7430485 100644 --- a/operator/config/crd/bases/execution.securecodebox.io_scheduledscans.yaml +++ b/operator/config/crd/bases/execution.securecodebox.io_scheduledscans.yaml @@ -29,6 +29,10 @@ spec: jsonPath: .spec.interval name: Interval type: string + - description: Schedule + jsonPath: .spec.schedule + name: Schedule + type: string - description: Total Finding Count jsonPath: .status.findings.count name: Findings From 1e4ea5c986bc15c9486988f256c68a17b22db63c Mon Sep 17 00:00:00 2001 From: Ilyes Ben Dlala Date: Tue, 23 May 2023 14:28:05 +0200 Subject: [PATCH 09/18] #150 Added a fake Clock that allows for testing crontab ScheduledScans fakeClock can fake the passing of time Signed-off-by: Ilyes Ben Dlala --- .../execution/scheduledscan_controller.go | 31 +++++++++++++++---- .../scheduledscan_controller_test.go | 8 ++++- operator/controllers/execution/suite_test.go | 12 +++++++ .../controllers/execution/test_utils_test.go | 3 +- 4 files changed, 46 insertions(+), 8 deletions(-) diff --git a/operator/controllers/execution/scheduledscan_controller.go b/operator/controllers/execution/scheduledscan_controller.go index 150dffabc9..6ac5fc87f3 100644 --- a/operator/controllers/execution/scheduledscan_controller.go +++ b/operator/controllers/execution/scheduledscan_controller.go @@ -34,6 +34,21 @@ type ScheduledScanReconciler struct { client.Client Log logr.Logger Scheme *runtime.Scheme + Clock +} + +/* +We'll mock out the clock to make it easier to jump around in time while testing, +the "real" clock just calls `time.Now`. +*/ +type realClock struct{} + +func (_ realClock) Now() time.Time { return time.Now() } + +// clock knows how to get the current time. +// It can be used to fake out timing for testing. +type Clock interface { + Now() time.Time } // +kubebuilder:rbac:groups=execution.securecodebox.io,resources=scheduledscans,verbs=get;list;watch;create;update;patch;delete @@ -101,14 +116,14 @@ func (r *ScheduledScanReconciler) Reconcile(ctx context.Context, req ctrl.Reques } // Calculate the next schedule - nextSchedule, err := getNextSchedule(scheduledScan, time.Now()) + nextSchedule, err := getNextSchedule(r, scheduledScan, r.Clock.Now()) if err != nil { log.Error(err, "Unable to calculate next schedule") return ctrl.Result{}, err } // check if it is time to start the next Scan - if !time.Now().Before(nextSchedule) { + if !r.Clock.Now().Before(nextSchedule) { if scheduledScan.Spec.RetriggerOnScanTypeChange == true { // generate hash for current state of the configured ScanType var scanType executionv1.ScanType @@ -156,13 +171,13 @@ func (r *ScheduledScanReconciler) Reconcile(ctx context.Context, req ctrl.Reques } // Recalculate next schedule - nextSchedule = time.Now().Add(scheduledScan.Spec.Interval.Duration) + nextSchedule = r.Clock.Now().Add(scheduledScan.Spec.Interval.Duration) } - return ctrl.Result{RequeueAfter: nextSchedule.Sub(time.Now())}, nil + return ctrl.Result{RequeueAfter: nextSchedule.Sub(r.Clock.Now())}, nil } -func getNextSchedule(scheduledScan executionv1.ScheduledScan, now time.Time) (next time.Time, err error) { +func getNextSchedule(r *ScheduledScanReconciler, scheduledScan executionv1.ScheduledScan, now time.Time) (next time.Time, err error) { // check if the Cron schedule is set if scheduledScan.Spec.Schedule != "" { sched, err := cron.ParseStandard(scheduledScan.Spec.Schedule) @@ -189,7 +204,7 @@ func getNextSchedule(scheduledScan executionv1.ScheduledScan, now time.Time) (ne if scheduledScan.Status.LastScheduleTime != nil { nextSchedule = scheduledScan.Status.LastScheduleTime.Add(scheduledScan.Spec.Interval.Duration) } else { - nextSchedule = time.Now().Add(-1 * time.Second) + nextSchedule = r.Clock.Now().Add(-1 * time.Second) } return nextSchedule, nil } @@ -246,6 +261,10 @@ func (r *ScheduledScanReconciler) deleteOldScans(scans []executionv1.Scan, maxCo // SetupWithManager sets up the controller and initializes every thing it needs func (r *ScheduledScanReconciler) SetupWithManager(mgr ctrl.Manager) error { + // set up a real clock, since we're not in a test + if r.Clock == nil { + r.Clock = realClock{} + } ctx := context.Background() if err := mgr.GetFieldIndexer().IndexField(ctx, &executionv1.Scan{}, ownerKey, func(rawObj client.Object) []string { // grab the job object, extract the owner... diff --git a/operator/controllers/execution/scheduledscan_controller_test.go b/operator/controllers/execution/scheduledscan_controller_test.go index 4a0992a003..ce806cc7db 100644 --- a/operator/controllers/execution/scheduledscan_controller_test.go +++ b/operator/controllers/execution/scheduledscan_controller_test.go @@ -5,6 +5,7 @@ package controllers import ( "context" + "time" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -52,6 +53,7 @@ var _ = Describe("ScheduledScan controller", func() { }, } It("Should drop all annotations not prefixed with \"*.securecodebox.io/*\"", func() { + FakeClock.Reset() // making sure the clock is reset before we start for _, test := range tests { scheduledScan := executionv1.ScheduledScan{ ObjectMeta: metav1.ObjectMeta{ @@ -66,6 +68,7 @@ var _ = Describe("ScheduledScan controller", func() { }) Context("A Scan is triggred due to a Scheduled Scan with Interval in Spec", func() { It("The ScheduledScan's Finding Summary shoud be updated of with the results of the successful Scan", func() { + FakeClock.Reset() // making sure the clock is reset before we start ctx := context.Background() namespace := "scantype-multiple-scheduled-scan-triggerd-test" @@ -107,6 +110,7 @@ var _ = Describe("ScheduledScan controller", func() { Context("A Scan is triggred due to a Scheduled Scan with Schedule in Spec", func() { It("The ScheduledScan's Finding Summary shoud be updated of with the results of the successful Scan", func() { + FakeClock.Reset() // making sure the clock is reset before we start ctx := context.Background() namespace := "scantype-multiple-scheduled-scan-triggerd-test-schedule" @@ -115,11 +119,13 @@ var _ = Describe("ScheduledScan controller", func() { scheduledScan := createScheduledScanWithSchedule(ctx, namespace, true) var scanlist executionv1.ScanList + // Fake a minute passing + FakeClock.TimeTravel(1 * time.Minute) // ensure that the ScheduledScan has been triggered waitForScheduledScanToBeTriggered(ctx, namespace) k8sClient.List(ctx, &scanlist, client.InNamespace(namespace)) - Expect(scheduledScan.Spec.Schedule).Should(Equal("* * * * *")) + Expect(scheduledScan.Spec.Schedule).Should(Equal("*/1 * * * *")) Expect(scanlist.Items).Should(HaveLen(1)) scan := scanlist.Items[0] diff --git a/operator/controllers/execution/suite_test.go b/operator/controllers/execution/suite_test.go index 425ea0079c..0e5e57da5d 100644 --- a/operator/controllers/execution/suite_test.go +++ b/operator/controllers/execution/suite_test.go @@ -8,6 +8,7 @@ import ( "context" "path/filepath" "testing" + "time" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -36,6 +37,16 @@ var testEnv *envtest.Environment var ctx context.Context var cancel context.CancelFunc +type fakeClock struct { + timeToAdd time.Duration +} + +func (f fakeClock) Now() time.Time { return time.Now().Add(f.timeToAdd) } +func (f fakeClock) TimeTravel(d time.Duration) { f.timeToAdd += d } +func (f fakeClock) Reset() { f.timeToAdd = 0 } + +var FakeClock = &fakeClock{timeToAdd: 0} + func TestAPIs(t *testing.T) { RegisterFailHandler(Fail) @@ -83,6 +94,7 @@ var _ = BeforeSuite(func() { Client: k8sManager.GetClient(), Scheme: k8sManager.GetScheme(), Log: ctrl.Log.WithName("controllers").WithName("ScheduledScanController"), + Clock: FakeClock, }).SetupWithManager(k8sManager) Expect(err).ToNot(HaveOccurred()) diff --git a/operator/controllers/execution/test_utils_test.go b/operator/controllers/execution/test_utils_test.go index 951bc21289..7d0619b6d3 100644 --- a/operator/controllers/execution/test_utils_test.go +++ b/operator/controllers/execution/test_utils_test.go @@ -97,7 +97,8 @@ func createScheduledScanWithSchedule(ctx context.Context, namespace string, retr Namespace: namespace, }, Spec: executionv1.ScheduledScanSpec{ - Schedule: "* * * * *", + Schedule: "*/1 * * * *", + Interval: metav1.Duration{Duration: 42 * time.Hour}, RetriggerOnScanTypeChange: retriggerOnScanTypeChange, ScanSpec: &executionv1.ScanSpec{ ScanType: "nmap", From 779db145c8d50af12ac47f8694c79424d7e4422f Mon Sep 17 00:00:00 2001 From: Ilyes Ben Dlala Date: Tue, 6 Jun 2023 15:03:22 +0200 Subject: [PATCH 10/18] #150 Commented out the test for cronjob scheduled scan until a solution is found and included the changes to FakeClock. It will be however later changed depending on the solution Signed-off-by: Ilyes Ben Dlala --- .../execution/scheduledscan_controller.go | 4 +- .../scheduledscan_controller_test.go | 45 +++++++++---------- operator/controllers/execution/suite_test.go | 9 ++-- .../controllers/execution/test_utils_test.go | 2 +- 4 files changed, 29 insertions(+), 31 deletions(-) diff --git a/operator/controllers/execution/scheduledscan_controller.go b/operator/controllers/execution/scheduledscan_controller.go index 6ac5fc87f3..863cc3fc5f 100644 --- a/operator/controllers/execution/scheduledscan_controller.go +++ b/operator/controllers/execution/scheduledscan_controller.go @@ -171,7 +171,7 @@ func (r *ScheduledScanReconciler) Reconcile(ctx context.Context, req ctrl.Reques } // Recalculate next schedule - nextSchedule = r.Clock.Now().Add(scheduledScan.Spec.Interval.Duration) + nextSchedule, err = getNextSchedule(r, scheduledScan, r.Clock.Now()) } return ctrl.Result{RequeueAfter: nextSchedule.Sub(r.Clock.Now())}, nil @@ -263,7 +263,7 @@ func (r *ScheduledScanReconciler) deleteOldScans(scans []executionv1.Scan, maxCo func (r *ScheduledScanReconciler) SetupWithManager(mgr ctrl.Manager) error { // set up a real clock, since we're not in a test if r.Clock == nil { - r.Clock = realClock{} + r.Clock = &realClock{} } ctx := context.Background() if err := mgr.GetFieldIndexer().IndexField(ctx, &executionv1.Scan{}, ownerKey, func(rawObj client.Object) []string { diff --git a/operator/controllers/execution/scheduledscan_controller_test.go b/operator/controllers/execution/scheduledscan_controller_test.go index ce806cc7db..b84354ad3d 100644 --- a/operator/controllers/execution/scheduledscan_controller_test.go +++ b/operator/controllers/execution/scheduledscan_controller_test.go @@ -5,7 +5,6 @@ package controllers import ( "context" - "time" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -53,7 +52,6 @@ var _ = Describe("ScheduledScan controller", func() { }, } It("Should drop all annotations not prefixed with \"*.securecodebox.io/*\"", func() { - FakeClock.Reset() // making sure the clock is reset before we start for _, test := range tests { scheduledScan := executionv1.ScheduledScan{ ObjectMeta: metav1.ObjectMeta{ @@ -68,7 +66,6 @@ var _ = Describe("ScheduledScan controller", func() { }) Context("A Scan is triggred due to a Scheduled Scan with Interval in Spec", func() { It("The ScheduledScan's Finding Summary shoud be updated of with the results of the successful Scan", func() { - FakeClock.Reset() // making sure the clock is reset before we start ctx := context.Background() namespace := "scantype-multiple-scheduled-scan-triggerd-test" @@ -107,26 +104,26 @@ var _ = Describe("ScheduledScan controller", func() { Expect(scheduledScan.Status.Findings.FindingCategories).Should(Equal(map[string]uint64{"Open Port": 42})) }) }) - - Context("A Scan is triggred due to a Scheduled Scan with Schedule in Spec", func() { - It("The ScheduledScan's Finding Summary shoud be updated of with the results of the successful Scan", func() { - FakeClock.Reset() // making sure the clock is reset before we start - ctx := context.Background() - namespace := "scantype-multiple-scheduled-scan-triggerd-test-schedule" - - createNamespace(ctx, namespace) - createScanType(ctx, namespace) - scheduledScan := createScheduledScanWithSchedule(ctx, namespace, true) - - var scanlist executionv1.ScanList - // Fake a minute passing - FakeClock.TimeTravel(1 * time.Minute) - // ensure that the ScheduledScan has been triggered - waitForScheduledScanToBeTriggered(ctx, namespace) - k8sClient.List(ctx, &scanlist, client.InNamespace(namespace)) - - Expect(scheduledScan.Spec.Schedule).Should(Equal("*/1 * * * *")) - Expect(scanlist.Items).Should(HaveLen(1)) + /* + Context("A Scan is triggred due to a Scheduled Scan with Schedule in Spec", func() { + It("The ScheduledScan's Finding Summary shoud be updated of with the results of the successful Scan", func() { + ctx := context.Background() + namespace := "scantype-multiple-scheduled-scan-triggerd-test-schedule" + + createNamespace(ctx, namespace) + createScanType(ctx, namespace) + scheduledScan := createScheduledScanWithSchedule(ctx, namespace, true) + + var scanlist executionv1.ScanList + // Fake a minute passing + FakeClock.TimeTravel(2 * time.Minute) + + // ensure that the ScheduledScan has been triggered + waitForScheduledScanToBeTriggered(ctx, namespace) + k8sClient.List(ctx, &scanlist, client.InNamespace(namespace)) + */ + // Expect(scheduledScan.Spec.Schedule).Should(Equal("*/2 * * * *")) + /* Expect(scanlist.Items).Should(HaveLen(1)) scan := scanlist.Items[0] scan.Status.State = "Done" @@ -151,5 +148,5 @@ var _ = Describe("ScheduledScan controller", func() { Expect(scheduledScan.Status.Findings.FindingSeverities).Should(Equal(executionv1.FindingSeverities{High: 42})) Expect(scheduledScan.Status.Findings.FindingCategories).Should(Equal(map[string]uint64{"Open Port": 42})) }) - }) + })*/ }) diff --git a/operator/controllers/execution/suite_test.go b/operator/controllers/execution/suite_test.go index 0e5e57da5d..aa1a4258a9 100644 --- a/operator/controllers/execution/suite_test.go +++ b/operator/controllers/execution/suite_test.go @@ -41,12 +41,13 @@ type fakeClock struct { timeToAdd time.Duration } -func (f fakeClock) Now() time.Time { return time.Now().Add(f.timeToAdd) } -func (f fakeClock) TimeTravel(d time.Duration) { f.timeToAdd += d } -func (f fakeClock) Reset() { f.timeToAdd = 0 } - +var FakeTime = time.Date(2023, 1, 1, 15, 0, 0, 0, time.UTC) var FakeClock = &fakeClock{timeToAdd: 0} +func (f *fakeClock) Now() time.Time { return FakeTime.Add(f.timeToAdd) } +func (f *fakeClock) TimeTravel(d time.Duration) { f.timeToAdd += d } +func (f *fakeClock) Reset() { f.timeToAdd = 0 } + func TestAPIs(t *testing.T) { RegisterFailHandler(Fail) diff --git a/operator/controllers/execution/test_utils_test.go b/operator/controllers/execution/test_utils_test.go index 7d0619b6d3..a3f996d78a 100644 --- a/operator/controllers/execution/test_utils_test.go +++ b/operator/controllers/execution/test_utils_test.go @@ -97,7 +97,7 @@ func createScheduledScanWithSchedule(ctx context.Context, namespace string, retr Namespace: namespace, }, Spec: executionv1.ScheduledScanSpec{ - Schedule: "*/1 * * * *", + Schedule: "*/2 * * * *", Interval: metav1.Duration{Duration: 42 * time.Hour}, RetriggerOnScanTypeChange: retriggerOnScanTypeChange, ScanSpec: &executionv1.ScanSpec{ From f95bd323308ea64c0bd79df7175795cea5240f9f Mon Sep 17 00:00:00 2001 From: Ilyes Ben Dlala Date: Tue, 6 Jun 2023 16:31:17 +0200 Subject: [PATCH 11/18] #150 replaced fakeClock with active waiting Signed-off-by: Ilyes Ben Dlala --- .../execution/scheduledscan_controller.go | 28 +++--------- .../scheduledscan_controller_test.go | 43 ++++++++++--------- operator/controllers/execution/suite_test.go | 1 - .../controllers/execution/test_utils_test.go | 2 +- 4 files changed, 28 insertions(+), 46 deletions(-) diff --git a/operator/controllers/execution/scheduledscan_controller.go b/operator/controllers/execution/scheduledscan_controller.go index 863cc3fc5f..e929dbaf95 100644 --- a/operator/controllers/execution/scheduledscan_controller.go +++ b/operator/controllers/execution/scheduledscan_controller.go @@ -34,21 +34,6 @@ type ScheduledScanReconciler struct { client.Client Log logr.Logger Scheme *runtime.Scheme - Clock -} - -/* -We'll mock out the clock to make it easier to jump around in time while testing, -the "real" clock just calls `time.Now`. -*/ -type realClock struct{} - -func (_ realClock) Now() time.Time { return time.Now() } - -// clock knows how to get the current time. -// It can be used to fake out timing for testing. -type Clock interface { - Now() time.Time } // +kubebuilder:rbac:groups=execution.securecodebox.io,resources=scheduledscans,verbs=get;list;watch;create;update;patch;delete @@ -116,14 +101,14 @@ func (r *ScheduledScanReconciler) Reconcile(ctx context.Context, req ctrl.Reques } // Calculate the next schedule - nextSchedule, err := getNextSchedule(r, scheduledScan, r.Clock.Now()) + nextSchedule, err := getNextSchedule(r, scheduledScan, time.Now()) if err != nil { log.Error(err, "Unable to calculate next schedule") return ctrl.Result{}, err } // check if it is time to start the next Scan - if !r.Clock.Now().Before(nextSchedule) { + if !time.Now().Before(nextSchedule) { if scheduledScan.Spec.RetriggerOnScanTypeChange == true { // generate hash for current state of the configured ScanType var scanType executionv1.ScanType @@ -171,10 +156,10 @@ func (r *ScheduledScanReconciler) Reconcile(ctx context.Context, req ctrl.Reques } // Recalculate next schedule - nextSchedule, err = getNextSchedule(r, scheduledScan, r.Clock.Now()) + nextSchedule, err = getNextSchedule(r, scheduledScan, time.Now()) } - return ctrl.Result{RequeueAfter: nextSchedule.Sub(r.Clock.Now())}, nil + return ctrl.Result{RequeueAfter: nextSchedule.Sub(time.Now())}, nil } func getNextSchedule(r *ScheduledScanReconciler, scheduledScan executionv1.ScheduledScan, now time.Time) (next time.Time, err error) { @@ -204,7 +189,7 @@ func getNextSchedule(r *ScheduledScanReconciler, scheduledScan executionv1.Sched if scheduledScan.Status.LastScheduleTime != nil { nextSchedule = scheduledScan.Status.LastScheduleTime.Add(scheduledScan.Spec.Interval.Duration) } else { - nextSchedule = r.Clock.Now().Add(-1 * time.Second) + nextSchedule = time.Now().Add(-1 * time.Second) } return nextSchedule, nil } @@ -262,9 +247,6 @@ func (r *ScheduledScanReconciler) deleteOldScans(scans []executionv1.Scan, maxCo // SetupWithManager sets up the controller and initializes every thing it needs func (r *ScheduledScanReconciler) SetupWithManager(mgr ctrl.Manager) error { // set up a real clock, since we're not in a test - if r.Clock == nil { - r.Clock = &realClock{} - } ctx := context.Background() if err := mgr.GetFieldIndexer().IndexField(ctx, &executionv1.Scan{}, ownerKey, func(rawObj client.Object) []string { // grab the job object, extract the owner... diff --git a/operator/controllers/execution/scheduledscan_controller_test.go b/operator/controllers/execution/scheduledscan_controller_test.go index b84354ad3d..837de262fb 100644 --- a/operator/controllers/execution/scheduledscan_controller_test.go +++ b/operator/controllers/execution/scheduledscan_controller_test.go @@ -5,6 +5,7 @@ package controllers import ( "context" + "time" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -104,26 +105,26 @@ var _ = Describe("ScheduledScan controller", func() { Expect(scheduledScan.Status.Findings.FindingCategories).Should(Equal(map[string]uint64{"Open Port": 42})) }) }) - /* - Context("A Scan is triggred due to a Scheduled Scan with Schedule in Spec", func() { - It("The ScheduledScan's Finding Summary shoud be updated of with the results of the successful Scan", func() { - ctx := context.Background() - namespace := "scantype-multiple-scheduled-scan-triggerd-test-schedule" - - createNamespace(ctx, namespace) - createScanType(ctx, namespace) - scheduledScan := createScheduledScanWithSchedule(ctx, namespace, true) - - var scanlist executionv1.ScanList - // Fake a minute passing - FakeClock.TimeTravel(2 * time.Minute) - - // ensure that the ScheduledScan has been triggered - waitForScheduledScanToBeTriggered(ctx, namespace) - k8sClient.List(ctx, &scanlist, client.InNamespace(namespace)) - */ - // Expect(scheduledScan.Spec.Schedule).Should(Equal("*/2 * * * *")) - /* Expect(scanlist.Items).Should(HaveLen(1)) + + Context("A Scan is triggred due to a Scheduled Scan with Schedule in Spec", func() { + It("The ScheduledScan's Finding Summary shoud be updated of with the results of the successful Scan", func() { + ctx := context.Background() + namespace := "scantype-multiple-scheduled-scan-triggerd-test-schedule" + + createNamespace(ctx, namespace) + createScanType(ctx, namespace) + scheduledScan := createScheduledScanWithSchedule(ctx, namespace, true) + + var scanlist executionv1.ScanList + + // ensure that the ScheduledScan has been triggered + time.Sleep(51 * time.Second) + + waitForScheduledScanToBeTriggered(ctx, namespace) + k8sClient.List(ctx, &scanlist, client.InNamespace(namespace)) + + Expect(scheduledScan.Spec.Schedule).Should(Equal("*/1 * * * *")) + Expect(scanlist.Items).Should(HaveLen(1)) scan := scanlist.Items[0] scan.Status.State = "Done" @@ -148,5 +149,5 @@ var _ = Describe("ScheduledScan controller", func() { Expect(scheduledScan.Status.Findings.FindingSeverities).Should(Equal(executionv1.FindingSeverities{High: 42})) Expect(scheduledScan.Status.Findings.FindingCategories).Should(Equal(map[string]uint64{"Open Port": 42})) }) - })*/ + }) }) diff --git a/operator/controllers/execution/suite_test.go b/operator/controllers/execution/suite_test.go index aa1a4258a9..ea73ca6ec4 100644 --- a/operator/controllers/execution/suite_test.go +++ b/operator/controllers/execution/suite_test.go @@ -95,7 +95,6 @@ var _ = BeforeSuite(func() { Client: k8sManager.GetClient(), Scheme: k8sManager.GetScheme(), Log: ctrl.Log.WithName("controllers").WithName("ScheduledScanController"), - Clock: FakeClock, }).SetupWithManager(k8sManager) Expect(err).ToNot(HaveOccurred()) diff --git a/operator/controllers/execution/test_utils_test.go b/operator/controllers/execution/test_utils_test.go index a3f996d78a..7d0619b6d3 100644 --- a/operator/controllers/execution/test_utils_test.go +++ b/operator/controllers/execution/test_utils_test.go @@ -97,7 +97,7 @@ func createScheduledScanWithSchedule(ctx context.Context, namespace string, retr Namespace: namespace, }, Spec: executionv1.ScheduledScanSpec{ - Schedule: "*/2 * * * *", + Schedule: "*/1 * * * *", Interval: metav1.Duration{Duration: 42 * time.Hour}, RetriggerOnScanTypeChange: retriggerOnScanTypeChange, ScanSpec: &executionv1.ScanSpec{ From 1e8f7ec2d784daaa4de0424bd144f23cbfa7870b Mon Sep 17 00:00:00 2001 From: Ilyes Ben Dlala Date: Tue, 13 Jun 2023 11:24:23 +0200 Subject: [PATCH 12/18] #150 Replaced tests of active waiting with polling for scheduledScan Cron Schedule tests This minimizes how much the test is slowed down (slower by between 0 and 50 seconds) Signed-off-by: Ilyes Ben Dlala --- .../execution/scantype_controller_test.go | 8 ++--- .../scheduledscan_controller_test.go | 31 ++----------------- 2 files changed, 7 insertions(+), 32 deletions(-) diff --git a/operator/controllers/execution/scantype_controller_test.go b/operator/controllers/execution/scantype_controller_test.go index d9ca48304e..1dcab32322 100644 --- a/operator/controllers/execution/scantype_controller_test.go +++ b/operator/controllers/execution/scantype_controller_test.go @@ -36,7 +36,7 @@ var _ = Describe("ScanType controller", func() { scheduledScan := createScheduledScanWithInterval(ctx, namespace, true) // ensure that the ScheduledScan has been triggered - waitForScheduledScanToBeTriggered(ctx, namespace) + waitForScheduledScanToBeTriggered(ctx, namespace, timeout) k8sClient.Get(ctx, types.NamespacedName{Name: "test-scan", Namespace: namespace}, &scheduledScan) initialExecutionTime := *scheduledScan.Status.LastScheduleTime @@ -77,7 +77,7 @@ var _ = Describe("ScanType controller", func() { scheduledScan := createScheduledScanWithInterval(ctx, namespace, true) // ensure that the ScheduledScan has been triggered - waitForScheduledScanToBeTriggered(ctx, namespace) + waitForScheduledScanToBeTriggered(ctx, namespace, timeout) k8sClient.Get(ctx, types.NamespacedName{Name: "test-scan", Namespace: namespace}, &scheduledScan) initialExecutionTime := *scheduledScan.Status.LastScheduleTime @@ -107,7 +107,7 @@ var _ = Describe("ScanType controller", func() { scheduledScan := createScheduledScanWithInterval(ctx, namespace, false) // ensure that the ScheduledScan has been triggered - waitForScheduledScanToBeTriggered(ctx, namespace) + waitForScheduledScanToBeTriggered(ctx, namespace, timeout) k8sClient.Get(ctx, types.NamespacedName{Name: "test-scan", Namespace: namespace}, &scheduledScan) initialExecutionTime := *scheduledScan.Status.LastScheduleTime @@ -139,7 +139,7 @@ var _ = Describe("ScanType controller", func() { }) }) -func waitForScheduledScanToBeTriggered(ctx context.Context, namespace string) { +func waitForScheduledScanToBeTriggered(ctx context.Context, namespace string, timeout time.Duration) { var scheduledScan executionv1.ScheduledScan By("Wait for ScheduledScan to trigger the initial Scan") Eventually(func() bool { diff --git a/operator/controllers/execution/scheduledscan_controller_test.go b/operator/controllers/execution/scheduledscan_controller_test.go index 837de262fb..703c8e9cdb 100644 --- a/operator/controllers/execution/scheduledscan_controller_test.go +++ b/operator/controllers/execution/scheduledscan_controller_test.go @@ -76,7 +76,7 @@ var _ = Describe("ScheduledScan controller", func() { var scanlist executionv1.ScanList // ensure that the ScheduledScan has been triggered - waitForScheduledScanToBeTriggered(ctx, namespace) + waitForScheduledScanToBeTriggered(ctx, namespace, timeout) k8sClient.List(ctx, &scanlist, client.InNamespace(namespace)) Expect(scanlist.Items).Should(HaveLen(1)) @@ -107,7 +107,7 @@ var _ = Describe("ScheduledScan controller", func() { }) Context("A Scan is triggred due to a Scheduled Scan with Schedule in Spec", func() { - It("The ScheduledScan's Finding Summary shoud be updated of with the results of the successful Scan", func() { + It("The ScheduledScan's should be triggered according to the Schedule", func() { ctx := context.Background() namespace := "scantype-multiple-scheduled-scan-triggerd-test-schedule" @@ -118,36 +118,11 @@ var _ = Describe("ScheduledScan controller", func() { var scanlist executionv1.ScanList // ensure that the ScheduledScan has been triggered - time.Sleep(51 * time.Second) - - waitForScheduledScanToBeTriggered(ctx, namespace) + waitForScheduledScanToBeTriggered(ctx, namespace, 61*time.Second) k8sClient.List(ctx, &scanlist, client.InNamespace(namespace)) Expect(scheduledScan.Spec.Schedule).Should(Equal("*/1 * * * *")) Expect(scanlist.Items).Should(HaveLen(1)) - - scan := scanlist.Items[0] - scan.Status.State = "Done" - - scan.Status.Findings = executionv1.FindingStats{ - Count: 42, - FindingSeverities: executionv1.FindingSeverities{High: 42}, - FindingCategories: map[string]uint64{"Open Port": 42}, - } - - k8sClient.Status().Update(ctx, &scan) - - Eventually(func() bool { - err := k8sClient.Get(ctx, types.NamespacedName{Name: "test-scan", Namespace: namespace}, &scheduledScan) - if errors.IsNotFound(err) { - panic("ScheduledScan should be present for this check!") - } - return scheduledScan.Status.Findings.Count != 0 - }, timeout, interval).Should(BeTrue()) - - Expect(scheduledScan.Status.Findings.Count).Should(Equal(uint64(42))) - Expect(scheduledScan.Status.Findings.FindingSeverities).Should(Equal(executionv1.FindingSeverities{High: 42})) - Expect(scheduledScan.Status.Findings.FindingCategories).Should(Equal(map[string]uint64{"Open Port": 42})) }) }) }) From 185482f19f912ca5e8a885ee114865e20494ed99 Mon Sep 17 00:00:00 2001 From: Ilyes Ben Dlala Date: Tue, 13 Jun 2023 13:25:31 +0200 Subject: [PATCH 13/18] #150 Increased the timeout for the scheduledScan Cronjob test This is done to make sure that any edge cases do not fail the tests we still expect to be a maximum 60s Signed-off-by: Ilyes Ben Dlala --- operator/controllers/execution/scheduledscan_controller_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/operator/controllers/execution/scheduledscan_controller_test.go b/operator/controllers/execution/scheduledscan_controller_test.go index 703c8e9cdb..2fade0cddb 100644 --- a/operator/controllers/execution/scheduledscan_controller_test.go +++ b/operator/controllers/execution/scheduledscan_controller_test.go @@ -118,7 +118,7 @@ var _ = Describe("ScheduledScan controller", func() { var scanlist executionv1.ScanList // ensure that the ScheduledScan has been triggered - waitForScheduledScanToBeTriggered(ctx, namespace, 61*time.Second) + waitForScheduledScanToBeTriggered(ctx, namespace, 90*time.Second) k8sClient.List(ctx, &scanlist, client.InNamespace(namespace)) Expect(scheduledScan.Spec.Schedule).Should(Equal("*/1 * * * *")) From 9c4fa1ced59fb1c79f48715c3121eec9cf49e169 Mon Sep 17 00:00:00 2001 From: Ilyes Ben Dlala Date: Fri, 16 Jun 2023 11:47:25 +0200 Subject: [PATCH 14/18] #150 Added a go tag to seperate between slow tests and fast tests fast tests can be called by "make test-fast" and all tests will run when command "make test" is used. This is done to keep the CI workflow as usual Signed-off-by: Ilyes Ben Dlala --- operator/Makefile | 6 ++- .../execution/scantype_controller_test.go | 3 ++ .../scheduledscan_controller_slow_test.go | 41 +++++++++++++++++++ .../scheduledscan_controller_test.go | 25 ++--------- .../controllers/execution/test_utils_test.go | 3 ++ 5 files changed, 56 insertions(+), 22 deletions(-) create mode 100644 operator/controllers/execution/scheduledscan_controller_slow_test.go diff --git a/operator/Makefile b/operator/Makefile index b7841cb51f..34f52a53cb 100644 --- a/operator/Makefile +++ b/operator/Makefile @@ -77,7 +77,11 @@ vet: ## Run go vet against code. .PHONY: test test: manifests generate fmt vet envtest ## Run tests. - KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" go test ./... -coverprofile cover.out + KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" go test -tags="fast slow" ./... -coverprofile cover.out + +.PHONY: test-fast +test-fast: manifests generate fmt vet envtest ## Run tests. + KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" go test -tags="fast" ./... -coverprofile cover.out .PHONY: view-coverage view-coverage: diff --git a/operator/controllers/execution/scantype_controller_test.go b/operator/controllers/execution/scantype_controller_test.go index 1dcab32322..27e5137ff5 100644 --- a/operator/controllers/execution/scantype_controller_test.go +++ b/operator/controllers/execution/scantype_controller_test.go @@ -2,6 +2,9 @@ // // SPDX-License-Identifier: Apache-2.0 +//go:build fast +// +build fast + package controllers import ( diff --git a/operator/controllers/execution/scheduledscan_controller_slow_test.go b/operator/controllers/execution/scheduledscan_controller_slow_test.go new file mode 100644 index 0000000000..fd3a273875 --- /dev/null +++ b/operator/controllers/execution/scheduledscan_controller_slow_test.go @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: the secureCodeBox authors +// +// SPDX-License-Identifier: Apache-2.0 + +//go:build slow +// +build slow + +package controllers + +import ( + "context" + "time" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + executionv1 "github.com/secureCodeBox/secureCodeBox/operator/apis/execution/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + //+kubebuilder:scaffold:imports +) + +var _ = Describe("ScheduledScan controller", func() { + Context("A Scan is triggred due to a Scheduled Scan with Schedule in Spec", func() { + It("The ScheduledScan's should be triggered according to the Schedule", func() { + ctx := context.Background() + namespace := "scantype-multiple-scheduled-scan-triggerd-test-schedule" + + createNamespace(ctx, namespace) + createScanType(ctx, namespace) + scheduledScan := createScheduledScanWithSchedule(ctx, namespace, true) + + var scanlist executionv1.ScanList + + // ensure that the ScheduledScan has been triggered + waitForScheduledScanToBeTriggered(ctx, namespace, 90*time.Second) + k8sClient.List(ctx, &scanlist, client.InNamespace(namespace)) + + Expect(scheduledScan.Spec.Schedule).Should(Equal("*/1 * * * *")) + Expect(scanlist.Items).Should(HaveLen(1)) + }) + }) +}) diff --git a/operator/controllers/execution/scheduledscan_controller_test.go b/operator/controllers/execution/scheduledscan_controller_test.go index 2fade0cddb..0e4e4ef923 100644 --- a/operator/controllers/execution/scheduledscan_controller_test.go +++ b/operator/controllers/execution/scheduledscan_controller_test.go @@ -1,11 +1,14 @@ // SPDX-FileCopyrightText: the secureCodeBox authors // // SPDX-License-Identifier: Apache-2.0 + +//go:build fast +// +build fast + package controllers import ( "context" - "time" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -105,24 +108,4 @@ var _ = Describe("ScheduledScan controller", func() { Expect(scheduledScan.Status.Findings.FindingCategories).Should(Equal(map[string]uint64{"Open Port": 42})) }) }) - - Context("A Scan is triggred due to a Scheduled Scan with Schedule in Spec", func() { - It("The ScheduledScan's should be triggered according to the Schedule", func() { - ctx := context.Background() - namespace := "scantype-multiple-scheduled-scan-triggerd-test-schedule" - - createNamespace(ctx, namespace) - createScanType(ctx, namespace) - scheduledScan := createScheduledScanWithSchedule(ctx, namespace, true) - - var scanlist executionv1.ScanList - - // ensure that the ScheduledScan has been triggered - waitForScheduledScanToBeTriggered(ctx, namespace, 90*time.Second) - k8sClient.List(ctx, &scanlist, client.InNamespace(namespace)) - - Expect(scheduledScan.Spec.Schedule).Should(Equal("*/1 * * * *")) - Expect(scanlist.Items).Should(HaveLen(1)) - }) - }) }) diff --git a/operator/controllers/execution/test_utils_test.go b/operator/controllers/execution/test_utils_test.go index 7d0619b6d3..a28d58c848 100644 --- a/operator/controllers/execution/test_utils_test.go +++ b/operator/controllers/execution/test_utils_test.go @@ -2,6 +2,9 @@ // // SPDX-License-Identifier: Apache-2.0 +//go:build fast +// +build fast + package controllers import ( From ede2c2d9bba18a68dd7f0761cc4815879c3a35d4 Mon Sep 17 00:00:00 2001 From: Ilyes Ben Dlala Date: Tue, 11 Jul 2023 09:58:50 +0200 Subject: [PATCH 15/18] #150 Removed fakeClock definition since it's no longer used Signed-off-by: Ilyes Ben Dlala --- operator/controllers/execution/suite_test.go | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/operator/controllers/execution/suite_test.go b/operator/controllers/execution/suite_test.go index ea73ca6ec4..8e639c84b3 100644 --- a/operator/controllers/execution/suite_test.go +++ b/operator/controllers/execution/suite_test.go @@ -37,17 +37,6 @@ var testEnv *envtest.Environment var ctx context.Context var cancel context.CancelFunc -type fakeClock struct { - timeToAdd time.Duration -} - -var FakeTime = time.Date(2023, 1, 1, 15, 0, 0, 0, time.UTC) -var FakeClock = &fakeClock{timeToAdd: 0} - -func (f *fakeClock) Now() time.Time { return FakeTime.Add(f.timeToAdd) } -func (f *fakeClock) TimeTravel(d time.Duration) { f.timeToAdd += d } -func (f *fakeClock) Reset() { f.timeToAdd = 0 } - func TestAPIs(t *testing.T) { RegisterFailHandler(Fail) From 2ddcfc1eaa6da07377131a2ff482ca4211973a0c Mon Sep 17 00:00:00 2001 From: Ilyes Ben Dlala Date: Tue, 11 Jul 2023 10:09:26 +0200 Subject: [PATCH 16/18] #150 removed unused library in operator test suite `time` Signed-off-by: Ilyes Ben Dlala --- operator/controllers/execution/suite_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/operator/controllers/execution/suite_test.go b/operator/controllers/execution/suite_test.go index 8e639c84b3..425ea0079c 100644 --- a/operator/controllers/execution/suite_test.go +++ b/operator/controllers/execution/suite_test.go @@ -8,7 +8,6 @@ import ( "context" "path/filepath" "testing" - "time" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" From 4ae1326b84fa1b69fef02a7a304628b888289367 Mon Sep 17 00:00:00 2001 From: Ilyes Ben Dlala Date: Fri, 14 Jul 2023 11:56:10 +0200 Subject: [PATCH 17/18] #150 Added k8s events to the parsing and handling schedule/interval of scheduledScan Signed-off-by: Ilyes Ben Dlala --- .../controllers/execution/scheduledscan_controller.go | 8 ++++++-- operator/controllers/execution/suite_test.go | 7 ++++--- operator/main.go | 7 ++++--- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/operator/controllers/execution/scheduledscan_controller.go b/operator/controllers/execution/scheduledscan_controller.go index e929dbaf95..7a98159785 100644 --- a/operator/controllers/execution/scheduledscan_controller.go +++ b/operator/controllers/execution/scheduledscan_controller.go @@ -17,6 +17,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/tools/record" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -32,8 +33,9 @@ var ( // ScheduledScanReconciler reconciles a ScheduledScan object type ScheduledScanReconciler struct { client.Client - Log logr.Logger - Scheme *runtime.Scheme + Log logr.Logger + Scheme *runtime.Scheme + Recorder record.EventRecorder } // +kubebuilder:rbac:groups=execution.securecodebox.io,resources=scheduledscans,verbs=get;list;watch;create;update;patch;delete @@ -167,6 +169,7 @@ func getNextSchedule(r *ScheduledScanReconciler, scheduledScan executionv1.Sched if scheduledScan.Spec.Schedule != "" { sched, err := cron.ParseStandard(scheduledScan.Spec.Schedule) if err != nil { + r.Recorder.Event(&scheduledScan, "Warning", "ScheduleParseError", fmt.Sprintf("Unparseable schedule %q: %v", scheduledScan.Spec.Schedule, err)) return time.Time{}, fmt.Errorf("Unparseable schedule %q: %v", scheduledScan.Spec.Schedule, err) } @@ -193,6 +196,7 @@ func getNextSchedule(r *ScheduledScanReconciler, scheduledScan executionv1.Sched } return nextSchedule, nil } + r.Recorder.Event(&scheduledScan, "Warning", "NoScheduleOrInterval", "No valid schedule or interval found") return time.Time{}, fmt.Errorf("No schedule or interval found") } diff --git a/operator/controllers/execution/suite_test.go b/operator/controllers/execution/suite_test.go index 425ea0079c..b87c6f125d 100644 --- a/operator/controllers/execution/suite_test.go +++ b/operator/controllers/execution/suite_test.go @@ -80,9 +80,10 @@ var _ = BeforeSuite(func() { Log: ctrl.Log.WithName("controllers").WithName("ScanTypeController"), }).SetupWithManager(k8sManager) err = (&ScheduledScanReconciler{ - Client: k8sManager.GetClient(), - Scheme: k8sManager.GetScheme(), - Log: ctrl.Log.WithName("controllers").WithName("ScheduledScanController"), + Client: k8sManager.GetClient(), + Scheme: k8sManager.GetScheme(), + Recorder: k8sManager.GetEventRecorderFor("ScheduledScanController"), + Log: ctrl.Log.WithName("controllers").WithName("ScheduledScanController"), }).SetupWithManager(k8sManager) Expect(err).ToNot(HaveOccurred()) diff --git a/operator/main.go b/operator/main.go index ad5d6bdac9..f502859a6d 100644 --- a/operator/main.go +++ b/operator/main.go @@ -79,9 +79,10 @@ func main() { os.Exit(1) } if err = (&executioncontrollers.ScheduledScanReconciler{ - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controllers").WithName("execution").WithName("ScheduledScan"), - Scheme: mgr.GetScheme(), + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controllers").WithName("execution").WithName("ScheduledScan"), + Recorder: mgr.GetEventRecorderFor("ScheduledScanController"), + Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "ScheduledScan") os.Exit(1) From 21fc70b4d92f60a676bb7e87264d5e4d4ed4edee Mon Sep 17 00:00:00 2001 From: Ilyes Ben Dlala Date: Fri, 14 Jul 2023 15:33:55 +0200 Subject: [PATCH 18/18] #150 Added missing rbac permissions create and patch k8s events Signed-off-by: Ilyes Ben Dlala --- operator/config/rbac/role.yaml | 7 +++++++ operator/controllers/execution/scantype_controller.go | 3 +++ operator/controllers/execution/scheduledscan_controller.go | 3 +++ operator/templates/rbac/role.yaml | 7 +++++++ 4 files changed, 20 insertions(+) diff --git a/operator/config/rbac/role.yaml b/operator/config/rbac/role.yaml index 661d011ab0..fecc10e53d 100644 --- a/operator/config/rbac/role.yaml +++ b/operator/config/rbac/role.yaml @@ -5,6 +5,13 @@ metadata: creationTimestamp: null name: manager-role rules: +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch - apiGroups: - batch resources: diff --git a/operator/controllers/execution/scantype_controller.go b/operator/controllers/execution/scantype_controller.go index 9cc0ea75a1..28d74600e0 100644 --- a/operator/controllers/execution/scantype_controller.go +++ b/operator/controllers/execution/scantype_controller.go @@ -31,6 +31,9 @@ type ScanTypeReconciler struct { // +kubebuilder:rbac:groups="execution.securecodebox.io",resources=scheduledscans,verbs=get;list;watch;create;update;patch // +kubebuilder:rbac:groups="execution.securecodebox.io/status",resources=scheduledscans,verbs=get;update;patch +// Allows the ScanType Controller to create and patch Events +// +kubebuilder:rbac:groups="",resources=events,verbs=create;patch + // Reconcile compares the Service object against the state of the cluster and updates both if needed func (r *ScanTypeReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { log := r.Log diff --git a/operator/controllers/execution/scheduledscan_controller.go b/operator/controllers/execution/scheduledscan_controller.go index 7a98159785..2b70c2897b 100644 --- a/operator/controllers/execution/scheduledscan_controller.go +++ b/operator/controllers/execution/scheduledscan_controller.go @@ -43,6 +43,9 @@ type ScheduledScanReconciler struct { // +kubebuilder:rbac:groups=execution.securecodebox.io,resources=scans,verbs=get;list;create // +kubebuilder:rbac:groups=execution.securecodebox.io,resources=scans/status,verbs=get +// Allows the ScheduledScan Controller to create and patch Events +// +kubebuilder:rbac:groups="",resources=events,verbs=create;patch + // Reconcile comapares the ScheduledScan Resource with the State of the Cluster and updates both accordingly func (r *ScheduledScanReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { log := r.Log.WithValues("scheduledscan", req.NamespacedName) diff --git a/operator/templates/rbac/role.yaml b/operator/templates/rbac/role.yaml index ac5d0c8aa8..d7306a4f3a 100644 --- a/operator/templates/rbac/role.yaml +++ b/operator/templates/rbac/role.yaml @@ -9,6 +9,13 @@ metadata: creationTimestamp: null name: manager-role rules: +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch - apiGroups: - batch resources: