diff --git a/hooks/cascading-scans/hook/hook.test.js b/hooks/cascading-scans/hook/hook.test.js index bbf39004de..c03a6889b3 100644 --- a/hooks/cascading-scans/hook/hook.test.js +++ b/hooks/cascading-scans/hook/hook.test.js @@ -3,6 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 const { getCascadingScans } = require("./hook"); +const {LabelSelectorRequirementOperator} = require("./kubernetes-label-selector"); let parentScan = undefined; let sslyzeCascadingRules = undefined; @@ -104,6 +105,7 @@ test("Should create subsequent scans for open HTTPS ports (NMAP findings)", () = "spec": Object { "cascades": Object {}, "env": Array [], + "hookSelector": Object {}, "initContainers": Array [], "parameters": Array [ "--regular", @@ -241,6 +243,7 @@ test("Should not crash when the annotations are not set", () => { "spec": Object { "cascades": Object {}, "env": Array [], + "hookSelector": Object {}, "initContainers": Array [], "parameters": Array [ "--regular", @@ -372,6 +375,7 @@ test("Should allow wildcards in cascadingRules", () => { "spec": Object { "cascades": Object {}, "env": Array [], + "hookSelector": Object {}, "initContainers": Array [], "parameters": Array [ "--regular", @@ -1128,6 +1132,7 @@ test("Templating should also apply to initContainer commands", () => { "spec": Object { "cascades": Object {}, "env": Array [], + "hookSelector": Object {}, "initContainers": Array [ Object { "command": Array [ @@ -1260,6 +1265,7 @@ test("Templating should not break special encoding (http://...) when using tripl "spec": Object { "cascades": Object {}, "env": Array [], + "hookSelector": Object {}, "initContainers": Array [ Object { "command": Array [ @@ -1301,10 +1307,150 @@ test("Templating should not break special encoding (http://...) when using tripl `); }); +test("should merge hookSelector into cascaded scan if inheritHookSelector is enabled", () => { + parentScan.spec.cascades.inheritHookSelector = true + const findings = [ + { + name: "Port 443 is open", + category: "Open Port", + attributes: { + state: "open", + hostname: "foobar.com", + port: 443, + service: "https" + } + } + ]; + + parentScan.spec.hookSelector = {} + parentScan.spec.hookSelector.matchLabels = { + "securecodebox.io/internal": "true", + } + parentScan.spec.hookSelector.matchExpressions = [ + { + key: "securecodebox.io/name", + operator: LabelSelectorRequirementOperator.In, + values: ["cascading-scans"] + } + ] + + sslyzeCascadingRules[0].spec.scanSpec.hookSelector = {}; + sslyzeCascadingRules[0].spec.scanSpec.hookSelector.matchExpressions = [ + { + key: "securecodebox.io/name", + operator: LabelSelectorRequirementOperator.NotIn, + values: ["cascading-scans"] + } + ] + + sslyzeCascadingRules[0].spec.scanSpec.hookSelector.matchLabels = { + "securecodebox.io/internal": "false", + } + + const cascadedScans = getCascadingScans( + parentScan, + findings, + sslyzeCascadingRules + ); + + const cascadedScan = cascadedScans[0]; + + expect(cascadedScan.spec.hookSelector).toMatchInlineSnapshot(` + Object { + "matchExpressions": Array [ + Object { + "key": "securecodebox.io/name", + "operator": "In", + "values": Array [ + "cascading-scans", + ], + }, + Object { + "key": "securecodebox.io/name", + "operator": "NotIn", + "values": Array [ + "cascading-scans", + ], + }, + ], + "matchLabels": Object { + "securecodebox.io/internal": "false", + }, + } + `); +}); + + +test("should not merge hookSelector into cascaded scan if inheritHookSelector is disabled", () => { + parentScan.spec.cascades.inheritHookSelector = false + const findings = [ + { + name: "Port 443 is open", + category: "Open Port", + attributes: { + state: "open", + hostname: "foobar.com", + port: 443, + service: "https" + } + } + ]; + + parentScan.spec.hookSelector = {} + parentScan.spec.hookSelector.matchLabels = { + "securecodebox.io/internal": "true", + } + parentScan.spec.hookSelector.matchExpressions = [ + { + key: "securecodebox.io/name", + operator: LabelSelectorRequirementOperator.In, + values: ["cascading-scans"] + } + ] + + sslyzeCascadingRules[0].spec.scanSpec.hookSelector = {}; + sslyzeCascadingRules[0].spec.scanSpec.hookSelector.matchExpressions = [ + { + key: "securecodebox.io/name", + operator: LabelSelectorRequirementOperator.NotIn, + values: ["cascading-scans"] + } + ] + + sslyzeCascadingRules[0].spec.scanSpec.hookSelector.matchLabels = { + "securecodebox.io/internal": "false", + } + + const cascadedScans = getCascadingScans( + parentScan, + findings, + sslyzeCascadingRules + ); + + const cascadedScan = cascadedScans[0]; + + expect(cascadedScan.spec.hookSelector).toMatchInlineSnapshot(` + Object { + "matchExpressions": Array [ + Object { + "key": "securecodebox.io/name", + "operator": "NotIn", + "values": Array [ + "cascading-scans", + ], + }, + ], + "matchLabels": Object { + "securecodebox.io/internal": "false", + }, + } + `); +}); test("should purge cascaded scan spec from parent scan", () => { parentScan.spec.cascades.inheritEnv = true parentScan.spec.cascades.inheritVolumes = true + parentScan.spec.cascades.inheritHookSelector = true const findings = [ { name: "Port 443 is open", @@ -1368,6 +1514,31 @@ test("should purge cascaded scan spec from parent scan", () => { } ] + parentScan.spec.hookSelector = {} + parentScan.spec.hookSelector.matchLabels = { + "securecodebox.io/internal": "true", + } + parentScan.spec.hookSelector.matchExpressions = [ + { + key: "securecodebox.io/name", + operator: LabelSelectorRequirementOperator.In, + values: ["cascading-scans"] + } + ] + + sslyzeCascadingRules[0].spec.scanSpec.hookSelector = {}; + sslyzeCascadingRules[0].spec.scanSpec.hookSelector.matchExpressions = [ + { + key: "securecodebox.io/name", + operator: LabelSelectorRequirementOperator.NotIn, + values: ["cascading-scans"] + } + ] + + sslyzeCascadingRules[0].spec.scanSpec.hookSelector.matchLabels = { + "securecodebox.io/internal": "false", + } + const cascadedScans = getCascadingScans( parentScan, findings, @@ -1450,6 +1621,18 @@ test("should purge cascaded scan spec from parent scan", () => { ] `) + expect(secondCascadedScan.spec.hookSelector.matchExpressions).toMatchInlineSnapshot(` + Array [ + Object { + "key": "securecodebox.io/name", + "operator": "In", + "values": Array [ + "cascading-scans", + ], + }, + ] + `) + expect(secondCascadedScan.spec.hookSelector.matchLabels).toMatchInlineSnapshot(`Object {}`) }); test("should not copy cascaded scan spec from parent scan if inheritance is undefined", () => { diff --git a/hooks/cascading-scans/hook/hook.ts b/hooks/cascading-scans/hook/hook.ts index 400affc663..7d8c469673 100644 --- a/hooks/cascading-scans/hook/hook.ts +++ b/hooks/cascading-scans/hook/hook.ts @@ -16,7 +16,8 @@ import { getCascadedRuleForScan, purgeCascadedRuleFromScan, mergeInheritedMap, - mergeInheritedArray + mergeInheritedArray, + mergeInheritedSelector, } from "./scan-helpers"; interface HandleArgs { @@ -110,7 +111,7 @@ function getCascadingScan( let { scanType, parameters } = cascadingRule.spec.scanSpec; - let { annotations, labels, env, volumes, volumeMounts, initContainers } = mergeCascadingRuleWithScan(parentScan, cascadingRule); + let { annotations, labels, env, volumes, volumeMounts, initContainers, hookSelector } = mergeCascadingRuleWithScan(parentScan, cascadingRule); let cascadingChain = getScanChain(parentScan); @@ -142,6 +143,7 @@ function getCascadingScan( ] }, spec: { + hookSelector, scanType, parameters, cascades: parentScan.spec.cascades, @@ -158,8 +160,8 @@ function mergeCascadingRuleWithScan( cascadingRule: CascadingRule ) { const { scanAnnotations, scanLabels } = cascadingRule.spec; - let { env = [], volumes = [], volumeMounts = [], initContainers = [] } = cascadingRule.spec.scanSpec; - let { inheritAnnotations, inheritLabels, inheritEnv, inheritVolumes, inheritInitContainers } = scan.spec.cascades; + let { env = [], volumes = [], volumeMounts = [], initContainers = [], hookSelector = {} } = cascadingRule.spec.scanSpec; + let { inheritAnnotations, inheritLabels, inheritEnv, inheritVolumes, inheritInitContainers, inheritHookSelector } = scan.spec.cascades; return { annotations: mergeInheritedMap(scan.metadata.annotations, scanAnnotations, inheritAnnotations), @@ -168,6 +170,7 @@ function mergeCascadingRuleWithScan( volumes: mergeInheritedArray(scan.spec.volumes, volumes, inheritVolumes), volumeMounts: mergeInheritedArray(scan.spec.volumeMounts, volumeMounts, inheritVolumes), initContainers: mergeInheritedArray(scan.spec.initContainers, initContainers, inheritInitContainers), + hookSelector: mergeInheritedSelector(scan.spec.hookSelector, hookSelector, inheritHookSelector), } } diff --git a/hooks/cascading-scans/hook/kubernetes-label-selector.ts b/hooks/cascading-scans/hook/kubernetes-label-selector.ts index 82122f0a4c..8bbee94b4c 100644 --- a/hooks/cascading-scans/hook/kubernetes-label-selector.ts +++ b/hooks/cascading-scans/hook/kubernetes-label-selector.ts @@ -19,8 +19,8 @@ export interface LabelSelectorRequirement { } export interface LabelSelector { - matchExpressions: Array; - matchLabels: Map; + matchExpressions?: Array; + matchLabels?: Map; } // generateSelectorString transforms a kubernetes labelSelector object in to the string representation diff --git a/hooks/cascading-scans/hook/scan-helpers.ts b/hooks/cascading-scans/hook/scan-helpers.ts index f19d08e6ba..ae1a84b53c 100644 --- a/hooks/cascading-scans/hook/scan-helpers.ts +++ b/hooks/cascading-scans/hook/scan-helpers.ts @@ -62,6 +62,7 @@ export interface ScanSpec { volumes?: Array; volumeMounts?: Array; initContainers?: Array; + hookSelector?: LabelSelector; } export interface CascadingInheritance { @@ -70,6 +71,7 @@ export interface CascadingInheritance { inheritEnv: boolean, inheritVolumes: boolean, inheritInitContainers: boolean, + inheritHookSelector: boolean, } export function mergeInheritedMap(parentProps, ruleProps, inherit: boolean = true) { @@ -82,13 +84,24 @@ export function mergeInheritedMap(parentProps, ruleProps, inherit: boolean = tru } } -export function mergeInheritedArray(parentArray, ruleArray, inherit: boolean = false) { +export function mergeInheritedArray(parentArray = [], ruleArray = [], inherit: boolean = false) { if (!inherit) { parentArray = []; } return (parentArray || []).concat(ruleArray) // CascadingRule's env overwrites scan's env } +export function mergeInheritedSelector(parentSelector: LabelSelector = {}, ruleSelector: LabelSelector = {}, inherit: boolean = false): LabelSelector { + let labelSelector: LabelSelector = {}; + if (parentSelector.matchExpressions || ruleSelector.matchExpressions) { + labelSelector.matchExpressions = mergeInheritedArray(parentSelector.matchExpressions, ruleSelector.matchExpressions, inherit); + } + if (parentSelector.matchLabels || ruleSelector.matchLabels) { + labelSelector.matchLabels = mergeInheritedMap(parentSelector.matchLabels, ruleSelector.matchLabels, inherit); + } + return labelSelector +} + export async function startSubsequentSecureCodeBoxScan(scan: Scan) { console.log(`Starting Scan ${scan.metadata.name}`); @@ -165,6 +178,19 @@ export function purgeCascadedRuleFromScan(scan: Scan, cascadedRuleUsedForParentS ); } + if (scan.spec.hookSelector !== undefined && cascadedRuleUsedForParentScan.spec.scanSpec.hookSelector !== undefined) { + if (scan.spec.hookSelector.matchExpressions !== undefined && cascadedRuleUsedForParentScan.spec.scanSpec.hookSelector.matchExpressions !== undefined) { + scan.spec.hookSelector.matchExpressions = scan.spec.hookSelector.matchExpressions.filter(scanHookSelector => + !cascadedRuleUsedForParentScan.spec.scanSpec.hookSelector.matchExpressions.some(ruleHookSelector => isEqual(scanHookSelector, ruleHookSelector)) + ); + } + if (scan.spec.hookSelector.matchLabels !== undefined && cascadedRuleUsedForParentScan.spec.scanSpec.hookSelector.matchLabels !== undefined) { + for (const label in cascadedRuleUsedForParentScan.spec.scanSpec.hookSelector.matchLabels) { + delete scan.spec.hookSelector.matchLabels[label] + } + } + } + return scan } diff --git a/hooks/cascading-scans/templates/cascading-scans-hook.yaml b/hooks/cascading-scans/templates/cascading-scans-hook.yaml index 167e5fe7f0..87ae80c24b 100644 --- a/hooks/cascading-scans/templates/cascading-scans-hook.yaml +++ b/hooks/cascading-scans/templates/cascading-scans-hook.yaml @@ -8,6 +8,10 @@ metadata: name: {{ include "cascading-scans.fullname" . }} labels: {{- include "cascading-scans.labels" . | nindent 4 }} + securecodebox.io/internal: "true" + {{- with .Values.hook.labels }} + {{ toYaml . }} + {{- end }} spec: type: ReadOnly image: "{{ .Values.hook.image.repository }}:{{ .Values.hook.image.tag | default .Chart.Version }}" diff --git a/hooks/cascading-scans/values.yaml b/hooks/cascading-scans/values.yaml index 32aa4e38ac..50160bdfe3 100644 --- a/hooks/cascading-scans/values.yaml +++ b/hooks/cascading-scans/values.yaml @@ -14,5 +14,8 @@ hook: # @default -- defaults to the charts version tag: null + # hook.labels -- Add Kubernetes Labels to the hook definition + labels: {} + # hook.ttlSecondsAfterFinished -- Seconds after which the kubernetes job for the hook will be deleted. Requires the Kubernetes TTLAfterFinished controller: https://kubernetes.io/docs/concepts/workloads/controllers/ttlafterfinished/ ttlSecondsAfterFinished: null diff --git a/hooks/finding-post-processing/templates/finding-post-processing-hook.yaml b/hooks/finding-post-processing/templates/finding-post-processing-hook.yaml index bd7bf7eaad..e01efa414f 100644 --- a/hooks/finding-post-processing/templates/finding-post-processing-hook.yaml +++ b/hooks/finding-post-processing/templates/finding-post-processing-hook.yaml @@ -8,6 +8,9 @@ metadata: name: {{ include "finding-post-processing.fullname" . }} labels: {{- include "finding-post-processing.labels" . | nindent 4 }} + {{- with .Values.hook.labels }} + {{ toYaml . }} + {{- end }} spec: type: ReadAndWrite image: "{{ .Values.hook.image.repository }}:{{ .Values.hook.image.tag | default .Chart.Version }}" diff --git a/hooks/finding-post-processing/values.yaml b/hooks/finding-post-processing/values.yaml index 9ae75ea875..4d16d013bf 100644 --- a/hooks/finding-post-processing/values.yaml +++ b/hooks/finding-post-processing/values.yaml @@ -30,5 +30,8 @@ hook: # @default -- defaults to the charts version tag: null + # hook.labels -- Add Kubernetes Labels to the hook definition + labels: {} + # hook.ttlSecondsAfterFinished -- Seconds after which the kubernetes job for the hook will be deleted. Requires the Kubernetes TTLAfterFinished controller: https://kubernetes.io/docs/concepts/workloads/controllers/ttlafterfinished/ ttlSecondsAfterFinished: null diff --git a/hooks/generic-webhook/templates/webhook-hook.yaml b/hooks/generic-webhook/templates/webhook-hook.yaml index 945d97a082..317b476b2a 100644 --- a/hooks/generic-webhook/templates/webhook-hook.yaml +++ b/hooks/generic-webhook/templates/webhook-hook.yaml @@ -8,6 +8,9 @@ metadata: name: {{ include "generic-webhook.fullname" . }} labels: {{- include "generic-webhook.labels" . | nindent 4 }} + {{- with .Values.hook.labels }} + {{ toYaml . }} + {{- end }} spec: type: ReadOnly image: "{{ .Values.hook.image.repository }}:{{ .Values.hook.image.tag | default .Chart.Version }}" diff --git a/hooks/generic-webhook/values.yaml b/hooks/generic-webhook/values.yaml index deae4c2eb3..39da0a067e 100644 --- a/hooks/generic-webhook/values.yaml +++ b/hooks/generic-webhook/values.yaml @@ -17,5 +17,8 @@ hook: # @default -- defaults to the charts version tag: null + # hook.labels -- Add Kubernetes Labels to the hook definition + labels: {} + # hook.ttlSecondsAfterFinished -- Seconds after which the kubernetes job for the hook will be deleted. Requires the Kubernetes TTLAfterFinished controller: https://kubernetes.io/docs/concepts/workloads/controllers/ttlafterfinished/ ttlSecondsAfterFinished: null diff --git a/hooks/notification/templates/notification-hook.yaml b/hooks/notification/templates/notification-hook.yaml index 5d1fd2700a..2961756cf6 100644 --- a/hooks/notification/templates/notification-hook.yaml +++ b/hooks/notification/templates/notification-hook.yaml @@ -6,6 +6,11 @@ apiVersion: "execution.securecodebox.io/v1" kind: ScanCompletionHook metadata: name: {{ include "notification-hook.fullname" . }} + labels: + {{- include "notification-hook.labels" . | nindent 4 }} + {{- with .Values.hook.labels }} + {{ toYaml . }} + {{- end }} spec: type: ReadOnly imagePullPolicy: "{{ .Values.hook.image.pullPolicy }}" diff --git a/hooks/notification/values.yaml b/hooks/notification/values.yaml index b541537e36..5cf9243c90 100644 --- a/hooks/notification/values.yaml +++ b/hooks/notification/values.yaml @@ -16,6 +16,9 @@ hook: # -- Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images pullPolicy: IfNotPresent + # hook.labels -- Add Kubernetes Labels to the hook definition + labels: {} + # hook.ttlSecondsAfterFinished -- seconds after which the kubernetes job for the hook will be deleted. Requires the Kubernetes TTLAfterFinished controller: https://kubernetes.io/docs/concepts/workloads/controllers/ttlafterfinished/ ttlSecondsAfterFinished: null diff --git a/hooks/persistence-defectdojo/templates/persistence-provider.yaml b/hooks/persistence-defectdojo/templates/persistence-provider.yaml index 8d03afccf0..58d374684e 100644 --- a/hooks/persistence-defectdojo/templates/persistence-provider.yaml +++ b/hooks/persistence-defectdojo/templates/persistence-provider.yaml @@ -9,6 +9,9 @@ metadata: labels: {{- include "persistence-defectdojo.labels" . | nindent 4 }} type: Unstructured + {{- with .Values.hook.labels }} + {{ toYaml . }} + {{- end }} spec: {{- if .Values.defectdojo.syncFindingsBack }} type: ReadAndWrite diff --git a/hooks/persistence-defectdojo/values.yaml b/hooks/persistence-defectdojo/values.yaml index 894c82c74b..4ec1a461a2 100644 --- a/hooks/persistence-defectdojo/values.yaml +++ b/hooks/persistence-defectdojo/values.yaml @@ -16,6 +16,9 @@ hook: # -- Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images pullPolicy: IfNotPresent + # hook.labels -- Add Kubernetes Labels to the hook definition + labels: {} + defectdojo: # -- Syncs back (two way sync) all imported findings from DefectDojo to SCB Findings Store. When set to false the hook will only import the findings to DefectDojo (one way sync). syncFindingsBack: true diff --git a/hooks/persistence-elastic/templates/persistence-provider.yaml b/hooks/persistence-elastic/templates/persistence-provider.yaml index 4040c9c37d..6a329759c9 100644 --- a/hooks/persistence-elastic/templates/persistence-provider.yaml +++ b/hooks/persistence-elastic/templates/persistence-provider.yaml @@ -9,6 +9,9 @@ metadata: labels: {{- include "persistence-elastic.labels" . | nindent 4 }} type: Structured + {{- with .Values.hook.labels }} + {{ toYaml . }} + {{- end }} spec: type: ReadOnly image: "{{ .Values.hook.image.repository }}:{{ .Values.hook.image.tag | default .Chart.Version }}" diff --git a/hooks/persistence-elastic/values.yaml b/hooks/persistence-elastic/values.yaml index 10e691287c..ae6c2fa922 100644 --- a/hooks/persistence-elastic/values.yaml +++ b/hooks/persistence-elastic/values.yaml @@ -86,5 +86,8 @@ hook: # @default -- defaults to the charts version tag: null + # hook.labels -- Add Kubernetes Labels to the hook definition + labels: {} + # hook.ttlSecondsAfterFinished -- Seconds after which the kubernetes job for the hook will be deleted. Requires the Kubernetes TTLAfterFinished controller: https://kubernetes.io/docs/concepts/workloads/controllers/ttlafterfinished/ ttlSecondsAfterFinished: null diff --git a/hooks/update-field/templates/update-field-hook.yaml b/hooks/update-field/templates/update-field-hook.yaml index 5acc24e9e3..901116780b 100644 --- a/hooks/update-field/templates/update-field-hook.yaml +++ b/hooks/update-field/templates/update-field-hook.yaml @@ -8,6 +8,9 @@ metadata: name: {{ include "update-field.fullname" . }} labels: {{- include "update-field.labels" . | nindent 4 }} + {{- with .Values.hook.labels }} + {{ toYaml . }} + {{- end }} spec: type: ReadAndWrite image: "{{ .Values.hook.image.repository }}:{{ .Values.hook.image.tag | default .Chart.Version }}" diff --git a/hooks/update-field/values.yaml b/hooks/update-field/values.yaml index 60ad92daec..d4ad8d3609 100644 --- a/hooks/update-field/values.yaml +++ b/hooks/update-field/values.yaml @@ -20,5 +20,8 @@ hook: # @default -- defaults to the charts version tag: null + # hook.labels -- Add Kubernetes Labels to the hook definition + labels: {} + # hook.ttlSecondsAfterFinished -- Seconds after which the kubernetes job for the hook will be deleted. Requires the Kubernetes TTLAfterFinished controller: https://kubernetes.io/docs/concepts/workloads/controllers/ttlafterfinished/ ttlSecondsAfterFinished: null diff --git a/operator/apis/execution/v1/scan_types.go b/operator/apis/execution/v1/scan_types.go index 9ddd4baf15..91409b8cdc 100644 --- a/operator/apis/execution/v1/scan_types.go +++ b/operator/apis/execution/v1/scan_types.go @@ -39,6 +39,11 @@ type CascadeSpec struct { // +kubebuilder:default=false InheritInitContainers bool `json:"inheritInitContainers"` + // InheritHookSelector defines whether cascading scans should inherit hookSelector from the parent scan. + // +optional + // +kubebuilder:default=false + InheritHookSelector bool `json:"inheritHookSelector"` + // matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels // map is equivalent to an element of matchExpressions, whose key field is "key", the // operator is "In", and the values array contains only "value". The requirements are ANDed. @@ -62,6 +67,9 @@ type ScanSpec struct { // +kubebuilder:validation:Required Parameters []string `json:"parameters,omitempty"` + // HookSelector allows to specify a LabelSelector with which the hooks are selected. + HookSelector *metav1.LabelSelector `json:"hookSelector,omitempty"` + // Env allows to specify environment vars for the scanner container. These will be merged will the env vars specified for the first container of the pod defined in the ScanType Env []corev1.EnvVar `json:"env,omitempty"` // Volumes allows to specify volumes for the scan container. diff --git a/operator/apis/execution/v1/zz_generated.deepcopy.go b/operator/apis/execution/v1/zz_generated.deepcopy.go index d7d4b10d9a..0e791245d3 100644 --- a/operator/apis/execution/v1/zz_generated.deepcopy.go +++ b/operator/apis/execution/v1/zz_generated.deepcopy.go @@ -424,6 +424,11 @@ func (in *ScanSpec) DeepCopyInto(out *ScanSpec) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.HookSelector != nil { + in, out := &in.HookSelector, &out.HookSelector + *out = new(metav1.LabelSelector) + (*in).DeepCopyInto(*out) + } if in.Env != nil { in, out := &in.Env, &out.Env *out = make([]corev1.EnvVar, len(*in)) diff --git a/operator/config/crd/bases/cascading.securecodebox.io_cascadingrules.yaml b/operator/config/crd/bases/cascading.securecodebox.io_cascadingrules.yaml index 76e0fecf58..8df98f340a 100644 --- a/operator/config/crd/bases/cascading.securecodebox.io_cascadingrules.yaml +++ b/operator/config/crd/bases/cascading.securecodebox.io_cascadingrules.yaml @@ -114,6 +114,11 @@ spec: description: InheritEnv defines whether cascading scans should inherit environment variables from the parent scan type: boolean + inheritHookSelector: + default: false + description: InheritHookSelector defines whether cascading + scans should inherit hookSelector from the parent scan. + type: boolean inheritInitContainers: default: false description: InheritInitContainers defines whether cascading @@ -283,6 +288,51 @@ spec: - name type: object type: array + hookSelector: + description: HookSelector allows to specify a LabelSelector with + which the hooks are selected. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector + that contains values, a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, NotIn, + Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If + the operator is In or NotIn, the values array must + be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced + during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A + single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field is "key", + the operator is "In", and the values array contains only + "value". The requirements are ANDed. + type: object + type: object initContainers: description: InitContainers allows to specify init containers for the scan container, to pre-load data into them. diff --git a/operator/config/crd/bases/execution.securecodebox.io_scans.yaml b/operator/config/crd/bases/execution.securecodebox.io_scans.yaml index 5f752dc084..3e24c3b2b0 100644 --- a/operator/config/crd/bases/execution.securecodebox.io_scans.yaml +++ b/operator/config/crd/bases/execution.securecodebox.io_scans.yaml @@ -76,6 +76,11 @@ spec: description: InheritEnv defines whether cascading scans should inherit environment variables from the parent scan type: boolean + inheritHookSelector: + default: false + description: InheritHookSelector defines whether cascading scans + should inherit hookSelector from the parent scan. + type: boolean inheritInitContainers: default: false description: InheritInitContainers defines whether cascading scans @@ -238,6 +243,51 @@ spec: - name type: object type: array + hookSelector: + description: HookSelector allows to specify a LabelSelector with which + the hooks are selected. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: A label selector requirement is a selector that + contains values, a key, and an operator that relates the key + and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: operator represents a key's relationship to + a set of values. Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of string values. If the + operator is In or NotIn, the values array must be non-empty. + If the operator is Exists or DoesNotExist, the values + array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single + {key,value} in the matchLabels map is equivalent to an element + of matchExpressions, whose key field is "key", the operator + is "In", and the values array contains only "value". The requirements + are ANDed. + type: object + type: object initContainers: description: InitContainers allows to specify init containers for the scan container, to pre-load data into them. diff --git a/operator/config/crd/bases/execution.securecodebox.io_scheduledscans.yaml b/operator/config/crd/bases/execution.securecodebox.io_scheduledscans.yaml index 1529f1c120..53043f17a2 100644 --- a/operator/config/crd/bases/execution.securecodebox.io_scheduledscans.yaml +++ b/operator/config/crd/bases/execution.securecodebox.io_scheduledscans.yaml @@ -96,6 +96,11 @@ spec: description: InheritEnv defines whether cascading scans should inherit environment variables from the parent scan type: boolean + inheritHookSelector: + default: false + description: InheritHookSelector defines whether cascading + scans should inherit hookSelector from the parent scan. + type: boolean inheritInitContainers: default: false description: InheritInitContainers defines whether cascading @@ -265,6 +270,51 @@ spec: - name type: object type: array + hookSelector: + description: HookSelector allows to specify a LabelSelector with + which the hooks are selected. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector + that contains values, a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, NotIn, + Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If + the operator is In or NotIn, the values array must + be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced + during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A + single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field is "key", + the operator is "In", and the values array contains only + "value". The requirements are ANDed. + type: object + type: object initContainers: description: InitContainers allows to specify init containers for the scan container, to pre-load data into them. diff --git a/operator/controllers/execution/scans/hook_reconciler.go b/operator/controllers/execution/scans/hook_reconciler.go index 1420e79065..b1f8de9523 100644 --- a/operator/controllers/execution/scans/hook_reconciler.go +++ b/operator/controllers/execution/scans/hook_reconciler.go @@ -7,6 +7,7 @@ package scancontrollers import ( "context" "fmt" + "k8s.io/apimachinery/pkg/labels" executionv1 "github.com/secureCodeBox/secureCodeBox/operator/apis/execution/v1" util "github.com/secureCodeBox/secureCodeBox/operator/utils" @@ -23,9 +24,17 @@ import ( func (r *ScanReconciler) setHookStatus(scan *executionv1.Scan) error { // Set (pending) Hook status on the scan ctx := context.Background() + labelSelector, err := r.getLabelSelector(scan) + if err != nil { + return err + } + var scanCompletionHooks executionv1.ScanCompletionHookList - if err := r.List(ctx, &scanCompletionHooks, client.InNamespace(scan.Namespace)); err != nil { + if err := r.List(ctx, &scanCompletionHooks, + client.InNamespace(scan.Namespace), + client.MatchingLabelsSelector{Selector: labelSelector}, + ); err != nil { r.Log.V(7).Info("Unable to fetch ScanCompletionHooks") return err } @@ -198,9 +207,17 @@ func containsJobForHook(jobs *batch.JobList, hook executionv1.ScanCompletionHook func (r *ScanReconciler) startReadOnlyHooks(scan *executionv1.Scan) error { ctx := context.Background() + labelSelector, err := r.getLabelSelector(scan) + if err != nil { + return err + } + var scanCompletionHooks executionv1.ScanCompletionHookList - if err := r.List(ctx, &scanCompletionHooks, client.InNamespace(scan.Namespace)); err != nil { + if err := r.List(ctx, &scanCompletionHooks, + client.InNamespace(scan.Namespace), + client.MatchingLabelsSelector{Selector: labelSelector}, + ); err != nil { r.Log.V(7).Info("Unable to fetch ScanCompletionHooks") return err } @@ -466,3 +483,11 @@ func (r *ScanReconciler) updateHookStatus(scan *executionv1.Scan, hookStatus exe } return nil } + +func (r *ScanReconciler) getLabelSelector(scan *executionv1.Scan) (labels.Selector, error) { + hookSelector := scan.Spec.HookSelector + if hookSelector == nil { + hookSelector = &metav1.LabelSelector{} + } + return metav1.LabelSelectorAsSelector(hookSelector) +} diff --git a/operator/crds/cascading.securecodebox.io_cascadingrules.yaml b/operator/crds/cascading.securecodebox.io_cascadingrules.yaml index 76e0fecf58..8df98f340a 100644 --- a/operator/crds/cascading.securecodebox.io_cascadingrules.yaml +++ b/operator/crds/cascading.securecodebox.io_cascadingrules.yaml @@ -114,6 +114,11 @@ spec: description: InheritEnv defines whether cascading scans should inherit environment variables from the parent scan type: boolean + inheritHookSelector: + default: false + description: InheritHookSelector defines whether cascading + scans should inherit hookSelector from the parent scan. + type: boolean inheritInitContainers: default: false description: InheritInitContainers defines whether cascading @@ -283,6 +288,51 @@ spec: - name type: object type: array + hookSelector: + description: HookSelector allows to specify a LabelSelector with + which the hooks are selected. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector + that contains values, a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, NotIn, + Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If + the operator is In or NotIn, the values array must + be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced + during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A + single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field is "key", + the operator is "In", and the values array contains only + "value". The requirements are ANDed. + type: object + type: object initContainers: description: InitContainers allows to specify init containers for the scan container, to pre-load data into them. diff --git a/operator/crds/execution.securecodebox.io_scans.yaml b/operator/crds/execution.securecodebox.io_scans.yaml index 5f752dc084..3e24c3b2b0 100644 --- a/operator/crds/execution.securecodebox.io_scans.yaml +++ b/operator/crds/execution.securecodebox.io_scans.yaml @@ -76,6 +76,11 @@ spec: description: InheritEnv defines whether cascading scans should inherit environment variables from the parent scan type: boolean + inheritHookSelector: + default: false + description: InheritHookSelector defines whether cascading scans + should inherit hookSelector from the parent scan. + type: boolean inheritInitContainers: default: false description: InheritInitContainers defines whether cascading scans @@ -238,6 +243,51 @@ spec: - name type: object type: array + hookSelector: + description: HookSelector allows to specify a LabelSelector with which + the hooks are selected. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: A label selector requirement is a selector that + contains values, a key, and an operator that relates the key + and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: operator represents a key's relationship to + a set of values. Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of string values. If the + operator is In or NotIn, the values array must be non-empty. + If the operator is Exists or DoesNotExist, the values + array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single + {key,value} in the matchLabels map is equivalent to an element + of matchExpressions, whose key field is "key", the operator + is "In", and the values array contains only "value". The requirements + are ANDed. + type: object + type: object initContainers: description: InitContainers allows to specify init containers for the scan container, to pre-load data into them. diff --git a/operator/crds/execution.securecodebox.io_scheduledscans.yaml b/operator/crds/execution.securecodebox.io_scheduledscans.yaml index 1529f1c120..53043f17a2 100644 --- a/operator/crds/execution.securecodebox.io_scheduledscans.yaml +++ b/operator/crds/execution.securecodebox.io_scheduledscans.yaml @@ -96,6 +96,11 @@ spec: description: InheritEnv defines whether cascading scans should inherit environment variables from the parent scan type: boolean + inheritHookSelector: + default: false + description: InheritHookSelector defines whether cascading + scans should inherit hookSelector from the parent scan. + type: boolean inheritInitContainers: default: false description: InheritInitContainers defines whether cascading @@ -265,6 +270,51 @@ spec: - name type: object type: array + hookSelector: + description: HookSelector allows to specify a LabelSelector with + which the hooks are selected. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector + that contains values, a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, NotIn, + Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If + the operator is In or NotIn, the values array must + be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced + during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A + single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field is "key", + the operator is "In", and the values array contains only + "value". The requirements are ANDed. + type: object + type: object initContainers: description: InitContainers allows to specify init containers for the scan container, to pre-load data into them.