Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 183 additions & 0 deletions hooks/cascading-scans/hook/hook.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -372,6 +375,7 @@ test("Should allow wildcards in cascadingRules", () => {
"spec": Object {
"cascades": Object {},
"env": Array [],
"hookSelector": Object {},
"initContainers": Array [],
"parameters": Array [
"--regular",
Expand Down Expand Up @@ -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 [
Expand Down Expand Up @@ -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 [
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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", () => {
Expand Down
11 changes: 7 additions & 4 deletions hooks/cascading-scans/hook/hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ import {
getCascadedRuleForScan,
purgeCascadedRuleFromScan,
mergeInheritedMap,
mergeInheritedArray
mergeInheritedArray,
mergeInheritedSelector,
} from "./scan-helpers";

interface HandleArgs {
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -142,6 +143,7 @@ function getCascadingScan(
]
},
spec: {
hookSelector,
scanType,
parameters,
cascades: parentScan.spec.cascades,
Expand All @@ -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),
Expand All @@ -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),
}
}

Expand Down
4 changes: 2 additions & 2 deletions hooks/cascading-scans/hook/kubernetes-label-selector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ export interface LabelSelectorRequirement {
}

export interface LabelSelector {
matchExpressions: Array<LabelSelectorRequirement>;
matchLabels: Map<string, string>;
matchExpressions?: Array<LabelSelectorRequirement>;
matchLabels?: Map<string, string>;
}

// generateSelectorString transforms a kubernetes labelSelector object in to the string representation
Expand Down
28 changes: 27 additions & 1 deletion hooks/cascading-scans/hook/scan-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export interface ScanSpec {
volumes?: Array<k8s.V1Volume>;
volumeMounts?: Array<k8s.V1VolumeMount>;
initContainers?: Array<k8s.V1Container>;
hookSelector?: LabelSelector;
}

export interface CascadingInheritance {
Expand All @@ -70,6 +71,7 @@ export interface CascadingInheritance {
inheritEnv: boolean,
inheritVolumes: boolean,
inheritInitContainers: boolean,
inheritHookSelector: boolean,
}

export function mergeInheritedMap(parentProps, ruleProps, inherit: boolean = true) {
Expand All @@ -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}`);

Expand Down Expand Up @@ -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
}

Expand Down
4 changes: 4 additions & 0 deletions hooks/cascading-scans/templates/cascading-scans-hook.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}"
Expand Down
3 changes: 3 additions & 0 deletions hooks/cascading-scans/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}"
Expand Down
Loading