From 14ec4557bd0c586e7a25cc40cb7aad5a04db526b Mon Sep 17 00:00:00 2001 From: Jop Zitman Date: Wed, 10 Nov 2021 12:54:52 +0100 Subject: [PATCH 01/54] Add reverse-matches Signed-off-by: Jop Zitman --- hooks/cascading-scans/hook/hook.test.js | 232 +++++++++++++++--- hooks/cascading-scans/hook/hook.ts | 42 +++- hooks/cascading-scans/hook/package-lock.json | 64 +++++ hooks/cascading-scans/hook/package.json | 4 +- .../hook/reverse-matches.test.js | 210 ++++++++++++++++ hooks/cascading-scans/hook/reverse-matches.ts | 118 +++++++++ hooks/cascading-scans/hook/scan-helpers.ts | 45 +++- hooks/cascading-scans/templates/role.yaml | 6 + .../execution/v1/parsedefinition_types.go | 2 + operator/apis/execution/v1/scan_types.go | 35 +++ .../execution/v1/zz_generated.deepcopy.go | 64 +++++ ...ading.securecodebox.io_cascadingrules.yaml | 89 +++++++ ...ion.securecodebox.io_parsedefinitions.yaml | 4 + .../execution.securecodebox.io_scans.yaml | 88 +++++++ ...ution.securecodebox.io_scheduledscans.yaml | 89 +++++++ ...ading.securecodebox.io_cascadingrules.yaml | 89 +++++++ ...ion.securecodebox.io_parsedefinitions.yaml | 4 + .../execution.securecodebox.io_scans.yaml | 88 +++++++ ...ution.securecodebox.io_scheduledscans.yaml | 89 +++++++ 19 files changed, 1324 insertions(+), 38 deletions(-) create mode 100644 hooks/cascading-scans/hook/reverse-matches.test.js create mode 100644 hooks/cascading-scans/hook/reverse-matches.ts diff --git a/hooks/cascading-scans/hook/hook.test.js b/hooks/cascading-scans/hook/hook.test.js index a7b60854a7..9221a869d9 100644 --- a/hooks/cascading-scans/hook/hook.test.js +++ b/hooks/cascading-scans/hook/hook.test.js @@ -3,9 +3,13 @@ // SPDX-License-Identifier: Apache-2.0 const { getCascadingScans } = require("./hook"); +const { + ScanAnnotationSelectorRequirementOperator, +} = require("./reverse-matches"); let parentScan = undefined; let sslyzeCascadingRules = undefined; +let parseDefinition = undefined; beforeEach(() => { parentScan = { @@ -21,6 +25,12 @@ beforeEach(() => { cascades: {} } }; + parseDefinition = { + meta: {}, + spec: { + selectorAttributeMappings: {}, + }, + } sslyzeCascadingRules = [ { @@ -73,7 +83,9 @@ test("Should create subsequent scans for open HTTPS ports (NMAP findings)", () = const cascadedScans = getCascadingScans( parentScan, findings, - sslyzeCascadingRules + sslyzeCascadingRules, + undefined, + parseDefinition, ); expect(cascadedScans).toMatchInlineSnapshot(` @@ -134,7 +146,7 @@ test("Should create no subsequent scans if there are no rules", () => { const cascadingRules = []; - const cascadedScans = getCascadingScans(parentScan, findings, cascadingRules); + const cascadedScans = getCascadingScans(parentScan, findings, cascadingRules, undefined, parseDefinition); expect(cascadedScans).toMatchInlineSnapshot(`Array []`); }); @@ -159,7 +171,9 @@ test("Should not try to do magic to the scan name if its something random", () = const cascadedScans = getCascadingScans( parentScan, findings, - sslyzeCascadingRules + sslyzeCascadingRules, + undefined, + parseDefinition, ); expect(cascadedScans[0].metadata.generateName).toEqual("foobar.com-tls-scans-"); @@ -185,7 +199,9 @@ test("Should not start a new scan when the corresponding cascadingRule is alread const cascadedScans = getCascadingScans( parentScan, findings, - sslyzeCascadingRules + sslyzeCascadingRules, + undefined, + parseDefinition, ); expect(cascadedScans).toMatchInlineSnapshot(`Array []`); @@ -210,7 +226,9 @@ test("Should not crash when the annotations are not set", () => { const cascadedScans = getCascadingScans( parentScan, findings, - sslyzeCascadingRules + sslyzeCascadingRules, + undefined, + parseDefinition, ); expect(cascadedScans).toMatchInlineSnapshot(` @@ -279,7 +297,9 @@ test("Should copy ENV fields from cascadingRule to created scan", () => { const cascadedScans = getCascadingScans( parentScan, findings, - sslyzeCascadingRules + sslyzeCascadingRules, + undefined, + parseDefinition, ); expect(cascadedScans[0].spec.env).toMatchInlineSnapshot(` @@ -341,7 +361,9 @@ test("Should allow wildcards in cascadingRules", () => { const cascadedScans = getCascadingScans( parentScan, findings, - sslyzeCascadingRules + sslyzeCascadingRules, + undefined, + parseDefinition, ); expect(cascadedScans).toMatchInlineSnapshot(` @@ -410,7 +432,9 @@ test("should not copy labels if inheritLabels is set to false", () => { const cascadedScans = getCascadingScans( parentScan, findings, - sslyzeCascadingRules + sslyzeCascadingRules, + undefined, + parseDefinition, ); for (const cascadedScan of cascadedScans) { @@ -443,7 +467,9 @@ test("should copy labels if inheritLabels is not set", () => { const cascadedScans = getCascadingScans( parentScan, findings, - sslyzeCascadingRules + sslyzeCascadingRules, + undefined, + parseDefinition, ); for (const cascadedScan of cascadedScans) { @@ -478,7 +504,9 @@ test("should copy labels if inheritLabels is set to true", () => { const cascadedScans = getCascadingScans( parentScan, findings, - sslyzeCascadingRules + sslyzeCascadingRules, + undefined, + parseDefinition, ); for (const cascadedScan of cascadedScans) { @@ -511,7 +539,9 @@ test("should not copy annotations if inheritAnnotations is set to false", () => const cascadedScans = getCascadingScans( parentScan, findings, - sslyzeCascadingRules + sslyzeCascadingRules, + undefined, + parseDefinition, ); for (const cascadedScan of cascadedScans) { @@ -543,7 +573,9 @@ test("should copy annotations if inheritAnnotations is not set", () => { const cascadedScans = getCascadingScans( parentScan, findings, - sslyzeCascadingRules + sslyzeCascadingRules, + undefined, + parseDefinition, ); for (const cascadedScan of cascadedScans) { @@ -576,7 +608,9 @@ test("should copy annotations if inheritAnnotations is set to true", () => { const cascadedScans = getCascadingScans( parentScan, findings, - sslyzeCascadingRules + sslyzeCascadingRules, + undefined, + parseDefinition, ); for (const cascadedScan of cascadedScans) { @@ -608,7 +642,9 @@ test("should copy scanLabels from CascadingRule to cascading scan", () => { const cascadedScans = getCascadingScans( parentScan, findings, - sslyzeCascadingRules + sslyzeCascadingRules, + undefined, + parseDefinition, ); const cascadedScan = cascadedScans[0] @@ -639,7 +675,9 @@ test("should copy scanAnnotations from CascadingRule to cascading scan", () => { const cascadedScans = getCascadingScans( parentScan, findings, - sslyzeCascadingRules + sslyzeCascadingRules, + undefined, + parseDefinition, ); const cascadedScan = cascadedScans[0] @@ -688,7 +726,8 @@ test("should properly parse template values in scanLabels and scanAnnotations", parentScan, findings, sslyzeCascadingRules, - sslyzeCascadingRules[0] + sslyzeCascadingRules[0], + parseDefinition, ); expect(sslyzeCascadingRules[0].spec.scanSpec.parameters).toEqual(["--regular", "{{$.hostOrIP}}:{{attributes.port}}"]) @@ -746,7 +785,9 @@ test("should copy proper finding ID into annotations", () => { const cascadedScans = getCascadingScans( parentScan, findings, - sslyzeCascadingRules + sslyzeCascadingRules, + undefined, + parseDefinition, ); const cascadedScan = cascadedScans[0] @@ -790,7 +831,9 @@ test("should merge environment variables into cascaded scan", () => { const cascadedScans = getCascadingScans( parentScan, findings, - sslyzeCascadingRules + sslyzeCascadingRules, + undefined, + parseDefinition, ); const cascadedScan = cascadedScans[0] @@ -844,7 +887,9 @@ test("should merge volumeMounts into cascaded scan", () => { const cascadedScans = getCascadingScans( parentScan, findings, - sslyzeCascadingRules + sslyzeCascadingRules, + undefined, + parseDefinition, ); const cascadedScan = cascadedScans[0] @@ -902,7 +947,9 @@ test("should merge volumes into cascaded scan", () => { const cascadedScans = getCascadingScans( parentScan, findings, - sslyzeCascadingRules + sslyzeCascadingRules, + undefined, + parseDefinition, ); const cascadedScan = cascadedScans[0]; @@ -959,7 +1006,9 @@ test("should merge initContainers into cascaded scan", () => { const cascadedScans = getCascadingScans( parentScan, findings, - sslyzeCascadingRules + sslyzeCascadingRules, + undefined, + parseDefinition, ); const cascadedScan = cascadedScans[0]; @@ -1018,7 +1067,9 @@ test("should not merge initContainers into cascaded scan if not instructed", () const cascadedScans = getCascadingScans( parentScan, findings, - sslyzeCascadingRules + sslyzeCascadingRules, + undefined, + parseDefinition, ); const cascadedScan = cascadedScans[0]; @@ -1090,7 +1141,9 @@ test("Templating should apply to environment variables", () => { const cascadedScans = getCascadingScans( parentScan, findings, - sslyzeCascadingRules + sslyzeCascadingRules, + undefined, + parseDefinition, ); expect(cascadedScans).toMatchInlineSnapshot(` @@ -1210,7 +1263,9 @@ test("Templating should apply to initContainer commands", () => { const cascadedScans = getCascadingScans( parentScan, findings, - sslyzeCascadingRules + sslyzeCascadingRules, + undefined, + parseDefinition, ); expect(cascadedScans).toMatchInlineSnapshot(` @@ -1343,7 +1398,9 @@ test("Templating should apply to initContainer environment variables", () => { const cascadedScans = getCascadingScans( parentScan, findings, - sslyzeCascadingRules + sslyzeCascadingRules, + undefined, + parseDefinition, ); expect(cascadedScans).toMatchInlineSnapshot(` @@ -1478,7 +1535,9 @@ test("Templating should not break special encoding (http://...) when using tripl const cascadedScans = getCascadingScans( parentScan, findings, - sslyzeCascadingRules + sslyzeCascadingRules, + undefined, + parseDefinition, ); expect(cascadedScans).toMatchInlineSnapshot(` @@ -1620,7 +1679,9 @@ test("should purge cascaded scan spec from parent scan", () => { const cascadedScans = getCascadingScans( parentScan, findings, - sslyzeCascadingRules + sslyzeCascadingRules, + undefined, + parseDefinition, ); const cascadedScan = cascadedScans[0] @@ -1663,7 +1724,8 @@ test("should purge cascaded scan spec from parent scan", () => { cascadedScan, findings, sslyzeCascadingRules, - sslyzeCascadingRules[0] // cascaded rule on parent + sslyzeCascadingRules[0], // cascaded rule on parent + parseDefinition, ); const secondCascadedScan = secondCascadedScans[0]; @@ -1768,7 +1830,9 @@ test("should not copy cascaded scan spec from parent scan if inheritance is unde const cascadedScans = getCascadingScans( parentScan, findings, - sslyzeCascadingRules + sslyzeCascadingRules, + undefined, + parseDefinition, ); const cascadedScan = cascadedScans[0] @@ -1810,7 +1874,8 @@ test("should not copy cascaded scan spec from parent scan if inheritance is unde cascadedScan, findings, sslyzeCascadingRules, - sslyzeCascadingRules[0] // cascaded rule on parent + sslyzeCascadingRules[0], // cascaded rule on parent + parseDefinition, ); const secondCascadedScan = secondCascadedScans[0]; @@ -1841,7 +1906,9 @@ test("should append cascading rule to further cascading scan chains", () => { const cascadedScans = getCascadingScans( parentScan, findings, - sslyzeCascadingRules + sslyzeCascadingRules, + undefined, + parseDefinition, ); const cascadedScan = cascadedScans[0] @@ -1883,10 +1950,111 @@ test("should append cascading rule to further cascading scan chains", () => { cascadedScan, findings, sslyzeCascadingRules, - sslyzeCascadingRules[0] // cascaded rule on parent + sslyzeCascadingRules[0], // cascaded rule on parent + parseDefinition, ); const secondCascadedScan = secondCascadedScans[0]; expect(secondCascadedScan.metadata.annotations["cascading.securecodebox.io/chain"]).toEqual("tls-scans,tls-scans-second") }); + +test("should not cascade if scan annotation selector does not match", () => { + parentScan.metadata.annotations["scope.engagement/ports"] = "80,443"; + parentScan.spec.cascades.scanAnnotationSelector = { + allOf: [ + { + key: "scope.engagement/ports", + operator: ScanAnnotationSelectorRequirementOperator.Contains, + values: ["{{$.port}}"], + }, + ], + }; + + const findings = [ + { + name: "Port 443 is open", + category: "Open Port", + attributes: { + state: "open", + hostname: "foobar.com", + port: 443, + service: "https", + }, + }, + { + name: "Port 8080 is open", + category: "Open Port", + attributes: { + state: "open", + hostname: "foobar.com", + port: 8080, + service: "https", + }, + }, + ]; + + parseDefinition.spec.selectorAttributeMappings["port"] = "{{attributes.port}}"; + + const cascadedScans = getCascadingScans( + parentScan, + findings, + sslyzeCascadingRules, + undefined, + parseDefinition + ); + + expect(cascadedScans).toMatchInlineSnapshot(` + Array [ + Object { + "apiVersion": "execution.securecodebox.io/v1", + "kind": "Scan", + "metadata": Object { + "annotations": Object { + "cascading.securecodebox.io/chain": "tls-scans", + "cascading.securecodebox.io/matched-finding": undefined, + "cascading.securecodebox.io/parent-scan": "nmap-foobar.com", + "scope.engagement/ports": "80,443", + "securecodebox.io/hook": "cascading-scans", + }, + "generateName": "sslyze-foobar.com-tls-scans-", + "labels": Object {}, + "ownerReferences": Array [ + Object { + "apiVersion": "execution.securecodebox.io/v1", + "blockOwnerDeletion": true, + "controller": true, + "kind": "Scan", + "name": "nmap-foobar.com", + "uid": undefined, + }, + ], + }, + "spec": Object { + "cascades": Object { + "scanAnnotationSelector": Object { + "allOf": Array [ + Object { + "key": "scope.engagement/ports", + "operator": "Contains", + "values": Array [ + "{{$.port}}", + ], + }, + ], + }, + }, + "env": Array [], + "initContainers": Array [], + "parameters": Array [ + "--regular", + "foobar.com:443", + ], + "scanType": "sslyze", + "volumeMounts": Array [], + "volumes": Array [], + }, + }, + ] + `); +}); diff --git a/hooks/cascading-scans/hook/hook.ts b/hooks/cascading-scans/hook/hook.ts index 205d2b7aa2..1dae623481 100644 --- a/hooks/cascading-scans/hook/hook.ts +++ b/hooks/cascading-scans/hook/hook.ts @@ -13,11 +13,16 @@ import { Scan, Finding, CascadingRule, + ParseDefinition, getCascadedRuleForScan, + getParseDefinitionForScan, purgeCascadedRuleFromScan, mergeInheritedMap, mergeInheritedArray } from "./scan-helpers"; +import { + isReverseMatch +} from "./reverse-matches"; interface HandleArgs { scan: Scan; @@ -28,8 +33,9 @@ export async function handle({ scan, getFindings }: HandleArgs) { const findings = await getFindings(); const cascadingRules = await getCascadingRules(scan); const cascadedRuleUsedForParentScan = await getCascadedRuleForScan(scan); + const parseDefinition = await getParseDefinition(scan); - const cascadingScans = getCascadingScans(scan, findings, cascadingRules, cascadedRuleUsedForParentScan); + const cascadingScans = getCascadingScans(scan, findings, cascadingRules, cascadedRuleUsedForParentScan, parseDefinition); for (const cascadingScan of cascadingScans) { await startSubsequentSecureCodeBoxScan(cascadingScan); @@ -41,6 +47,11 @@ async function getCascadingRules(scan: Scan): Promise> { return >await getCascadingRulesForScan(scan); } +async function getParseDefinition(scan: Scan): Promise { + // Explicit Cast to the proper Type + return await getParseDefinitionForScan(scan); +} + /** * Goes thought the Findings and the CascadingRules * and returns a List of Scans which should be started based on both. @@ -49,7 +60,8 @@ export function getCascadingScans( parentScan: Scan, findings: Array, cascadingRules: Array, - cascadedRuleUsedForParentScan: CascadingRule + cascadedRuleUsedForParentScan: CascadingRule, + parseDefinition: ParseDefinition, ): Array { let cascadingScans: Array = []; const cascadingRuleChain = getScanChain(parentScan); @@ -66,7 +78,7 @@ export function getCascadingScans( continue; } - cascadingScans = cascadingScans.concat(getScansMatchingRule(parentScan, findings, cascadingRule)) + cascadingScans = cascadingScans.concat(getScansMatchingRule(parentScan, findings, cascadingRule, parseDefinition)) } return cascadingScans; @@ -85,9 +97,31 @@ export function getScanChain(parentScan: Scan) { return [] } -function getScansMatchingRule(parentScan: Scan, findings: Array, cascadingRule: CascadingRule) { +function getScansMatchingRule( + parentScan: Scan, + findings: Array, + cascadingRule: CascadingRule, + parseDefinition: ParseDefinition, +) { const cascadingScans: Array = []; for (const finding of findings) { + // Check if the scan matches for the current finding + const reverseMatches = isReverseMatch( + parentScan.spec.cascades.scanAnnotationSelector, + parentScan.metadata.annotations, + finding, + parseDefinition.spec.selectorAttributeMappings, + ); + + if (!reverseMatches) { + console.log(`Cascading Rule ${cascadingRule.metadata.name} not triggered as scan annotation selector did not match`); + console.log(`Scan annotations ${parentScan.metadata.annotations}`); + console.log(`Scan annotation selector ${parentScan.spec.cascades.scanAnnotationSelector}`); + console.log(`Selector Attribute Mappings ${parseDefinition.spec.selectorAttributeMappings}`); + console.log(`Finding ${finding}`); + continue; + } + // Check if one (ore more) of the CascadingRule matchers apply to the finding const matches = cascadingRule.spec.matches.anyOf.some(matchesRule => isMatch(finding, matchesRule) || isMatchWith(finding, matchesRule, wildcardMatcher) diff --git a/hooks/cascading-scans/hook/package-lock.json b/hooks/cascading-scans/hook/package-lock.json index adb5aefed8..784cdeb0ee 100644 --- a/hooks/cascading-scans/hook/package-lock.json +++ b/hooks/cascading-scans/hook/package-lock.json @@ -10,11 +10,13 @@ "license": "Apache-2.0", "dependencies": { "@kubernetes/client-node": "^0.15.0", + "ip-address": "^8.1.0", "lodash": "^4.17.21", "matcher": "^4.0.0", "mustache": "^4.2.0" }, "devDependencies": { + "@types/ip-address": "^7.0.0", "@types/lodash": "^4.14.171", "@types/node": "^14.17.5", "jest": "^27.0.6", @@ -1074,6 +1076,16 @@ "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==" }, + "node_modules/@types/ip-address": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@types/ip-address/-/ip-address-7.0.0.tgz", + "integrity": "sha512-OyDm4EwZsYPDUjXz3ktiuQE8PJIPO1uUZMfvZMcWmykWjm3WVyI78rAnHkqKV3pMR7iDRKfalI+RxG5JBDUo5w==", + "deprecated": "This is a stub types definition. ip-address provides its own type definitions, so you do not need this installed.", + "dev": true, + "dependencies": { + "ip-address": "*" + } + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", @@ -2559,6 +2571,28 @@ "node": ">= 0.10" } }, + "node_modules/ip-address": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-8.1.0.tgz", + "integrity": "sha512-Wz91gZKpNKoXtqvY8ScarKYwhXoK4r/b5QuT+uywe/azv0/nUCo7Bh0IRRI7F9DHR06kJNWtzMGLIbXavngbKA==", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "1.1.2" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/ip-address/node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha1-sBMHyym2GKHtJux56RH4A8TaAEA=" + }, + "node_modules/ip-address/node_modules/sprintf-js": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", + "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==" + }, "node_modules/is-ci": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.0.tgz", @@ -5816,6 +5850,15 @@ "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==" }, + "@types/ip-address": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@types/ip-address/-/ip-address-7.0.0.tgz", + "integrity": "sha512-OyDm4EwZsYPDUjXz3ktiuQE8PJIPO1uUZMfvZMcWmykWjm3WVyI78rAnHkqKV3pMR7iDRKfalI+RxG5JBDUo5w==", + "dev": true, + "requires": { + "ip-address": "*" + } + }, "@types/istanbul-lib-coverage": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", @@ -6967,6 +7010,27 @@ "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==" }, + "ip-address": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-8.1.0.tgz", + "integrity": "sha512-Wz91gZKpNKoXtqvY8ScarKYwhXoK4r/b5QuT+uywe/azv0/nUCo7Bh0IRRI7F9DHR06kJNWtzMGLIbXavngbKA==", + "requires": { + "jsbn": "1.1.0", + "sprintf-js": "1.1.2" + }, + "dependencies": { + "jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha1-sBMHyym2GKHtJux56RH4A8TaAEA=" + }, + "sprintf-js": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", + "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==" + } + } + }, "is-ci": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.0.tgz", diff --git a/hooks/cascading-scans/hook/package.json b/hooks/cascading-scans/hook/package.json index 438a417cda..3f6b994f1b 100644 --- a/hooks/cascading-scans/hook/package.json +++ b/hooks/cascading-scans/hook/package.json @@ -9,7 +9,7 @@ }, "main": "hook.js", "scripts": { - "build": "npx tsc hook.ts --sourceMap", + "build": "npx tsc hook.ts reverse-matches.ts --sourceMap", "test": "jest . --verbose false" }, "keywords": [ @@ -38,11 +38,13 @@ "license": "Apache-2.0", "dependencies": { "@kubernetes/client-node": "^0.15.0", + "ip-address": "^8.1.0", "lodash": "^4.17.21", "matcher": "^4.0.0", "mustache": "^4.2.0" }, "devDependencies": { + "@types/ip-address": "^7.0.0", "@types/lodash": "^4.14.171", "@types/node": "^14.17.5", "jest": "^27.0.6", diff --git a/hooks/cascading-scans/hook/reverse-matches.test.js b/hooks/cascading-scans/hook/reverse-matches.test.js new file mode 100644 index 0000000000..cef677fe08 --- /dev/null +++ b/hooks/cascading-scans/hook/reverse-matches.test.js @@ -0,0 +1,210 @@ +// SPDX-FileCopyrightText: 2021 iteratec GmbH +// +// SPDX-License-Identifier: Apache-2.0 + +const { isReverseMatch } = require("./reverse-matches"); + +test("Matches using templates populated with finding", () => { + const annotations = { + "engagement.scope/domains": "example.com,subdomain.example.com", + } + const scanAnnotationSelector = { + validOnMissingRender: false, + allOf: [ + { + key: "engagement.scope/domains", + operator: "Contains", + values: ["{{attributes.hostname}}"], + } + ] + } + const finding = { + attributes: { + hostname: "example.com", + } + }; + + const cascadedScans = isReverseMatch( + scanAnnotationSelector, + annotations, + finding, + {} + ); + + expect(cascadedScans).toBe(true); +}); + +test("Does not match using if selector does not match", () => { + const annotations = { + "engagement.scope/domains": "subdomain.example.com", + } + const scanAnnotationSelector = { + validOnMissingRender: false, + allOf: [ + { + key: "engagement.scope/domains", + operator: "Contains", + values: ["{{attributes.hostname}}"], + } + ] + } + const finding = { + attributes: { + hostname: "example.com", + } + }; + + const cascadedScans = isReverseMatch( + scanAnnotationSelector, + annotations, + finding, + {} + ); + + expect(cascadedScans).toBe(false); +}); + +test("Matches InCIDR if attributes.ip in subnet", () => { + const annotations = { + "engagement.scope/cidr": "10.0.0.0/16", + } + const scanAnnotationSelector = { + validOnMissingRender: false, + allOf: [ + { + key: "engagement.scope/cidr", + operator: "InCIDR", + values: ["{{attributes.ip}}"], + } + ] + } + const finding = { + attributes: { + ip: "10.0.1.0", + } + }; + + const cascadedScans = isReverseMatch( + scanAnnotationSelector, + annotations, + finding, + {} + ); + + expect(cascadedScans).toBe(true); +}); + +test("Does not match InCIDR if attributes.ip not in subnet", () => { + const annotations = { + "engagement.scope/cidr": "10.0.0.0/32", + } + const scanAnnotationSelector = { + validOnMissingRender: false, + allOf: [ + { + key: "engagement.scope/cidr", + operator: "InCIDR", + values: ["{{attributes.ip}}"], + } + ] + } + const finding = { + attributes: { + ip: "10.0.1.0", + } + }; + + const cascadedScans = isReverseMatch( + scanAnnotationSelector, + annotations, + finding, + {} + ); + + expect(cascadedScans).toBe(false); +}); + +test("Matches using templates populated with finding and a mapped selector", () => { + const annotations = { + "engagement.scope/domains": "example.com,subdomain.example.com", + } + const scanAnnotationSelector = { + requiresMapping: false, + validOnMissingRender: false, + allOf: [ + { + key: "engagement.scope/domains", + operator: "Contains", + values: ["{{$.hostname}}"], + } + ] + } + const finding = { + attributes: { + hostname: "example.com", + } + }; + + const selectorAttributeMappings = { + "hostname": "{{attributes.hostname}}", + } + + const cascadedScans = isReverseMatch( + scanAnnotationSelector, + annotations, + finding, + selectorAttributeMappings + ); + + expect(cascadedScans).toBe(true); +}); + +test("Matches if mapping is not available: validOnMissingRender true", () => { + const annotations = { + "engagement.scope/domains": "example.com,subdomain.example.com", + } + const scanAnnotationSelector = { + validOnMissingRender: true, + allOf: [ + { + key: "engagement.scope/domains", + operator: "Contains", + values: ["{{$.hostname}}"], + } + ] + } + + const cascadedScans = isReverseMatch( + scanAnnotationSelector, + annotations, + {}, + {}, + ); + + expect(cascadedScans).toBe(true); +}); + +test("Does not match if mapping is not available: validOnMissingRender false", () => { + const annotations = { + "engagement.scope/domains": "example.com,subdomain.example.com", + } + const scanAnnotationSelector = { + validOnMissingRender: false, + allOf: [ + { + key: "engagement.scope/domains", + operator: "Contains", + values: ["{{$.hostname}}"], + } + ] + } + + const cascadedScans = isReverseMatch( + scanAnnotationSelector, + annotations, + {}, + {}, + ); + + expect(cascadedScans).toBe(false); +}); diff --git a/hooks/cascading-scans/hook/reverse-matches.ts b/hooks/cascading-scans/hook/reverse-matches.ts new file mode 100644 index 0000000000..9c7f225ae8 --- /dev/null +++ b/hooks/cascading-scans/hook/reverse-matches.ts @@ -0,0 +1,118 @@ +import { + Finding, + ScanAnnotationSelector, + SelectorAttributeMappings, +} from "./scan-helpers"; +import { + V1ObjectMeta +} from "@kubernetes/client-node/dist/gen/model/v1ObjectMeta"; +import * as Mustache from "mustache"; +import { Address4 } from "ip-address"; + +export enum ScanAnnotationSelectorRequirementOperator { + In = "In", + NotIn = "NotIn", + Exists = "Exists", + DoesNotExist = "DoesNotExist", + Contains = "Contains", + DoesNotContain = "DoesNotContain", + InCIDR = "InCIDR", + NotInCIDR = "NotInCIDR", +} + +export interface ScanAnnotationSelectorRequirement { + key: string; + operator: ScanAnnotationSelectorRequirementOperator; + values: Array; +} + +export function isReverseMatch( + scanAnnotationSelector: ScanAnnotationSelector, + scanAnnotations: V1ObjectMeta['annotations'], + finding: Finding, + selectorAttributeMappings: SelectorAttributeMappings, +) { + if (scanAnnotationSelector === undefined) return true; + + function validateRequirement({key, operator, values}: ScanAnnotationSelectorRequirement) { + const operatorFunction = operators[operator]; + if (operatorFunction === undefined) { + throw new Error(`Unknown operator '${operator}'`); + } + const value = scanAnnotations[key]; + const renders = values.map(templateValue); + if (renders.some(render => !render[1])) { + return scanAnnotationSelector.validOnMissingRender; + } + return operatorFunction({lhs: value, rhs: renders.map(render => render[0])}); + } + + function templateValue(value: string): [string, boolean] { + if (value === undefined) return [undefined, true]; + let mapped = Mustache.render(value, { + $: { + ...selectorAttributeMappings + } + }); + if (mapped == "") { + mapped = value; + } + let rendered = Mustache.render(mapped, finding); + return [rendered, rendered != ""]; + } + + if (scanAnnotationSelector.allOf !== undefined) return scanAnnotationSelector.allOf.every(validateRequirement); + + if (scanAnnotationSelector.anyOf !== undefined) return scanAnnotationSelector.anyOf.some(validateRequirement); + + if (scanAnnotationSelector.noneOf !== undefined) return !scanAnnotationSelector.noneOf.some(validateRequirement); + + return true; +} + +interface operands { + lhs: string, + rhs: string[], +} + +const operators: { [key in ScanAnnotationSelectorRequirementOperator]:(props: operands) => boolean; } = { + [ScanAnnotationSelectorRequirementOperator.In]: operatorIn, + [ScanAnnotationSelectorRequirementOperator.NotIn]: props => !operatorIn(props), + [ScanAnnotationSelectorRequirementOperator.Exists]: operatorExists, + [ScanAnnotationSelectorRequirementOperator.DoesNotExist]: props => !operatorExists(props), + [ScanAnnotationSelectorRequirementOperator.Contains]: operatorContains, + [ScanAnnotationSelectorRequirementOperator.DoesNotContain]: props => !operatorContains(props), + [ScanAnnotationSelectorRequirementOperator.InCIDR]: operatorInCIDR, + [ScanAnnotationSelectorRequirementOperator.NotInCIDR]: props => !operatorInCIDR(props), +} + +function operatorIn({lhs, rhs}: operands): boolean { + if (rhs === undefined) { + throw new Error("Values may not be undefined when using the operator 'In'") + } + return rhs.includes(lhs); +} +function operatorExists({lhs, rhs}: operands): boolean { + if (rhs !== undefined) { + throw new Error("Values must be undefined when using the operator 'Exists'") + } + return lhs !== undefined; +} + +function operatorContains({lhs, rhs}: operands): boolean { + if (rhs === undefined) { + throw new Error("Values may not be undefined when using the operator 'Contains'") + } + + const valueArray = lhs.split(","); + + return rhs.every(value => valueArray.includes(value)); +} + +function operatorInCIDR({lhs, rhs}: operands): boolean { + if (rhs === undefined) { + throw new Error("Values may not be undefined when using the operator 'InCIDR'") + } + const subnet = new Address4(lhs); + return rhs.every(value => new Address4(value).isInSubnet(subnet)); +} diff --git a/hooks/cascading-scans/hook/scan-helpers.ts b/hooks/cascading-scans/hook/scan-helpers.ts index f19d08e6ba..dbd948e616 100644 --- a/hooks/cascading-scans/hook/scan-helpers.ts +++ b/hooks/cascading-scans/hook/scan-helpers.ts @@ -6,10 +6,11 @@ import * as k8s from "@kubernetes/client-node"; import { generateSelectorString, - LabelSelector + LabelSelector, LabelSelectorRequirement } from "./kubernetes-label-selector"; import {isEqual} from "lodash"; import {getScanChain} from "./hook"; +import {ScanAnnotationSelectorRequirement} from "./reverse-matches"; // configure k8s client const kc = new k8s.KubeConfig(); @@ -52,6 +53,7 @@ export interface Matches { export interface Scan { metadata: k8s.V1ObjectMeta; spec: ScanSpec; + status?: ScanStatus; } export interface ScanSpec { @@ -64,7 +66,15 @@ export interface ScanSpec { initContainers?: Array; } +export interface ScanAnnotationSelector { + validOnMissingRender: boolean, + anyOf?: Array, + allOf?: Array, + noneOf?: Array, +} + export interface CascadingInheritance { + scanAnnotationSelector: ScanAnnotationSelector, inheritLabels: boolean, inheritAnnotations: boolean, inheritEnv: boolean, @@ -72,6 +82,21 @@ export interface CascadingInheritance { inheritInitContainers: boolean, } +export interface ScanStatus { + rawResultType: string, +} + +export interface ParseDefinition { + metadata: k8s.V1ObjectMeta; + spec: ParseDefinitionSpec; +} + +export interface ParseDefinitionSpec { + selectorAttributeMappings: SelectorAttributeMappings, +} + +export type SelectorAttributeMappings = { [key: string]: string; }; + export function mergeInheritedMap(parentProps, ruleProps, inherit: boolean = true) { if (!inherit) { parentProps = {}; @@ -141,6 +166,24 @@ export async function getCascadingRulesForScan(scan: Scan) { } } +export async function getParseDefinitionForScan(scan: Scan) { + try { + const response: any = await k8sApiCRD.getNamespacedCustomObject( + "cascading.securecodebox.io", + "v1", + namespace, + "cascadingrules", + scan.status.rawResultType, + ); + + return response.body; + } catch (err) { + console.error(`Failed to get ParseDefinition ${scan.status.rawResultType} from the kubernetes api`); + console.error(err); + process.exit(1); + } +} + // To ensure that the environment variables and volumes from the cascading rule are only applied to the matched scan // (and not its children), this function purges the cascading rule spec from the parent scan when inheriting them. export function purgeCascadedRuleFromScan(scan: Scan, cascadedRuleUsedForParentScan?: CascadingRule) : Scan { diff --git a/hooks/cascading-scans/templates/role.yaml b/hooks/cascading-scans/templates/role.yaml index acf2bd764d..d76e832ab3 100644 --- a/hooks/cascading-scans/templates/role.yaml +++ b/hooks/cascading-scans/templates/role.yaml @@ -17,6 +17,12 @@ rules: verbs: - get - create + - apiGroups: + - execution.securecodebox.io + resources: + - parsedefinitions + verbs: + - list - apiGroups: - execution.securecodebox.io resources: diff --git a/operator/apis/execution/v1/parsedefinition_types.go b/operator/apis/execution/v1/parsedefinition_types.go index 190c1de99f..ac43567c5e 100644 --- a/operator/apis/execution/v1/parsedefinition_types.go +++ b/operator/apis/execution/v1/parsedefinition_types.go @@ -17,6 +17,8 @@ type ParseDefinitionSpec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // Important: Run "make" to regenerate code after modifying this file + SelectorAttributeMappings map[string]string `json:"selectorAttributeMappings,omitempty"` + // Image is the reference to the parser container image which ca transform the raw scan report into findings Image string `json:"image,omitempty"` // ImagePullSecrets used to access private parser images diff --git a/operator/apis/execution/v1/scan_types.go b/operator/apis/execution/v1/scan_types.go index 9ddd4baf15..ae250ef025 100644 --- a/operator/apis/execution/v1/scan_types.go +++ b/operator/apis/execution/v1/scan_types.go @@ -14,6 +14,10 @@ import ( // CascadeSpec describes how and when cascading scans should be generated. type CascadeSpec struct { + // InheritLabels defines whether cascading scans should inherit labels from the parent scan + // +optional + ScanAnnotationSelector ScanAnnotationSelector `json:"scanAnnotationSelector"` + // InheritLabels defines whether cascading scans should inherit labels from the parent scan // +optional // +kubebuilder:default=true @@ -49,6 +53,37 @@ type CascadeSpec struct { MatchExpressions []metav1.LabelSelectorRequirement `json:"matchExpressions,omitempty" protobuf:"bytes,2,rep,name=matchExpressions"` } +type ScanAnnotationSelector struct { + // ValidOnMissingRender defines whether if a templating variable is not present, that condition should match + // +optional + // +kubebuilder:default=false + ValidOnMissingRender bool `json:"validOnMissingRender"` + + // AnyOf is a list of label selector requirements. The requirements are ANDed. + // +optional + AnyOf []ScanAnnotationSelectorRequirement `json:"anyOf,omitempty" protobuf:"bytes,2,rep,name=anyOf"` + + // AllOf is a list of label selector requirements. The requirements are ANDed. + // +optional + AllOf []ScanAnnotationSelectorRequirement `json:"allOf,omitempty" protobuf:"bytes,2,rep,name=allOf"` + + // NoneOf is a list of label selector requirements. The requirements are ANDed. + // +optional + NoneOf []ScanAnnotationSelectorRequirement `json:"noneOf,omitempty" protobuf:"bytes,2,rep,name=noneOf"` +} + +// ScanAnnotationSelectorRequirement is a selector that contains values, a key, and an operator that +// relates the key and values. +type ScanAnnotationSelectorRequirement struct { + // key is the label key that the selector applies to. + Key string `json:"key" protobuf:"bytes,1,opt,name=key"` + // operator represents a key's relationship to a set of values. + Operator string `json:"operator" protobuf:"bytes,2,opt,name=operator"` + // values is an array of string values. + // +optional + Values []string `json:"values,omitempty" protobuf:"bytes,3,rep,name=values"` +} + // ScanSpec defines the desired state of Scan type ScanSpec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster diff --git a/operator/apis/execution/v1/zz_generated.deepcopy.go b/operator/apis/execution/v1/zz_generated.deepcopy.go index d7d4b10d9a..7df1d0d603 100644 --- a/operator/apis/execution/v1/zz_generated.deepcopy.go +++ b/operator/apis/execution/v1/zz_generated.deepcopy.go @@ -18,6 +18,7 @@ import ( // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CascadeSpec) DeepCopyInto(out *CascadeSpec) { *out = *in + in.ScanAnnotationSelector.DeepCopyInto(&out.ScanAnnotationSelector) if in.MatchLabels != nil { in, out := &in.MatchLabels, &out.MatchLabels *out = make(map[string]string, len(*in)) @@ -174,6 +175,13 @@ func (in *ParseDefinitionList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ParseDefinitionSpec) DeepCopyInto(out *ParseDefinitionSpec) { *out = *in + if in.SelectorAttributeMappings != nil { + in, out := &in.SelectorAttributeMappings, &out.SelectorAttributeMappings + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } if in.ImagePullSecrets != nil { in, out := &in.ImagePullSecrets, &out.ImagePullSecrets *out = make([]corev1.LocalObjectReference, len(*in)) @@ -259,6 +267,62 @@ func (in *Scan) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ScanAnnotationSelector) DeepCopyInto(out *ScanAnnotationSelector) { + *out = *in + if in.AnyOf != nil { + in, out := &in.AnyOf, &out.AnyOf + *out = make([]ScanAnnotationSelectorRequirement, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.AllOf != nil { + in, out := &in.AllOf, &out.AllOf + *out = make([]ScanAnnotationSelectorRequirement, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.NoneOf != nil { + in, out := &in.NoneOf, &out.NoneOf + *out = make([]ScanAnnotationSelectorRequirement, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ScanAnnotationSelector. +func (in *ScanAnnotationSelector) DeepCopy() *ScanAnnotationSelector { + if in == nil { + return nil + } + out := new(ScanAnnotationSelector) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ScanAnnotationSelectorRequirement) DeepCopyInto(out *ScanAnnotationSelectorRequirement) { + *out = *in + if in.Values != nil { + in, out := &in.Values, &out.Values + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ScanAnnotationSelectorRequirement. +func (in *ScanAnnotationSelectorRequirement) DeepCopy() *ScanAnnotationSelectorRequirement { + if in == nil { + return nil + } + out := new(ScanAnnotationSelectorRequirement) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ScanCompletionHook) DeepCopyInto(out *ScanCompletionHook) { *out = *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..f2257cff0d 100644 --- a/operator/config/crd/bases/cascading.securecodebox.io_cascadingrules.yaml +++ b/operator/config/crd/bases/cascading.securecodebox.io_cascadingrules.yaml @@ -171,6 +171,95 @@ spec: the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object + scanAnnotationSelector: + description: InheritLabels defines whether cascading scans + should inherit labels from the parent scan + properties: + allOf: + description: AllOf is a list of label selector requirements. + The requirements are ANDed. + items: + description: ScanAnnotationSelectorRequirement 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. + type: string + values: + description: values is an array of string values. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + anyOf: + description: AnyOf is a list of label selector requirements. + The requirements are ANDed. + items: + description: ScanAnnotationSelectorRequirement 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. + type: string + values: + description: values is an array of string values. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + noneOf: + description: NoneOf is a list of label selector requirements. + The requirements are ANDed. + items: + description: ScanAnnotationSelectorRequirement 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. + type: string + values: + description: values is an array of string values. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + validOnMissingRender: + default: false + description: ValidOnMissingRender defines whether if a + templating variable is not present, that condition should + match + type: boolean + type: object type: object env: description: Env allows to specify environment vars for the scanner diff --git a/operator/config/crd/bases/execution.securecodebox.io_parsedefinitions.yaml b/operator/config/crd/bases/execution.securecodebox.io_parsedefinitions.yaml index 791a24f434..d17e68063d 100644 --- a/operator/config/crd/bases/execution.securecodebox.io_parsedefinitions.yaml +++ b/operator/config/crd/bases/execution.securecodebox.io_parsedefinitions.yaml @@ -169,6 +169,10 @@ spec: type: string type: object type: array + selectorAttributeMappings: + additionalProperties: + type: string + type: object ttlSecondsAfterFinished: description: TTLSecondsAfterFinished configures the ttlSecondsAfterFinished field for the created parse job diff --git a/operator/config/crd/bases/execution.securecodebox.io_scans.yaml b/operator/config/crd/bases/execution.securecodebox.io_scans.yaml index 5f752dc084..444a7b8077 100644 --- a/operator/config/crd/bases/execution.securecodebox.io_scans.yaml +++ b/operator/config/crd/bases/execution.securecodebox.io_scans.yaml @@ -132,6 +132,94 @@ spec: is "In", and the values array contains only "value". The requirements are ANDed. type: object + scanAnnotationSelector: + description: InheritLabels defines whether cascading scans should + inherit labels from the parent scan + properties: + allOf: + description: AllOf is a list of label selector requirements. + The requirements are ANDed. + items: + description: ScanAnnotationSelectorRequirement 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. + type: string + values: + description: values is an array of string values. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + anyOf: + description: AnyOf is a list of label selector requirements. + The requirements are ANDed. + items: + description: ScanAnnotationSelectorRequirement 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. + type: string + values: + description: values is an array of string values. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + noneOf: + description: NoneOf is a list of label selector requirements. + The requirements are ANDed. + items: + description: ScanAnnotationSelectorRequirement 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. + type: string + values: + description: values is an array of string values. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + validOnMissingRender: + default: false + description: ValidOnMissingRender defines whether if a templating + variable is not present, that condition should match + type: boolean + type: object type: object env: description: Env allows to specify environment vars for the scanner diff --git a/operator/config/crd/bases/execution.securecodebox.io_scheduledscans.yaml b/operator/config/crd/bases/execution.securecodebox.io_scheduledscans.yaml index 1529f1c120..771a3d6faa 100644 --- a/operator/config/crd/bases/execution.securecodebox.io_scheduledscans.yaml +++ b/operator/config/crd/bases/execution.securecodebox.io_scheduledscans.yaml @@ -153,6 +153,95 @@ spec: the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object + scanAnnotationSelector: + description: InheritLabels defines whether cascading scans + should inherit labels from the parent scan + properties: + allOf: + description: AllOf is a list of label selector requirements. + The requirements are ANDed. + items: + description: ScanAnnotationSelectorRequirement 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. + type: string + values: + description: values is an array of string values. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + anyOf: + description: AnyOf is a list of label selector requirements. + The requirements are ANDed. + items: + description: ScanAnnotationSelectorRequirement 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. + type: string + values: + description: values is an array of string values. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + noneOf: + description: NoneOf is a list of label selector requirements. + The requirements are ANDed. + items: + description: ScanAnnotationSelectorRequirement 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. + type: string + values: + description: values is an array of string values. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + validOnMissingRender: + default: false + description: ValidOnMissingRender defines whether if a + templating variable is not present, that condition should + match + type: boolean + type: object type: object env: description: Env allows to specify environment vars for the scanner diff --git a/operator/crds/cascading.securecodebox.io_cascadingrules.yaml b/operator/crds/cascading.securecodebox.io_cascadingrules.yaml index 76e0fecf58..f2257cff0d 100644 --- a/operator/crds/cascading.securecodebox.io_cascadingrules.yaml +++ b/operator/crds/cascading.securecodebox.io_cascadingrules.yaml @@ -171,6 +171,95 @@ spec: the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object + scanAnnotationSelector: + description: InheritLabels defines whether cascading scans + should inherit labels from the parent scan + properties: + allOf: + description: AllOf is a list of label selector requirements. + The requirements are ANDed. + items: + description: ScanAnnotationSelectorRequirement 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. + type: string + values: + description: values is an array of string values. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + anyOf: + description: AnyOf is a list of label selector requirements. + The requirements are ANDed. + items: + description: ScanAnnotationSelectorRequirement 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. + type: string + values: + description: values is an array of string values. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + noneOf: + description: NoneOf is a list of label selector requirements. + The requirements are ANDed. + items: + description: ScanAnnotationSelectorRequirement 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. + type: string + values: + description: values is an array of string values. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + validOnMissingRender: + default: false + description: ValidOnMissingRender defines whether if a + templating variable is not present, that condition should + match + type: boolean + type: object type: object env: description: Env allows to specify environment vars for the scanner diff --git a/operator/crds/execution.securecodebox.io_parsedefinitions.yaml b/operator/crds/execution.securecodebox.io_parsedefinitions.yaml index 791a24f434..d17e68063d 100644 --- a/operator/crds/execution.securecodebox.io_parsedefinitions.yaml +++ b/operator/crds/execution.securecodebox.io_parsedefinitions.yaml @@ -169,6 +169,10 @@ spec: type: string type: object type: array + selectorAttributeMappings: + additionalProperties: + type: string + type: object ttlSecondsAfterFinished: description: TTLSecondsAfterFinished configures the ttlSecondsAfterFinished field for the created parse job diff --git a/operator/crds/execution.securecodebox.io_scans.yaml b/operator/crds/execution.securecodebox.io_scans.yaml index 5f752dc084..444a7b8077 100644 --- a/operator/crds/execution.securecodebox.io_scans.yaml +++ b/operator/crds/execution.securecodebox.io_scans.yaml @@ -132,6 +132,94 @@ spec: is "In", and the values array contains only "value". The requirements are ANDed. type: object + scanAnnotationSelector: + description: InheritLabels defines whether cascading scans should + inherit labels from the parent scan + properties: + allOf: + description: AllOf is a list of label selector requirements. + The requirements are ANDed. + items: + description: ScanAnnotationSelectorRequirement 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. + type: string + values: + description: values is an array of string values. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + anyOf: + description: AnyOf is a list of label selector requirements. + The requirements are ANDed. + items: + description: ScanAnnotationSelectorRequirement 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. + type: string + values: + description: values is an array of string values. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + noneOf: + description: NoneOf is a list of label selector requirements. + The requirements are ANDed. + items: + description: ScanAnnotationSelectorRequirement 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. + type: string + values: + description: values is an array of string values. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + validOnMissingRender: + default: false + description: ValidOnMissingRender defines whether if a templating + variable is not present, that condition should match + type: boolean + type: object type: object env: description: Env allows to specify environment vars for the scanner diff --git a/operator/crds/execution.securecodebox.io_scheduledscans.yaml b/operator/crds/execution.securecodebox.io_scheduledscans.yaml index 1529f1c120..771a3d6faa 100644 --- a/operator/crds/execution.securecodebox.io_scheduledscans.yaml +++ b/operator/crds/execution.securecodebox.io_scheduledscans.yaml @@ -153,6 +153,95 @@ spec: the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object + scanAnnotationSelector: + description: InheritLabels defines whether cascading scans + should inherit labels from the parent scan + properties: + allOf: + description: AllOf is a list of label selector requirements. + The requirements are ANDed. + items: + description: ScanAnnotationSelectorRequirement 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. + type: string + values: + description: values is an array of string values. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + anyOf: + description: AnyOf is a list of label selector requirements. + The requirements are ANDed. + items: + description: ScanAnnotationSelectorRequirement 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. + type: string + values: + description: values is an array of string values. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + noneOf: + description: NoneOf is a list of label selector requirements. + The requirements are ANDed. + items: + description: ScanAnnotationSelectorRequirement 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. + type: string + values: + description: values is an array of string values. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + validOnMissingRender: + default: false + description: ValidOnMissingRender defines whether if a + templating variable is not present, that condition should + match + type: boolean + type: object type: object env: description: Env allows to specify environment vars for the scanner From cf49cb360b551d1e1be47bf1ea46be4a891f9d6b Mon Sep 17 00:00:00 2001 From: Jop Zitman Date: Mon, 15 Nov 2021 10:46:24 +0100 Subject: [PATCH 02/54] AND together `anyOf`, `allOf`, and `noneOf` Signed-off-by: Jop Zitman --- .../hook/reverse-matches.test.js | 37 +++++++++++++++++++ hooks/cascading-scans/hook/reverse-matches.ts | 12 +++--- 2 files changed, 42 insertions(+), 7 deletions(-) diff --git a/hooks/cascading-scans/hook/reverse-matches.test.js b/hooks/cascading-scans/hook/reverse-matches.test.js index cef677fe08..629d1a67c2 100644 --- a/hooks/cascading-scans/hook/reverse-matches.test.js +++ b/hooks/cascading-scans/hook/reverse-matches.test.js @@ -64,6 +64,43 @@ test("Does not match using if selector does not match", () => { expect(cascadedScans).toBe(false); }); +test("Does not match if one of selector types does not match", () => { + const annotations = { + "engagement.scope/domains": "example.com", + } + const scanAnnotationSelector = { + validOnMissingRender: false, + allOf: [ + { + key: "engagement.scope/domains", + operator: "Contains", + values: ["{{attributes.hostname}}"], + } + ], + noneOf: [ + { + key: "engagement.scope/domains", + operator: "Contains", + values: ["{{attributes.hostname}}"], + } + ] + } + const finding = { + attributes: { + hostname: "example.com", + } + }; + + const cascadedScans = isReverseMatch( + scanAnnotationSelector, + annotations, + finding, + {} + ); + + expect(cascadedScans).toBe(false); +}); + test("Matches InCIDR if attributes.ip in subnet", () => { const annotations = { "engagement.scope/cidr": "10.0.0.0/16", diff --git a/hooks/cascading-scans/hook/reverse-matches.ts b/hooks/cascading-scans/hook/reverse-matches.ts index 9c7f225ae8..1a6ebd495a 100644 --- a/hooks/cascading-scans/hook/reverse-matches.ts +++ b/hooks/cascading-scans/hook/reverse-matches.ts @@ -61,13 +61,11 @@ export function isReverseMatch( return [rendered, rendered != ""]; } - if (scanAnnotationSelector.allOf !== undefined) return scanAnnotationSelector.allOf.every(validateRequirement); - - if (scanAnnotationSelector.anyOf !== undefined) return scanAnnotationSelector.anyOf.some(validateRequirement); - - if (scanAnnotationSelector.noneOf !== undefined) return !scanAnnotationSelector.noneOf.some(validateRequirement); - - return true; + return [ + scanAnnotationSelector.allOf !== undefined ? scanAnnotationSelector.allOf.every(validateRequirement) : true, + scanAnnotationSelector.anyOf !== undefined ? scanAnnotationSelector.anyOf.some(validateRequirement) : true, + scanAnnotationSelector.noneOf !== undefined ? !scanAnnotationSelector.noneOf.some(validateRequirement) : true, + ].every(entry => entry === true); } interface operands { From 86899c9afaf0dd0ed485a9609962ea16b73e71ac Mon Sep 17 00:00:00 2001 From: Jop Zitman Date: Mon, 15 Nov 2021 11:31:03 +0100 Subject: [PATCH 03/54] Implement subdomainOf operator Signed-off-by: Jop Zitman --- hooks/cascading-scans/hook/package-lock.json | 117 +++++++++++++++++- hooks/cascading-scans/hook/package.json | 5 +- .../hook/reverse-matches.test.js | 63 ++++++++++ hooks/cascading-scans/hook/reverse-matches.ts | 41 ++++++ 4 files changed, 223 insertions(+), 3 deletions(-) diff --git a/hooks/cascading-scans/hook/package-lock.json b/hooks/cascading-scans/hook/package-lock.json index 784cdeb0ee..a252220ac7 100644 --- a/hooks/cascading-scans/hook/package-lock.json +++ b/hooks/cascading-scans/hook/package-lock.json @@ -13,7 +13,8 @@ "ip-address": "^8.1.0", "lodash": "^4.17.21", "matcher": "^4.0.0", - "mustache": "^4.2.0" + "mustache": "^4.2.0", + "parse-domain": "^4.1.0" }, "devDependencies": { "@types/ip-address": "^7.0.0", @@ -2593,6 +2594,14 @@ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==" }, + "node_modules/ip-regex": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", + "integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==", + "engines": { + "node": ">=8" + } + }, "node_modules/is-ci": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.0.tgz", @@ -2634,6 +2643,17 @@ "node": ">=6" } }, + "node_modules/is-ip": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-ip/-/is-ip-3.1.0.tgz", + "integrity": "sha512-35vd5necO7IitFPjd/YBeqwWnyDWbuLH9ZXQdMfDA8TEo7pv5X8yfrvVO3xbJbLUlERCMvf6X0hTUamQxCYJ9Q==", + "dependencies": { + "ip-regex": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -3788,6 +3808,36 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, + "node_modules/node-fetch": { + "version": "2.6.6", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz", + "integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -3985,6 +4035,19 @@ "node": ">=6" } }, + "node_modules/parse-domain": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/parse-domain/-/parse-domain-4.1.0.tgz", + "integrity": "sha512-zas79foMEsbMbIcJoPx26+NISWa3jTzZykOW9mXfRzvgadHvAHGd7qcCc1FbSWbD1I4qP71UWAxlTgu7Uq6IQg==", + "dependencies": { + "is-ip": "^3.1.0", + "node-fetch": "^2.6.1", + "punycode": "^2.1.1" + }, + "bin": { + "parse-domain-update": "bin/update.js" + } + }, "node_modules/parse5": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", @@ -7031,6 +7094,11 @@ } } }, + "ip-regex": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", + "integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==" + }, "is-ci": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.0.tgz", @@ -7060,6 +7128,14 @@ "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", "dev": true }, + "is-ip": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-ip/-/is-ip-3.1.0.tgz", + "integrity": "sha512-35vd5necO7IitFPjd/YBeqwWnyDWbuLH9ZXQdMfDA8TEo7pv5X8yfrvVO3xbJbLUlERCMvf6X0hTUamQxCYJ9Q==", + "requires": { + "ip-regex": "^4.0.0" + } + }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -7950,6 +8026,35 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, + "node-fetch": { + "version": "2.6.6", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz", + "integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==", + "requires": { + "whatwg-url": "^5.0.0" + }, + "dependencies": { + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + } + }, "node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -8087,6 +8192,16 @@ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, + "parse-domain": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/parse-domain/-/parse-domain-4.1.0.tgz", + "integrity": "sha512-zas79foMEsbMbIcJoPx26+NISWa3jTzZykOW9mXfRzvgadHvAHGd7qcCc1FbSWbD1I4qP71UWAxlTgu7Uq6IQg==", + "requires": { + "is-ip": "^3.1.0", + "node-fetch": "^2.6.1", + "punycode": "^2.1.1" + } + }, "parse5": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", diff --git a/hooks/cascading-scans/hook/package.json b/hooks/cascading-scans/hook/package.json index 3f6b994f1b..c185ecd918 100644 --- a/hooks/cascading-scans/hook/package.json +++ b/hooks/cascading-scans/hook/package.json @@ -9,7 +9,7 @@ }, "main": "hook.js", "scripts": { - "build": "npx tsc hook.ts reverse-matches.ts --sourceMap", + "build": "npx tsc hook.ts reverse-matches.ts --sourceMap --esModuleInterop", "test": "jest . --verbose false" }, "keywords": [ @@ -41,7 +41,8 @@ "ip-address": "^8.1.0", "lodash": "^4.17.21", "matcher": "^4.0.0", - "mustache": "^4.2.0" + "mustache": "^4.2.0", + "parse-domain": "^4.1.0" }, "devDependencies": { "@types/ip-address": "^7.0.0", diff --git a/hooks/cascading-scans/hook/reverse-matches.test.js b/hooks/cascading-scans/hook/reverse-matches.test.js index 629d1a67c2..1a8fcf7061 100644 --- a/hooks/cascading-scans/hook/reverse-matches.test.js +++ b/hooks/cascading-scans/hook/reverse-matches.test.js @@ -245,3 +245,66 @@ test("Does not match if mapping is not available: validOnMissingRender false", ( expect(cascadedScans).toBe(false); }); + +test("Matches subdomainOf if is subdomain", () => { + const annotations = { + "engagement.scope/domain": "example.com", + } + const scanAnnotationSelector = { + validOnMissingRender: false, + allOf: [ + { + key: "engagement.scope/domain", + operator: "SubdomainOf", + values: ["{{attributes.hostname}}"], + } + ] + } + + const finding = { + attributes: { + hostname: "subdomain.example.com", + } + }; + + const cascadedScans = isReverseMatch( + scanAnnotationSelector, + annotations, + finding, + {}, + ); + + expect(cascadedScans).toBe(true); +}); + +test("Does not match subdomainOf if is not subdomain", () => { + const annotations = { + "engagement.scope/domain": "example.com", + } + const scanAnnotationSelector = { + validOnMissingRender: false, + allOf: [ + { + key: "engagement.scope/domain", + operator: "SubdomainOf", + values: ["{{attributes.hostname}}"], + } + ] + } + + const finding = { + attributes: { + hostname: "notexample.com", + } + }; + + const cascadedScans = isReverseMatch( + scanAnnotationSelector, + annotations, + finding, + {}, + ); + + expect(cascadedScans).toBe(false); +}); + diff --git a/hooks/cascading-scans/hook/reverse-matches.ts b/hooks/cascading-scans/hook/reverse-matches.ts index 1a6ebd495a..45ddb11f50 100644 --- a/hooks/cascading-scans/hook/reverse-matches.ts +++ b/hooks/cascading-scans/hook/reverse-matches.ts @@ -8,6 +8,15 @@ import { } from "@kubernetes/client-node/dist/gen/model/v1ObjectMeta"; import * as Mustache from "mustache"; import { Address4 } from "ip-address"; +import { + fromUrl, + parseDomain, + ParseResultType +} from "parse-domain"; +import { + isEqual, + takeRight +} from "lodash"; export enum ScanAnnotationSelectorRequirementOperator { In = "In", @@ -18,6 +27,8 @@ export enum ScanAnnotationSelectorRequirementOperator { DoesNotContain = "DoesNotContain", InCIDR = "InCIDR", NotInCIDR = "NotInCIDR", + SubdomainOf = "SubdomainOf", + NotSubdomainOf = "NotSubdomainOf", } export interface ScanAnnotationSelectorRequirement { @@ -82,6 +93,8 @@ const operators: { [key in ScanAnnotationSelectorRequirementOperator]:(props: op [ScanAnnotationSelectorRequirementOperator.DoesNotContain]: props => !operatorContains(props), [ScanAnnotationSelectorRequirementOperator.InCIDR]: operatorInCIDR, [ScanAnnotationSelectorRequirementOperator.NotInCIDR]: props => !operatorInCIDR(props), + [ScanAnnotationSelectorRequirementOperator.SubdomainOf]: operatorSubdomainOf, + [ScanAnnotationSelectorRequirementOperator.NotSubdomainOf]: props => !operatorSubdomainOf(props), } function operatorIn({lhs, rhs}: operands): boolean { @@ -114,3 +127,31 @@ function operatorInCIDR({lhs, rhs}: operands): boolean { const subnet = new Address4(lhs); return rhs.every(value => new Address4(value).isInSubnet(subnet)); } + +function operatorSubdomainOf({lhs, rhs}: operands): boolean { + if (rhs === undefined) { + throw new Error("Values may not be undefined when using the operator 'SubdomainOf'") + } + const lhsResult = parseDomain(fromUrl(lhs)); + if (lhsResult.type == ParseResultType.Listed) { + return rhs.every(value => { + const rhsResult = parseDomain(fromUrl(value)); + if (rhsResult.type == ParseResultType.Listed) { + // Equal length domains can pass as subdomain of + if (lhsResult.labels.length > rhsResult.labels.length) { + return false; + } + + // Check if last part of domain is equal + return isEqual( + lhsResult.labels, + takeRight(rhsResult.labels, lhsResult.labels.length) + ); + } + console.log(`${rhs} is an invalid domain name`) + return false; + }) + } else { + throw new Error(`${lhs} is an invalid domain name`); + } +} From f207d10ac9c5d11645849f83fbfcdabaf5b209be Mon Sep 17 00:00:00 2001 From: Jop Zitman Date: Mon, 15 Nov 2021 12:14:56 +0100 Subject: [PATCH 04/54] Implement "global" validation/error handling Signed-off-by: Jop Zitman --- .../hook/reverse-matches.test.js | 27 +++++ hooks/cascading-scans/hook/reverse-matches.ts | 110 ++++++++++++------ 2 files changed, 101 insertions(+), 36 deletions(-) diff --git a/hooks/cascading-scans/hook/reverse-matches.test.js b/hooks/cascading-scans/hook/reverse-matches.test.js index 1a8fcf7061..a1270b758c 100644 --- a/hooks/cascading-scans/hook/reverse-matches.test.js +++ b/hooks/cascading-scans/hook/reverse-matches.test.js @@ -308,3 +308,30 @@ test("Does not match subdomainOf if is not subdomain", () => { expect(cascadedScans).toBe(false); }); +test("Throws errors when missing fields", () => { + const scanAnnotationSelector = { + validOnMissingRender: false, + allOf: [ + { + key: "engagement.scope/domain", + operator: "In", + values: ["{{attributes.hostname}}"], + } + ] + } + + const finding = { + attributes: { + hostname: "notexample.com", + } + }; + + const cascadedScans = () => isReverseMatch( + scanAnnotationSelector, + {}, + finding, + {}, + ); + + expect(cascadedScans).toThrowError("using operator 'In': annotation may not be undefined"); +}); diff --git a/hooks/cascading-scans/hook/reverse-matches.ts b/hooks/cascading-scans/hook/reverse-matches.ts index 45ddb11f50..60f70486a4 100644 --- a/hooks/cascading-scans/hook/reverse-matches.ts +++ b/hooks/cascading-scans/hook/reverse-matches.ts @@ -46,16 +46,25 @@ export function isReverseMatch( if (scanAnnotationSelector === undefined) return true; function validateRequirement({key, operator, values}: ScanAnnotationSelectorRequirement) { - const operatorFunction = operators[operator]; + const { operator: operatorFunction, validator: validatorFunction } = operatorFunctions[operator]; if (operatorFunction === undefined) { - throw new Error(`Unknown operator '${operator}'`); + throw new Error(`Unknown operator '${operatorFunction}'`); } const value = scanAnnotations[key]; const renders = values.map(templateValue); if (renders.some(render => !render[1])) { return scanAnnotationSelector.validOnMissingRender; } - return operatorFunction({lhs: value, rhs: renders.map(render => render[0])}); + + const props = {lhs: value, rhs: renders.map(render => render[0])}; + + try { + validatorFunction(props); + } catch (error) { + throw new Error(`using operator '${operator}': ${error.message}`); + } + + return operatorFunction(props); } function templateValue(value: string): [string, boolean] { @@ -79,59 +88,88 @@ export function isReverseMatch( ].every(entry => entry === true); } -interface operands { +interface Operands { lhs: string, rhs: string[], } -const operators: { [key in ScanAnnotationSelectorRequirementOperator]:(props: operands) => boolean; } = { - [ScanAnnotationSelectorRequirementOperator.In]: operatorIn, - [ScanAnnotationSelectorRequirementOperator.NotIn]: props => !operatorIn(props), - [ScanAnnotationSelectorRequirementOperator.Exists]: operatorExists, - [ScanAnnotationSelectorRequirementOperator.DoesNotExist]: props => !operatorExists(props), - [ScanAnnotationSelectorRequirementOperator.Contains]: operatorContains, - [ScanAnnotationSelectorRequirementOperator.DoesNotContain]: props => !operatorContains(props), - [ScanAnnotationSelectorRequirementOperator.InCIDR]: operatorInCIDR, - [ScanAnnotationSelectorRequirementOperator.NotInCIDR]: props => !operatorInCIDR(props), - [ScanAnnotationSelectorRequirementOperator.SubdomainOf]: operatorSubdomainOf, - [ScanAnnotationSelectorRequirementOperator.NotSubdomainOf]: props => !operatorSubdomainOf(props), +interface OperatorFunctions { + operator: (operands: Operands) => boolean, + validator: (operands: Operands) => void, } -function operatorIn({lhs, rhs}: operands): boolean { - if (rhs === undefined) { - throw new Error("Values may not be undefined when using the operator 'In'") +const defaultValidator: OperatorFunctions["validator"] = props => validate(props, false, false); + +const operatorFunctions: { [key in ScanAnnotationSelectorRequirementOperator]: OperatorFunctions } = { + [ScanAnnotationSelectorRequirementOperator.In]: { + operator: operatorIn, + validator: defaultValidator, + }, + [ScanAnnotationSelectorRequirementOperator.NotIn]: { + operator: props => !operatorIn(props), + validator: defaultValidator, + }, + [ScanAnnotationSelectorRequirementOperator.Exists]: { + operator: operatorExists, + validator: props => validate(props, true, true), + }, + [ScanAnnotationSelectorRequirementOperator.DoesNotExist]: { + operator: props => !operatorExists(props), + validator: props => validate(props, true, true), + }, + [ScanAnnotationSelectorRequirementOperator.Contains]: { + operator: operatorContains, + validator: defaultValidator, + }, + [ScanAnnotationSelectorRequirementOperator.DoesNotContain]: { + operator: props => !operatorContains(props), + validator: defaultValidator, + }, + [ScanAnnotationSelectorRequirementOperator.InCIDR]: { + operator: operatorInCIDR, + validator: defaultValidator, + }, + [ScanAnnotationSelectorRequirementOperator.NotInCIDR]: { + operator: props => !operatorInCIDR(props), + validator: defaultValidator, + }, + [ScanAnnotationSelectorRequirementOperator.SubdomainOf]: { + operator: operatorSubdomainOf, + validator: defaultValidator, + }, + [ScanAnnotationSelectorRequirementOperator.NotSubdomainOf]: { + operator: props => !operatorSubdomainOf(props), + validator: defaultValidator, + }, +} + +function validate({lhs, rhs}: Operands, lhsUndefinedAllowed, rhsUndefinedAllowed) { + if (!lhsUndefinedAllowed && lhs === undefined) { + throw new Error(`annotation may not be undefined`) + } + if (!rhsUndefinedAllowed && rhs === undefined) { + throw new Error(`values may not be undefined`) } +} + +function operatorIn({lhs, rhs}: Operands): boolean { return rhs.includes(lhs); } -function operatorExists({lhs, rhs}: operands): boolean { - if (rhs !== undefined) { - throw new Error("Values must be undefined when using the operator 'Exists'") - } +function operatorExists({lhs, rhs}: Operands): boolean { return lhs !== undefined; } -function operatorContains({lhs, rhs}: operands): boolean { - if (rhs === undefined) { - throw new Error("Values may not be undefined when using the operator 'Contains'") - } - +function operatorContains({lhs, rhs}: Operands): boolean { const valueArray = lhs.split(","); - return rhs.every(value => valueArray.includes(value)); } -function operatorInCIDR({lhs, rhs}: operands): boolean { - if (rhs === undefined) { - throw new Error("Values may not be undefined when using the operator 'InCIDR'") - } +function operatorInCIDR({lhs, rhs}: Operands): boolean { const subnet = new Address4(lhs); return rhs.every(value => new Address4(value).isInSubnet(subnet)); } -function operatorSubdomainOf({lhs, rhs}: operands): boolean { - if (rhs === undefined) { - throw new Error("Values may not be undefined when using the operator 'SubdomainOf'") - } +function operatorSubdomainOf({lhs, rhs}: Operands): boolean { const lhsResult = parseDomain(fromUrl(lhs)); if (lhsResult.type == ParseResultType.Listed) { return rhs.every(value => { From 2aef704d5653058f145d8e5088f0f5b4186748a6 Mon Sep 17 00:00:00 2001 From: Jop Zitman Date: Tue, 16 Nov 2021 11:01:44 +0100 Subject: [PATCH 05/54] Select only annotations with domain `scope.cascading.securecodebox.io/` Signed-off-by: Jop Zitman --- hooks/cascading-scans/hook/hook.test.js | 8 +-- .../hook/reverse-matches.test.js | 71 +++++++++++++------ hooks/cascading-scans/hook/reverse-matches.ts | 6 ++ 3 files changed, 59 insertions(+), 26 deletions(-) diff --git a/hooks/cascading-scans/hook/hook.test.js b/hooks/cascading-scans/hook/hook.test.js index 60562e7e4d..bf6a1d9c85 100644 --- a/hooks/cascading-scans/hook/hook.test.js +++ b/hooks/cascading-scans/hook/hook.test.js @@ -2149,11 +2149,11 @@ test("should append cascading rule to further cascading scan chains", () => { }); test("should not cascade if scan annotation selector does not match", () => { - parentScan.metadata.annotations["scope.engagement/ports"] = "80,443"; + parentScan.metadata.annotations["scope.cascading.securecodebox.io/ports"] = "80,443"; parentScan.spec.cascades.scanAnnotationSelector = { allOf: [ { - key: "scope.engagement/ports", + key: "scope.cascading.securecodebox.io/ports", operator: ScanAnnotationSelectorRequirementOperator.Contains, values: ["{{$.port}}"], }, @@ -2203,7 +2203,7 @@ test("should not cascade if scan annotation selector does not match", () => { "cascading.securecodebox.io/chain": "tls-scans", "cascading.securecodebox.io/matched-finding": undefined, "cascading.securecodebox.io/parent-scan": "nmap-foobar.com", - "scope.engagement/ports": "80,443", + "scope.cascading.securecodebox.io/ports": "80,443", "securecodebox.io/hook": "cascading-scans", }, "generateName": "sslyze-foobar.com-tls-scans-", @@ -2224,7 +2224,7 @@ test("should not cascade if scan annotation selector does not match", () => { "scanAnnotationSelector": Object { "allOf": Array [ Object { - "key": "scope.engagement/ports", + "key": "scope.cascading.securecodebox.io/ports", "operator": "Contains", "values": Array [ "{{$.port}}", diff --git a/hooks/cascading-scans/hook/reverse-matches.test.js b/hooks/cascading-scans/hook/reverse-matches.test.js index a1270b758c..a22b2a8a83 100644 --- a/hooks/cascading-scans/hook/reverse-matches.test.js +++ b/hooks/cascading-scans/hook/reverse-matches.test.js @@ -4,15 +4,42 @@ const { isReverseMatch } = require("./reverse-matches"); +test("Should error if selecting an invalid key", () => { + const scanAnnotationSelector = { + validOnMissingRender: false, + allOf: [ + { + key: "engagement.scope/domains", + operator: "Contains", + values: ["{{attributes.hostname}}"], + } + ] + } + const finding = { + attributes: { + hostname: "example.com", + } + }; + + const cascadedScans = () => isReverseMatch( + scanAnnotationSelector, + {}, + finding, + {} + ); + + expect(cascadedScans).toThrowError("key 'engagement.scope/domains' is invalid: key does not start with 'scope.cascading.securecodebox.io/'"); +}); + test("Matches using templates populated with finding", () => { const annotations = { - "engagement.scope/domains": "example.com,subdomain.example.com", + "scope.cascading.securecodebox.io/domains": "example.com,subdomain.example.com", } const scanAnnotationSelector = { validOnMissingRender: false, allOf: [ { - key: "engagement.scope/domains", + key: "scope.cascading.securecodebox.io/domains", operator: "Contains", values: ["{{attributes.hostname}}"], } @@ -36,13 +63,13 @@ test("Matches using templates populated with finding", () => { test("Does not match using if selector does not match", () => { const annotations = { - "engagement.scope/domains": "subdomain.example.com", + "scope.cascading.securecodebox.io/domains": "subdomain.example.com", } const scanAnnotationSelector = { validOnMissingRender: false, allOf: [ { - key: "engagement.scope/domains", + key: "scope.cascading.securecodebox.io/domains", operator: "Contains", values: ["{{attributes.hostname}}"], } @@ -66,20 +93,20 @@ test("Does not match using if selector does not match", () => { test("Does not match if one of selector types does not match", () => { const annotations = { - "engagement.scope/domains": "example.com", + "scope.cascading.securecodebox.io/domains": "example.com", } const scanAnnotationSelector = { validOnMissingRender: false, allOf: [ { - key: "engagement.scope/domains", + key: "scope.cascading.securecodebox.io/domains", operator: "Contains", values: ["{{attributes.hostname}}"], } ], noneOf: [ { - key: "engagement.scope/domains", + key: "scope.cascading.securecodebox.io/domains", operator: "Contains", values: ["{{attributes.hostname}}"], } @@ -103,13 +130,13 @@ test("Does not match if one of selector types does not match", () => { test("Matches InCIDR if attributes.ip in subnet", () => { const annotations = { - "engagement.scope/cidr": "10.0.0.0/16", + "scope.cascading.securecodebox.io/cidr": "10.0.0.0/16", } const scanAnnotationSelector = { validOnMissingRender: false, allOf: [ { - key: "engagement.scope/cidr", + key: "scope.cascading.securecodebox.io/cidr", operator: "InCIDR", values: ["{{attributes.ip}}"], } @@ -133,13 +160,13 @@ test("Matches InCIDR if attributes.ip in subnet", () => { test("Does not match InCIDR if attributes.ip not in subnet", () => { const annotations = { - "engagement.scope/cidr": "10.0.0.0/32", + "scope.cascading.securecodebox.io/cidr": "10.0.0.0/32", } const scanAnnotationSelector = { validOnMissingRender: false, allOf: [ { - key: "engagement.scope/cidr", + key: "scope.cascading.securecodebox.io/cidr", operator: "InCIDR", values: ["{{attributes.ip}}"], } @@ -163,14 +190,14 @@ test("Does not match InCIDR if attributes.ip not in subnet", () => { test("Matches using templates populated with finding and a mapped selector", () => { const annotations = { - "engagement.scope/domains": "example.com,subdomain.example.com", + "scope.cascading.securecodebox.io/domains": "example.com,subdomain.example.com", } const scanAnnotationSelector = { requiresMapping: false, validOnMissingRender: false, allOf: [ { - key: "engagement.scope/domains", + key: "scope.cascading.securecodebox.io/domains", operator: "Contains", values: ["{{$.hostname}}"], } @@ -198,13 +225,13 @@ test("Matches using templates populated with finding and a mapped selector", () test("Matches if mapping is not available: validOnMissingRender true", () => { const annotations = { - "engagement.scope/domains": "example.com,subdomain.example.com", + "scope.cascading.securecodebox.io/domains": "example.com,subdomain.example.com", } const scanAnnotationSelector = { validOnMissingRender: true, allOf: [ { - key: "engagement.scope/domains", + key: "scope.cascading.securecodebox.io/domains", operator: "Contains", values: ["{{$.hostname}}"], } @@ -223,13 +250,13 @@ test("Matches if mapping is not available: validOnMissingRender true", () => { test("Does not match if mapping is not available: validOnMissingRender false", () => { const annotations = { - "engagement.scope/domains": "example.com,subdomain.example.com", + "scope.cascading.securecodebox.io/domains": "example.com,subdomain.example.com", } const scanAnnotationSelector = { validOnMissingRender: false, allOf: [ { - key: "engagement.scope/domains", + key: "scope.cascading.securecodebox.io/domains", operator: "Contains", values: ["{{$.hostname}}"], } @@ -248,13 +275,13 @@ test("Does not match if mapping is not available: validOnMissingRender false", ( test("Matches subdomainOf if is subdomain", () => { const annotations = { - "engagement.scope/domain": "example.com", + "scope.cascading.securecodebox.io/domain": "example.com", } const scanAnnotationSelector = { validOnMissingRender: false, allOf: [ { - key: "engagement.scope/domain", + key: "scope.cascading.securecodebox.io/domain", operator: "SubdomainOf", values: ["{{attributes.hostname}}"], } @@ -279,13 +306,13 @@ test("Matches subdomainOf if is subdomain", () => { test("Does not match subdomainOf if is not subdomain", () => { const annotations = { - "engagement.scope/domain": "example.com", + "scope.cascading.securecodebox.io/domain": "example.com", } const scanAnnotationSelector = { validOnMissingRender: false, allOf: [ { - key: "engagement.scope/domain", + key: "scope.cascading.securecodebox.io/domain", operator: "SubdomainOf", values: ["{{attributes.hostname}}"], } @@ -313,7 +340,7 @@ test("Throws errors when missing fields", () => { validOnMissingRender: false, allOf: [ { - key: "engagement.scope/domain", + key: "scope.cascading.securecodebox.io/domain", operator: "In", values: ["{{attributes.hostname}}"], } diff --git a/hooks/cascading-scans/hook/reverse-matches.ts b/hooks/cascading-scans/hook/reverse-matches.ts index 60f70486a4..1e8e3085a3 100644 --- a/hooks/cascading-scans/hook/reverse-matches.ts +++ b/hooks/cascading-scans/hook/reverse-matches.ts @@ -37,6 +37,8 @@ export interface ScanAnnotationSelectorRequirement { values: Array; } +const scopeDomain = "scope.cascading.securecodebox.io/" + export function isReverseMatch( scanAnnotationSelector: ScanAnnotationSelector, scanAnnotations: V1ObjectMeta['annotations'], @@ -46,6 +48,10 @@ export function isReverseMatch( if (scanAnnotationSelector === undefined) return true; function validateRequirement({key, operator, values}: ScanAnnotationSelectorRequirement) { + if (!key.startsWith(`${scopeDomain}`)) { + throw new Error(`key '${key}' is invalid: key does not start with '${scopeDomain}'`); + } + const { operator: operatorFunction, validator: validatorFunction } = operatorFunctions[operator]; if (operatorFunction === undefined) { throw new Error(`Unknown operator '${operatorFunction}'`); From 9c5f93c2026dba2b4c1c789ee730c10d6bcc226d Mon Sep 17 00:00:00 2001 From: Jop Zitman Date: Tue, 16 Nov 2021 11:40:17 +0100 Subject: [PATCH 06/54] Enforce immutability of `scope.cascading.securecodebox.io/` annotations Signed-off-by: Jop Zitman --- hooks/cascading-scans/hook/hook.test.js | 45 +++++++++++++++++++ hooks/cascading-scans/hook/hook.ts | 9 +++- hooks/cascading-scans/hook/reverse-matches.ts | 2 +- 3 files changed, 53 insertions(+), 3 deletions(-) diff --git a/hooks/cascading-scans/hook/hook.test.js b/hooks/cascading-scans/hook/hook.test.js index bf6a1d9c85..272b9b3df3 100644 --- a/hooks/cascading-scans/hook/hook.test.js +++ b/hooks/cascading-scans/hook/hook.test.js @@ -2248,3 +2248,48 @@ test("should not cascade if scan annotation selector does not match", () => { ] `); }); + + +test("scope annotations should be completely immutable", () => { + parentScan.metadata.annotations["scope.cascading.securecodebox.io/domains"] = "example.com"; + parentScan.metadata.annotations["not.a.scope.annotation"] = "really"; + parentScan.spec.cascades.inheritAnnotations = false; + sslyzeCascadingRules[0].spec.scanAnnotations = { + "scope.cascading.securecodebox.io/domains": "malicious.example.com", + "scope.cascading.securecodebox.io/ports": "443", + "another.not.a.scope.annotation": "really", + }; + const findings = [ + { + name: "Port 443 is open", + category: "Open Port", + attributes: { + state: "open", + hostname: "foobar.com", + port: 443, + service: "https" + } + } + ]; + + const cascadedScans = getCascadingScans( + parentScan, + findings, + sslyzeCascadingRules, + undefined, + parseDefinition, + ); + + const cascadedScan = cascadedScans[0] + + expect(cascadedScan.metadata.annotations).toMatchInlineSnapshot(` + Object { + "another.not.a.scope.annotation": "really", + "cascading.securecodebox.io/chain": "tls-scans", + "cascading.securecodebox.io/matched-finding": undefined, + "cascading.securecodebox.io/parent-scan": "nmap-foobar.com", + "scope.cascading.securecodebox.io/domains": "example.com", + "securecodebox.io/hook": "cascading-scans", + } + `) +}); diff --git a/hooks/cascading-scans/hook/hook.ts b/hooks/cascading-scans/hook/hook.ts index 29e4bcd3e2..b770c163e4 100644 --- a/hooks/cascading-scans/hook/hook.ts +++ b/hooks/cascading-scans/hook/hook.ts @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -import { isMatch, isMatchWith, isString, mapValues, cloneDeep } from "lodash"; +import { isMatch, isMatchWith, isString, mapValues, cloneDeep, pickBy } from "lodash"; import { isMatch as wildcardIsMatch } from "matcher"; import * as Mustache from "mustache"; @@ -22,7 +22,8 @@ import { mergeInheritedSelector, } from "./scan-helpers"; import { - isReverseMatch + isReverseMatch, + scopeDomain, } from "./reverse-matches"; interface HandleArgs { @@ -79,6 +80,9 @@ export function getCascadingScans( continue; } + // Remove scope annotations from cascading rule + cascadingRule.spec.scanAnnotations = pickBy(cascadingRule.spec.scanAnnotations, (value, key) => !key.startsWith(scopeDomain)) + cascadingScans = cascadingScans.concat(getScansMatchingRule(parentScan, findings, cascadingRule, parseDefinition)) } @@ -164,6 +168,7 @@ function getCascadingScan( ...cascadingChain, cascadingRule.metadata.name ].join(","), + ...pickBy(parentScan.metadata.annotations, (value, key) => key.startsWith(scopeDomain)), }, ownerReferences: [ { diff --git a/hooks/cascading-scans/hook/reverse-matches.ts b/hooks/cascading-scans/hook/reverse-matches.ts index 1e8e3085a3..92593fea0e 100644 --- a/hooks/cascading-scans/hook/reverse-matches.ts +++ b/hooks/cascading-scans/hook/reverse-matches.ts @@ -37,7 +37,7 @@ export interface ScanAnnotationSelectorRequirement { values: Array; } -const scopeDomain = "scope.cascading.securecodebox.io/" +export const scopeDomain = "scope.cascading.securecodebox.io/" export function isReverseMatch( scanAnnotationSelector: ScanAnnotationSelector, From da3eeae10c15965eb0293d4d34435f71e532b64c Mon Sep 17 00:00:00 2001 From: Jop Zitman Date: Tue, 16 Nov 2021 12:09:12 +0100 Subject: [PATCH 07/54] Loudly fail if new scope annotations are defined in a cascading rule Signed-off-by: Jop Zitman --- hooks/cascading-scans/hook/hook.test.js | 9 ++++++--- hooks/cascading-scans/hook/hook.ts | 10 +++++++--- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/hooks/cascading-scans/hook/hook.test.js b/hooks/cascading-scans/hook/hook.test.js index 272b9b3df3..3782bb0280 100644 --- a/hooks/cascading-scans/hook/hook.test.js +++ b/hooks/cascading-scans/hook/hook.test.js @@ -2256,7 +2256,6 @@ test("scope annotations should be completely immutable", () => { parentScan.spec.cascades.inheritAnnotations = false; sslyzeCascadingRules[0].spec.scanAnnotations = { "scope.cascading.securecodebox.io/domains": "malicious.example.com", - "scope.cascading.securecodebox.io/ports": "443", "another.not.a.scope.annotation": "really", }; const findings = [ @@ -2272,7 +2271,7 @@ test("scope annotations should be completely immutable", () => { } ]; - const cascadedScans = getCascadingScans( + const cascadedScans = () => getCascadingScans( parentScan, findings, sslyzeCascadingRules, @@ -2280,7 +2279,11 @@ test("scope annotations should be completely immutable", () => { parseDefinition, ); - const cascadedScan = cascadedScans[0] + expect(cascadedScans).toThrowError("may not add scope annotation 'scope.cascading.securecodebox.io/domains':'malicious.example.com' in Cascading Rule spec"); + + delete sslyzeCascadingRules[0].spec.scanAnnotations["scope.cascading.securecodebox.io/domains"] + + const cascadedScan = cascadedScans()[0] expect(cascadedScan.metadata.annotations).toMatchInlineSnapshot(` Object { diff --git a/hooks/cascading-scans/hook/hook.ts b/hooks/cascading-scans/hook/hook.ts index b770c163e4..e6ed0e40d9 100644 --- a/hooks/cascading-scans/hook/hook.ts +++ b/hooks/cascading-scans/hook/hook.ts @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -import { isMatch, isMatchWith, isString, mapValues, cloneDeep, pickBy } from "lodash"; +import { isMatch, isMatchWith, isString, mapValues, cloneDeep, pickBy, forEach } from "lodash"; import { isMatch as wildcardIsMatch } from "matcher"; import * as Mustache from "mustache"; @@ -80,8 +80,12 @@ export function getCascadingScans( continue; } - // Remove scope annotations from cascading rule - cascadingRule.spec.scanAnnotations = pickBy(cascadingRule.spec.scanAnnotations, (value, key) => !key.startsWith(scopeDomain)) + // Check for new scope annotations + forEach(cascadingRule.spec.scanAnnotations, (value, key) => { + if (key.startsWith(scopeDomain)) { + throw new Error(`may not add scope annotation '${key}':'${value}' in Cascading Rule spec`) + } + }); cascadingScans = cascadingScans.concat(getScansMatchingRule(parentScan, findings, cascadingRule, parseDefinition)) } From 869f4d228232e58ca97e307ca1ea08e67048a672 Mon Sep 17 00:00:00 2001 From: Jop Zitman Date: Wed, 17 Nov 2021 16:20:37 +0100 Subject: [PATCH 08/54] Rename ScanAnnotationSelector to ScopeLimiter and SelectorAttributeMappings to ScopeLimiterAliases Signed-off-by: Jop Zitman --- hooks/cascading-scans/hook/hook.test.js | 14 +-- hooks/cascading-scans/hook/hook.ts | 10 +- .../hook/reverse-matches.test.js | 52 ++++---- hooks/cascading-scans/hook/reverse-matches.ts | 50 ++++---- hooks/cascading-scans/hook/scan-helpers.ts | 16 +-- .../execution/v1/parsedefinition_types.go | 2 +- operator/apis/execution/v1/scan_types.go | 14 +-- .../execution/v1/zz_generated.deepcopy.go | 118 +++++++++--------- ...ading.securecodebox.io_cascadingrules.yaml | 20 +-- ...ion.securecodebox.io_parsedefinitions.yaml | 2 +- .../execution.securecodebox.io_scans.yaml | 20 +-- ...ution.securecodebox.io_scheduledscans.yaml | 20 +-- ...ading.securecodebox.io_cascadingrules.yaml | 20 +-- ...ion.securecodebox.io_parsedefinitions.yaml | 2 +- .../execution.securecodebox.io_scans.yaml | 20 +-- ...ution.securecodebox.io_scheduledscans.yaml | 20 +-- 16 files changed, 200 insertions(+), 200 deletions(-) diff --git a/hooks/cascading-scans/hook/hook.test.js b/hooks/cascading-scans/hook/hook.test.js index 3782bb0280..f7253bf24f 100644 --- a/hooks/cascading-scans/hook/hook.test.js +++ b/hooks/cascading-scans/hook/hook.test.js @@ -5,7 +5,7 @@ const { getCascadingScans } = require("./hook"); const {LabelSelectorRequirementOperator} = require("./kubernetes-label-selector"); const { - ScanAnnotationSelectorRequirementOperator, + ScopeLimiterRequirementOperator, } = require("./reverse-matches"); let parentScan = undefined; @@ -29,7 +29,7 @@ beforeEach(() => { parseDefinition = { meta: {}, spec: { - selectorAttributeMappings: {}, + scopeLimiterAliases: {}, }, } @@ -2148,13 +2148,13 @@ test("should append cascading rule to further cascading scan chains", () => { expect(secondCascadedScan.metadata.annotations["cascading.securecodebox.io/chain"]).toEqual("tls-scans,tls-scans-second") }); -test("should not cascade if scan annotation selector does not match", () => { +test("should not cascade if scope limiter does not pass", () => { parentScan.metadata.annotations["scope.cascading.securecodebox.io/ports"] = "80,443"; - parentScan.spec.cascades.scanAnnotationSelector = { + parentScan.spec.cascades.scopeLimiter = { allOf: [ { key: "scope.cascading.securecodebox.io/ports", - operator: ScanAnnotationSelectorRequirementOperator.Contains, + operator: ScopeLimiterRequirementOperator.Contains, values: ["{{$.port}}"], }, ], @@ -2183,7 +2183,7 @@ test("should not cascade if scan annotation selector does not match", () => { }, ]; - parseDefinition.spec.selectorAttributeMappings["port"] = "{{attributes.port}}"; + parseDefinition.spec.scopeLimiterAliases["port"] = "{{attributes.port}}"; const cascadedScans = getCascadingScans( parentScan, @@ -2221,7 +2221,7 @@ test("should not cascade if scan annotation selector does not match", () => { }, "spec": Object { "cascades": Object { - "scanAnnotationSelector": Object { + "scopeLimiter": Object { "allOf": Array [ Object { "key": "scope.cascading.securecodebox.io/ports", diff --git a/hooks/cascading-scans/hook/hook.ts b/hooks/cascading-scans/hook/hook.ts index e6ed0e40d9..393c8e7b8e 100644 --- a/hooks/cascading-scans/hook/hook.ts +++ b/hooks/cascading-scans/hook/hook.ts @@ -116,17 +116,17 @@ function getScansMatchingRule( for (const finding of findings) { // Check if the scan matches for the current finding const reverseMatches = isReverseMatch( - parentScan.spec.cascades.scanAnnotationSelector, + parentScan.spec.cascades.scopeLimiter, parentScan.metadata.annotations, finding, - parseDefinition.spec.selectorAttributeMappings, + parseDefinition.spec.scopeLimiterAliases, ); if (!reverseMatches) { - console.log(`Cascading Rule ${cascadingRule.metadata.name} not triggered as scan annotation selector did not match`); + console.log(`Cascading Rule ${cascadingRule.metadata.name} not triggered as scope limiter did not pass`); console.log(`Scan annotations ${parentScan.metadata.annotations}`); - console.log(`Scan annotation selector ${parentScan.spec.cascades.scanAnnotationSelector}`); - console.log(`Selector Attribute Mappings ${parseDefinition.spec.selectorAttributeMappings}`); + console.log(`Scope limiter ${parentScan.spec.cascades.scopeLimiter}`); + console.log(`Scope limiter aliases ${parseDefinition.spec.scopeLimiterAliases}`); console.log(`Finding ${finding}`); continue; } diff --git a/hooks/cascading-scans/hook/reverse-matches.test.js b/hooks/cascading-scans/hook/reverse-matches.test.js index a22b2a8a83..492cf40720 100644 --- a/hooks/cascading-scans/hook/reverse-matches.test.js +++ b/hooks/cascading-scans/hook/reverse-matches.test.js @@ -5,7 +5,7 @@ const { isReverseMatch } = require("./reverse-matches"); test("Should error if selecting an invalid key", () => { - const scanAnnotationSelector = { + const scopeLimiter = { validOnMissingRender: false, allOf: [ { @@ -22,7 +22,7 @@ test("Should error if selecting an invalid key", () => { }; const cascadedScans = () => isReverseMatch( - scanAnnotationSelector, + scopeLimiter, {}, finding, {} @@ -35,7 +35,7 @@ test("Matches using templates populated with finding", () => { const annotations = { "scope.cascading.securecodebox.io/domains": "example.com,subdomain.example.com", } - const scanAnnotationSelector = { + const scopeLimiter = { validOnMissingRender: false, allOf: [ { @@ -52,7 +52,7 @@ test("Matches using templates populated with finding", () => { }; const cascadedScans = isReverseMatch( - scanAnnotationSelector, + scopeLimiter, annotations, finding, {} @@ -65,7 +65,7 @@ test("Does not match using if selector does not match", () => { const annotations = { "scope.cascading.securecodebox.io/domains": "subdomain.example.com", } - const scanAnnotationSelector = { + const scopeLimiter = { validOnMissingRender: false, allOf: [ { @@ -82,7 +82,7 @@ test("Does not match using if selector does not match", () => { }; const cascadedScans = isReverseMatch( - scanAnnotationSelector, + scopeLimiter, annotations, finding, {} @@ -95,7 +95,7 @@ test("Does not match if one of selector types does not match", () => { const annotations = { "scope.cascading.securecodebox.io/domains": "example.com", } - const scanAnnotationSelector = { + const scopeLimiter = { validOnMissingRender: false, allOf: [ { @@ -119,7 +119,7 @@ test("Does not match if one of selector types does not match", () => { }; const cascadedScans = isReverseMatch( - scanAnnotationSelector, + scopeLimiter, annotations, finding, {} @@ -132,7 +132,7 @@ test("Matches InCIDR if attributes.ip in subnet", () => { const annotations = { "scope.cascading.securecodebox.io/cidr": "10.0.0.0/16", } - const scanAnnotationSelector = { + const scopeLimiter = { validOnMissingRender: false, allOf: [ { @@ -149,7 +149,7 @@ test("Matches InCIDR if attributes.ip in subnet", () => { }; const cascadedScans = isReverseMatch( - scanAnnotationSelector, + scopeLimiter, annotations, finding, {} @@ -162,7 +162,7 @@ test("Does not match InCIDR if attributes.ip not in subnet", () => { const annotations = { "scope.cascading.securecodebox.io/cidr": "10.0.0.0/32", } - const scanAnnotationSelector = { + const scopeLimiter = { validOnMissingRender: false, allOf: [ { @@ -179,7 +179,7 @@ test("Does not match InCIDR if attributes.ip not in subnet", () => { }; const cascadedScans = isReverseMatch( - scanAnnotationSelector, + scopeLimiter, annotations, finding, {} @@ -192,7 +192,7 @@ test("Matches using templates populated with finding and a mapped selector", () const annotations = { "scope.cascading.securecodebox.io/domains": "example.com,subdomain.example.com", } - const scanAnnotationSelector = { + const scopeLimiter = { requiresMapping: false, validOnMissingRender: false, allOf: [ @@ -209,15 +209,15 @@ test("Matches using templates populated with finding and a mapped selector", () } }; - const selectorAttributeMappings = { + const scopeLimiterAliases = { "hostname": "{{attributes.hostname}}", } const cascadedScans = isReverseMatch( - scanAnnotationSelector, + scopeLimiter, annotations, finding, - selectorAttributeMappings + scopeLimiterAliases ); expect(cascadedScans).toBe(true); @@ -227,7 +227,7 @@ test("Matches if mapping is not available: validOnMissingRender true", () => { const annotations = { "scope.cascading.securecodebox.io/domains": "example.com,subdomain.example.com", } - const scanAnnotationSelector = { + const scopeLimiter = { validOnMissingRender: true, allOf: [ { @@ -239,7 +239,7 @@ test("Matches if mapping is not available: validOnMissingRender true", () => { } const cascadedScans = isReverseMatch( - scanAnnotationSelector, + scopeLimiter, annotations, {}, {}, @@ -252,7 +252,7 @@ test("Does not match if mapping is not available: validOnMissingRender false", ( const annotations = { "scope.cascading.securecodebox.io/domains": "example.com,subdomain.example.com", } - const scanAnnotationSelector = { + const scopeLimiter = { validOnMissingRender: false, allOf: [ { @@ -264,7 +264,7 @@ test("Does not match if mapping is not available: validOnMissingRender false", ( } const cascadedScans = isReverseMatch( - scanAnnotationSelector, + scopeLimiter, annotations, {}, {}, @@ -277,7 +277,7 @@ test("Matches subdomainOf if is subdomain", () => { const annotations = { "scope.cascading.securecodebox.io/domain": "example.com", } - const scanAnnotationSelector = { + const scopeLimiter = { validOnMissingRender: false, allOf: [ { @@ -295,7 +295,7 @@ test("Matches subdomainOf if is subdomain", () => { }; const cascadedScans = isReverseMatch( - scanAnnotationSelector, + scopeLimiter, annotations, finding, {}, @@ -308,7 +308,7 @@ test("Does not match subdomainOf if is not subdomain", () => { const annotations = { "scope.cascading.securecodebox.io/domain": "example.com", } - const scanAnnotationSelector = { + const scopeLimiter = { validOnMissingRender: false, allOf: [ { @@ -326,7 +326,7 @@ test("Does not match subdomainOf if is not subdomain", () => { }; const cascadedScans = isReverseMatch( - scanAnnotationSelector, + scopeLimiter, annotations, finding, {}, @@ -336,7 +336,7 @@ test("Does not match subdomainOf if is not subdomain", () => { }); test("Throws errors when missing fields", () => { - const scanAnnotationSelector = { + const scopeLimiter = { validOnMissingRender: false, allOf: [ { @@ -354,7 +354,7 @@ test("Throws errors when missing fields", () => { }; const cascadedScans = () => isReverseMatch( - scanAnnotationSelector, + scopeLimiter, {}, finding, {}, diff --git a/hooks/cascading-scans/hook/reverse-matches.ts b/hooks/cascading-scans/hook/reverse-matches.ts index 92593fea0e..3cc4e6bc07 100644 --- a/hooks/cascading-scans/hook/reverse-matches.ts +++ b/hooks/cascading-scans/hook/reverse-matches.ts @@ -1,7 +1,7 @@ import { Finding, - ScanAnnotationSelector, - SelectorAttributeMappings, + ScopeLimiter, + ScopeLimiterAliases, } from "./scan-helpers"; import { V1ObjectMeta @@ -18,7 +18,7 @@ import { takeRight } from "lodash"; -export enum ScanAnnotationSelectorRequirementOperator { +export enum ScopeLimiterRequirementOperator { In = "In", NotIn = "NotIn", Exists = "Exists", @@ -31,23 +31,23 @@ export enum ScanAnnotationSelectorRequirementOperator { NotSubdomainOf = "NotSubdomainOf", } -export interface ScanAnnotationSelectorRequirement { +export interface ScopeLimiterRequirement { key: string; - operator: ScanAnnotationSelectorRequirementOperator; + operator: ScopeLimiterRequirementOperator; values: Array; } export const scopeDomain = "scope.cascading.securecodebox.io/" export function isReverseMatch( - scanAnnotationSelector: ScanAnnotationSelector, + scopeLimiter: ScopeLimiter, scanAnnotations: V1ObjectMeta['annotations'], finding: Finding, - selectorAttributeMappings: SelectorAttributeMappings, + scopeLimiterAliases: ScopeLimiterAliases, ) { - if (scanAnnotationSelector === undefined) return true; + if (scopeLimiter === undefined) return true; - function validateRequirement({key, operator, values}: ScanAnnotationSelectorRequirement) { + function validateRequirement({key, operator, values}: ScopeLimiterRequirement) { if (!key.startsWith(`${scopeDomain}`)) { throw new Error(`key '${key}' is invalid: key does not start with '${scopeDomain}'`); } @@ -59,7 +59,7 @@ export function isReverseMatch( const value = scanAnnotations[key]; const renders = values.map(templateValue); if (renders.some(render => !render[1])) { - return scanAnnotationSelector.validOnMissingRender; + return scopeLimiter.validOnMissingRender; } const props = {lhs: value, rhs: renders.map(render => render[0])}; @@ -77,7 +77,7 @@ export function isReverseMatch( if (value === undefined) return [undefined, true]; let mapped = Mustache.render(value, { $: { - ...selectorAttributeMappings + ...scopeLimiterAliases } }); if (mapped == "") { @@ -88,9 +88,9 @@ export function isReverseMatch( } return [ - scanAnnotationSelector.allOf !== undefined ? scanAnnotationSelector.allOf.every(validateRequirement) : true, - scanAnnotationSelector.anyOf !== undefined ? scanAnnotationSelector.anyOf.some(validateRequirement) : true, - scanAnnotationSelector.noneOf !== undefined ? !scanAnnotationSelector.noneOf.some(validateRequirement) : true, + scopeLimiter.allOf !== undefined ? scopeLimiter.allOf.every(validateRequirement) : true, + scopeLimiter.anyOf !== undefined ? scopeLimiter.anyOf.some(validateRequirement) : true, + scopeLimiter.noneOf !== undefined ? !scopeLimiter.noneOf.some(validateRequirement) : true, ].every(entry => entry === true); } @@ -106,44 +106,44 @@ interface OperatorFunctions { const defaultValidator: OperatorFunctions["validator"] = props => validate(props, false, false); -const operatorFunctions: { [key in ScanAnnotationSelectorRequirementOperator]: OperatorFunctions } = { - [ScanAnnotationSelectorRequirementOperator.In]: { +const operatorFunctions: { [key in ScopeLimiterRequirementOperator]: OperatorFunctions } = { + [ScopeLimiterRequirementOperator.In]: { operator: operatorIn, validator: defaultValidator, }, - [ScanAnnotationSelectorRequirementOperator.NotIn]: { + [ScopeLimiterRequirementOperator.NotIn]: { operator: props => !operatorIn(props), validator: defaultValidator, }, - [ScanAnnotationSelectorRequirementOperator.Exists]: { + [ScopeLimiterRequirementOperator.Exists]: { operator: operatorExists, validator: props => validate(props, true, true), }, - [ScanAnnotationSelectorRequirementOperator.DoesNotExist]: { + [ScopeLimiterRequirementOperator.DoesNotExist]: { operator: props => !operatorExists(props), validator: props => validate(props, true, true), }, - [ScanAnnotationSelectorRequirementOperator.Contains]: { + [ScopeLimiterRequirementOperator.Contains]: { operator: operatorContains, validator: defaultValidator, }, - [ScanAnnotationSelectorRequirementOperator.DoesNotContain]: { + [ScopeLimiterRequirementOperator.DoesNotContain]: { operator: props => !operatorContains(props), validator: defaultValidator, }, - [ScanAnnotationSelectorRequirementOperator.InCIDR]: { + [ScopeLimiterRequirementOperator.InCIDR]: { operator: operatorInCIDR, validator: defaultValidator, }, - [ScanAnnotationSelectorRequirementOperator.NotInCIDR]: { + [ScopeLimiterRequirementOperator.NotInCIDR]: { operator: props => !operatorInCIDR(props), validator: defaultValidator, }, - [ScanAnnotationSelectorRequirementOperator.SubdomainOf]: { + [ScopeLimiterRequirementOperator.SubdomainOf]: { operator: operatorSubdomainOf, validator: defaultValidator, }, - [ScanAnnotationSelectorRequirementOperator.NotSubdomainOf]: { + [ScopeLimiterRequirementOperator.NotSubdomainOf]: { operator: props => !operatorSubdomainOf(props), validator: defaultValidator, }, diff --git a/hooks/cascading-scans/hook/scan-helpers.ts b/hooks/cascading-scans/hook/scan-helpers.ts index 09dbdbefbe..23c795a47a 100644 --- a/hooks/cascading-scans/hook/scan-helpers.ts +++ b/hooks/cascading-scans/hook/scan-helpers.ts @@ -10,7 +10,7 @@ import { } from "./kubernetes-label-selector"; import {isEqual} from "lodash"; import {getScanChain} from "./hook"; -import {ScanAnnotationSelectorRequirement} from "./reverse-matches"; +import {ScopeLimiterRequirement} from "./reverse-matches"; // configure k8s client const kc = new k8s.KubeConfig(); @@ -67,15 +67,15 @@ export interface ScanSpec { hookSelector?: LabelSelector; } -export interface ScanAnnotationSelector { +export interface ScopeLimiter { validOnMissingRender: boolean, - anyOf?: Array, - allOf?: Array, - noneOf?: Array, + anyOf?: Array, + allOf?: Array, + noneOf?: Array, } export interface CascadingInheritance { - scanAnnotationSelector: ScanAnnotationSelector, + scopeLimiter: ScopeLimiter, inheritLabels: boolean, inheritAnnotations: boolean, inheritEnv: boolean, @@ -94,10 +94,10 @@ export interface ParseDefinition { } export interface ParseDefinitionSpec { - selectorAttributeMappings: SelectorAttributeMappings, + scopeLimiterAliases: ScopeLimiterAliases, } -export type SelectorAttributeMappings = { [key: string]: string; }; +export type ScopeLimiterAliases = { [key: string]: string; }; export function mergeInheritedMap(parentProps, ruleProps, inherit: boolean = true) { if (!inherit) { diff --git a/operator/apis/execution/v1/parsedefinition_types.go b/operator/apis/execution/v1/parsedefinition_types.go index ac43567c5e..d0c9422502 100644 --- a/operator/apis/execution/v1/parsedefinition_types.go +++ b/operator/apis/execution/v1/parsedefinition_types.go @@ -17,7 +17,7 @@ type ParseDefinitionSpec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // Important: Run "make" to regenerate code after modifying this file - SelectorAttributeMappings map[string]string `json:"selectorAttributeMappings,omitempty"` + ScopeLimiterAliases map[string]string `json:"scopeLimiterAliases,omitempty"` // Image is the reference to the parser container image which ca transform the raw scan report into findings Image string `json:"image,omitempty"` diff --git a/operator/apis/execution/v1/scan_types.go b/operator/apis/execution/v1/scan_types.go index b641380011..fe1aa37742 100644 --- a/operator/apis/execution/v1/scan_types.go +++ b/operator/apis/execution/v1/scan_types.go @@ -16,7 +16,7 @@ import ( type CascadeSpec struct { // InheritLabels defines whether cascading scans should inherit labels from the parent scan // +optional - ScanAnnotationSelector ScanAnnotationSelector `json:"scanAnnotationSelector"` + ScopeLimiter ScopeLimiter `json:"scopeLimiter"` // InheritLabels defines whether cascading scans should inherit labels from the parent scan // +optional @@ -58,7 +58,7 @@ type CascadeSpec struct { MatchExpressions []metav1.LabelSelectorRequirement `json:"matchExpressions,omitempty" protobuf:"bytes,2,rep,name=matchExpressions"` } -type ScanAnnotationSelector struct { +type ScopeLimiter struct { // ValidOnMissingRender defines whether if a templating variable is not present, that condition should match // +optional // +kubebuilder:default=false @@ -66,20 +66,20 @@ type ScanAnnotationSelector struct { // AnyOf is a list of label selector requirements. The requirements are ANDed. // +optional - AnyOf []ScanAnnotationSelectorRequirement `json:"anyOf,omitempty" protobuf:"bytes,2,rep,name=anyOf"` + AnyOf []ScopeLimiterRequirement `json:"anyOf,omitempty" protobuf:"bytes,2,rep,name=anyOf"` // AllOf is a list of label selector requirements. The requirements are ANDed. // +optional - AllOf []ScanAnnotationSelectorRequirement `json:"allOf,omitempty" protobuf:"bytes,2,rep,name=allOf"` + AllOf []ScopeLimiterRequirement `json:"allOf,omitempty" protobuf:"bytes,2,rep,name=allOf"` // NoneOf is a list of label selector requirements. The requirements are ANDed. // +optional - NoneOf []ScanAnnotationSelectorRequirement `json:"noneOf,omitempty" protobuf:"bytes,2,rep,name=noneOf"` + NoneOf []ScopeLimiterRequirement `json:"noneOf,omitempty" protobuf:"bytes,2,rep,name=noneOf"` } -// ScanAnnotationSelectorRequirement is a selector that contains values, a key, and an operator that +// ScopeLimiterRequirement is a selector that contains values, a key, and an operator that // relates the key and values. -type ScanAnnotationSelectorRequirement struct { +type ScopeLimiterRequirement struct { // key is the label key that the selector applies to. Key string `json:"key" protobuf:"bytes,1,opt,name=key"` // operator represents a key's relationship to a set of values. diff --git a/operator/apis/execution/v1/zz_generated.deepcopy.go b/operator/apis/execution/v1/zz_generated.deepcopy.go index a6dd788a0c..d086eca826 100644 --- a/operator/apis/execution/v1/zz_generated.deepcopy.go +++ b/operator/apis/execution/v1/zz_generated.deepcopy.go @@ -18,7 +18,7 @@ import ( // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CascadeSpec) DeepCopyInto(out *CascadeSpec) { *out = *in - in.ScanAnnotationSelector.DeepCopyInto(&out.ScanAnnotationSelector) + in.ScopeLimiter.DeepCopyInto(&out.ScopeLimiter) if in.MatchLabels != nil { in, out := &in.MatchLabels, &out.MatchLabels *out = make(map[string]string, len(*in)) @@ -175,8 +175,8 @@ func (in *ParseDefinitionList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ParseDefinitionSpec) DeepCopyInto(out *ParseDefinitionSpec) { *out = *in - if in.SelectorAttributeMappings != nil { - in, out := &in.SelectorAttributeMappings, &out.SelectorAttributeMappings + if in.ScopeLimiterAliases != nil { + in, out := &in.ScopeLimiterAliases, &out.ScopeLimiterAliases *out = make(map[string]string, len(*in)) for key, val := range *in { (*out)[key] = val @@ -267,62 +267,6 @@ func (in *Scan) DeepCopyObject() runtime.Object { return nil } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ScanAnnotationSelector) DeepCopyInto(out *ScanAnnotationSelector) { - *out = *in - if in.AnyOf != nil { - in, out := &in.AnyOf, &out.AnyOf - *out = make([]ScanAnnotationSelectorRequirement, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.AllOf != nil { - in, out := &in.AllOf, &out.AllOf - *out = make([]ScanAnnotationSelectorRequirement, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.NoneOf != nil { - in, out := &in.NoneOf, &out.NoneOf - *out = make([]ScanAnnotationSelectorRequirement, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ScanAnnotationSelector. -func (in *ScanAnnotationSelector) DeepCopy() *ScanAnnotationSelector { - if in == nil { - return nil - } - out := new(ScanAnnotationSelector) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ScanAnnotationSelectorRequirement) DeepCopyInto(out *ScanAnnotationSelectorRequirement) { - *out = *in - if in.Values != nil { - in, out := &in.Values, &out.Values - *out = make([]string, len(*in)) - copy(*out, *in) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ScanAnnotationSelectorRequirement. -func (in *ScanAnnotationSelectorRequirement) DeepCopy() *ScanAnnotationSelectorRequirement { - if in == nil { - return nil - } - out := new(ScanAnnotationSelectorRequirement) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ScanCompletionHook) DeepCopyInto(out *ScanCompletionHook) { *out = *in @@ -763,3 +707,59 @@ func (in *ScheduledScanStatus) DeepCopy() *ScheduledScanStatus { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ScopeLimiter) DeepCopyInto(out *ScopeLimiter) { + *out = *in + if in.AnyOf != nil { + in, out := &in.AnyOf, &out.AnyOf + *out = make([]ScopeLimiterRequirement, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.AllOf != nil { + in, out := &in.AllOf, &out.AllOf + *out = make([]ScopeLimiterRequirement, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.NoneOf != nil { + in, out := &in.NoneOf, &out.NoneOf + *out = make([]ScopeLimiterRequirement, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ScopeLimiter. +func (in *ScopeLimiter) DeepCopy() *ScopeLimiter { + if in == nil { + return nil + } + out := new(ScopeLimiter) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ScopeLimiterRequirement) DeepCopyInto(out *ScopeLimiterRequirement) { + *out = *in + if in.Values != nil { + in, out := &in.Values, &out.Values + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ScopeLimiterRequirement. +func (in *ScopeLimiterRequirement) DeepCopy() *ScopeLimiterRequirement { + if in == nil { + return nil + } + out := new(ScopeLimiterRequirement) + in.DeepCopyInto(out) + return out +} diff --git a/operator/config/crd/bases/cascading.securecodebox.io_cascadingrules.yaml b/operator/config/crd/bases/cascading.securecodebox.io_cascadingrules.yaml index e1e790b5c2..3c12d03190 100644 --- a/operator/config/crd/bases/cascading.securecodebox.io_cascadingrules.yaml +++ b/operator/config/crd/bases/cascading.securecodebox.io_cascadingrules.yaml @@ -176,7 +176,7 @@ spec: the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object - scanAnnotationSelector: + scopeLimiter: description: InheritLabels defines whether cascading scans should inherit labels from the parent scan properties: @@ -184,9 +184,9 @@ spec: description: AllOf is a list of label selector requirements. The requirements are ANDed. items: - description: ScanAnnotationSelectorRequirement is a - selector that contains values, a key, and an operator - that relates the key and values. + description: ScopeLimiterRequirement 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 @@ -210,9 +210,9 @@ spec: description: AnyOf is a list of label selector requirements. The requirements are ANDed. items: - description: ScanAnnotationSelectorRequirement is a - selector that contains values, a key, and an operator - that relates the key and values. + description: ScopeLimiterRequirement 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 @@ -236,9 +236,9 @@ spec: description: NoneOf is a list of label selector requirements. The requirements are ANDed. items: - description: ScanAnnotationSelectorRequirement is a - selector that contains values, a key, and an operator - that relates the key and values. + description: ScopeLimiterRequirement 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 diff --git a/operator/config/crd/bases/execution.securecodebox.io_parsedefinitions.yaml b/operator/config/crd/bases/execution.securecodebox.io_parsedefinitions.yaml index d17e68063d..4473217c8d 100644 --- a/operator/config/crd/bases/execution.securecodebox.io_parsedefinitions.yaml +++ b/operator/config/crd/bases/execution.securecodebox.io_parsedefinitions.yaml @@ -169,7 +169,7 @@ spec: type: string type: object type: array - selectorAttributeMappings: + scopeLimiterAliases: additionalProperties: type: string type: object diff --git a/operator/config/crd/bases/execution.securecodebox.io_scans.yaml b/operator/config/crd/bases/execution.securecodebox.io_scans.yaml index 10431a44d2..58e5876d12 100644 --- a/operator/config/crd/bases/execution.securecodebox.io_scans.yaml +++ b/operator/config/crd/bases/execution.securecodebox.io_scans.yaml @@ -137,7 +137,7 @@ spec: is "In", and the values array contains only "value". The requirements are ANDed. type: object - scanAnnotationSelector: + scopeLimiter: description: InheritLabels defines whether cascading scans should inherit labels from the parent scan properties: @@ -145,9 +145,9 @@ spec: description: AllOf is a list of label selector requirements. The requirements are ANDed. items: - description: ScanAnnotationSelectorRequirement is a selector - that contains values, a key, and an operator that relates - the key and values. + description: ScopeLimiterRequirement 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 @@ -171,9 +171,9 @@ spec: description: AnyOf is a list of label selector requirements. The requirements are ANDed. items: - description: ScanAnnotationSelectorRequirement is a selector - that contains values, a key, and an operator that relates - the key and values. + description: ScopeLimiterRequirement 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 @@ -197,9 +197,9 @@ spec: description: NoneOf is a list of label selector requirements. The requirements are ANDed. items: - description: ScanAnnotationSelectorRequirement is a selector - that contains values, a key, and an operator that relates - the key and values. + description: ScopeLimiterRequirement 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 diff --git a/operator/config/crd/bases/execution.securecodebox.io_scheduledscans.yaml b/operator/config/crd/bases/execution.securecodebox.io_scheduledscans.yaml index 09d4e60f47..c5111a0cc5 100644 --- a/operator/config/crd/bases/execution.securecodebox.io_scheduledscans.yaml +++ b/operator/config/crd/bases/execution.securecodebox.io_scheduledscans.yaml @@ -158,7 +158,7 @@ spec: the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object - scanAnnotationSelector: + scopeLimiter: description: InheritLabels defines whether cascading scans should inherit labels from the parent scan properties: @@ -166,9 +166,9 @@ spec: description: AllOf is a list of label selector requirements. The requirements are ANDed. items: - description: ScanAnnotationSelectorRequirement is a - selector that contains values, a key, and an operator - that relates the key and values. + description: ScopeLimiterRequirement 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 @@ -192,9 +192,9 @@ spec: description: AnyOf is a list of label selector requirements. The requirements are ANDed. items: - description: ScanAnnotationSelectorRequirement is a - selector that contains values, a key, and an operator - that relates the key and values. + description: ScopeLimiterRequirement 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 @@ -218,9 +218,9 @@ spec: description: NoneOf is a list of label selector requirements. The requirements are ANDed. items: - description: ScanAnnotationSelectorRequirement is a - selector that contains values, a key, and an operator - that relates the key and values. + description: ScopeLimiterRequirement 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 diff --git a/operator/crds/cascading.securecodebox.io_cascadingrules.yaml b/operator/crds/cascading.securecodebox.io_cascadingrules.yaml index e1e790b5c2..3c12d03190 100644 --- a/operator/crds/cascading.securecodebox.io_cascadingrules.yaml +++ b/operator/crds/cascading.securecodebox.io_cascadingrules.yaml @@ -176,7 +176,7 @@ spec: the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object - scanAnnotationSelector: + scopeLimiter: description: InheritLabels defines whether cascading scans should inherit labels from the parent scan properties: @@ -184,9 +184,9 @@ spec: description: AllOf is a list of label selector requirements. The requirements are ANDed. items: - description: ScanAnnotationSelectorRequirement is a - selector that contains values, a key, and an operator - that relates the key and values. + description: ScopeLimiterRequirement 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 @@ -210,9 +210,9 @@ spec: description: AnyOf is a list of label selector requirements. The requirements are ANDed. items: - description: ScanAnnotationSelectorRequirement is a - selector that contains values, a key, and an operator - that relates the key and values. + description: ScopeLimiterRequirement 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 @@ -236,9 +236,9 @@ spec: description: NoneOf is a list of label selector requirements. The requirements are ANDed. items: - description: ScanAnnotationSelectorRequirement is a - selector that contains values, a key, and an operator - that relates the key and values. + description: ScopeLimiterRequirement 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 diff --git a/operator/crds/execution.securecodebox.io_parsedefinitions.yaml b/operator/crds/execution.securecodebox.io_parsedefinitions.yaml index d17e68063d..4473217c8d 100644 --- a/operator/crds/execution.securecodebox.io_parsedefinitions.yaml +++ b/operator/crds/execution.securecodebox.io_parsedefinitions.yaml @@ -169,7 +169,7 @@ spec: type: string type: object type: array - selectorAttributeMappings: + scopeLimiterAliases: additionalProperties: type: string type: object diff --git a/operator/crds/execution.securecodebox.io_scans.yaml b/operator/crds/execution.securecodebox.io_scans.yaml index 10431a44d2..58e5876d12 100644 --- a/operator/crds/execution.securecodebox.io_scans.yaml +++ b/operator/crds/execution.securecodebox.io_scans.yaml @@ -137,7 +137,7 @@ spec: is "In", and the values array contains only "value". The requirements are ANDed. type: object - scanAnnotationSelector: + scopeLimiter: description: InheritLabels defines whether cascading scans should inherit labels from the parent scan properties: @@ -145,9 +145,9 @@ spec: description: AllOf is a list of label selector requirements. The requirements are ANDed. items: - description: ScanAnnotationSelectorRequirement is a selector - that contains values, a key, and an operator that relates - the key and values. + description: ScopeLimiterRequirement 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 @@ -171,9 +171,9 @@ spec: description: AnyOf is a list of label selector requirements. The requirements are ANDed. items: - description: ScanAnnotationSelectorRequirement is a selector - that contains values, a key, and an operator that relates - the key and values. + description: ScopeLimiterRequirement 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 @@ -197,9 +197,9 @@ spec: description: NoneOf is a list of label selector requirements. The requirements are ANDed. items: - description: ScanAnnotationSelectorRequirement is a selector - that contains values, a key, and an operator that relates - the key and values. + description: ScopeLimiterRequirement 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 diff --git a/operator/crds/execution.securecodebox.io_scheduledscans.yaml b/operator/crds/execution.securecodebox.io_scheduledscans.yaml index 09d4e60f47..c5111a0cc5 100644 --- a/operator/crds/execution.securecodebox.io_scheduledscans.yaml +++ b/operator/crds/execution.securecodebox.io_scheduledscans.yaml @@ -158,7 +158,7 @@ spec: the operator is "In", and the values array contains only "value". The requirements are ANDed. type: object - scanAnnotationSelector: + scopeLimiter: description: InheritLabels defines whether cascading scans should inherit labels from the parent scan properties: @@ -166,9 +166,9 @@ spec: description: AllOf is a list of label selector requirements. The requirements are ANDed. items: - description: ScanAnnotationSelectorRequirement is a - selector that contains values, a key, and an operator - that relates the key and values. + description: ScopeLimiterRequirement 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 @@ -192,9 +192,9 @@ spec: description: AnyOf is a list of label selector requirements. The requirements are ANDed. items: - description: ScanAnnotationSelectorRequirement is a - selector that contains values, a key, and an operator - that relates the key and values. + description: ScopeLimiterRequirement 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 @@ -218,9 +218,9 @@ spec: description: NoneOf is a list of label selector requirements. The requirements are ANDed. items: - description: ScanAnnotationSelectorRequirement is a - selector that contains values, a key, and an operator - that relates the key and values. + description: ScopeLimiterRequirement 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 From 2ff80be16a74bb3ed7401cbcc71669969432993f Mon Sep 17 00:00:00 2001 From: Jop Zitman Date: Wed, 17 Nov 2021 16:30:52 +0100 Subject: [PATCH 09/54] Add `scopeLimiterAliases` to scanner values.yaml Signed-off-by: Jop Zitman --- scanners/amass/templates/amass-parse-definition.yaml | 2 ++ scanners/amass/values.yaml | 3 +++ .../templates/angularjs-csti-scanner-parse-definition.yaml | 2 ++ scanners/angularjs-csti-scanner/values.yaml | 3 +++ scanners/cmseek/templates/cmseek-parse-definition.yaml | 2 ++ scanners/cmseek/values.yaml | 3 +++ .../templates/git-repo-scanner-parse-definition.yaml | 2 ++ scanners/git-repo-scanner/values.yaml | 3 +++ scanners/gitleaks/templates/gitleaks-parse-definition.yaml | 2 ++ scanners/gitleaks/values.yaml | 3 +++ .../kube-hunter/templates/kube-hunter-parse-definition.yaml | 2 ++ scanners/kube-hunter/values.yaml | 3 +++ scanners/kubeaudit/templates/kubeaudit-parse-definition.yaml | 2 ++ scanners/kubeaudit/values.yaml | 3 +++ scanners/ncrack/templates/ncrack-parse-definition.yaml | 2 ++ scanners/ncrack/values.yaml | 3 +++ scanners/nikto/templates/nikto-parse-definition.yaml | 2 ++ scanners/nikto/values.yaml | 3 +++ scanners/nmap/templates/nmap-parse-definition.yaml | 2 ++ scanners/nmap/values.yaml | 3 +++ scanners/nuclei/templates/nuclei-parse-definition.yaml | 2 ++ scanners/nuclei/values.yaml | 3 +++ .../templates/screenshooter-parse-definition.yaml | 2 ++ scanners/screenshooter/values.yaml | 3 +++ scanners/semgrep/templates/semgrep-parse-definition.yaml | 2 ++ scanners/semgrep/values.yaml | 2 ++ scanners/ssh-scan/templates/ssh-scan-parse-definition.yaml | 2 ++ scanners/ssh-scan/values.yaml | 3 +++ scanners/sslyze/templates/sslyze-parse-definition.yaml | 2 ++ scanners/sslyze/values.yaml | 3 +++ scanners/test-scan/templates/test-scan-parse-definition.yaml | 2 ++ scanners/test-scan/values.yaml | 3 +++ scanners/trivy/templates/trivy-parse-definition.yaml | 2 ++ scanners/trivy/values.yaml | 3 +++ scanners/typo3scan/templates/typo3scan-parse-definition.yaml | 2 ++ scanners/typo3scan/values.yaml | 3 +++ scanners/whatweb/templates/whatweb-parse-definition.yaml | 2 ++ scanners/whatweb/values.yaml | 3 +++ scanners/wpscan/templates/wpscan-parse-definition.yaml | 2 ++ scanners/wpscan/values.yaml | 3 +++ .../zap-advanced/templates/zap-advanced-parse-definition.yaml | 2 ++ scanners/zap-advanced/values.yaml | 3 +++ scanners/zap/templates/zap-parse-definition.yaml | 2 ++ scanners/zap/values.yaml | 3 +++ 44 files changed, 109 insertions(+) diff --git a/scanners/amass/templates/amass-parse-definition.yaml b/scanners/amass/templates/amass-parse-definition.yaml index 90ff3ef0ae..73d8d9e7cb 100644 --- a/scanners/amass/templates/amass-parse-definition.yaml +++ b/scanners/amass/templates/amass-parse-definition.yaml @@ -12,3 +12,5 @@ spec: ttlSecondsAfterFinished: {{ .Values.parser.ttlSecondsAfterFinished }} env: {{- toYaml .Values.parser.env | nindent 4 }} + scopeLimiterAliases: + {{- toYaml .Values.parser.scopeLimiterAliases | nindent 4 }} diff --git a/scanners/amass/values.yaml b/scanners/amass/values.yaml index b84f89ce77..69bb54a012 100644 --- a/scanners/amass/values.yaml +++ b/scanners/amass/values.yaml @@ -17,6 +17,9 @@ parser: # parser.env -- Optional environment variables mapped into each parseJob (see: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) env: [] + # parser.scopeLimiterAliases -- Optional finding aliases to be used in the scopeLimiter. + scopeLimiterAliases: {} + scanner: image: # scanner.image.repository -- Container Image to run the scan diff --git a/scanners/angularjs-csti-scanner/templates/angularjs-csti-scanner-parse-definition.yaml b/scanners/angularjs-csti-scanner/templates/angularjs-csti-scanner-parse-definition.yaml index 6a6b126b62..f616decb4c 100644 --- a/scanners/angularjs-csti-scanner/templates/angularjs-csti-scanner-parse-definition.yaml +++ b/scanners/angularjs-csti-scanner/templates/angularjs-csti-scanner-parse-definition.yaml @@ -12,3 +12,5 @@ spec: ttlSecondsAfterFinished: {{ .Values.parser.ttlSecondsAfterFinished }} env: {{- toYaml .Values.parser.env | nindent 4 }} + scopeLimiterAliases: + {{- toYaml .Values.parser.scopeLimiterAliases | nindent 4 }} diff --git a/scanners/angularjs-csti-scanner/values.yaml b/scanners/angularjs-csti-scanner/values.yaml index cfac783b10..33a90a01b6 100644 --- a/scanners/angularjs-csti-scanner/values.yaml +++ b/scanners/angularjs-csti-scanner/values.yaml @@ -17,6 +17,9 @@ parser: # parser.env -- Optional environment variables mapped into each parseJob (see: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) env: [] + # parser.scopeLimiterAliases -- Optional finding aliases to be used in the scopeLimiter. + scopeLimiterAliases: {} + scanner: image: # scanner.image.repository -- Container Image to run the scan diff --git a/scanners/cmseek/templates/cmseek-parse-definition.yaml b/scanners/cmseek/templates/cmseek-parse-definition.yaml index 183b26cd4d..85635deed6 100644 --- a/scanners/cmseek/templates/cmseek-parse-definition.yaml +++ b/scanners/cmseek/templates/cmseek-parse-definition.yaml @@ -12,3 +12,5 @@ spec: ttlSecondsAfterFinished: {{ .Values.parser.ttlSecondsAfterFinished }} env: {{- toYaml .Values.parser.env | nindent 4 }} + scopeLimiterAliases: + {{- toYaml .Values.parser.scopeLimiterAliases | nindent 4 }} diff --git a/scanners/cmseek/values.yaml b/scanners/cmseek/values.yaml index aa7bfc0cb9..2b12898b51 100644 --- a/scanners/cmseek/values.yaml +++ b/scanners/cmseek/values.yaml @@ -17,6 +17,9 @@ parser: # parser.env -- Optional environment variables mapped into each parseJob (see: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) env: [] + # parser.scopeLimiterAliases -- Optional finding aliases to be used in the scopeLimiter. + scopeLimiterAliases: {} + scanner: image: # scanner.image.repository -- Container Image to run the scan diff --git a/scanners/git-repo-scanner/templates/git-repo-scanner-parse-definition.yaml b/scanners/git-repo-scanner/templates/git-repo-scanner-parse-definition.yaml index f6a0d4ee29..5555a4cc58 100644 --- a/scanners/git-repo-scanner/templates/git-repo-scanner-parse-definition.yaml +++ b/scanners/git-repo-scanner/templates/git-repo-scanner-parse-definition.yaml @@ -12,3 +12,5 @@ spec: ttlSecondsAfterFinished: {{ .Values.parser.ttlSecondsAfterFinished }} env: {{- toYaml .Values.parser.env | nindent 4 }} + scopeLimiterAliases: + {{- toYaml .Values.parser.scopeLimiterAliases | nindent 4 }} diff --git a/scanners/git-repo-scanner/values.yaml b/scanners/git-repo-scanner/values.yaml index f9a526f404..50cb204759 100644 --- a/scanners/git-repo-scanner/values.yaml +++ b/scanners/git-repo-scanner/values.yaml @@ -17,6 +17,9 @@ parser: # parser.env -- Optional environment variables mapped into each parseJob (see: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) env: [] + # parser.scopeLimiterAliases -- Optional finding aliases to be used in the scopeLimiter. + scopeLimiterAliases: {} + scanner: image: # scanner.image.repository -- Container Image to run the scan diff --git a/scanners/gitleaks/templates/gitleaks-parse-definition.yaml b/scanners/gitleaks/templates/gitleaks-parse-definition.yaml index 10b8cde9f9..b4029108a4 100644 --- a/scanners/gitleaks/templates/gitleaks-parse-definition.yaml +++ b/scanners/gitleaks/templates/gitleaks-parse-definition.yaml @@ -12,3 +12,5 @@ spec: ttlSecondsAfterFinished: {{ .Values.parser.ttlSecondsAfterFinished }} env: {{- toYaml .Values.parser.env | nindent 4 }} + scopeLimiterAliases: + {{- toYaml .Values.parser.scopeLimiterAliases | nindent 4 }} diff --git a/scanners/gitleaks/values.yaml b/scanners/gitleaks/values.yaml index 058a6c9764..00e5be0a21 100644 --- a/scanners/gitleaks/values.yaml +++ b/scanners/gitleaks/values.yaml @@ -17,6 +17,9 @@ parser: # parser.env -- Optional environment variables mapped into each parseJob (see: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) env: [] + # parser.scopeLimiterAliases -- Optional finding aliases to be used in the scopeLimiter. + scopeLimiterAliases: {} + scanner: image: # scanner.image.repository -- Container Image to run the scan diff --git a/scanners/kube-hunter/templates/kube-hunter-parse-definition.yaml b/scanners/kube-hunter/templates/kube-hunter-parse-definition.yaml index fbd7fffdcd..c78c1a3268 100644 --- a/scanners/kube-hunter/templates/kube-hunter-parse-definition.yaml +++ b/scanners/kube-hunter/templates/kube-hunter-parse-definition.yaml @@ -12,3 +12,5 @@ spec: ttlSecondsAfterFinished: {{ .Values.parser.ttlSecondsAfterFinished }} env: {{- toYaml .Values.parser.env | nindent 4 }} + scopeLimiterAliases: + {{- toYaml .Values.parser.scopeLimiterAliases | nindent 4 }} diff --git a/scanners/kube-hunter/values.yaml b/scanners/kube-hunter/values.yaml index 262b311ee9..ce377170a7 100644 --- a/scanners/kube-hunter/values.yaml +++ b/scanners/kube-hunter/values.yaml @@ -17,6 +17,9 @@ parser: # parser.env -- Optional environment variables mapped into each parseJob (see: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) env: [] + # parser.scopeLimiterAliases -- Optional finding aliases to be used in the scopeLimiter. + scopeLimiterAliases: {} + scanner: image: # scanner.image.repository -- Container Image to run the scan diff --git a/scanners/kubeaudit/templates/kubeaudit-parse-definition.yaml b/scanners/kubeaudit/templates/kubeaudit-parse-definition.yaml index 6c27c3c926..6fd7aa76a9 100644 --- a/scanners/kubeaudit/templates/kubeaudit-parse-definition.yaml +++ b/scanners/kubeaudit/templates/kubeaudit-parse-definition.yaml @@ -12,3 +12,5 @@ spec: ttlSecondsAfterFinished: {{ .Values.parser.ttlSecondsAfterFinished }} env: {{- toYaml .Values.parser.env | nindent 4 }} + scopeLimiterAliases: + {{- toYaml .Values.parser.scopeLimiterAliases | nindent 4 }} diff --git a/scanners/kubeaudit/values.yaml b/scanners/kubeaudit/values.yaml index 20f671715c..8f3b619a2b 100644 --- a/scanners/kubeaudit/values.yaml +++ b/scanners/kubeaudit/values.yaml @@ -17,6 +17,9 @@ parser: # parser.env -- Optional environment variables mapped into each parseJob (see: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) env: [] + # parser.scopeLimiterAliases -- Optional finding aliases to be used in the scopeLimiter. + scopeLimiterAliases: {} + scanner: image: # scanner.image.repository -- Container Image to run the scan diff --git a/scanners/ncrack/templates/ncrack-parse-definition.yaml b/scanners/ncrack/templates/ncrack-parse-definition.yaml index 6f1de86452..150e9f5185 100644 --- a/scanners/ncrack/templates/ncrack-parse-definition.yaml +++ b/scanners/ncrack/templates/ncrack-parse-definition.yaml @@ -12,3 +12,5 @@ spec: ttlSecondsAfterFinished: {{ .Values.parser.ttlSecondsAfterFinished }} env: {{- toYaml .Values.parser.env | nindent 4 }} + scopeLimiterAliases: + {{- toYaml .Values.parser.scopeLimiterAliases | nindent 4 }} diff --git a/scanners/ncrack/values.yaml b/scanners/ncrack/values.yaml index 678a730352..d83485592c 100644 --- a/scanners/ncrack/values.yaml +++ b/scanners/ncrack/values.yaml @@ -23,6 +23,9 @@ parser: # parser.env -- Optional environment variables mapped into each parseJob (see: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) env: [] + # parser.scopeLimiterAliases -- Optional finding aliases to be used in the scopeLimiter. + scopeLimiterAliases: {} + scanner: image: # scanner.image.repository -- Container Image to run the scan diff --git a/scanners/nikto/templates/nikto-parse-definition.yaml b/scanners/nikto/templates/nikto-parse-definition.yaml index 3a40e64fcd..9cbf15a066 100644 --- a/scanners/nikto/templates/nikto-parse-definition.yaml +++ b/scanners/nikto/templates/nikto-parse-definition.yaml @@ -12,3 +12,5 @@ spec: ttlSecondsAfterFinished: {{ .Values.parser.ttlSecondsAfterFinished }} env: {{- toYaml .Values.parser.env | nindent 4 }} + scopeLimiterAliases: + {{- toYaml .Values.parser.scopeLimiterAliases | nindent 4 }} diff --git a/scanners/nikto/values.yaml b/scanners/nikto/values.yaml index 3d044a9d70..602e2f6af9 100644 --- a/scanners/nikto/values.yaml +++ b/scanners/nikto/values.yaml @@ -17,6 +17,9 @@ parser: # parser.env -- Optional environment variables mapped into each parseJob (see: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) env: [] + # parser.scopeLimiterAliases -- Optional finding aliases to be used in the scopeLimiter. + scopeLimiterAliases: {} + scanner: image: # scanner.image.repository -- Container Image to run the scan diff --git a/scanners/nmap/templates/nmap-parse-definition.yaml b/scanners/nmap/templates/nmap-parse-definition.yaml index 821df44060..9e5bc83e52 100644 --- a/scanners/nmap/templates/nmap-parse-definition.yaml +++ b/scanners/nmap/templates/nmap-parse-definition.yaml @@ -12,3 +12,5 @@ spec: ttlSecondsAfterFinished: {{ .Values.parser.ttlSecondsAfterFinished }} env: {{- toYaml .Values.parser.env | nindent 4 }} + scopeLimiterAliases: + {{- toYaml .Values.parser.scopeLimiterAliases | nindent 4 }} diff --git a/scanners/nmap/values.yaml b/scanners/nmap/values.yaml index 802aff0163..597b882e73 100644 --- a/scanners/nmap/values.yaml +++ b/scanners/nmap/values.yaml @@ -17,6 +17,9 @@ parser: # parser.env -- Optional environment variables mapped into each parseJob (see: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) env: [] + # parser.scopeLimiterAliases -- Optional finding aliases to be used in the scopeLimiter. + scopeLimiterAliases: {} + scanner: image: # scanner.image.repository -- Container Image to run the scan diff --git a/scanners/nuclei/templates/nuclei-parse-definition.yaml b/scanners/nuclei/templates/nuclei-parse-definition.yaml index ef96f49f86..b0a1f19fbb 100644 --- a/scanners/nuclei/templates/nuclei-parse-definition.yaml +++ b/scanners/nuclei/templates/nuclei-parse-definition.yaml @@ -12,3 +12,5 @@ spec: ttlSecondsAfterFinished: {{ .Values.parser.ttlSecondsAfterFinished }} env: {{- toYaml .Values.parser.env | nindent 4 }} + scopeLimiterAliases: + {{- toYaml .Values.parser.scopeLimiterAliases | nindent 4 }} diff --git a/scanners/nuclei/values.yaml b/scanners/nuclei/values.yaml index d6fac86b14..16ca0973f5 100644 --- a/scanners/nuclei/values.yaml +++ b/scanners/nuclei/values.yaml @@ -17,6 +17,9 @@ parser: # parser.env -- Optional environment variables mapped into each parseJob (see: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) env: [] + # parser.scopeLimiterAliases -- Optional finding aliases to be used in the scopeLimiter. + scopeLimiterAliases: {} + scanner: image: # scanner.image.repository -- Container Image to run the scan diff --git a/scanners/screenshooter/templates/screenshooter-parse-definition.yaml b/scanners/screenshooter/templates/screenshooter-parse-definition.yaml index bfb1253627..0d83f29343 100644 --- a/scanners/screenshooter/templates/screenshooter-parse-definition.yaml +++ b/scanners/screenshooter/templates/screenshooter-parse-definition.yaml @@ -12,3 +12,5 @@ spec: ttlSecondsAfterFinished: {{ .Values.parser.ttlSecondsAfterFinished }} env: {{- toYaml .Values.parser.env | nindent 4 }} + scopeLimiterAliases: + {{- toYaml .Values.parser.scopeLimiterAliases | nindent 4 }} diff --git a/scanners/screenshooter/values.yaml b/scanners/screenshooter/values.yaml index 5f5362f996..229bb8ae9e 100644 --- a/scanners/screenshooter/values.yaml +++ b/scanners/screenshooter/values.yaml @@ -17,6 +17,9 @@ parser: # parser.env -- Optional environment variables mapped into each parseJob (see: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) env: [] + # parser.scopeLimiterAliases -- Optional finding aliases to be used in the scopeLimiter. + scopeLimiterAliases: {} + scanner: image: # scanner.image.repository -- Container Image to run the scan diff --git a/scanners/semgrep/templates/semgrep-parse-definition.yaml b/scanners/semgrep/templates/semgrep-parse-definition.yaml index e45c12a23f..c565e9861f 100644 --- a/scanners/semgrep/templates/semgrep-parse-definition.yaml +++ b/scanners/semgrep/templates/semgrep-parse-definition.yaml @@ -12,3 +12,5 @@ spec: ttlSecondsAfterFinished: {{ .Values.parser.ttlSecondsAfterFinished }} env: {{- toYaml .Values.parser.env | nindent 4 }} + scopeLimiterAliases: + {{- toYaml .Values.parser.scopeLimiterAliases | nindent 4 }} diff --git a/scanners/semgrep/values.yaml b/scanners/semgrep/values.yaml index 8468a4311f..02bb74e731 100644 --- a/scanners/semgrep/values.yaml +++ b/scanners/semgrep/values.yaml @@ -7,6 +7,8 @@ parser: backoffLimit: 3 env: [] + # parser.scopeLimiterAliases -- Optional finding aliases to be used in the scopeLimiter. + scopeLimiterAliases: {} scanner: image: diff --git a/scanners/ssh-scan/templates/ssh-scan-parse-definition.yaml b/scanners/ssh-scan/templates/ssh-scan-parse-definition.yaml index 4b8c1b30f3..3c43579b05 100644 --- a/scanners/ssh-scan/templates/ssh-scan-parse-definition.yaml +++ b/scanners/ssh-scan/templates/ssh-scan-parse-definition.yaml @@ -12,3 +12,5 @@ spec: ttlSecondsAfterFinished: {{ .Values.parser.ttlSecondsAfterFinished }} env: {{- toYaml .Values.parser.env | nindent 4 }} + scopeLimiterAliases: + {{- toYaml .Values.parser.scopeLimiterAliases | nindent 4 }} diff --git a/scanners/ssh-scan/values.yaml b/scanners/ssh-scan/values.yaml index 960d988e85..88568984ea 100644 --- a/scanners/ssh-scan/values.yaml +++ b/scanners/ssh-scan/values.yaml @@ -17,6 +17,9 @@ parser: # parser.env -- Optional environment variables mapped into each parseJob (see: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) env: [] + # parser.scopeLimiterAliases -- Optional finding aliases to be used in the scopeLimiter. + scopeLimiterAliases: {} + scanner: image: # scanner.image.repository -- Container Image to run the scan diff --git a/scanners/sslyze/templates/sslyze-parse-definition.yaml b/scanners/sslyze/templates/sslyze-parse-definition.yaml index 7c3f968a48..4151d995ae 100644 --- a/scanners/sslyze/templates/sslyze-parse-definition.yaml +++ b/scanners/sslyze/templates/sslyze-parse-definition.yaml @@ -12,3 +12,5 @@ spec: ttlSecondsAfterFinished: {{ .Values.parser.ttlSecondsAfterFinished }} env: {{- toYaml .Values.parser.env | nindent 4 }} + scopeLimiterAliases: + {{- toYaml .Values.parser.scopeLimiterAliases | nindent 4 }} diff --git a/scanners/sslyze/values.yaml b/scanners/sslyze/values.yaml index 62f6c1dedf..df51d4c6e7 100644 --- a/scanners/sslyze/values.yaml +++ b/scanners/sslyze/values.yaml @@ -17,6 +17,9 @@ parser: # parser.env -- Optional environment variables mapped into each parseJob (see: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) env: [] + # parser.scopeLimiterAliases -- Optional finding aliases to be used in the scopeLimiter. + scopeLimiterAliases: {} + scanner: image: # scanner.image.repository -- Container Image to run the scan diff --git a/scanners/test-scan/templates/test-scan-parse-definition.yaml b/scanners/test-scan/templates/test-scan-parse-definition.yaml index 06131c7190..4a802ea5d6 100644 --- a/scanners/test-scan/templates/test-scan-parse-definition.yaml +++ b/scanners/test-scan/templates/test-scan-parse-definition.yaml @@ -12,3 +12,5 @@ spec: ttlSecondsAfterFinished: {{ .Values.parser.ttlSecondsAfterFinished }} env: {{- toYaml .Values.parser.env | nindent 4 }} + scopeLimiterAliases: + {{- toYaml .Values.parser.scopeLimiterAliases | nindent 4 }} diff --git a/scanners/test-scan/values.yaml b/scanners/test-scan/values.yaml index aa9163e960..a2840bc4bb 100644 --- a/scanners/test-scan/values.yaml +++ b/scanners/test-scan/values.yaml @@ -17,6 +17,9 @@ parser: # parser.env -- Optional environment variables mapped into each parseJob (see: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) env: [] + # parser.scopeLimiterAliases -- Optional finding aliases to be used in the scopeLimiter. + scopeLimiterAliases: {} + scanner: image: # scanner.image.repository -- Container Image to run the scan diff --git a/scanners/trivy/templates/trivy-parse-definition.yaml b/scanners/trivy/templates/trivy-parse-definition.yaml index 4d502b3bd7..5c5a8fccaf 100644 --- a/scanners/trivy/templates/trivy-parse-definition.yaml +++ b/scanners/trivy/templates/trivy-parse-definition.yaml @@ -12,3 +12,5 @@ spec: ttlSecondsAfterFinished: {{ .Values.parser.ttlSecondsAfterFinished }} env: {{- toYaml .Values.parser.env | nindent 4 }} + scopeLimiterAliases: + {{- toYaml .Values.parser.scopeLimiterAliases | nindent 4 }} diff --git a/scanners/trivy/values.yaml b/scanners/trivy/values.yaml index 378d8871d3..d46493794e 100644 --- a/scanners/trivy/values.yaml +++ b/scanners/trivy/values.yaml @@ -17,6 +17,9 @@ parser: # parser.env -- Optional environment variables mapped into each parseJob (see: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) env: [] + # parser.scopeLimiterAliases -- Optional finding aliases to be used in the scopeLimiter. + scopeLimiterAliases: {} + scanner: image: # scanner.image.repository -- Container Image to run the scan diff --git a/scanners/typo3scan/templates/typo3scan-parse-definition.yaml b/scanners/typo3scan/templates/typo3scan-parse-definition.yaml index ca31754977..576814b774 100644 --- a/scanners/typo3scan/templates/typo3scan-parse-definition.yaml +++ b/scanners/typo3scan/templates/typo3scan-parse-definition.yaml @@ -12,3 +12,5 @@ spec: ttlSecondsAfterFinished: {{ .Values.parser.ttlSecondsAfterFinished }} env: {{- toYaml .Values.parser.env | nindent 4 }} + scopeLimiterAliases: + {{- toYaml .Values.parser.scopeLimiterAliases | nindent 4 }} diff --git a/scanners/typo3scan/values.yaml b/scanners/typo3scan/values.yaml index a97ceef2fb..f826a2c27d 100644 --- a/scanners/typo3scan/values.yaml +++ b/scanners/typo3scan/values.yaml @@ -17,6 +17,9 @@ parser: # parser.env -- Optional environment variables mapped into each parseJob (see: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) env: [] + # parser.scopeLimiterAliases -- Optional finding aliases to be used in the scopeLimiter. + scopeLimiterAliases: {} + scanner: image: # scanner.image.repository -- Container Image to run the scan diff --git a/scanners/whatweb/templates/whatweb-parse-definition.yaml b/scanners/whatweb/templates/whatweb-parse-definition.yaml index 1f2cf6bafd..84bbd21792 100644 --- a/scanners/whatweb/templates/whatweb-parse-definition.yaml +++ b/scanners/whatweb/templates/whatweb-parse-definition.yaml @@ -12,3 +12,5 @@ spec: ttlSecondsAfterFinished: {{ .Values.parser.ttlSecondsAfterFinished }} env: {{- toYaml .Values.parser.env | nindent 4 }} + scopeLimiterAliases: + {{- toYaml .Values.parser.scopeLimiterAliases | nindent 4 }} diff --git a/scanners/whatweb/values.yaml b/scanners/whatweb/values.yaml index ea22d7b275..078ff0da95 100644 --- a/scanners/whatweb/values.yaml +++ b/scanners/whatweb/values.yaml @@ -17,6 +17,9 @@ parser: # parser.env -- Optional environment variables mapped into each parseJob (see: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) env: [] + # parser.scopeLimiterAliases -- Optional finding aliases to be used in the scopeLimiter. + scopeLimiterAliases: {} + scanner: image: # scanner.image.repository -- Container Image to run the scan diff --git a/scanners/wpscan/templates/wpscan-parse-definition.yaml b/scanners/wpscan/templates/wpscan-parse-definition.yaml index b992674ec8..763fb786db 100644 --- a/scanners/wpscan/templates/wpscan-parse-definition.yaml +++ b/scanners/wpscan/templates/wpscan-parse-definition.yaml @@ -12,3 +12,5 @@ spec: ttlSecondsAfterFinished: {{ .Values.parser.ttlSecondsAfterFinished }} env: {{- toYaml .Values.parser.env | nindent 4 }} + scopeLimiterAliases: + {{- toYaml .Values.parser.scopeLimiterAliases | nindent 4 }} diff --git a/scanners/wpscan/values.yaml b/scanners/wpscan/values.yaml index 2296184f69..1ea5f3e475 100644 --- a/scanners/wpscan/values.yaml +++ b/scanners/wpscan/values.yaml @@ -17,6 +17,9 @@ parser: # parser.env -- Optional environment variables mapped into each parseJob (see: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) env: [] + # parser.scopeLimiterAliases -- Optional finding aliases to be used in the scopeLimiter. + scopeLimiterAliases: {} + scanner: image: # scanner.image.repository -- Container Image to run the scan diff --git a/scanners/zap-advanced/templates/zap-advanced-parse-definition.yaml b/scanners/zap-advanced/templates/zap-advanced-parse-definition.yaml index 7647e57835..0e5aba5295 100644 --- a/scanners/zap-advanced/templates/zap-advanced-parse-definition.yaml +++ b/scanners/zap-advanced/templates/zap-advanced-parse-definition.yaml @@ -14,3 +14,5 @@ spec: ttlSecondsAfterFinished: {{ .Values.parser.ttlSecondsAfterFinished }} env: {{- toYaml .Values.parser.env | nindent 4 }} + scopeLimiterAliases: + {{- toYaml .Values.parser.scopeLimiterAliases | nindent 4 }} diff --git a/scanners/zap-advanced/values.yaml b/scanners/zap-advanced/values.yaml index a4327ccec0..b33cdef5ca 100644 --- a/scanners/zap-advanced/values.yaml +++ b/scanners/zap-advanced/values.yaml @@ -17,6 +17,9 @@ parser: # parser.env -- Optional environment variables mapped into each parseJob (see: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) env: [] + # parser.scopeLimiterAliases -- Optional finding aliases to be used in the scopeLimiter. + scopeLimiterAliases: {} + scanner: image: # scanner.image.repository -- Container Image to run the scan diff --git a/scanners/zap/templates/zap-parse-definition.yaml b/scanners/zap/templates/zap-parse-definition.yaml index 96737bc4e3..3d3d961d2a 100644 --- a/scanners/zap/templates/zap-parse-definition.yaml +++ b/scanners/zap/templates/zap-parse-definition.yaml @@ -12,3 +12,5 @@ spec: ttlSecondsAfterFinished: {{ .Values.parser.ttlSecondsAfterFinished }} env: {{- toYaml .Values.parser.env | nindent 4 }} + scopeLimiterAliases: + {{- toYaml .Values.parser.scopeLimiterAliases | nindent 4 }} diff --git a/scanners/zap/values.yaml b/scanners/zap/values.yaml index cb9b2887b0..b1aee17a7e 100644 --- a/scanners/zap/values.yaml +++ b/scanners/zap/values.yaml @@ -17,6 +17,9 @@ parser: # parser.env -- Optional environment variables mapped into each parseJob (see: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) env: [] + # parser.scopeLimiterAliases -- Optional finding aliases to be used in the scopeLimiter. + scopeLimiterAliases: {} + scanner: image: # scanner.image.repository -- Container Image to run the scan From 8751a3ddec044d9b5108f043ddbf9f8c708b762f Mon Sep 17 00:00:00 2001 From: Jop Zitman Date: Wed, 17 Nov 2021 16:33:24 +0100 Subject: [PATCH 10/54] Remove unused import Signed-off-by: Jop Zitman --- hooks/cascading-scans/hook/scan-helpers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hooks/cascading-scans/hook/scan-helpers.ts b/hooks/cascading-scans/hook/scan-helpers.ts index 23c795a47a..318cbece94 100644 --- a/hooks/cascading-scans/hook/scan-helpers.ts +++ b/hooks/cascading-scans/hook/scan-helpers.ts @@ -6,7 +6,7 @@ import * as k8s from "@kubernetes/client-node"; import { generateSelectorString, - LabelSelector, LabelSelectorRequirement + LabelSelector } from "./kubernetes-label-selector"; import {isEqual} from "lodash"; import {getScanChain} from "./hook"; From 013c2e2864099d799c4687bf569de05431acf4af Mon Sep 17 00:00:00 2001 From: Jop Zitman Date: Wed, 17 Nov 2021 18:44:11 +0100 Subject: [PATCH 11/54] Make scope limiter actually run in cluster Signed-off-by: Jop Zitman --- hooks/cascading-scans/hook/Dockerfile | 4 ++-- hooks/cascading-scans/hook/scan-helpers.ts | 4 ++-- hooks/cascading-scans/templates/role.yaml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/hooks/cascading-scans/hook/Dockerfile b/hooks/cascading-scans/hook/Dockerfile index 0fb147e958..0791c00b15 100644 --- a/hooks/cascading-scans/hook/Dockerfile +++ b/hooks/cascading-scans/hook/Dockerfile @@ -15,10 +15,10 @@ RUN mkdir -p /home/app WORKDIR /home/app COPY package.json package-lock.json ./ RUN npm ci -COPY hook.ts scan-helpers.ts kubernetes-label-selector.ts ./ +COPY hook.ts scan-helpers.ts reverse-matches.ts kubernetes-label-selector.ts ./ RUN npm run build FROM ${namespace:-securecodebox}/hook-sdk-nodejs:${baseImageTag:-latest} WORKDIR /home/app/hook-wrapper/hook/ COPY --from=install --chown=app:app /home/app/node_modules/ ./node_modules/ -COPY --from=build --chown=app:app /home/app/hook.js /home/app/hook.js.map /home/app/scan-helpers.js /home/app/scan-helpers.js.map /home/app/kubernetes-label-selector.js /home/app/kubernetes-label-selector.js.map ./ +COPY --from=build --chown=app:app /home/app/hook.js /home/app/hook.js.map /home/app/scan-helpers.js /home/app/scan-helpers.js.map /home/app/reverse-matches.js /home/app/reverse-matches.js.map /home/app/kubernetes-label-selector.js /home/app/kubernetes-label-selector.js.map ./ diff --git a/hooks/cascading-scans/hook/scan-helpers.ts b/hooks/cascading-scans/hook/scan-helpers.ts index 318cbece94..d4650ad48b 100644 --- a/hooks/cascading-scans/hook/scan-helpers.ts +++ b/hooks/cascading-scans/hook/scan-helpers.ts @@ -182,10 +182,10 @@ export async function getCascadingRulesForScan(scan: Scan) { export async function getParseDefinitionForScan(scan: Scan) { try { const response: any = await k8sApiCRD.getNamespacedCustomObject( - "cascading.securecodebox.io", + "execution.securecodebox.io", "v1", namespace, - "cascadingrules", + "parsedefinitions", scan.status.rawResultType, ); diff --git a/hooks/cascading-scans/templates/role.yaml b/hooks/cascading-scans/templates/role.yaml index d76e832ab3..dcd4f12c4a 100644 --- a/hooks/cascading-scans/templates/role.yaml +++ b/hooks/cascading-scans/templates/role.yaml @@ -22,7 +22,7 @@ rules: resources: - parsedefinitions verbs: - - list + - get - apiGroups: - execution.securecodebox.io resources: From 59d9a287824501f06e825b36e9876ee1012fad72 Mon Sep 17 00:00:00 2001 From: Jop Zitman Date: Wed, 17 Nov 2021 18:44:48 +0100 Subject: [PATCH 12/54] JSON stringify logs Signed-off-by: Jop Zitman --- hooks/cascading-scans/hook/hook.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/hooks/cascading-scans/hook/hook.ts b/hooks/cascading-scans/hook/hook.ts index 393c8e7b8e..e15d1a7d3d 100644 --- a/hooks/cascading-scans/hook/hook.ts +++ b/hooks/cascading-scans/hook/hook.ts @@ -124,10 +124,10 @@ function getScansMatchingRule( if (!reverseMatches) { console.log(`Cascading Rule ${cascadingRule.metadata.name} not triggered as scope limiter did not pass`); - console.log(`Scan annotations ${parentScan.metadata.annotations}`); - console.log(`Scope limiter ${parentScan.spec.cascades.scopeLimiter}`); - console.log(`Scope limiter aliases ${parseDefinition.spec.scopeLimiterAliases}`); - console.log(`Finding ${finding}`); + console.log(`Scan annotations ${JSON.stringify(parentScan.metadata.annotations)}`); + console.log(`Scope limiter ${JSON.stringify(parentScan.spec.cascades.scopeLimiter)}`); + console.log(`Scope limiter aliases ${JSON.stringify(parseDefinition.spec.scopeLimiterAliases)}`); + console.log(`Finding ${JSON.stringify(finding)}`); continue; } From 54169c01d96d351a50c82dc37a9e494385b9f850 Mon Sep 17 00:00:00 2001 From: malexmave Date: Thu, 18 Nov 2021 09:11:08 +0000 Subject: [PATCH 13/54] Updating Helm Docs Signed-off-by: GitHub Actions Signed-off-by: Jop Zitman --- hooks/cascading-scans/README.md | 1 + hooks/finding-post-processing/README.md | 1 + hooks/generic-webhook/README.md | 1 + hooks/notification/README.md | 1 + hooks/persistence-defectdojo/README.md | 1 + hooks/persistence-elastic/README.md | 1 + hooks/update-field/README.md | 1 + scanners/amass/README.md | 1 + scanners/angularjs-csti-scanner/README.md | 1 + scanners/git-repo-scanner/README.md | 1 + scanners/gitleaks/README.md | 1 + scanners/kube-hunter/README.md | 1 + scanners/kubeaudit/README.md | 1 + scanners/ncrack/README.md | 1 + scanners/nikto/README.md | 1 + scanners/nmap/README.md | 1 + scanners/nuclei/README.md | 1 + scanners/screenshooter/README.md | 1 + scanners/semgrep/README.md | 1 + scanners/ssh-scan/README.md | 1 + scanners/sslyze/README.md | 1 + scanners/test-scan/README.md | 1 + scanners/trivy/README.md | 1 + scanners/wpscan/README.md | 1 + scanners/zap-advanced/README.md | 1 + scanners/zap/README.md | 1 + 26 files changed, 26 insertions(+) diff --git a/hooks/cascading-scans/README.md b/hooks/cascading-scans/README.md index 97165e0fdc..939af95e1e 100644 --- a/hooks/cascading-scans/README.md +++ b/hooks/cascading-scans/README.md @@ -162,6 +162,7 @@ zap-http zap-baseline-scan non-invasive medium |-----|------|---------|-------------| | hook.image.repository | string | `"docker.io/securecodebox/hook-cascading-scans"` | Hook image repository | | hook.image.tag | string | defaults to the charts version | The image Tag defaults to the charts version if not defined. | +| hook.labels | object | `{}` | Add Kubernetes Labels to the hook definition | | hook.ttlSecondsAfterFinished | string | `nil` | 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/ | ## License diff --git a/hooks/finding-post-processing/README.md b/hooks/finding-post-processing/README.md index 56d496a2bb..62640d02e7 100644 --- a/hooks/finding-post-processing/README.md +++ b/hooks/finding-post-processing/README.md @@ -89,6 +89,7 @@ The `override` field specifies the desired fields and values that need to be upd |-----|------|---------|-------------| | hook.image.repository | string | `"docker.io/securecodebox/hook-finding-post-processing"` | Hook image repository | | hook.image.tag | string | defaults to the charts version | The image Tag defaults to the charts version if not defined. | +| hook.labels | object | `{}` | Add Kubernetes Labels to the hook definition | | hook.ttlSecondsAfterFinished | string | `nil` | 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/ | | rules | list | `[]` | | diff --git a/hooks/generic-webhook/README.md b/hooks/generic-webhook/README.md index 9efaad1dfb..38042a414d 100644 --- a/hooks/generic-webhook/README.md +++ b/hooks/generic-webhook/README.md @@ -57,6 +57,7 @@ Kubernetes: `>=v1.11.0-0` |-----|------|---------|-------------| | hook.image.repository | string | `"docker.io/securecodebox/hook-generic-webhook"` | Hook image repository | | hook.image.tag | string | defaults to the charts version | The image Tag defaults to the charts version if not defined. | +| hook.labels | object | `{}` | Add Kubernetes Labels to the hook definition | | hook.ttlSecondsAfterFinished | string | `nil` | 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/ | | webhookUrl | string | `"http://example.com"` | The URL of your WebHook endpoint | diff --git a/hooks/notification/README.md b/hooks/notification/README.md index 6ca700b61f..a9f09eaa12 100644 --- a/hooks/notification/README.md +++ b/hooks/notification/README.md @@ -346,6 +346,7 @@ To fill your template with data we provide the following objects. | hook.image.pullPolicy | string | `"IfNotPresent"` | 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 | | hook.image.repository | string | `"docker.io/securecodebox/hook-notification"` | Hook image repository | | hook.image.tag | string | defaults to the charts version | Image tag | +| hook.labels | object | `{}` | Add Kubernetes Labels to the hook definition | | hook.ttlSecondsAfterFinished | string | `nil` | 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/ | | notificationChannels[0].endPoint | string | `"SOME_ENV_KEY"` | | | notificationChannels[0].name | string | `"slack"` | | diff --git a/hooks/persistence-defectdojo/README.md b/hooks/persistence-defectdojo/README.md index d34fa3d1b1..4a41ce2832 100644 --- a/hooks/persistence-defectdojo/README.md +++ b/hooks/persistence-defectdojo/README.md @@ -230,6 +230,7 @@ spec: | hook.image.pullPolicy | string | `"IfNotPresent"` | 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 | | hook.image.repository | string | `"docker.io/securecodebox/hook-persistence-defectdojo"` | Hook image repository | | hook.image.tag | string | `nil` | Container image tag | +| hook.labels | object | `{}` | Add Kubernetes Labels to the hook definition | ## License [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) diff --git a/hooks/persistence-elastic/README.md b/hooks/persistence-elastic/README.md index c2094c39f2..908a886cd3 100644 --- a/hooks/persistence-elastic/README.md +++ b/hooks/persistence-elastic/README.md @@ -82,6 +82,7 @@ the [Luxon documentation](https://moment.github.io/luxon/docs/manual/formatting. | fullnameOverride | string | `""` | | | hook.image.repository | string | `"docker.io/securecodebox/hook-persistence-elastic"` | Hook image repository | | hook.image.tag | string | defaults to the charts version | The image Tag defaults to the charts version if not defined. | +| hook.labels | object | `{}` | Add Kubernetes Labels to the hook definition | | hook.ttlSecondsAfterFinished | string | `nil` | 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/ | | imagePullSecrets | list | `[]` | | | indexAppendNamespace | bool | `true` | Define if the name of the namespace where this hook is deployed to must be added to the index name. The namespace can be used to separate index by tenants (namespaces). | diff --git a/hooks/update-field/README.md b/hooks/update-field/README.md index 714d428014..8e82704c28 100644 --- a/hooks/update-field/README.md +++ b/hooks/update-field/README.md @@ -64,6 +64,7 @@ helm upgrade --install ufh secureCodeBox/update-field-hook --set attribute.name= | attribute.value | string | `"my-own-category"` | The value of the attribute you want to add to each finding result | | hook.image.repository | string | `"docker.io/securecodebox/hook-update-field"` | Hook image repository | | hook.image.tag | string | defaults to the charts version | The image Tag defaults to the charts version if not defined. | +| hook.labels | object | `{}` | Add Kubernetes Labels to the hook definition | | hook.ttlSecondsAfterFinished | string | `nil` | 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/ | ## License diff --git a/scanners/amass/README.md b/scanners/amass/README.md index e3775f52b5..228df7f1c7 100644 --- a/scanners/amass/README.md +++ b/scanners/amass/README.md @@ -74,6 +74,7 @@ Kubernetes: `>=v1.11.0-0` | parser.image.pullPolicy | string | `"IfNotPresent"` | 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 | | parser.image.repository | string | `"docker.io/securecodebox/parser-amass"` | Parser image repository | | parser.image.tag | string | defaults to the charts version | Parser image tag | +| parser.scopeLimiterAliases | object | `{}` | Optional finding aliases to be used in the scopeLimiter. | | parser.ttlSecondsAfterFinished | string | `nil` | seconds after which the kubernetes job for the parser will be deleted. Requires the Kubernetes TTLAfterFinished controller: https://kubernetes.io/docs/concepts/workloads/controllers/ttlafterfinished/ | | scanner.activeDeadlineSeconds | string | `nil` | There are situations where you want to fail a scan Job after some amount of time. To do so, set activeDeadlineSeconds to define an active deadline (in seconds) when considering a scan Job as failed. (see: https://kubernetes.io/docs/concepts/workloads/controllers/job/#job-termination-and-cleanup) | | scanner.backoffLimit | int | 3 | There are situations where you want to fail a scan Job after some amount of retries due to a logical error in configuration etc. To do so, set backoffLimit to specify the number of retries before considering a scan Job as failed. (see: https://kubernetes.io/docs/concepts/workloads/controllers/job/#pod-backoff-failure-policy) | diff --git a/scanners/angularjs-csti-scanner/README.md b/scanners/angularjs-csti-scanner/README.md index d0bab273c2..3b1f77e407 100644 --- a/scanners/angularjs-csti-scanner/README.md +++ b/scanners/angularjs-csti-scanner/README.md @@ -173,6 +173,7 @@ options.scope.request_methods = [ | parser.image.pullPolicy | string | `"IfNotPresent"` | 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 | | parser.image.repository | string | `"docker.io/securecodebox/parser-angularjs-csti-scanner"` | Parser image repository | | parser.image.tag | string | defaults to the charts version | Parser image tag | +| parser.scopeLimiterAliases | object | `{}` | Optional finding aliases to be used in the scopeLimiter. | | parser.ttlSecondsAfterFinished | string | `nil` | seconds after which the kubernetes job for the parser will be deleted. Requires the Kubernetes TTLAfterFinished controller: https://kubernetes.io/docs/concepts/workloads/controllers/ttlafterfinished/ | | scanner.activeDeadlineSeconds | string | `nil` | There are situations where you want to fail a scan Job after some amount of time. To do so, set activeDeadlineSeconds to define an active deadline (in seconds) when considering a scan Job as failed. (see: https://kubernetes.io/docs/concepts/workloads/controllers/job/#job-termination-and-cleanup) | | scanner.backoffLimit | int | 3 | There are situations where you want to fail a scan Job after some amount of retries due to a logical error in configuration etc. To do so, set backoffLimit to specify the number of retries before considering a scan Job as failed. (see: https://kubernetes.io/docs/concepts/workloads/controllers/job/#pod-backoff-failure-policy) | diff --git a/scanners/git-repo-scanner/README.md b/scanners/git-repo-scanner/README.md index 394ff48f31..0567d7ea41 100644 --- a/scanners/git-repo-scanner/README.md +++ b/scanners/git-repo-scanner/README.md @@ -104,6 +104,7 @@ Kubernetes: `>=v1.11.0-0` | parser.image.pullPolicy | string | `"IfNotPresent"` | 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 | | parser.image.repository | string | `"docker.io/securecodebox/parser-git-repo-scanner"` | Parser image repository | | parser.image.tag | string | defaults to the charts version | Parser image tag | +| parser.scopeLimiterAliases | object | `{}` | Optional finding aliases to be used in the scopeLimiter. | | parser.ttlSecondsAfterFinished | string | `nil` | seconds after which the kubernetes job for the parser will be deleted. Requires the Kubernetes TTLAfterFinished controller: https://kubernetes.io/docs/concepts/workloads/controllers/ttlafterfinished/ | | scanner.activeDeadlineSeconds | string | `nil` | There are situations where you want to fail a scan Job after some amount of time. To do so, set activeDeadlineSeconds to define an active deadline (in seconds) when considering a scan Job as failed. (see: https://kubernetes.io/docs/concepts/workloads/controllers/job/#job-termination-and-cleanup) | | scanner.backoffLimit | int | 3 | There are situations where you want to fail a scan Job after some amount of retries due to a logical error in configuration etc. To do so, set backoffLimit to specify the number of retries before considering a scan Job as failed. (see: https://kubernetes.io/docs/concepts/workloads/controllers/job/#pod-backoff-failure-policy) | diff --git a/scanners/gitleaks/README.md b/scanners/gitleaks/README.md index 607ad9b3d4..3b6221a60a 100644 --- a/scanners/gitleaks/README.md +++ b/scanners/gitleaks/README.md @@ -166,6 +166,7 @@ For more information on how to use cascades take a look at | parser.image.pullPolicy | string | `"IfNotPresent"` | 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 | | parser.image.repository | string | `"docker.io/securecodebox/parser-gitleaks"` | Parser image repository | | parser.image.tag | string | defaults to the charts version | Parser image tag | +| parser.scopeLimiterAliases | object | `{}` | Optional finding aliases to be used in the scopeLimiter. | | parser.ttlSecondsAfterFinished | string | `nil` | seconds after which the kubernetes job for the parser will be deleted. Requires the Kubernetes TTLAfterFinished controller: https://kubernetes.io/docs/concepts/workloads/controllers/ttlafterfinished/ | | scanner.activeDeadlineSeconds | string | `nil` | There are situations where you want to fail a scan Job after some amount of time. To do so, set activeDeadlineSeconds to define an active deadline (in seconds) when considering a scan Job as failed. (see: https://kubernetes.io/docs/concepts/workloads/controllers/job/#job-termination-and-cleanup) | | scanner.backoffLimit | int | 3 | There are situations where you want to fail a scan Job after some amount of retries due to a logical error in configuration etc. To do so, set backoffLimit to specify the number of retries before considering a scan Job as failed. (see: https://kubernetes.io/docs/concepts/workloads/controllers/job/#pod-backoff-failure-policy) | diff --git a/scanners/kube-hunter/README.md b/scanners/kube-hunter/README.md index 2a1cdba6fb..9bbf43bbe1 100644 --- a/scanners/kube-hunter/README.md +++ b/scanners/kube-hunter/README.md @@ -67,6 +67,7 @@ Kubernetes: `>=v1.11.0-0` | parser.image.pullPolicy | string | `"IfNotPresent"` | 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 | | parser.image.repository | string | `"docker.io/securecodebox/parser-kube-hunter"` | Parser image repository | | parser.image.tag | string | defaults to the charts version | Parser image tag | +| parser.scopeLimiterAliases | object | `{}` | Optional finding aliases to be used in the scopeLimiter. | | parser.ttlSecondsAfterFinished | string | `nil` | seconds after which the kubernetes job for the parser will be deleted. Requires the Kubernetes TTLAfterFinished controller: https://kubernetes.io/docs/concepts/workloads/controllers/ttlafterfinished/ | | scanner.activeDeadlineSeconds | string | `nil` | There are situations where you want to fail a scan Job after some amount of time. To do so, set activeDeadlineSeconds to define an active deadline (in seconds) when considering a scan Job as failed. (see: https://kubernetes.io/docs/concepts/workloads/controllers/job/#job-termination-and-cleanup) | | scanner.backoffLimit | int | 3 | There are situations where you want to fail a scan Job after some amount of retries due to a logical error in configuration etc. To do so, set backoffLimit to specify the number of retries before considering a scan Job as failed. (see: https://kubernetes.io/docs/concepts/workloads/controllers/job/#pod-backoff-failure-policy) | diff --git a/scanners/kubeaudit/README.md b/scanners/kubeaudit/README.md index 8f2da2bdc6..c3f6d7e7a9 100644 --- a/scanners/kubeaudit/README.md +++ b/scanners/kubeaudit/README.md @@ -71,6 +71,7 @@ Kubernetes: `>=v1.11.0-0` | parser.image.pullPolicy | string | `"IfNotPresent"` | 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 | | parser.image.repository | string | `"docker.io/securecodebox/parser-kubeaudit"` | Parser image repository | | parser.image.tag | string | defaults to the charts version | Parser image tag | +| parser.scopeLimiterAliases | object | `{}` | Optional finding aliases to be used in the scopeLimiter. | | parser.ttlSecondsAfterFinished | string | `nil` | seconds after which the kubernetes job for the parser will be deleted. Requires the Kubernetes TTLAfterFinished controller: https://kubernetes.io/docs/concepts/workloads/controllers/ttlafterfinished/ | | scanner.activeDeadlineSeconds | string | `nil` | There are situations where you want to fail a scan Job after some amount of time. To do so, set activeDeadlineSeconds to define an active deadline (in seconds) when considering a scan Job as failed. (see: https://kubernetes.io/docs/concepts/workloads/controllers/job/#job-termination-and-cleanup) | | scanner.backoffLimit | int | 3 | There are situations where you want to fail a scan Job after some amount of retries due to a logical error in configuration etc. To do so, set backoffLimit to specify the number of retries before considering a scan Job as failed. (see: https://kubernetes.io/docs/concepts/workloads/controllers/job/#pod-backoff-failure-policy) | diff --git a/scanners/ncrack/README.md b/scanners/ncrack/README.md index b7b2ea0b0a..56508890ab 100644 --- a/scanners/ncrack/README.md +++ b/scanners/ncrack/README.md @@ -215,6 +215,7 @@ helm delete ncrack | parser.image.pullPolicy | string | `"IfNotPresent"` | 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 | | parser.image.repository | string | `"docker.io/securecodebox/parser-ncrack"` | Parser image repository | | parser.image.tag | string | defaults to the charts version | Parser image tag | +| parser.scopeLimiterAliases | object | `{}` | Optional finding aliases to be used in the scopeLimiter. | | parser.ttlSecondsAfterFinished | string | `nil` | seconds after which the kubernetes job for the parser will be deleted. Requires the Kubernetes TTLAfterFinished controller: https://kubernetes.io/docs/concepts/workloads/controllers/ttlafterfinished/ | | scanner.activeDeadlineSeconds | string | `nil` | There are situations where you want to fail a scan Job after some amount of time. To do so, set activeDeadlineSeconds to define an active deadline (in seconds) when considering a scan Job as failed. (see: https://kubernetes.io/docs/concepts/workloads/controllers/job/#job-termination-and-cleanup) | | scanner.backoffLimit | int | 3 | There are situations where you want to fail a scan Job after some amount of retries due to a logical error in configuration etc. To do so, set backoffLimit to specify the number of retries before considering a scan Job as failed. (see: https://kubernetes.io/docs/concepts/workloads/controllers/job/#pod-backoff-failure-policy) | diff --git a/scanners/nikto/README.md b/scanners/nikto/README.md index 5cb3551118..daec720c08 100644 --- a/scanners/nikto/README.md +++ b/scanners/nikto/README.md @@ -86,6 +86,7 @@ Kubernetes: `>=v1.11.0-0` | parser.image.pullPolicy | string | `"IfNotPresent"` | 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 | | parser.image.repository | string | `"docker.io/securecodebox/parser-nikto"` | Parser image repository | | parser.image.tag | string | defaults to the charts version | Parser image tag | +| parser.scopeLimiterAliases | object | `{}` | Optional finding aliases to be used in the scopeLimiter. | | parser.ttlSecondsAfterFinished | string | `nil` | seconds after which the kubernetes job for the parser will be deleted. Requires the Kubernetes TTLAfterFinished controller: https://kubernetes.io/docs/concepts/workloads/controllers/ttlafterfinished/ | | scanner.activeDeadlineSeconds | string | `nil` | There are situations where you want to fail a scan Job after some amount of time. To do so, set activeDeadlineSeconds to define an active deadline (in seconds) when considering a scan Job as failed. (see: https://kubernetes.io/docs/concepts/workloads/controllers/job/#job-termination-and-cleanup) | | scanner.backoffLimit | int | 3 | There are situations where you want to fail a scan Job after some amount of retries due to a logical error in configuration etc. To do so, set backoffLimit to specify the number of retries before considering a scan Job as failed. (see: https://kubernetes.io/docs/concepts/workloads/controllers/job/#pod-backoff-failure-policy) | diff --git a/scanners/nmap/README.md b/scanners/nmap/README.md index 97a1402b90..9fa77daeba 100644 --- a/scanners/nmap/README.md +++ b/scanners/nmap/README.md @@ -137,6 +137,7 @@ spec: | parser.image.pullPolicy | string | `"IfNotPresent"` | 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 | | parser.image.repository | string | `"docker.io/securecodebox/parser-nmap"` | Parser image repository | | parser.image.tag | string | defaults to the charts version | Parser image tag | +| parser.scopeLimiterAliases | object | `{}` | Optional finding aliases to be used in the scopeLimiter. | | parser.ttlSecondsAfterFinished | string | `nil` | seconds after which the kubernetes job for the parser will be deleted. Requires the Kubernetes TTLAfterFinished controller: https://kubernetes.io/docs/concepts/workloads/controllers/ttlafterfinished/ | | scanner.activeDeadlineSeconds | string | `nil` | There are situations where you want to fail a scan Job after some amount of time. To do so, set activeDeadlineSeconds to define an active deadline (in seconds) when considering a scan Job as failed. (see: https://kubernetes.io/docs/concepts/workloads/controllers/job/#job-termination-and-cleanup) | | scanner.backoffLimit | int | 3 | There are situations where you want to fail a scan Job after some amount of retries due to a logical error in configuration etc. To do so, set backoffLimit to specify the number of retries before considering a scan Job as failed. (see: https://kubernetes.io/docs/concepts/workloads/controllers/job/#pod-backoff-failure-policy) | diff --git a/scanners/nuclei/README.md b/scanners/nuclei/README.md index 7c57180a1e..304f37fdf7 100644 --- a/scanners/nuclei/README.md +++ b/scanners/nuclei/README.md @@ -170,6 +170,7 @@ Kubernetes: `>=v1.11.0-0` | parser.image.pullPolicy | string | `"IfNotPresent"` | 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 | | parser.image.repository | string | `"docker.io/securecodebox/parser-nuclei"` | Parser image repository | | parser.image.tag | string | defaults to the charts version | Parser image tag | +| parser.scopeLimiterAliases | object | `{}` | Optional finding aliases to be used in the scopeLimiter. | | parser.ttlSecondsAfterFinished | string | `nil` | seconds after which the kubernetes job for the parser will be deleted. Requires the Kubernetes TTLAfterFinished controller: https://kubernetes.io/docs/concepts/workloads/controllers/ttlafterfinished/ | | scanner.activeDeadlineSeconds | string | `nil` | There are situations where you want to fail a scan Job after some amount of time. To do so, set activeDeadlineSeconds to define an active deadline (in seconds) when considering a scan Job as failed. (see: https://kubernetes.io/docs/concepts/workloads/controllers/job/#job-termination-and-cleanup) | | scanner.backoffLimit | int | 3 | There are situations where you want to fail a scan Job after some amount of retries due to a logical error in configuration etc. To do so, set backoffLimit to specify the number of retries before considering a scan Job as failed. (see: https://kubernetes.io/docs/concepts/workloads/controllers/job/#pod-backoff-failure-policy) | diff --git a/scanners/screenshooter/README.md b/scanners/screenshooter/README.md index ff5e4ed986..5479278c0b 100644 --- a/scanners/screenshooter/README.md +++ b/scanners/screenshooter/README.md @@ -66,6 +66,7 @@ Kubernetes: `>=v1.11.0-0` | parser.image.pullPolicy | string | `"IfNotPresent"` | 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 | | parser.image.repository | string | `"docker.io/securecodebox/parser-screenshooter"` | Parser image repository | | parser.image.tag | string | defaults to the charts version | Parser image tag | +| parser.scopeLimiterAliases | object | `{}` | Optional finding aliases to be used in the scopeLimiter. | | parser.ttlSecondsAfterFinished | string | `nil` | seconds after which the kubernetes job for the parser will be deleted. Requires the Kubernetes TTLAfterFinished controller: https://kubernetes.io/docs/concepts/workloads/controllers/ttlafterfinished/ | | scanner.activeDeadlineSeconds | string | `nil` | There are situations where you want to fail a scan Job after some amount of time. To do so, set activeDeadlineSeconds to define an active deadline (in seconds) when considering a scan Job as failed. (see: https://kubernetes.io/docs/concepts/workloads/controllers/job/#job-termination-and-cleanup) | | scanner.backoffLimit | int | 3 | There are situations where you want to fail a scan Job after some amount of retries due to a logical error in configuration etc. To do so, set backoffLimit to specify the number of retries before considering a scan Job as failed. (see: https://kubernetes.io/docs/concepts/workloads/controllers/job/#pod-backoff-failure-policy) | diff --git a/scanners/semgrep/README.md b/scanners/semgrep/README.md index fa4d46e74c..0c35e7eb90 100644 --- a/scanners/semgrep/README.md +++ b/scanners/semgrep/README.md @@ -178,6 +178,7 @@ Kubernetes: `>=v1.11.0-0` | parser.image.pullPolicy | string | `"IfNotPresent"` | | | parser.image.repository | string | `"securecodebox/parser-semgrep"` | | | parser.image.tag | string | `nil` | | +| parser.scopeLimiterAliases | object | `{}` | Optional finding aliases to be used in the scopeLimiter. | | scanner.backoffLimit | int | `3` | | | scanner.env | list | `[]` | | | scanner.extraContainers | list | `[]` | | diff --git a/scanners/ssh-scan/README.md b/scanners/ssh-scan/README.md index b12dd7af41..2503fcfa18 100644 --- a/scanners/ssh-scan/README.md +++ b/scanners/ssh-scan/README.md @@ -100,6 +100,7 @@ Kubernetes: `>=v1.11.0-0` | parser.image.pullPolicy | string | `"IfNotPresent"` | 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 | | parser.image.repository | string | `"docker.io/securecodebox/parser-ssh-scan"` | Parser image repository | | parser.image.tag | string | defaults to the charts version | Parser image tag | +| parser.scopeLimiterAliases | object | `{}` | Optional finding aliases to be used in the scopeLimiter. | | parser.ttlSecondsAfterFinished | string | `nil` | seconds after which the kubernetes job for the parser will be deleted. Requires the Kubernetes TTLAfterFinished controller: https://kubernetes.io/docs/concepts/workloads/controllers/ttlafterfinished/ | | scanner.activeDeadlineSeconds | string | `nil` | There are situations where you want to fail a scan Job after some amount of time. To do so, set activeDeadlineSeconds to define an active deadline (in seconds) when considering a scan Job as failed. (see: https://kubernetes.io/docs/concepts/workloads/controllers/job/#job-termination-and-cleanup) | | scanner.backoffLimit | int | 3 | There are situations where you want to fail a scan Job after some amount of retries due to a logical error in configuration etc. To do so, set backoffLimit to specify the number of retries before considering a scan Job as failed. (see: https://kubernetes.io/docs/concepts/workloads/controllers/job/#pod-backoff-failure-policy) | diff --git a/scanners/sslyze/README.md b/scanners/sslyze/README.md index d3d26b415c..99cc3a960a 100644 --- a/scanners/sslyze/README.md +++ b/scanners/sslyze/README.md @@ -167,6 +167,7 @@ Kubernetes: `>=v1.11.0-0` | parser.image.pullPolicy | string | `"IfNotPresent"` | 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 | | parser.image.repository | string | `"docker.io/securecodebox/parser-sslyze"` | Parser image repository | | parser.image.tag | string | defaults to the charts version | Parser image tag | +| parser.scopeLimiterAliases | object | `{}` | Optional finding aliases to be used in the scopeLimiter. | | parser.ttlSecondsAfterFinished | string | `nil` | seconds after which the kubernetes job for the parser will be deleted. Requires the Kubernetes TTLAfterFinished controller: https://kubernetes.io/docs/concepts/workloads/controllers/ttlafterfinished/ | | scanner.activeDeadlineSeconds | string | `nil` | There are situations where you want to fail a scan Job after some amount of time. To do so, set activeDeadlineSeconds to define an active deadline (in seconds) when considering a scan Job as failed. (see: https://kubernetes.io/docs/concepts/workloads/controllers/job/#job-termination-and-cleanup) | | scanner.backoffLimit | int | 3 | There are situations where you want to fail a scan Job after some amount of retries due to a logical error in configuration etc. To do so, set backoffLimit to specify the number of retries before considering a scan Job as failed. (see: https://kubernetes.io/docs/concepts/workloads/controllers/job/#pod-backoff-failure-policy) | diff --git a/scanners/test-scan/README.md b/scanners/test-scan/README.md index 6d289e3f84..77117741a9 100644 --- a/scanners/test-scan/README.md +++ b/scanners/test-scan/README.md @@ -60,6 +60,7 @@ Kubernetes: `>=v1.11.0-0` | parser.image.pullPolicy | string | `"IfNotPresent"` | 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 | | parser.image.repository | string | `"docker.io/securecodebox/parser-test-scan"` | Parser image repository | | parser.image.tag | string | defaults to the charts version | Parser image tag | +| parser.scopeLimiterAliases | object | `{}` | Optional finding aliases to be used in the scopeLimiter. | | parser.ttlSecondsAfterFinished | string | `nil` | seconds after which the kubernetes job for the parser will be deleted. Requires the Kubernetes TTLAfterFinished controller: https://kubernetes.io/docs/concepts/workloads/controllers/ttlafterfinished/ | | scanner.activeDeadlineSeconds | string | `nil` | There are situations where you want to fail a scan Job after some amount of time. To do so, set activeDeadlineSeconds to define an active deadline (in seconds) when considering a scan Job as failed. (see: https://kubernetes.io/docs/concepts/workloads/controllers/job/#job-termination-and-cleanup) | | scanner.backoffLimit | int | 3 | There are situations where you want to fail a scan Job after some amount of retries due to a logical error in configuration etc. To do so, set backoffLimit to specify the number of retries before considering a scan Job as failed. (see: https://kubernetes.io/docs/concepts/workloads/controllers/job/#pod-backoff-failure-policy) | diff --git a/scanners/trivy/README.md b/scanners/trivy/README.md index 87336643de..5678708b48 100644 --- a/scanners/trivy/README.md +++ b/scanners/trivy/README.md @@ -193,6 +193,7 @@ Kubernetes: `>=v1.11.0-0` | parser.image.pullPolicy | string | `"IfNotPresent"` | 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 | | parser.image.repository | string | `"docker.io/securecodebox/parser-trivy"` | Parser image repository | | parser.image.tag | string | defaults to the charts version | Parser image tag | +| parser.scopeLimiterAliases | object | `{}` | Optional finding aliases to be used in the scopeLimiter. | | parser.ttlSecondsAfterFinished | string | `nil` | seconds after which the kubernetes job for the parser will be deleted. Requires the Kubernetes TTLAfterFinished controller: https://kubernetes.io/docs/concepts/workloads/controllers/ttlafterfinished/ | | scanner.activeDeadlineSeconds | string | `nil` | There are situations where you want to fail a scan Job after some amount of time. To do so, set activeDeadlineSeconds to define an active deadline (in seconds) when considering a scan Job as failed. (see: https://kubernetes.io/docs/concepts/workloads/controllers/job/#job-termination-and-cleanup) | | scanner.backoffLimit | int | 3 | There are situations where you want to fail a scan Job after some amount of retries due to a logical error in configuration etc. To do so, set backoffLimit to specify the number of retries before considering a scan Job as failed. (see: https://kubernetes.io/docs/concepts/workloads/controllers/job/#pod-backoff-failure-policy) | diff --git a/scanners/wpscan/README.md b/scanners/wpscan/README.md index bc33752e77..a1aa90f9f4 100644 --- a/scanners/wpscan/README.md +++ b/scanners/wpscan/README.md @@ -106,6 +106,7 @@ Kubernetes: `>=v1.11.0-0` | parser.image.pullPolicy | string | `"IfNotPresent"` | 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 | | parser.image.repository | string | `"docker.io/securecodebox/parser-wpscan"` | Parser image repository | | parser.image.tag | string | defaults to the charts version | Parser image tag | +| parser.scopeLimiterAliases | object | `{}` | Optional finding aliases to be used in the scopeLimiter. | | parser.ttlSecondsAfterFinished | string | `nil` | seconds after which the kubernetes job for the parser will be deleted. Requires the Kubernetes TTLAfterFinished controller: https://kubernetes.io/docs/concepts/workloads/controllers/ttlafterfinished/ | | scanner.activeDeadlineSeconds | string | `nil` | There are situations where you want to fail a scan Job after some amount of time. To do so, set activeDeadlineSeconds to define an active deadline (in seconds) when considering a scan Job as failed. (see: https://kubernetes.io/docs/concepts/workloads/controllers/job/#job-termination-and-cleanup) | | scanner.backoffLimit | int | 3 | There are situations where you want to fail a scan Job after some amount of retries due to a logical error in configuration etc. To do so, set backoffLimit to specify the number of retries before considering a scan Job as failed. (see: https://kubernetes.io/docs/concepts/workloads/controllers/job/#pod-backoff-failure-policy) | diff --git a/scanners/zap-advanced/README.md b/scanners/zap-advanced/README.md index ab7210807f..5d0702ef92 100644 --- a/scanners/zap-advanced/README.md +++ b/scanners/zap-advanced/README.md @@ -484,6 +484,7 @@ zapConfiguration: | parser.image.pullPolicy | string | `"IfNotPresent"` | 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 | | parser.image.repository | string | `"docker.io/securecodebox/parser-zap"` | Parser image repository | | parser.image.tag | string | defaults to the charts version | Parser image tag | +| parser.scopeLimiterAliases | object | `{}` | Optional finding aliases to be used in the scopeLimiter. | | parser.ttlSecondsAfterFinished | string | `nil` | seconds after which the kubernetes job for the parser will be deleted. Requires the Kubernetes TTLAfterFinished controller: https://kubernetes.io/docs/concepts/workloads/controllers/ttlafterfinished/ | | scanner.activeDeadlineSeconds | string | `nil` | There are situations where you want to fail a scan Job after some amount of time. To do so, set activeDeadlineSeconds to define an active deadline (in seconds) when considering a scan Job as failed. (see: https://kubernetes.io/docs/concepts/workloads/controllers/job/#job-termination-and-cleanup) | | scanner.backoffLimit | int | 3 | There are situations where you want to fail a scan Job after some amount of retries due to a logical error in configuration etc. To do so, set backoffLimit to specify the number of retries before considering a scan Job as failed. (see: https://kubernetes.io/docs/concepts/workloads/controllers/job/#pod-backoff-failure-policy) | diff --git a/scanners/zap/README.md b/scanners/zap/README.md index a537a52cd0..44f7ca7643 100644 --- a/scanners/zap/README.md +++ b/scanners/zap/README.md @@ -106,6 +106,7 @@ That's why we introduced this `zap-advanced` scanner chart, which introduces ext | parser.image.pullPolicy | string | `"IfNotPresent"` | 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 | | parser.image.repository | string | `"docker.io/securecodebox/parser-zap"` | Parser image repository | | parser.image.tag | string | defaults to the charts version | Parser image tag | +| parser.scopeLimiterAliases | object | `{}` | Optional finding aliases to be used in the scopeLimiter. | | parser.ttlSecondsAfterFinished | string | `nil` | seconds after which the kubernetes job for the parser will be deleted. Requires the Kubernetes TTLAfterFinished controller: https://kubernetes.io/docs/concepts/workloads/controllers/ttlafterfinished/ | | scanner.activeDeadlineSeconds | string | `nil` | There are situations where you want to fail a scan Job after some amount of time. To do so, set activeDeadlineSeconds to define an active deadline (in seconds) when considering a scan Job as failed. (see: https://kubernetes.io/docs/concepts/workloads/controllers/job/#job-termination-and-cleanup) | | scanner.backoffLimit | int | 3 | There are situations where you want to fail a scan Job after some amount of retries due to a logical error in configuration etc. To do so, set backoffLimit to specify the number of retries before considering a scan Job as failed. (see: https://kubernetes.io/docs/concepts/workloads/controllers/job/#pod-backoff-failure-policy) | From a44901679bc537b6ed273d725236b3903b75116c Mon Sep 17 00:00:00 2001 From: Max Maass Date: Thu, 18 Nov 2021 11:53:22 +0100 Subject: [PATCH 14/54] Add more test cases While reviewing the functionality, I wrote some more test cases to understand how some parts of it work. Might as well include them upstream :). Signed-off-by: Max Maass --- .../hook/reverse-matches.test.js | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/hooks/cascading-scans/hook/reverse-matches.test.js b/hooks/cascading-scans/hook/reverse-matches.test.js index 492cf40720..bcaf5ea158 100644 --- a/hooks/cascading-scans/hook/reverse-matches.test.js +++ b/hooks/cascading-scans/hook/reverse-matches.test.js @@ -304,6 +304,99 @@ test("Matches subdomainOf if is subdomain", () => { expect(cascadedScans).toBe(true); }); +test("Matches subdomainOf if is the domain itself", () => { + const annotations = { + "scope.cascading.securecodebox.io/domain": "example.com", + } + const scopeLimiter = { + validOnMissingRender: false, + allOf: [ + { + key: "scope.cascading.securecodebox.io/domain", + operator: "SubdomainOf", + values: ["{{attributes.hostname}}"], + } + ] + } + + const finding = { + attributes: { + hostname: "example.com", + } + }; + + const cascadedScans = isReverseMatch( + scopeLimiter, + annotations, + finding, + {}, + ); + + expect(cascadedScans).toBe(true); +}); + +test("Matches subdomainOf if providing a sub-sub domain of a sub-domain", () => { + const annotations = { + "scope.cascading.securecodebox.io/domain": "www.example.com", + } + const scopeLimiter = { + validOnMissingRender: false, + allOf: [ + { + key: "scope.cascading.securecodebox.io/domain", + operator: "SubdomainOf", + values: ["{{attributes.hostname}}"], + } + ] + } + + const finding = { + attributes: { + hostname: "test.www.example.com", + } + }; + + const cascadedScans = isReverseMatch( + scopeLimiter, + annotations, + finding, + {}, + ); + + expect(cascadedScans).toBe(true); +}); + +test("Does not match subdomainOf if providing a sub domain of a different sub-domain", () => { + const annotations = { + "scope.cascading.securecodebox.io/domain": "www.example.com", + } + const scopeLimiter = { + validOnMissingRender: false, + allOf: [ + { + key: "scope.cascading.securecodebox.io/domain", + operator: "SubdomainOf", + values: ["{{attributes.hostname}}"], + } + ] + } + + const finding = { + attributes: { + hostname: "test.example.com", + } + }; + + const cascadedScans = isReverseMatch( + scopeLimiter, + annotations, + finding, + {}, + ); + + expect(cascadedScans).toBe(false); +}); + test("Does not match subdomainOf if is not subdomain", () => { const annotations = { "scope.cascading.securecodebox.io/domain": "example.com", From 97c3df1e51bd6195ab11a829e1265dfd4591d1a8 Mon Sep 17 00:00:00 2001 From: Max Maass Date: Thu, 18 Nov 2021 12:36:24 +0100 Subject: [PATCH 15/54] Add a few more test cases Signed-off-by: Max Maass --- .../hook/reverse-matches.test.js | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/hooks/cascading-scans/hook/reverse-matches.test.js b/hooks/cascading-scans/hook/reverse-matches.test.js index bcaf5ea158..4de708771b 100644 --- a/hooks/cascading-scans/hook/reverse-matches.test.js +++ b/hooks/cascading-scans/hook/reverse-matches.test.js @@ -366,6 +366,68 @@ test("Matches subdomainOf if providing a sub-sub domain of a sub-domain", () => expect(cascadedScans).toBe(true); }); +test("Matches subdomainOf if providing a deep subdomain of a deep subdomain", () => { + const annotations = { + "scope.cascading.securecodebox.io/domain": "a.b.c.d.e.example.com", + } + const scopeLimiter = { + validOnMissingRender: false, + allOf: [ + { + key: "scope.cascading.securecodebox.io/domain", + operator: "SubdomainOf", + values: ["{{attributes.hostname}}"], + } + ] + } + + const finding = { + attributes: { + hostname: "z.a.b.c.d.e.example.com", + } + }; + + const cascadedScans = isReverseMatch( + scopeLimiter, + annotations, + finding, + {}, + ); + + expect(cascadedScans).toBe(true); +}); + +test("Does not match subdomainOf even if differences are deep in the subdomain tree", () => { + const annotations = { + "scope.cascading.securecodebox.io/domain": "a.b.c.d.e.example.com", + } + const scopeLimiter = { + validOnMissingRender: false, + allOf: [ + { + key: "scope.cascading.securecodebox.io/domain", + operator: "SubdomainOf", + values: ["{{attributes.hostname}}"], + } + ] + } + + const finding = { + attributes: { + hostname: "z.b.c.d.e.example.com", + } + }; + + const cascadedScans = isReverseMatch( + scopeLimiter, + annotations, + finding, + {}, + ); + + expect(cascadedScans).toBe(false); +}); + test("Does not match subdomainOf if providing a sub domain of a different sub-domain", () => { const annotations = { "scope.cascading.securecodebox.io/domain": "www.example.com", From 7d5581b1f73ddf51fd30680eb4edc3ef2449e29d Mon Sep 17 00:00:00 2001 From: Jop Zitman Date: Thu, 18 Nov 2021 19:04:10 +0100 Subject: [PATCH 16/54] Add `scopeLimiterAliases` to new scanner template Signed-off-by: Jop Zitman --- .../new-scanner/templates/new-scanner-parse-definition.yaml | 2 ++ .templates/new-scanner/values.yaml | 3 +++ 2 files changed, 5 insertions(+) diff --git a/.templates/new-scanner/templates/new-scanner-parse-definition.yaml b/.templates/new-scanner/templates/new-scanner-parse-definition.yaml index b2e8d0a628..2e9a3dc630 100644 --- a/.templates/new-scanner/templates/new-scanner-parse-definition.yaml +++ b/.templates/new-scanner/templates/new-scanner-parse-definition.yaml @@ -12,3 +12,5 @@ spec: ttlSecondsAfterFinished: {{ .Values.parser.ttlSecondsAfterFinished }} env: {{- toYaml .Values.parser.env | nindent 4 }} + scopeLimiterAliases: + {{- toYaml .Values.parser.scopeLimiterAliases | nindent 4 }} diff --git a/.templates/new-scanner/values.yaml b/.templates/new-scanner/values.yaml index be49b8636f..ef7e16fd64 100644 --- a/.templates/new-scanner/values.yaml +++ b/.templates/new-scanner/values.yaml @@ -17,6 +17,9 @@ parser: # parser.env -- Optional environment variables mapped into each parseJob (see: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) env: [] + # parser.scopeLimiterAliases -- Optional finding aliases to be used in the scopeLimiter. + scopeLimiterAliases: {} + scanner: image: # scanner.image.repository -- Container Image to run the scan From a158fb18379bc40c225ff5884d3f75c94140fadf Mon Sep 17 00:00:00 2001 From: Jop Zitman Date: Thu, 18 Nov 2021 19:12:02 +0100 Subject: [PATCH 17/54] Rename `reverse-matches.ts` to `scope-limiter.ts` and `isReverseMatch` to `isInScope` Signed-off-by: Jop Zitman --- hooks/cascading-scans/hook/Dockerfile | 4 +-- hooks/cascading-scans/hook/hook.test.js | 2 +- hooks/cascading-scans/hook/hook.ts | 8 ++--- hooks/cascading-scans/hook/package.json | 2 +- hooks/cascading-scans/hook/scan-helpers.ts | 2 +- ...-matches.test.js => scope-limiter.test.js} | 32 +++++++++---------- .../{reverse-matches.ts => scope-limiter.ts} | 2 +- 7 files changed, 26 insertions(+), 26 deletions(-) rename hooks/cascading-scans/hook/{reverse-matches.test.js => scope-limiter.test.js} (93%) rename hooks/cascading-scans/hook/{reverse-matches.ts => scope-limiter.ts} (99%) diff --git a/hooks/cascading-scans/hook/Dockerfile b/hooks/cascading-scans/hook/Dockerfile index 0791c00b15..1552e30e31 100644 --- a/hooks/cascading-scans/hook/Dockerfile +++ b/hooks/cascading-scans/hook/Dockerfile @@ -15,10 +15,10 @@ RUN mkdir -p /home/app WORKDIR /home/app COPY package.json package-lock.json ./ RUN npm ci -COPY hook.ts scan-helpers.ts reverse-matches.ts kubernetes-label-selector.ts ./ +COPY hook.ts scan-helpers.ts scope-limiter.ts kubernetes-label-selector.ts ./ RUN npm run build FROM ${namespace:-securecodebox}/hook-sdk-nodejs:${baseImageTag:-latest} WORKDIR /home/app/hook-wrapper/hook/ COPY --from=install --chown=app:app /home/app/node_modules/ ./node_modules/ -COPY --from=build --chown=app:app /home/app/hook.js /home/app/hook.js.map /home/app/scan-helpers.js /home/app/scan-helpers.js.map /home/app/reverse-matches.js /home/app/reverse-matches.js.map /home/app/kubernetes-label-selector.js /home/app/kubernetes-label-selector.js.map ./ +COPY --from=build --chown=app:app /home/app/hook.js /home/app/hook.js.map /home/app/scan-helpers.js /home/app/scan-helpers.js.map /home/app/scope-limiter.js /home/app/scope-limiter.js.map /home/app/kubernetes-label-selector.js /home/app/kubernetes-label-selector.js.map ./ diff --git a/hooks/cascading-scans/hook/hook.test.js b/hooks/cascading-scans/hook/hook.test.js index f7253bf24f..d45edadb9d 100644 --- a/hooks/cascading-scans/hook/hook.test.js +++ b/hooks/cascading-scans/hook/hook.test.js @@ -6,7 +6,7 @@ const { getCascadingScans } = require("./hook"); const {LabelSelectorRequirementOperator} = require("./kubernetes-label-selector"); const { ScopeLimiterRequirementOperator, -} = require("./reverse-matches"); +} = require("./scope-limiter"); let parentScan = undefined; let sslyzeCascadingRules = undefined; diff --git a/hooks/cascading-scans/hook/hook.ts b/hooks/cascading-scans/hook/hook.ts index e15d1a7d3d..56eb85b6d5 100644 --- a/hooks/cascading-scans/hook/hook.ts +++ b/hooks/cascading-scans/hook/hook.ts @@ -22,9 +22,9 @@ import { mergeInheritedSelector, } from "./scan-helpers"; import { - isReverseMatch, + isInScope, scopeDomain, -} from "./reverse-matches"; +} from "./scope-limiter"; interface HandleArgs { scan: Scan; @@ -115,14 +115,14 @@ function getScansMatchingRule( const cascadingScans: Array = []; for (const finding of findings) { // Check if the scan matches for the current finding - const reverseMatches = isReverseMatch( + const inScope = isInScope( parentScan.spec.cascades.scopeLimiter, parentScan.metadata.annotations, finding, parseDefinition.spec.scopeLimiterAliases, ); - if (!reverseMatches) { + if (!inScope) { console.log(`Cascading Rule ${cascadingRule.metadata.name} not triggered as scope limiter did not pass`); console.log(`Scan annotations ${JSON.stringify(parentScan.metadata.annotations)}`); console.log(`Scope limiter ${JSON.stringify(parentScan.spec.cascades.scopeLimiter)}`); diff --git a/hooks/cascading-scans/hook/package.json b/hooks/cascading-scans/hook/package.json index c185ecd918..0561bd68e4 100644 --- a/hooks/cascading-scans/hook/package.json +++ b/hooks/cascading-scans/hook/package.json @@ -9,7 +9,7 @@ }, "main": "hook.js", "scripts": { - "build": "npx tsc hook.ts reverse-matches.ts --sourceMap --esModuleInterop", + "build": "npx tsc hook.ts scope-limiter.ts --sourceMap --esModuleInterop", "test": "jest . --verbose false" }, "keywords": [ diff --git a/hooks/cascading-scans/hook/scan-helpers.ts b/hooks/cascading-scans/hook/scan-helpers.ts index d4650ad48b..19b07532b4 100644 --- a/hooks/cascading-scans/hook/scan-helpers.ts +++ b/hooks/cascading-scans/hook/scan-helpers.ts @@ -10,7 +10,7 @@ import { } from "./kubernetes-label-selector"; import {isEqual} from "lodash"; import {getScanChain} from "./hook"; -import {ScopeLimiterRequirement} from "./reverse-matches"; +import {ScopeLimiterRequirement} from "./scope-limiter"; // configure k8s client const kc = new k8s.KubeConfig(); diff --git a/hooks/cascading-scans/hook/reverse-matches.test.js b/hooks/cascading-scans/hook/scope-limiter.test.js similarity index 93% rename from hooks/cascading-scans/hook/reverse-matches.test.js rename to hooks/cascading-scans/hook/scope-limiter.test.js index 4de708771b..8d1632d76c 100644 --- a/hooks/cascading-scans/hook/reverse-matches.test.js +++ b/hooks/cascading-scans/hook/scope-limiter.test.js @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -const { isReverseMatch } = require("./reverse-matches"); +const { isInScope } = require("./scope-limiter"); test("Should error if selecting an invalid key", () => { const scopeLimiter = { @@ -21,7 +21,7 @@ test("Should error if selecting an invalid key", () => { } }; - const cascadedScans = () => isReverseMatch( + const cascadedScans = () => isInScope( scopeLimiter, {}, finding, @@ -51,7 +51,7 @@ test("Matches using templates populated with finding", () => { } }; - const cascadedScans = isReverseMatch( + const cascadedScans = isInScope( scopeLimiter, annotations, finding, @@ -81,7 +81,7 @@ test("Does not match using if selector does not match", () => { } }; - const cascadedScans = isReverseMatch( + const cascadedScans = isInScope( scopeLimiter, annotations, finding, @@ -118,7 +118,7 @@ test("Does not match if one of selector types does not match", () => { } }; - const cascadedScans = isReverseMatch( + const cascadedScans = isInScope( scopeLimiter, annotations, finding, @@ -148,7 +148,7 @@ test("Matches InCIDR if attributes.ip in subnet", () => { } }; - const cascadedScans = isReverseMatch( + const cascadedScans = isInScope( scopeLimiter, annotations, finding, @@ -178,7 +178,7 @@ test("Does not match InCIDR if attributes.ip not in subnet", () => { } }; - const cascadedScans = isReverseMatch( + const cascadedScans = isInScope( scopeLimiter, annotations, finding, @@ -213,7 +213,7 @@ test("Matches using templates populated with finding and a mapped selector", () "hostname": "{{attributes.hostname}}", } - const cascadedScans = isReverseMatch( + const cascadedScans = isInScope( scopeLimiter, annotations, finding, @@ -238,7 +238,7 @@ test("Matches if mapping is not available: validOnMissingRender true", () => { ] } - const cascadedScans = isReverseMatch( + const cascadedScans = isInScope( scopeLimiter, annotations, {}, @@ -263,7 +263,7 @@ test("Does not match if mapping is not available: validOnMissingRender false", ( ] } - const cascadedScans = isReverseMatch( + const cascadedScans = isInScope( scopeLimiter, annotations, {}, @@ -294,7 +294,7 @@ test("Matches subdomainOf if is subdomain", () => { } }; - const cascadedScans = isReverseMatch( + const cascadedScans = isInScope( scopeLimiter, annotations, finding, @@ -325,7 +325,7 @@ test("Matches subdomainOf if is the domain itself", () => { } }; - const cascadedScans = isReverseMatch( + const cascadedScans = isInScope( scopeLimiter, annotations, finding, @@ -356,7 +356,7 @@ test("Matches subdomainOf if providing a sub-sub domain of a sub-domain", () => } }; - const cascadedScans = isReverseMatch( + const cascadedScans = isInScope( scopeLimiter, annotations, finding, @@ -449,7 +449,7 @@ test("Does not match subdomainOf if providing a sub domain of a different sub-do } }; - const cascadedScans = isReverseMatch( + const cascadedScans = isInScope( scopeLimiter, annotations, finding, @@ -480,7 +480,7 @@ test("Does not match subdomainOf if is not subdomain", () => { } }; - const cascadedScans = isReverseMatch( + const cascadedScans = isInScope( scopeLimiter, annotations, finding, @@ -508,7 +508,7 @@ test("Throws errors when missing fields", () => { } }; - const cascadedScans = () => isReverseMatch( + const cascadedScans = () => isInScope( scopeLimiter, {}, finding, diff --git a/hooks/cascading-scans/hook/reverse-matches.ts b/hooks/cascading-scans/hook/scope-limiter.ts similarity index 99% rename from hooks/cascading-scans/hook/reverse-matches.ts rename to hooks/cascading-scans/hook/scope-limiter.ts index 3cc4e6bc07..f350496d50 100644 --- a/hooks/cascading-scans/hook/reverse-matches.ts +++ b/hooks/cascading-scans/hook/scope-limiter.ts @@ -39,7 +39,7 @@ export interface ScopeLimiterRequirement { export const scopeDomain = "scope.cascading.securecodebox.io/" -export function isReverseMatch( +export function isInScope( scopeLimiter: ScopeLimiter, scanAnnotations: V1ObjectMeta['annotations'], finding: Finding, From 548ba43ba4987bba27b02b70ce37b9675321f307 Mon Sep 17 00:00:00 2001 From: Jop Zitman Date: Thu, 18 Nov 2021 19:14:44 +0100 Subject: [PATCH 18/54] Error operator name instead of operator function Signed-off-by: Jop Zitman --- hooks/cascading-scans/hook/scope-limiter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hooks/cascading-scans/hook/scope-limiter.ts b/hooks/cascading-scans/hook/scope-limiter.ts index f350496d50..d9c9dd7fb0 100644 --- a/hooks/cascading-scans/hook/scope-limiter.ts +++ b/hooks/cascading-scans/hook/scope-limiter.ts @@ -54,7 +54,7 @@ export function isInScope( const { operator: operatorFunction, validator: validatorFunction } = operatorFunctions[operator]; if (operatorFunction === undefined) { - throw new Error(`Unknown operator '${operatorFunction}'`); + throw new Error(`Unknown operator '${operator}'`); } const value = scanAnnotations[key]; const renders = values.map(templateValue); From cccd6220360ac54a9fc6b3ceee071a822038e413 Mon Sep 17 00:00:00 2001 From: Jop Zitman Date: Thu, 18 Nov 2021 19:24:00 +0100 Subject: [PATCH 19/54] Rename lhs and rhs to their semantic names Signed-off-by: Jop Zitman --- hooks/cascading-scans/hook/scope-limiter.ts | 61 +++++++++++---------- 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/hooks/cascading-scans/hook/scope-limiter.ts b/hooks/cascading-scans/hook/scope-limiter.ts index d9c9dd7fb0..d399a77a20 100644 --- a/hooks/cascading-scans/hook/scope-limiter.ts +++ b/hooks/cascading-scans/hook/scope-limiter.ts @@ -62,7 +62,10 @@ export function isInScope( return scopeLimiter.validOnMissingRender; } - const props = {lhs: value, rhs: renders.map(render => render[0])}; + const props: Operands = { + scopeAnnotationValue: value, + findingValues: renders.map(render => render[0]) + }; try { validatorFunction(props); @@ -95,8 +98,8 @@ export function isInScope( } interface Operands { - lhs: string, - rhs: string[], + scopeAnnotationValue: string, + findingValues: string[], } interface OperatorFunctions { @@ -149,53 +152,53 @@ const operatorFunctions: { [key in ScopeLimiterRequirementOperator]: OperatorFun }, } -function validate({lhs, rhs}: Operands, lhsUndefinedAllowed, rhsUndefinedAllowed) { - if (!lhsUndefinedAllowed && lhs === undefined) { - throw new Error(`annotation may not be undefined`) +function validate({scopeAnnotationValue, findingValues}: Operands, scopeAnnotationValueUndefinedAllowed, findingValuesUndefinedAllowed) { + if (!scopeAnnotationValueUndefinedAllowed && scopeAnnotationValue === undefined) { + throw new Error(`the referenced annotation may not be undefined`) } - if (!rhsUndefinedAllowed && rhs === undefined) { - throw new Error(`values may not be undefined`) + if (!findingValuesUndefinedAllowed && findingValues === undefined) { + throw new Error(`the values field may not be undefined`) } } -function operatorIn({lhs, rhs}: Operands): boolean { - return rhs.includes(lhs); +function operatorIn({scopeAnnotationValue, findingValues}: Operands): boolean { + return findingValues.includes(scopeAnnotationValue); } -function operatorExists({lhs, rhs}: Operands): boolean { - return lhs !== undefined; +function operatorExists({scopeAnnotationValue, findingValues}: Operands): boolean { + return scopeAnnotationValue !== undefined; } -function operatorContains({lhs, rhs}: Operands): boolean { - const valueArray = lhs.split(","); - return rhs.every(value => valueArray.includes(value)); +function operatorContains({scopeAnnotationValue, findingValues}: Operands): boolean { + const scopeAnnotationValues = scopeAnnotationValue.split(","); + return findingValues.every(findingValue => scopeAnnotationValues.includes(findingValue)); } -function operatorInCIDR({lhs, rhs}: Operands): boolean { - const subnet = new Address4(lhs); - return rhs.every(value => new Address4(value).isInSubnet(subnet)); +function operatorInCIDR({scopeAnnotationValue, findingValues}: Operands): boolean { + const scopeAnnotationSubnet = new Address4(scopeAnnotationValue); + return findingValues.every(findingValue => new Address4(findingValue).isInSubnet(scopeAnnotationSubnet)); } -function operatorSubdomainOf({lhs, rhs}: Operands): boolean { - const lhsResult = parseDomain(fromUrl(lhs)); - if (lhsResult.type == ParseResultType.Listed) { - return rhs.every(value => { - const rhsResult = parseDomain(fromUrl(value)); - if (rhsResult.type == ParseResultType.Listed) { +function operatorSubdomainOf({scopeAnnotationValue, findingValues}: Operands): boolean { + const scopeAnnotationDomain = parseDomain(fromUrl(scopeAnnotationValue)); + if (scopeAnnotationDomain.type == ParseResultType.Listed) { + return findingValues.every(findingValue => { + const findingDomain = parseDomain(fromUrl(findingValue)); + if (findingDomain.type == ParseResultType.Listed) { // Equal length domains can pass as subdomain of - if (lhsResult.labels.length > rhsResult.labels.length) { + if (scopeAnnotationDomain.labels.length > findingDomain.labels.length) { return false; } // Check if last part of domain is equal return isEqual( - lhsResult.labels, - takeRight(rhsResult.labels, lhsResult.labels.length) + scopeAnnotationDomain.labels, + takeRight(findingDomain.labels, scopeAnnotationDomain.labels.length) ); } - console.log(`${rhs} is an invalid domain name`) + console.log(`${findingValue} is an invalid domain name`) return false; }) } else { - throw new Error(`${lhs} is an invalid domain name`); + throw new Error(`${scopeAnnotationValue} is an invalid domain name`); } } From 3f1dbd5f72085f5485652f4b65cdf60160cb7aca Mon Sep 17 00:00:00 2001 From: Jop Zitman Date: Fri, 19 Nov 2021 11:02:45 +0100 Subject: [PATCH 20/54] Get rid of redundant operators Signed-off-by: Jop Zitman --- hooks/cascading-scans/hook/scope-limiter.ts | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/hooks/cascading-scans/hook/scope-limiter.ts b/hooks/cascading-scans/hook/scope-limiter.ts index d399a77a20..7682eda9e4 100644 --- a/hooks/cascading-scans/hook/scope-limiter.ts +++ b/hooks/cascading-scans/hook/scope-limiter.ts @@ -21,8 +21,6 @@ import { export enum ScopeLimiterRequirementOperator { In = "In", NotIn = "NotIn", - Exists = "Exists", - DoesNotExist = "DoesNotExist", Contains = "Contains", DoesNotContain = "DoesNotContain", InCIDR = "InCIDR", @@ -118,14 +116,6 @@ const operatorFunctions: { [key in ScopeLimiterRequirementOperator]: OperatorFun operator: props => !operatorIn(props), validator: defaultValidator, }, - [ScopeLimiterRequirementOperator.Exists]: { - operator: operatorExists, - validator: props => validate(props, true, true), - }, - [ScopeLimiterRequirementOperator.DoesNotExist]: { - operator: props => !operatorExists(props), - validator: props => validate(props, true, true), - }, [ScopeLimiterRequirementOperator.Contains]: { operator: operatorContains, validator: defaultValidator, @@ -164,9 +154,6 @@ function validate({scopeAnnotationValue, findingValues}: Operands, scopeAnnotati function operatorIn({scopeAnnotationValue, findingValues}: Operands): boolean { return findingValues.includes(scopeAnnotationValue); } -function operatorExists({scopeAnnotationValue, findingValues}: Operands): boolean { - return scopeAnnotationValue !== undefined; -} function operatorContains({scopeAnnotationValue, findingValues}: Operands): boolean { const scopeAnnotationValues = scopeAnnotationValue.split(","); From 0c1d8c9956098f621db83f01caad56ba90f58f45 Mon Sep 17 00:00:00 2001 From: Jop Zitman Date: Fri, 19 Nov 2021 11:10:47 +0100 Subject: [PATCH 21/54] Add inline documentation for every operator Signed-off-by: Jop Zitman --- hooks/cascading-scans/hook/scope-limiter.ts | 25 +++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/hooks/cascading-scans/hook/scope-limiter.ts b/hooks/cascading-scans/hook/scope-limiter.ts index 7682eda9e4..981c370e26 100644 --- a/hooks/cascading-scans/hook/scope-limiter.ts +++ b/hooks/cascading-scans/hook/scope-limiter.ts @@ -151,20 +151,45 @@ function validate({scopeAnnotationValue, findingValues}: Operands, scopeAnnotati } } +/** + * The scope annotation value exists in one of the finding values. + * Matching example: + * scopeAnnotationValue: "example.com" + * findingValues: ["example.com", "subdomain.example.com"] + */ function operatorIn({scopeAnnotationValue, findingValues}: Operands): boolean { return findingValues.includes(scopeAnnotationValue); } +/** + * The scope annotation value is considered a comma-seperated list and checks if every finding value is in that list. + * Matching example: + * scopeAnnotationValue: "example.com,subdomain.example.com,other.example.com" + * findingValues: ["example.com", "subdomain.example.com"] + */ function operatorContains({scopeAnnotationValue, findingValues}: Operands): boolean { const scopeAnnotationValues = scopeAnnotationValue.split(","); return findingValues.every(findingValue => scopeAnnotationValues.includes(findingValue)); } +/** + * The scope annotation value is considered CIDR and checks if every finding value is within the subnet of that CIDR. + * Matching example: + * scopeAnnotationValue: "10.10.0.0/16" + * findingValues: ["10.10.1.2", "10.10.1.3"] + */ function operatorInCIDR({scopeAnnotationValue, findingValues}: Operands): boolean { const scopeAnnotationSubnet = new Address4(scopeAnnotationValue); return findingValues.every(findingValue => new Address4(findingValue).isInSubnet(scopeAnnotationSubnet)); } +/** + * Checks if every finding value is a subdomain of the scope annotation value. + * Inclusive; i.e. example.com is a subdomain of example.com. + * Matching example: + * scopeAnnotationValue: "example.com" + * findingValues: ["subdomain.example.com", "example.com"] + */ function operatorSubdomainOf({scopeAnnotationValue, findingValues}: Operands): boolean { const scopeAnnotationDomain = parseDomain(fromUrl(scopeAnnotationValue)); if (scopeAnnotationDomain.type == ParseResultType.Listed) { From c40992bff130e1a9be002b8610e963b4db825b0d Mon Sep 17 00:00:00 2001 From: Jop Zitman Date: Thu, 25 Nov 2021 13:42:31 +0100 Subject: [PATCH 22/54] Fix unit tests Signed-off-by: Jop Zitman --- hooks/cascading-scans/hook/scope-limiter.test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hooks/cascading-scans/hook/scope-limiter.test.js b/hooks/cascading-scans/hook/scope-limiter.test.js index 8d1632d76c..49fb9ee97a 100644 --- a/hooks/cascading-scans/hook/scope-limiter.test.js +++ b/hooks/cascading-scans/hook/scope-limiter.test.js @@ -387,7 +387,7 @@ test("Matches subdomainOf if providing a deep subdomain of a deep subdomain", () } }; - const cascadedScans = isReverseMatch( + const cascadedScans = isInScope( scopeLimiter, annotations, finding, @@ -418,7 +418,7 @@ test("Does not match subdomainOf even if differences are deep in the subdomain t } }; - const cascadedScans = isReverseMatch( + const cascadedScans = isInScope( scopeLimiter, annotations, finding, @@ -515,5 +515,5 @@ test("Throws errors when missing fields", () => { {}, ); - expect(cascadedScans).toThrowError("using operator 'In': annotation may not be undefined"); + expect(cascadedScans).toThrowError("using operator 'In': the referenced annotation may not be undefined"); }); From e67f8c2d1c338ec48afa31ed231637d3f84dfb82 Mon Sep 17 00:00:00 2001 From: Jop Zitman Date: Thu, 25 Nov 2021 13:53:40 +0100 Subject: [PATCH 23/54] Add IPv6 support to InCIDR Signed-off-by: Jop Zitman --- .../hook/scope-limiter.test.js | 121 ++++++++++++++++++ hooks/cascading-scans/hook/scope-limiter.ts | 35 ++++- 2 files changed, 152 insertions(+), 4 deletions(-) diff --git a/hooks/cascading-scans/hook/scope-limiter.test.js b/hooks/cascading-scans/hook/scope-limiter.test.js index 49fb9ee97a..908edb383a 100644 --- a/hooks/cascading-scans/hook/scope-limiter.test.js +++ b/hooks/cascading-scans/hook/scope-limiter.test.js @@ -188,6 +188,127 @@ test("Does not match InCIDR if attributes.ip not in subnet", () => { expect(cascadedScans).toBe(false); }); +test("Matches InCIDR if attributes.ip in subnet IPv6", () => { + const annotations = { + "scope.cascading.securecodebox.io/cidr": "2001:0:ce49:7601:e866:efff:62c3:fffe/16", + } + const scopeLimiter = { + validOnMissingRender: false, + allOf: [ + { + key: "scope.cascading.securecodebox.io/cidr", + operator: "InCIDR", + values: ["{{attributes.ip}}"], + } + ] + } + const finding = { + attributes: { + ip: "2001:0:ce49:7601:e866:efff:62c3:ffff", + } + }; + + const cascadedScans = isInScope( + scopeLimiter, + annotations, + finding, + {} + ); + + expect(cascadedScans).toBe(true); +}); + +test("Matches InCIDR if there is an IPv4/6 mismatch", () => { + const annotations = { + "scope.cascading.securecodebox.io/cidr": "2001:0:ce49:7601:e866:efff:62c3:fffe/16", + } + const scopeLimiter = { + validOnMissingRender: false, + allOf: [ + { + key: "scope.cascading.securecodebox.io/cidr", + operator: "InCIDR", + values: ["{{attributes.ip}}"], + } + ] + } + const finding = { + attributes: { + ip: "10.0.1.0", + } + }; + + const cascadedScans = isInScope( + scopeLimiter, + annotations, + finding, + {} + ); + + expect(cascadedScans).toBe(true); +}); + +test("Throws error if IPv4 address is invalid even if scope is in IPv6", () => { + const annotations = { + "scope.cascading.securecodebox.io/cidr": "2001:0:ce49:7601:e866:efff:62c3:fffe/16", + } + const scopeLimiter = { + validOnMissingRender: false, + allOf: [ + { + key: "scope.cascading.securecodebox.io/cidr", + operator: "InCIDR", + values: ["{{attributes.ip}}"], + } + ] + } + const finding = { + attributes: { + ip: "10.0.0.257", // Invalid IPv4 + } + }; + + const cascadedScans = () => isInScope( + scopeLimiter, + annotations, + finding, + {} + ); + + expect(cascadedScans).toThrowError("Bad characters detected in address: .."); +}); + + +test("Throws error if IPv6 address is invalid even if scope is in IPv4", () => { + const annotations = { + "scope.cascading.securecodebox.io/cidr": "10.0.0.0/16", + } + const scopeLimiter = { + validOnMissingRender: false, + allOf: [ + { + key: "scope.cascading.securecodebox.io/cidr", + operator: "InCIDR", + values: ["{{attributes.ip}}"], + } + ] + } + const finding = { + attributes: { + ip: "2001:0:ce49:7601:e866:efff:62c3", + } + }; + + const cascadedScans = () => isInScope( + scopeLimiter, + annotations, + finding, + {} + ); + + expect(cascadedScans).toThrowError("Incorrect number of groups found"); +}); + test("Matches using templates populated with finding and a mapped selector", () => { const annotations = { "scope.cascading.securecodebox.io/domains": "example.com,subdomain.example.com", diff --git a/hooks/cascading-scans/hook/scope-limiter.ts b/hooks/cascading-scans/hook/scope-limiter.ts index 981c370e26..bbd4106e71 100644 --- a/hooks/cascading-scans/hook/scope-limiter.ts +++ b/hooks/cascading-scans/hook/scope-limiter.ts @@ -7,7 +7,7 @@ import { V1ObjectMeta } from "@kubernetes/client-node/dist/gen/model/v1ObjectMeta"; import * as Mustache from "mustache"; -import { Address4 } from "ip-address"; +import { Address4, Address6, AddressError } from "ip-address"; import { fromUrl, parseDomain, @@ -174,13 +174,40 @@ function operatorContains({scopeAnnotationValue, findingValues}: Operands): bool /** * The scope annotation value is considered CIDR and checks if every finding value is within the subnet of that CIDR. + * Supports both IPv4 and IPv6. If the scope is defined in IPv4, will only validate IPv4 IPs in the finding values. + * Vice-versa for IPv6 defined in scope and IPv4 found in values. Note that all IPs in finding values must be valid + * addresses, regardless of whether IPv4 or IPv6 was used in the scope definition. * Matching example: * scopeAnnotationValue: "10.10.0.0/16" - * findingValues: ["10.10.1.2", "10.10.1.3"] + * findingValues: ["10.10.1.2", "10.10.1.3", "2001:0:ce49:7601:e866:efff:62c3:fffe"] */ function operatorInCIDR({scopeAnnotationValue, findingValues}: Operands): boolean { - const scopeAnnotationSubnet = new Address4(scopeAnnotationValue); - return findingValues.every(findingValue => new Address4(findingValue).isInSubnet(scopeAnnotationSubnet)); + + function getIPv4Or6(ipValue: string): Address4 | Address6 { + try { + return new Address4(ipValue); + } catch (e) { + if (e.name === "AddressError" && e.message === "Invalid IPv4 address.") { + try { + return new Address6(ipValue); + } catch (e) { + if (e instanceof AddressError && e.message === "Invalid IPv6 address.") { + throw new Error(`${ipValue} is neither a IPv4 or IPv6`); + } else throw e; + } + } else throw e; + } + } + + let scopeAnnotationSubnet = getIPv4Or6(scopeAnnotationValue); + + + return findingValues.every(findingValue => { + const address = getIPv4Or6(findingValue); + if (address.constructor !== scopeAnnotationSubnet.constructor) return true; + + return address.isInSubnet(scopeAnnotationSubnet); + }); } /** From b081011ecf1fa3e79ee47c91ad005bda75eec299 Mon Sep 17 00:00:00 2001 From: Jop Zitman Date: Fri, 26 Nov 2021 11:10:25 +0100 Subject: [PATCH 24/54] Add support for templating a list from findings Signed-off-by: Jop Zitman --- .../hook/scope-limiter.test.js | 39 +++++++++++++++++++ hooks/cascading-scans/hook/scope-limiter.ts | 27 +++++++++++-- 2 files changed, 62 insertions(+), 4 deletions(-) diff --git a/hooks/cascading-scans/hook/scope-limiter.test.js b/hooks/cascading-scans/hook/scope-limiter.test.js index 908edb383a..78b1b15342 100644 --- a/hooks/cascading-scans/hook/scope-limiter.test.js +++ b/hooks/cascading-scans/hook/scope-limiter.test.js @@ -638,3 +638,42 @@ test("Throws errors when missing fields", () => { expect(cascadedScans).toThrowError("using operator 'In': the referenced annotation may not be undefined"); }); + +test("Test templating into a list", () => { + const annotations = { + "scope.cascading.securecodebox.io/CIDR": "127.0.0.0/8", + } + const scopeLimiter = { + validOnMissingRender: false, + allOf: [ + { + key: "scope.cascading.securecodebox.io/CIDR", + operator: "InCIDR", + // values: ["{{#attributes.addresses}}{{ip}},{{/attributes.addresses}}"], + values: ["{{#list}}attributes.addresses.ip{{/list}}"], + } + ] + } + + const finding = { + attributes: { + addresses: [ + { + "ip": "127.0.0.1" + }, + { + "ip": "fe80::4eb3:e128:53cc:5722" + } + ] + } + }; + + const cascadedScans = isInScope( + scopeLimiter, + annotations, + finding, + {}, + ); + + expect(cascadedScans).toBe(true); +}); diff --git a/hooks/cascading-scans/hook/scope-limiter.ts b/hooks/cascading-scans/hook/scope-limiter.ts index bbd4106e71..fe3df9e33d 100644 --- a/hooks/cascading-scans/hook/scope-limiter.ts +++ b/hooks/cascading-scans/hook/scope-limiter.ts @@ -14,6 +14,7 @@ import { ParseResultType } from "parse-domain"; import { + flatten, isEqual, takeRight } from "lodash"; @@ -62,7 +63,7 @@ export function isInScope( const props: Operands = { scopeAnnotationValue: value, - findingValues: renders.map(render => render[0]) + findingValues: flatten(renders.map(render => render[0])) }; try { @@ -74,7 +75,7 @@ export function isInScope( return operatorFunction(props); } - function templateValue(value: string): [string, boolean] { + function templateValue(value: string): [string[], boolean] { if (value === undefined) return [undefined, true]; let mapped = Mustache.render(value, { $: { @@ -84,8 +85,26 @@ export function isInScope( if (mapped == "") { mapped = value; } - let rendered = Mustache.render(mapped, finding); - return [rendered, rendered != ""]; + const delimiter = ";;;;" + let rendered = Mustache.render(mapped, { + ...finding, + "list": function () { + return function (text, render) { + const path = text.split("."); + const listKey = path.slice(0, path.length - 1).join("."); + const objectKey = path.pop(); + return render(`{{#${listKey}}}{{${objectKey}}}${delimiter}{{/${listKey}}}`); + } + } + } + ); + if (rendered.includes(delimiter)) { + let list = rendered.split(delimiter); + list = list.slice(0, list.length - 1); + return [list, list.every(value => value != "")] + } else { + return [[rendered], rendered != ""]; + } } return [ From ecab082dfbe10e0d21eba46eff138a7e17f8096b Mon Sep 17 00:00:00 2001 From: Jop Zitman Date: Fri, 26 Nov 2021 11:26:32 +0100 Subject: [PATCH 25/54] Handle whitespace explicitly in list function Signed-off-by: Jop Zitman --- hooks/cascading-scans/hook/scope-limiter.test.js | 3 +-- hooks/cascading-scans/hook/scope-limiter.ts | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/hooks/cascading-scans/hook/scope-limiter.test.js b/hooks/cascading-scans/hook/scope-limiter.test.js index 78b1b15342..08d2300c56 100644 --- a/hooks/cascading-scans/hook/scope-limiter.test.js +++ b/hooks/cascading-scans/hook/scope-limiter.test.js @@ -649,8 +649,7 @@ test("Test templating into a list", () => { { key: "scope.cascading.securecodebox.io/CIDR", operator: "InCIDR", - // values: ["{{#attributes.addresses}}{{ip}},{{/attributes.addresses}}"], - values: ["{{#list}}attributes.addresses.ip{{/list}}"], + values: ["{{#list}} attributes.addresses.ip {{/list}}"], } ] } diff --git a/hooks/cascading-scans/hook/scope-limiter.ts b/hooks/cascading-scans/hook/scope-limiter.ts index fe3df9e33d..8f8cdf7c0a 100644 --- a/hooks/cascading-scans/hook/scope-limiter.ts +++ b/hooks/cascading-scans/hook/scope-limiter.ts @@ -90,7 +90,7 @@ export function isInScope( ...finding, "list": function () { return function (text, render) { - const path = text.split("."); + const path = text.trim().split("."); const listKey = path.slice(0, path.length - 1).join("."); const objectKey = path.pop(); return render(`{{#${listKey}}}{{${objectKey}}}${delimiter}{{/${listKey}}}`); From 7d98c1e9d12f09ae6a06991a804b9e5a9f825d42 Mon Sep 17 00:00:00 2001 From: Jop Zitman Date: Fri, 26 Nov 2021 11:26:39 +0100 Subject: [PATCH 26/54] Consistency Signed-off-by: Jop Zitman --- hooks/cascading-scans/hook/scope-limiter.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hooks/cascading-scans/hook/scope-limiter.ts b/hooks/cascading-scans/hook/scope-limiter.ts index 8f8cdf7c0a..682ff00cce 100644 --- a/hooks/cascading-scans/hook/scope-limiter.ts +++ b/hooks/cascading-scans/hook/scope-limiter.ts @@ -7,7 +7,7 @@ import { V1ObjectMeta } from "@kubernetes/client-node/dist/gen/model/v1ObjectMeta"; import * as Mustache from "mustache"; -import { Address4, Address6, AddressError } from "ip-address"; +import { Address4, Address6 } from "ip-address"; import { fromUrl, parseDomain, @@ -210,7 +210,7 @@ function operatorInCIDR({scopeAnnotationValue, findingValues}: Operands): boolea try { return new Address6(ipValue); } catch (e) { - if (e instanceof AddressError && e.message === "Invalid IPv6 address.") { + if (e.name === "AddressError" && e.message === "Invalid IPv6 address.") { throw new Error(`${ipValue} is neither a IPv4 or IPv6`); } else throw e; } From 5a6b31edbfc9502585f8eff8fa086e3163ba3f16 Mon Sep 17 00:00:00 2001 From: Jop Zitman Date: Fri, 26 Nov 2021 11:35:20 +0100 Subject: [PATCH 27/54] Add more tests and error handling Signed-off-by: Jop Zitman --- .../hook/scope-limiter.test.js | 50 +++++++++++++++++++ hooks/cascading-scans/hook/scope-limiter.ts | 3 ++ 2 files changed, 53 insertions(+) diff --git a/hooks/cascading-scans/hook/scope-limiter.test.js b/hooks/cascading-scans/hook/scope-limiter.test.js index 08d2300c56..6ae4cc8673 100644 --- a/hooks/cascading-scans/hook/scope-limiter.test.js +++ b/hooks/cascading-scans/hook/scope-limiter.test.js @@ -676,3 +676,53 @@ test("Test templating into a list", () => { expect(cascadedScans).toBe(true); }); + +test("Test templating list with invalid keys", () => { + const annotations = { + "scope.cascading.securecodebox.io/CIDR": "127.0.0.0/8", + } + const scopeLimiter = { + validOnMissingRender: false, + allOf: [ + { + key: "scope.cascading.securecodebox.io/CIDR", + operator: "InCIDR", + values: ["{{#list}}attributes.randomkey.ip{{/list}}"], + } + ] + } + + const cascadedScans = isInScope( + scopeLimiter, + annotations, + {}, + {}, + ); + + expect(cascadedScans).toBe(false); +}); + +test("Test templating list with too short key", () => { + const annotations = { + "scope.cascading.securecodebox.io/CIDR": "127.0.0.0/8", + } + const scopeLimiter = { + validOnMissingRender: false, + allOf: [ + { + key: "scope.cascading.securecodebox.io/CIDR", + operator: "InCIDR", + values: ["{{#list}}attributes.randomkey{{/list}}"], + } + ] + } + + const cascadedScans = () => isInScope( + scopeLimiter, + annotations, + {}, + {}, + ); + + expect(cascadedScans).toThrowError("Invalid list key 'attributes.randomkey'. List key must be at least 3 levels deep. E.g. 'attributes.addresses.ip'"); +}); diff --git a/hooks/cascading-scans/hook/scope-limiter.ts b/hooks/cascading-scans/hook/scope-limiter.ts index 682ff00cce..692812b2fa 100644 --- a/hooks/cascading-scans/hook/scope-limiter.ts +++ b/hooks/cascading-scans/hook/scope-limiter.ts @@ -91,6 +91,9 @@ export function isInScope( "list": function () { return function (text, render) { const path = text.trim().split("."); + if (path.length < 3) { + throw new Error(`Invalid list key '${text}'. List key must be at least 3 levels deep. E.g. 'attributes.addresses.ip'`) + } const listKey = path.slice(0, path.length - 1).join("."); const objectKey = path.pop(); return render(`{{#${listKey}}}{{${objectKey}}}${delimiter}{{/${listKey}}}`); From 3f2ba8144ce431990993c3620d25483482719de8 Mon Sep 17 00:00:00 2001 From: Jop Zitman Date: Fri, 26 Nov 2021 12:04:10 +0100 Subject: [PATCH 28/54] Add tons of comments everywhere and convert `templateValue` return type into an object for readability. Signed-off-by: Jop Zitman --- hooks/cascading-scans/hook/scope-limiter.ts | 45 ++++++++++++++++----- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/hooks/cascading-scans/hook/scope-limiter.ts b/hooks/cascading-scans/hook/scope-limiter.ts index 692812b2fa..23757338b7 100644 --- a/hooks/cascading-scans/hook/scope-limiter.ts +++ b/hooks/cascading-scans/hook/scope-limiter.ts @@ -46,24 +46,30 @@ export function isInScope( ) { if (scopeLimiter === undefined) return true; - function validateRequirement({key, operator, values}: ScopeLimiterRequirement) { + // Checks whether the key/operator/values pair successfully resolves + function validateRequirement({key, operator, values}: ScopeLimiterRequirement): boolean { if (!key.startsWith(`${scopeDomain}`)) { throw new Error(`key '${key}' is invalid: key does not start with '${scopeDomain}'`); } + // Retrieve operator and validator functions from user operator input const { operator: operatorFunction, validator: validatorFunction } = operatorFunctions[operator]; if (operatorFunction === undefined) { throw new Error(`Unknown operator '${operator}'`); } - const value = scanAnnotations[key]; - const renders = values.map(templateValue); - if (renders.some(render => !render[1])) { + const scopeAnnotationValue = scanAnnotations[key]; + + // Template the user values input using Mustache + const findingValues = values.map(templateValue); + // If one of the user values couldn't be rendered, fallback to user-defined behaviour + if (findingValues.some(render => !render.rendered)) { return scopeLimiter.validOnMissingRender; } const props: Operands = { - scopeAnnotationValue: value, - findingValues: flatten(renders.map(render => render[0])) + scopeAnnotationValue, + // flatten is the values to get rid of nested lists (caused by our custom Mustache list function) + findingValues: flatten(findingValues.map(render => render.values)) }; try { @@ -75,19 +81,26 @@ export function isInScope( return operatorFunction(props); } - function templateValue(value: string): [string[], boolean] { - if (value === undefined) return [undefined, true]; + function templateValue(value: string): {values: string[], rendered: boolean} { + if (value === undefined) return { + values: [], + rendered: true, + }; + // First try to render scope limiter aliases let mapped = Mustache.render(value, { $: { ...scopeLimiterAliases } }); + // If it couldn't be rendered as an alias, try render it again with finding if (mapped == "") { mapped = value; } const delimiter = ";;;;" let rendered = Mustache.render(mapped, { ...finding, + // Custom Mustache list function to select attributes inside a list. + // returns a string containing a list delimited by `delimiter` defined above. "list": function () { return function (text, render) { const path = text.trim().split("."); @@ -101,15 +114,25 @@ export function isInScope( } } ); + // If the final render includes a delimiter, unpack the rendered string to an actual list if (rendered.includes(delimiter)) { let list = rendered.split(delimiter); + // The last element is always an empty string list = list.slice(0, list.length - 1); - return [list, list.every(value => value != "")] + return { + values: list, + rendered: list.every(value => value != ""), + } } else { - return [[rendered], rendered != ""]; + return { + values: [rendered], + rendered: rendered != "", + } } } + // All the different scope limiter fields must match (i.e. results of `allOf`, `anyOf`, `noneOf` are ANDed). + // If one of those fields is not declared, regard it as matched. return [ scopeLimiter.allOf !== undefined ? scopeLimiter.allOf.every(validateRequirement) : true, scopeLimiter.anyOf !== undefined ? scopeLimiter.anyOf.some(validateRequirement) : true, @@ -127,6 +150,7 @@ interface OperatorFunctions { validator: (operands: Operands) => void, } +// This validator ensures that neither the scope annotation nor the finding values can be undefined const defaultValidator: OperatorFunctions["validator"] = props => validate(props, false, false); const operatorFunctions: { [key in ScopeLimiterRequirementOperator]: OperatorFunctions } = { @@ -226,6 +250,7 @@ function operatorInCIDR({scopeAnnotationValue, findingValues}: Operands): boolea return findingValues.every(findingValue => { const address = getIPv4Or6(findingValue); + // Can't compare IPv4 with IPv6, so we return regard such comparison as true if (address.constructor !== scopeAnnotationSubnet.constructor) return true; return address.isInSubnet(scopeAnnotationSubnet); From 058340fdb5a735bf732d34a82aa0f1eb7ba2d4f6 Mon Sep 17 00:00:00 2001 From: Jop Zitman Date: Fri, 26 Nov 2021 12:17:12 +0100 Subject: [PATCH 29/54] Reduce list requirement key depth Signed-off-by: Jop Zitman --- hooks/cascading-scans/hook/scope-limiter.test.js | 2 +- hooks/cascading-scans/hook/scope-limiter.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/hooks/cascading-scans/hook/scope-limiter.test.js b/hooks/cascading-scans/hook/scope-limiter.test.js index 6ae4cc8673..6d5a2602b1 100644 --- a/hooks/cascading-scans/hook/scope-limiter.test.js +++ b/hooks/cascading-scans/hook/scope-limiter.test.js @@ -724,5 +724,5 @@ test("Test templating list with too short key", () => { {}, ); - expect(cascadedScans).toThrowError("Invalid list key 'attributes.randomkey'. List key must be at least 3 levels deep. E.g. 'attributes.addresses.ip'"); + expect(cascadedScans).toThrowError("Invalid list key 'attributes.randomkey'. List key must be at least 2 levels deep. E.g. 'attributes.addresses.ip'"); }); diff --git a/hooks/cascading-scans/hook/scope-limiter.ts b/hooks/cascading-scans/hook/scope-limiter.ts index 23757338b7..8886236696 100644 --- a/hooks/cascading-scans/hook/scope-limiter.ts +++ b/hooks/cascading-scans/hook/scope-limiter.ts @@ -104,8 +104,8 @@ export function isInScope( "list": function () { return function (text, render) { const path = text.trim().split("."); - if (path.length < 3) { - throw new Error(`Invalid list key '${text}'. List key must be at least 3 levels deep. E.g. 'attributes.addresses.ip'`) + if (path.length < 2) { + throw new Error(`Invalid list key '${text}'. List key must be at least 2 levels deep. E.g. 'attributes.addresses.ip'`) } const listKey = path.slice(0, path.length - 1).join("."); const objectKey = path.pop(); From 275cbfcdb80bb1f035efbfb4075fe979bf81d81e Mon Sep 17 00:00:00 2001 From: Jop Zitman Date: Fri, 26 Nov 2021 12:23:39 +0100 Subject: [PATCH 30/54] Actually test with a key that is too short. Signed-off-by: Jop Zitman --- hooks/cascading-scans/hook/scope-limiter.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hooks/cascading-scans/hook/scope-limiter.test.js b/hooks/cascading-scans/hook/scope-limiter.test.js index 6d5a2602b1..1774354e3c 100644 --- a/hooks/cascading-scans/hook/scope-limiter.test.js +++ b/hooks/cascading-scans/hook/scope-limiter.test.js @@ -712,7 +712,7 @@ test("Test templating list with too short key", () => { { key: "scope.cascading.securecodebox.io/CIDR", operator: "InCIDR", - values: ["{{#list}}attributes.randomkey{{/list}}"], + values: ["{{#list}}attributes{{/list}}"], } ] } From c86600ef37a29574d5214df60afce3d36a96b782 Mon Sep 17 00:00:00 2001 From: Max Maass Date: Fri, 26 Nov 2021 12:25:40 +0100 Subject: [PATCH 31/54] Fix expected error message for unit test Signed-off-by: Max Maass --- hooks/cascading-scans/hook/scope-limiter.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hooks/cascading-scans/hook/scope-limiter.test.js b/hooks/cascading-scans/hook/scope-limiter.test.js index 1774354e3c..54bc3b3981 100644 --- a/hooks/cascading-scans/hook/scope-limiter.test.js +++ b/hooks/cascading-scans/hook/scope-limiter.test.js @@ -724,5 +724,5 @@ test("Test templating list with too short key", () => { {}, ); - expect(cascadedScans).toThrowError("Invalid list key 'attributes.randomkey'. List key must be at least 2 levels deep. E.g. 'attributes.addresses.ip'"); + expect(cascadedScans).toThrowError("Invalid list key 'attributes'. List key must be at least 2 levels deep. E.g. 'attributes.addresses.ip'"); }); From 9d7d7aaa7d18d7fa29353f0efdb87ab882a8201b Mon Sep 17 00:00:00 2001 From: Max Maass Date: Fri, 26 Nov 2021 13:18:48 +0100 Subject: [PATCH 32/54] Add some more unit tests Signed-off-by: Max Maass --- .../hook/scope-limiter.test.js | 172 +++++++++++++++++- 1 file changed, 171 insertions(+), 1 deletion(-) diff --git a/hooks/cascading-scans/hook/scope-limiter.test.js b/hooks/cascading-scans/hook/scope-limiter.test.js index 54bc3b3981..6b40933d0f 100644 --- a/hooks/cascading-scans/hook/scope-limiter.test.js +++ b/hooks/cascading-scans/hook/scope-limiter.test.js @@ -639,7 +639,7 @@ test("Throws errors when missing fields", () => { expect(cascadedScans).toThrowError("using operator 'In': the referenced annotation may not be undefined"); }); -test("Test templating into a list", () => { +test("Test templating into a list from multiple subkeys", () => { const annotations = { "scope.cascading.securecodebox.io/CIDR": "127.0.0.0/8", } @@ -677,6 +677,176 @@ test("Test templating into a list", () => { expect(cascadedScans).toBe(true); }); +test("Templating into list fails if key is not present in all findings", () => { + const annotations = { + "scope.cascading.securecodebox.io/CIDR": "127.0.0.0/8", + } + const scopeLimiter = { + validOnMissingRender: false, + allOf: [ + { + key: "scope.cascading.securecodebox.io/CIDR", + operator: "InCIDR", + values: ["{{#list}} attributes.addresses.ip {{/list}}"], + } + ] + } + + const finding = { + attributes: { + addresses: [ + { + "ip": "127.0.0.1" + }, + { + "ip": "fe80::4eb3:e128:53cc:5722" + }, + { + "other_key": "test" + } + ] + } + }; + + const cascadedScans = isInScope( + scopeLimiter, + annotations, + finding, + {}, + ); + + expect(cascadedScans).toBe(false); +}); + +test("Templating into list does not fail scope if validOnMissingRender is set and templating key is not present in all findings", () => { + const annotations = { + "scope.cascading.securecodebox.io/CIDR": "127.0.0.0/8", + } + const scopeLimiter = { + validOnMissingRender: true, + allOf: [ + { + key: "scope.cascading.securecodebox.io/CIDR", + operator: "InCIDR", + values: ["{{#list}} attributes.addresses.ip {{/list}}"], + } + ] + } + + const finding = { + attributes: { + addresses: [ + { + "ip": "127.0.0.1" + }, + { + "ip": "fe80::4eb3:e128:53cc:5722" + }, + { + "other_key": "test" + } + ] + } + }; + + const cascadedScans = isInScope( + scopeLimiter, + annotations, + finding, + {}, + ); + + expect(cascadedScans).toBe(true); +}); + +test("Test matching both IPv4 and v6 addresses in CIDR", () => { + const annotations = { + "scope.cascading.securecodebox.io/CIDR4": "127.0.0.0/8", + "scope.cascading.securecodebox.io/CIDR6": "2001:0:ce49:7601:e866:efff:62c3:fffe/16", + } + const scopeLimiter = { + validOnMissingRender: false, + allOf: [ + { + key: "scope.cascading.securecodebox.io/CIDR4", + operator: "InCIDR", + values: ["{{#list}} attributes.addresses.ip {{/list}}"], + }, + { + key: "scope.cascading.securecodebox.io/CIDR6", + operator: "InCIDR", + values: ["{{#list}} attributes.addresses.ip {{/list}}"], + } + ] + } + + const finding = { + attributes: { + addresses: [ + { + "ip": "127.0.0.5" + }, + { + "ip": "2001:0:ce49:7601:e866:efff:62c3:fefe" + }, + ] + } + }; + + const cascadedScans = isInScope( + scopeLimiter, + annotations, + finding, + {}, + ); + + expect(cascadedScans).toBe(true); +}); + +test("Test failing one of the IPv4 and v6 addresses", () => { + const annotations = { + "scope.cascading.securecodebox.io/CIDR4": "127.0.0.0/8", + "scope.cascading.securecodebox.io/CIDR6": "2001:0:ce49:7601:e866:efff:62c3:fffe/16", + } + const scopeLimiter = { + validOnMissingRender: false, + allOf: [ + { + key: "scope.cascading.securecodebox.io/CIDR4", + operator: "InCIDR", + values: ["{{#list}} attributes.addresses.ip {{/list}}"], + }, + { + key: "scope.cascading.securecodebox.io/CIDR6", + operator: "InCIDR", + values: ["{{#list}} attributes.addresses.ip {{/list}}"], + } + ] + } + + const finding = { + attributes: { + addresses: [ + { + "ip": "192.168.178.42" + }, + { + "ip": "2001:0:ce49:7601:e866:efff:62c3:fefe" + }, + ] + } + }; + + const cascadedScans = isInScope( + scopeLimiter, + annotations, + finding, + {}, + ); + + expect(cascadedScans).toBe(false); +}); + test("Test templating list with invalid keys", () => { const annotations = { "scope.cascading.securecodebox.io/CIDR": "127.0.0.0/8", From 33906e40925507ba7686e25c66a334e6f4f610dc Mon Sep 17 00:00:00 2001 From: Jop Zitman Date: Fri, 26 Nov 2021 14:50:03 +0100 Subject: [PATCH 33/54] Add failing test Signed-off-by: Jop Zitman --- .../hook/scope-limiter.test.js | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/hooks/cascading-scans/hook/scope-limiter.test.js b/hooks/cascading-scans/hook/scope-limiter.test.js index 6b40933d0f..20f1382f9b 100644 --- a/hooks/cascading-scans/hook/scope-limiter.test.js +++ b/hooks/cascading-scans/hook/scope-limiter.test.js @@ -896,3 +896,34 @@ test("Test templating list with too short key", () => { expect(cascadedScans).toThrowError("Invalid list key 'attributes'. List key must be at least 2 levels deep. E.g. 'attributes.addresses.ip'"); }); + +test("Test templating list of strings", () => { + const annotations = { + "scope.cascading.securecodebox.io/domain": "example.com", + } + const scopeLimiter = { + validOnMissingRender: false, + allOf: [ + { + key: "scope.cascading.securecodebox.io/domain", + operator: "SubdomainOf", + values: ["{{attributes.domains}}"], + } + ] + } + + const finding = { + attributes: { + domains: ["example.com", "subdomain.example.com"], + } + }; + + const cascadedScans = isInScope( + scopeLimiter, + annotations, + finding, + {}, + ); + + expect(cascadedScans).toBe(true); +}); From 09a0c51dbd4069c4fc9981b92717520abf5564c9 Mon Sep 17 00:00:00 2001 From: Max Maass Date: Fri, 26 Nov 2021 15:34:30 +0100 Subject: [PATCH 34/54] Add test for v6 constraint without v6 address Signed-off-by: Max Maass --- .../hook/scope-limiter.test.js | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/hooks/cascading-scans/hook/scope-limiter.test.js b/hooks/cascading-scans/hook/scope-limiter.test.js index 20f1382f9b..d834dd188c 100644 --- a/hooks/cascading-scans/hook/scope-limiter.test.js +++ b/hooks/cascading-scans/hook/scope-limiter.test.js @@ -847,6 +847,47 @@ test("Test failing one of the IPv4 and v6 addresses", () => { expect(cascadedScans).toBe(false); }); +test("Test v6 constraint without v6 address present does not block the scope", () => { + const annotations = { + "scope.cascading.securecodebox.io/CIDR4": "127.0.0.0/8", + "scope.cascading.securecodebox.io/CIDR6": "2001:0:ce49:7601:e866:efff:62c3:fffe/16", + } + const scopeLimiter = { + validOnMissingRender: false, + allOf: [ + { + key: "scope.cascading.securecodebox.io/CIDR4", + operator: "InCIDR", + values: ["{{#list}} attributes.addresses.ip {{/list}}"], + }, + { + key: "scope.cascading.securecodebox.io/CIDR6", + operator: "InCIDR", + values: ["{{#list}} attributes.addresses.ip {{/list}}"], + } + ] + } + + const finding = { + attributes: { + addresses: [ + { + "ip": "192.168.178.42" + }, + ] + } + }; + + const cascadedScans = isInScope( + scopeLimiter, + annotations, + finding, + {}, + ); + + expect(cascadedScans).toBe(false); +}); + test("Test templating list with invalid keys", () => { const annotations = { "scope.cascading.securecodebox.io/CIDR": "127.0.0.0/8", From 6ed9c26f79d94b59ff9657c512115c1c9ed0bc3d Mon Sep 17 00:00:00 2001 From: Jop Zitman Date: Mon, 29 Nov 2021 13:31:22 +0100 Subject: [PATCH 35/54] Rework test suite to bdd and implement `list`, `split`, and `keyinobjectlist` Signed-off-by: Jop Zitman --- .../hook/scope-limiter.test.js | 1380 +++++++---------- hooks/cascading-scans/hook/scope-limiter.ts | 38 +- 2 files changed, 550 insertions(+), 868 deletions(-) diff --git a/hooks/cascading-scans/hook/scope-limiter.test.js b/hooks/cascading-scans/hook/scope-limiter.test.js index d834dd188c..0b06ee3b59 100644 --- a/hooks/cascading-scans/hook/scope-limiter.test.js +++ b/hooks/cascading-scans/hook/scope-limiter.test.js @@ -4,967 +4,631 @@ const { isInScope } = require("./scope-limiter"); -test("Should error if selecting an invalid key", () => { - const scopeLimiter = { +let scopeLimiter = undefined +let annotations = undefined +let finding = undefined +let scopeLimiterAliases = undefined + +const cascadedScans = () => isInScope( + scopeLimiter, + annotations, + finding, + scopeLimiterAliases +) + +beforeEach(function () { + scopeLimiter = { validOnMissingRender: false, - allOf: [ - { - key: "engagement.scope/domains", - operator: "Contains", - values: ["{{attributes.hostname}}"], - } - ] + allOf: [], + anyOf: [], + noneOf: [], } - const finding = { - attributes: { - hostname: "example.com", - } - }; - - const cascadedScans = () => isInScope( - scopeLimiter, - {}, - finding, - {} - ); - - expect(cascadedScans).toThrowError("key 'engagement.scope/domains' is invalid: key does not start with 'scope.cascading.securecodebox.io/'"); -}); - -test("Matches using templates populated with finding", () => { - const annotations = { - "scope.cascading.securecodebox.io/domains": "example.com,subdomain.example.com", - } - const scopeLimiter = { - validOnMissingRender: false, - allOf: [ - { - key: "scope.cascading.securecodebox.io/domains", - operator: "Contains", - values: ["{{attributes.hostname}}"], - } - ] - } - const finding = { - attributes: { - hostname: "example.com", - } - }; - const cascadedScans = isInScope( - scopeLimiter, - annotations, - finding, - {} - ); + annotations = {} - expect(cascadedScans).toBe(true); -}); - -test("Does not match using if selector does not match", () => { - const annotations = { - "scope.cascading.securecodebox.io/domains": "subdomain.example.com", - } - const scopeLimiter = { - validOnMissingRender: false, - allOf: [ - { - key: "scope.cascading.securecodebox.io/domains", - operator: "Contains", - values: ["{{attributes.hostname}}"], - } - ] - } - const finding = { + finding = { attributes: { hostname: "example.com", } - }; - - const cascadedScans = isInScope( - scopeLimiter, - annotations, - finding, - {} - ); - - expect(cascadedScans).toBe(false); -}); - -test("Does not match if one of selector types does not match", () => { - const annotations = { - "scope.cascading.securecodebox.io/domains": "example.com", - } - const scopeLimiter = { - validOnMissingRender: false, - allOf: [ - { - key: "scope.cascading.securecodebox.io/domains", - operator: "Contains", - values: ["{{attributes.hostname}}"], - } - ], - noneOf: [ - { - key: "scope.cascading.securecodebox.io/domains", - operator: "Contains", - values: ["{{attributes.hostname}}"], - } - ] } - const finding = { - attributes: { - hostname: "example.com", - } - }; - - const cascadedScans = isInScope( - scopeLimiter, - annotations, - finding, - {} - ); - expect(cascadedScans).toBe(false); -}); + scopeLimiterAliases = {} +}) -test("Matches InCIDR if attributes.ip in subnet", () => { - const annotations = { - "scope.cascading.securecodebox.io/cidr": "10.0.0.0/16", - } - const scopeLimiter = { - validOnMissingRender: false, - allOf: [ - { - key: "scope.cascading.securecodebox.io/cidr", - operator: "InCIDR", - values: ["{{attributes.ip}}"], - } - ] - } - const finding = { - attributes: { - ip: "10.0.1.0", +it("Requirement key must start with 'scope.cascading.securecodebox.io/'", () => { + scopeLimiter.allOf = [ + { + key: "engagement.scope/domains", + operator: "Contains", + values: ["{{attributes.hostname}}"], } - }; - - const cascadedScans = isInScope( - scopeLimiter, - annotations, - finding, - {} - ); - - expect(cascadedScans).toBe(true); + ] + expect(cascadedScans).toThrowError("key 'engagement.scope/domains' is invalid: key does not start with 'scope.cascading.securecodebox.io/'"); }); -test("Does not match InCIDR if attributes.ip not in subnet", () => { - const annotations = { - "scope.cascading.securecodebox.io/cidr": "10.0.0.0/32", - } - const scopeLimiter = { - validOnMissingRender: false, - allOf: [ - { - key: "scope.cascading.securecodebox.io/cidr", - operator: "InCIDR", - values: ["{{attributes.ip}}"], - } - ] - } - const finding = { - attributes: { - ip: "10.0.1.0", +test("Requirement key must map to an annotation", () => { + scopeLimiter.allOf = [ + { + key: "scope.cascading.securecodebox.io/domain", + operator: "In", + values: ["{{attributes.hostname}}"], } - }; + ] - const cascadedScans = isInScope( - scopeLimiter, - annotations, - finding, - {} - ); - - expect(cascadedScans).toBe(false); -}); - -test("Matches InCIDR if attributes.ip in subnet IPv6", () => { - const annotations = { - "scope.cascading.securecodebox.io/cidr": "2001:0:ce49:7601:e866:efff:62c3:fffe/16", - } - const scopeLimiter = { - validOnMissingRender: false, - allOf: [ - { - key: "scope.cascading.securecodebox.io/cidr", - operator: "InCIDR", - values: ["{{attributes.ip}}"], - } - ] - } - const finding = { + finding = { attributes: { - ip: "2001:0:ce49:7601:e866:efff:62c3:ffff", + hostname: "notexample.com", } }; - const cascadedScans = isInScope( - scopeLimiter, - annotations, - finding, - {} - ); - - expect(cascadedScans).toBe(true); + expect(cascadedScans).toThrowError("using operator 'In': the referenced annotation may not be undefined"); }); -test("Matches InCIDR if there is an IPv4/6 mismatch", () => { - const annotations = { - "scope.cascading.securecodebox.io/cidr": "2001:0:ce49:7601:e866:efff:62c3:fffe/16", - } - const scopeLimiter = { - validOnMissingRender: false, - allOf: [ - { - key: "scope.cascading.securecodebox.io/cidr", - operator: "InCIDR", - values: ["{{attributes.ip}}"], - } - ] - } - const finding = { - attributes: { - ip: "10.0.1.0", +describe("Templating", function () { + it("does not support requirement key", () => { + annotations = { + "scope.cascading.securecodebox.io/example.com": "example.com" } - }; - - const cascadedScans = isInScope( - scopeLimiter, - annotations, - finding, - {} - ); - - expect(cascadedScans).toBe(true); -}); - -test("Throws error if IPv4 address is invalid even if scope is in IPv6", () => { - const annotations = { - "scope.cascading.securecodebox.io/cidr": "2001:0:ce49:7601:e866:efff:62c3:fffe/16", - } - const scopeLimiter = { - validOnMissingRender: false, - allOf: [ + scopeLimiter.allOf = [ { - key: "scope.cascading.securecodebox.io/cidr", - operator: "InCIDR", - values: ["{{attributes.ip}}"], + key: "scope.cascading.securecodebox.io/{{attributes.hostname}}", + operator: "Contains", + values: ["{{attributes.hostname}}"], } ] - } - const finding = { - attributes: { - ip: "10.0.0.257", // Invalid IPv4 - } - }; - - const cascadedScans = () => isInScope( - scopeLimiter, - annotations, - finding, - {} - ); - - expect(cascadedScans).toThrowError("Bad characters detected in address: .."); -}); - + expect(cascadedScans).toThrowError("using operator 'Contains': the referenced annotation may not be undefined"); + }); -test("Throws error if IPv6 address is invalid even if scope is in IPv4", () => { - const annotations = { - "scope.cascading.securecodebox.io/cidr": "10.0.0.0/16", - } - const scopeLimiter = { - validOnMissingRender: false, - allOf: [ - { - key: "scope.cascading.securecodebox.io/cidr", - operator: "InCIDR", - values: ["{{attributes.ip}}"], - } - ] - } - const finding = { - attributes: { - ip: "2001:0:ce49:7601:e866:efff:62c3", + it("supports requirement value", () => { + annotations = { + "scope.cascading.securecodebox.io/domains": "example.com,subdomain.example.com", } - }; - - const cascadedScans = () => isInScope( - scopeLimiter, - annotations, - finding, - {} - ); - - expect(cascadedScans).toThrowError("Incorrect number of groups found"); -}); - -test("Matches using templates populated with finding and a mapped selector", () => { - const annotations = { - "scope.cascading.securecodebox.io/domains": "example.com,subdomain.example.com", - } - const scopeLimiter = { - requiresMapping: false, - validOnMissingRender: false, - allOf: [ + scopeLimiter.allOf = [ { key: "scope.cascading.securecodebox.io/domains", operator: "Contains", - values: ["{{$.hostname}}"], + values: ["{{attributes.hostname}}"], } ] - } - const finding = { - attributes: { - hostname: "example.com", - } - }; - - const scopeLimiterAliases = { - "hostname": "{{attributes.hostname}}", - } - - const cascadedScans = isInScope( - scopeLimiter, - annotations, - finding, - scopeLimiterAliases - ); - - expect(cascadedScans).toBe(true); -}); + expect(cascadedScans()).toBe(true); + }); -test("Matches if mapping is not available: validOnMissingRender true", () => { - const annotations = { - "scope.cascading.securecodebox.io/domains": "example.com,subdomain.example.com", - } - const scopeLimiter = { - validOnMissingRender: true, - allOf: [ - { - key: "scope.cascading.securecodebox.io/domains", - operator: "Contains", - values: ["{{$.hostname}}"], + describe("validOnMissingRender", function () { + test("does not match if mapping is not available: validOnMissingRender false", () => { + annotations = { + "scope.cascading.securecodebox.io/domains": "example.com,subdomain.example.com", } - ] - } + scopeLimiter.allOf = [ + { + key: "scope.cascading.securecodebox.io/domains", + operator: "Contains", + values: ["{{$.hostname}}"], + } + ] - const cascadedScans = isInScope( - scopeLimiter, - annotations, - {}, - {}, - ); + finding = {} - expect(cascadedScans).toBe(true); -}); + expect(cascadedScans()).toBe(false); + }); + }) -test("Does not match if mapping is not available: validOnMissingRender false", () => { - const annotations = { - "scope.cascading.securecodebox.io/domains": "example.com,subdomain.example.com", - } - const scopeLimiter = { - validOnMissingRender: false, - allOf: [ - { - key: "scope.cascading.securecodebox.io/domains", - operator: "Contains", - values: ["{{$.hostname}}"], + describe("aliases", function () { + test("matches using templates populated with finding and a mapped selector", () => { + annotations = { + "scope.cascading.securecodebox.io/domains": "example.com,subdomain.example.com", } - ] - } - - const cascadedScans = isInScope( - scopeLimiter, - annotations, - {}, - {}, - ); - - expect(cascadedScans).toBe(false); -}); - -test("Matches subdomainOf if is subdomain", () => { - const annotations = { - "scope.cascading.securecodebox.io/domain": "example.com", - } - const scopeLimiter = { - validOnMissingRender: false, - allOf: [ - { - key: "scope.cascading.securecodebox.io/domain", - operator: "SubdomainOf", - values: ["{{attributes.hostname}}"], + scopeLimiter.allOf = [ + { + key: "scope.cascading.securecodebox.io/domains", + operator: "Contains", + values: ["{{$.hostname}}"], + } + ] + scopeLimiterAliases = { + "hostname": "{{attributes.hostname}}", } - ] - } - - const finding = { - attributes: { - hostname: "subdomain.example.com", - } - }; + expect(cascadedScans()).toBe(true); + }); - const cascadedScans = isInScope( - scopeLimiter, - annotations, - finding, - {}, - ); - - expect(cascadedScans).toBe(true); -}); - -test("Matches subdomainOf if is the domain itself", () => { - const annotations = { - "scope.cascading.securecodebox.io/domain": "example.com", - } - const scopeLimiter = { - validOnMissingRender: false, - allOf: [ - { - key: "scope.cascading.securecodebox.io/domain", - operator: "SubdomainOf", - values: ["{{attributes.hostname}}"], + test("Matches if mapping is not available: validOnMissingRender true", () => { + annotations = { + "scope.cascading.securecodebox.io/domains": "example.com,subdomain.example.com", } - ] - } - const finding = { - attributes: { - hostname: "example.com", - } - }; - - const cascadedScans = isInScope( - scopeLimiter, - annotations, - finding, - {}, - ); - - expect(cascadedScans).toBe(true); -}); + scopeLimiter.validOnMissingRender = true + scopeLimiter.allOf = [ + { + key: "scope.cascading.securecodebox.io/domains", + operator: "Contains", + values: ["{{$.hostname}}"], + } + ] + finding = {} -test("Matches subdomainOf if providing a sub-sub domain of a sub-domain", () => { - const annotations = { - "scope.cascading.securecodebox.io/domain": "www.example.com", - } - const scopeLimiter = { - validOnMissingRender: false, - allOf: [ - { - key: "scope.cascading.securecodebox.io/domain", - operator: "SubdomainOf", - values: ["{{attributes.hostname}}"], - } - ] - } + expect(cascadedScans()).toBe(true); + }); - const finding = { - attributes: { - hostname: "test.www.example.com", - } - }; + }) - const cascadedScans = isInScope( - scopeLimiter, - annotations, - finding, - {}, - ); + describe("lists", function () { + describe("list", function () { + test("matches with list of strings", () => { + annotations = { + "scope.cascading.securecodebox.io/domain": "example.com", + } + scopeLimiter.allOf = [ + { + key: "scope.cascading.securecodebox.io/domain", + operator: "SubdomainOf", + values: ["{{#list}}attributes.domains{{/list}}"], + } + ] + + finding = { + attributes: { + domains: ["example.com", "subdomain.example.com"], + } + }; + + expect(cascadedScans()).toBe(true); + }); + + test("fails with too short key", () => { + annotations = { + "scope.cascading.securecodebox.io/CIDR": "127.0.0.0/8", + } + scopeLimiter.allOf = [ + { + key: "scope.cascading.securecodebox.io/CIDR", + operator: "InCIDR", + values: ["{{#list}}attributes{{/list}}"], + } + ] + + finding = {} + + expect(cascadedScans).toThrowError("Invalid list key 'attributes'. List key must be at least 2 levels deep. E.g. 'attributes.addresses'"); + }); + }) + + describe("split", function () { + test("matches on simple string", () => { + annotations = { + "scope.cascading.securecodebox.io/domains": "example.com", + } + scopeLimiter.allOf = [ + { + key: "scope.cascading.securecodebox.io/domains", + operator: "SubdomainOf", + values: ["{{#split}}subdomain.example.com,www.example.com{{/split}}"], + } + ] + expect(cascadedScans()).toBe(true); + }); + + test("matches on template", () => { + annotations = { + "scope.cascading.securecodebox.io/domains": "example.com", + } + scopeLimiter.allOf = [ + { + key: "scope.cascading.securecodebox.io/domains", + operator: "SubdomainOf", + values: ["{{#split}}{{attributes.hostnames}}{{/split}}"], + } + ] + + finding = { + attributes: { + hostnames: ["subdomain.example.com", "www.example.com"], + } + } - expect(cascadedScans).toBe(true); -}); + expect(cascadedScans()).toBe(true) + }); + }) -test("Matches subdomainOf if providing a deep subdomain of a deep subdomain", () => { - const annotations = { - "scope.cascading.securecodebox.io/domain": "a.b.c.d.e.example.com", - } - const scopeLimiter = { - validOnMissingRender: false, - allOf: [ - { - key: "scope.cascading.securecodebox.io/domain", - operator: "SubdomainOf", - values: ["{{attributes.hostname}}"], + describe("keyinobjectlist", function () { + test("matches if templating key is present in all list entries", () => { + annotations = { + "scope.cascading.securecodebox.io/CIDR": "127.0.0.0/8", + } + scopeLimiter.allOf = [ + { + key: "scope.cascading.securecodebox.io/CIDR", + operator: "InCIDR", + values: ["{{#keyinobjectlist}}attributes.addresses.ip{{/keyinobjectlist}}"], + } + ] + + finding = { + attributes: { + addresses: [ + { + "ip": "127.0.0.1" + }, + { + "ip": "fe80::4eb3:e128:53cc:5722" + } + ] + } + }; + + expect(cascadedScans()).toBe(true); + }); + + test("does not match if list with invalid keys", () => { + annotations = { + "scope.cascading.securecodebox.io/CIDR": "127.0.0.0/8", + } + scopeLimiter.allOf = [ + { + key: "scope.cascading.securecodebox.io/CIDR", + operator: "InCIDR", + values: ["{{#keyinobjectlist}}attributes.randomkey.ip{{/keyinobjectlist}}"], + } + ] + + finding = {} + + expect(cascadedScans()).toBe(false); + }); + + test("does not match if templating key is not present in all list entries", () => { + annotations = { + "scope.cascading.securecodebox.io/CIDR": "127.0.0.0/8", + } + scopeLimiter.allOf = [ + { + key: "scope.cascading.securecodebox.io/CIDR", + operator: "InCIDR", + values: ["{{#keyinobjectlist}}attributes.addresses.ip{{/keyinobjectlist}}"], + } + ] + + finding = { + attributes: { + addresses: [ + { + "ip": "127.0.0.1" + }, + { + "ip": "fe80::4eb3:e128:53cc:5722" + }, + { + "other_key": "test" + } + ] + } + }; + + expect(cascadedScans()).toBe(false); + }); + + test("matches if validOnMissingRender is set and templating key is not present in all list entries", () => { + annotations = { + "scope.cascading.securecodebox.io/CIDR": "127.0.0.0/8", + } + scopeLimiter.validOnMissingRender = true + scopeLimiter.allOf = [ + { + key: "scope.cascading.securecodebox.io/CIDR", + operator: "InCIDR", + values: ["{{#keyinobjectlist}}attributes.addresses.ip{{/keyinobjectlist}}"], + } + ] + + finding = { + attributes: { + addresses: [ + { + "ip": "127.0.0.1" + }, + { + "ip": "fe80::4eb3:e128:53cc:5722" + }, + { + "other_key": "test" + } + ] + } + }; + + expect(cascadedScans()).toBe(true); + }); + }) + + }) +}) + +describe("Operator", function () { + describe("Contains", function () { + it("does not match if selector should not match", () => { + annotations = { + "scope.cascading.securecodebox.io/domains": "subdomain.example.com", } - ] - } - - const finding = { - attributes: { - hostname: "z.a.b.c.d.e.example.com", - } - }; - - const cascadedScans = isInScope( - scopeLimiter, - annotations, - finding, - {}, - ); - - expect(cascadedScans).toBe(true); -}); - -test("Does not match subdomainOf even if differences are deep in the subdomain tree", () => { - const annotations = { - "scope.cascading.securecodebox.io/domain": "a.b.c.d.e.example.com", - } - const scopeLimiter = { - validOnMissingRender: false, - allOf: [ - { - key: "scope.cascading.securecodebox.io/domain", - operator: "SubdomainOf", - values: ["{{attributes.hostname}}"], + scopeLimiter.allOf = [ + { + key: "scope.cascading.securecodebox.io/domains", + operator: "Contains", + values: ["example.com"], + } + ] + expect(cascadedScans()).toBe(false); + }); + }); + + describe("InCIDR", function () { + it("matches if ip in subnet", () => { + annotations = { + "scope.cascading.securecodebox.io/cidr": "10.0.0.0/16", } - ] - } - - const finding = { - attributes: { - hostname: "z.b.c.d.e.example.com", - } - }; - - const cascadedScans = isInScope( - scopeLimiter, - annotations, - finding, - {}, - ); - - expect(cascadedScans).toBe(false); -}); + scopeLimiter.allOf = [ + { + key: "scope.cascading.securecodebox.io/cidr", + operator: "InCIDR", + values: ["10.0.1.0"], + } + ] + expect(cascadedScans()).toBe(true); + }); -test("Does not match subdomainOf if providing a sub domain of a different sub-domain", () => { - const annotations = { - "scope.cascading.securecodebox.io/domain": "www.example.com", - } - const scopeLimiter = { - validOnMissingRender: false, - allOf: [ - { - key: "scope.cascading.securecodebox.io/domain", - operator: "SubdomainOf", - values: ["{{attributes.hostname}}"], + test("does not match if ip not in subnet", () => { + annotations = { + "scope.cascading.securecodebox.io/cidr": "10.0.0.0/32", } - ] - } - - const finding = { - attributes: { - hostname: "test.example.com", - } - }; - - const cascadedScans = isInScope( - scopeLimiter, - annotations, - finding, - {}, - ); + scopeLimiter.allOf = [ + { + key: "scope.cascading.securecodebox.io/cidr", + operator: "InCIDR", + values: ["10.0.1.0"], + } + ] - expect(cascadedScans).toBe(false); -}); + expect(cascadedScans()).toBe(false); + }); -test("Does not match subdomainOf if is not subdomain", () => { - const annotations = { - "scope.cascading.securecodebox.io/domain": "example.com", - } - const scopeLimiter = { - validOnMissingRender: false, - allOf: [ - { - key: "scope.cascading.securecodebox.io/domain", - operator: "SubdomainOf", - values: ["{{attributes.hostname}}"], + test("matches if ip in subnet (IPv6)", () => { + annotations = { + "scope.cascading.securecodebox.io/cidr": "2001:0:ce49:7601:e866:efff:62c3:fffe/16", } - ] - } - - const finding = { - attributes: { - hostname: "notexample.com", - } - }; - - const cascadedScans = isInScope( - scopeLimiter, - annotations, - finding, - {}, - ); + scopeLimiter.allOf = [ + { + key: "scope.cascading.securecodebox.io/cidr", + operator: "InCIDR", + values: ["2001:0:ce49:7601:e866:efff:62c3:ffff"], + } + ] - expect(cascadedScans).toBe(false); -}); + expect(cascadedScans()).toBe(true); + }); -test("Throws errors when missing fields", () => { - const scopeLimiter = { - validOnMissingRender: false, - allOf: [ - { - key: "scope.cascading.securecodebox.io/domain", - operator: "In", - values: ["{{attributes.hostname}}"], + test("matches if there is an IPv4/6 mismatch", () => { + annotations = { + "scope.cascading.securecodebox.io/cidr": "2001:0:ce49:7601:e866:efff:62c3:fffe/16", } - ] - } - - const finding = { - attributes: { - hostname: "notexample.com", - } - }; - - const cascadedScans = () => isInScope( - scopeLimiter, - {}, - finding, - {}, - ); + scopeLimiter.allOf = [ + { + key: "scope.cascading.securecodebox.io/cidr", + operator: "InCIDR", + values: ["10.0.1.0"], + } + ] - expect(cascadedScans).toThrowError("using operator 'In': the referenced annotation may not be undefined"); -}); + expect(cascadedScans()).toBe(true); + }); -test("Test templating into a list from multiple subkeys", () => { - const annotations = { - "scope.cascading.securecodebox.io/CIDR": "127.0.0.0/8", - } - const scopeLimiter = { - validOnMissingRender: false, - allOf: [ - { - key: "scope.cascading.securecodebox.io/CIDR", - operator: "InCIDR", - values: ["{{#list}} attributes.addresses.ip {{/list}}"], + test("does not match if there is an IPv4/6 mismatch AND an out-of-scope IPv4/6 match", () => { + annotations = { + "scope.cascading.securecodebox.io/CIDR4": "127.0.0.0/8", + "scope.cascading.securecodebox.io/CIDR6": "2001:0:ce49:7601:e866:efff:62c3:fffe/16", } - ] - } - - const finding = { - attributes: { - addresses: [ + scopeLimiter.allOf = [ { - "ip": "127.0.0.1" + key: "scope.cascading.securecodebox.io/CIDR4", + operator: "InCIDR", + values: ["192.168.178.42"], }, { - "ip": "fe80::4eb3:e128:53cc:5722" + key: "scope.cascading.securecodebox.io/CIDR6", + operator: "InCIDR", + values: ["192.168.178.42"], } ] - } - }; - - const cascadedScans = isInScope( - scopeLimiter, - annotations, - finding, - {}, - ); - - expect(cascadedScans).toBe(true); -}); + expect(cascadedScans()).toBe(false); + }); -test("Templating into list fails if key is not present in all findings", () => { - const annotations = { - "scope.cascading.securecodebox.io/CIDR": "127.0.0.0/8", - } - const scopeLimiter = { - validOnMissingRender: false, - allOf: [ - { - key: "scope.cascading.securecodebox.io/CIDR", - operator: "InCIDR", - values: ["{{#list}} attributes.addresses.ip {{/list}}"], + test("does not match if there exist out-of-scope matched IPv4/6 entries", () => { + annotations = { + "scope.cascading.securecodebox.io/CIDR4": "127.0.0.0/8", + "scope.cascading.securecodebox.io/CIDR6": "2001:0:ce49:7601:e866:efff:62c3:fffe/16", } - ] - } - - const finding = { - attributes: { - addresses: [ + scopeLimiter.allOf = [ { - "ip": "127.0.0.1" + key: "scope.cascading.securecodebox.io/CIDR4", + operator: "InCIDR", + values: ["192.168.178.42", "2001:0:ce49:7601:e866:efff:62c3:fefe"], }, { - "ip": "fe80::4eb3:e128:53cc:5722" - }, - { - "other_key": "test" + key: "scope.cascading.securecodebox.io/CIDR6", + operator: "InCIDR", + values: ["192.168.178.42", "2001:0:ce49:7601:e866:efff:62c3:fefe"], } ] - } - }; - - const cascadedScans = isInScope( - scopeLimiter, - annotations, - finding, - {}, - ); - - expect(cascadedScans).toBe(false); -}); + expect(cascadedScans()).toBe(false); + }); -test("Templating into list does not fail scope if validOnMissingRender is set and templating key is not present in all findings", () => { - const annotations = { - "scope.cascading.securecodebox.io/CIDR": "127.0.0.0/8", - } - const scopeLimiter = { - validOnMissingRender: true, - allOf: [ - { - key: "scope.cascading.securecodebox.io/CIDR", - operator: "InCIDR", - values: ["{{#list}} attributes.addresses.ip {{/list}}"], + test("matches if there exist only in-scope matched IPv4/6 entries", () => { + annotations = { + "scope.cascading.securecodebox.io/CIDR4": "127.0.0.0/8", + "scope.cascading.securecodebox.io/CIDR6": "2001:0:ce49:7601:e866:efff:62c3:fffe/16", } - ] - } - - const finding = { - attributes: { - addresses: [ + scopeLimiter.allOf = [ { - "ip": "127.0.0.1" + key: "scope.cascading.securecodebox.io/CIDR4", + operator: "InCIDR", + values: ["127.0.0.5", "2001:0:ce49:7601:e866:efff:62c3:fefe"], }, { - "ip": "fe80::4eb3:e128:53cc:5722" - }, - { - "other_key": "test" + key: "scope.cascading.securecodebox.io/CIDR6", + operator: "InCIDR", + values: ["127.0.0.5", "2001:0:ce49:7601:e866:efff:62c3:fefe"], } ] - } - }; - - const cascadedScans = isInScope( - scopeLimiter, - annotations, - finding, - {}, - ); - - expect(cascadedScans).toBe(true); -}); + expect(cascadedScans()).toBe(true); + }); -test("Test matching both IPv4 and v6 addresses in CIDR", () => { - const annotations = { - "scope.cascading.securecodebox.io/CIDR4": "127.0.0.0/8", - "scope.cascading.securecodebox.io/CIDR6": "2001:0:ce49:7601:e866:efff:62c3:fffe/16", - } - const scopeLimiter = { - validOnMissingRender: false, - allOf: [ - { - key: "scope.cascading.securecodebox.io/CIDR4", - operator: "InCIDR", - values: ["{{#list}} attributes.addresses.ip {{/list}}"], - }, - { - key: "scope.cascading.securecodebox.io/CIDR6", - operator: "InCIDR", - values: ["{{#list}} attributes.addresses.ip {{/list}}"], + test("throws error if IPv4 address is invalid even if scope is in IPv6", () => { + annotations = { + "scope.cascading.securecodebox.io/cidr": "2001:0:ce49:7601:e866:efff:62c3:fffe/16", } - ] - } - - const finding = { - attributes: { - addresses: [ - { - "ip": "127.0.0.5" - }, + scopeLimiter.allOf = [ { - "ip": "2001:0:ce49:7601:e866:efff:62c3:fefe" - }, + key: "scope.cascading.securecodebox.io/cidr", + operator: "InCIDR", + values: ["10.0.0.257"], // Invalid IPv4 + } ] - } - }; - - const cascadedScans = isInScope( - scopeLimiter, - annotations, - finding, - {}, - ); - expect(cascadedScans).toBe(true); -}); + expect(cascadedScans).toThrowError("Bad characters detected in address: .."); + }); -test("Test failing one of the IPv4 and v6 addresses", () => { - const annotations = { - "scope.cascading.securecodebox.io/CIDR4": "127.0.0.0/8", - "scope.cascading.securecodebox.io/CIDR6": "2001:0:ce49:7601:e866:efff:62c3:fffe/16", - } - const scopeLimiter = { - validOnMissingRender: false, - allOf: [ - { - key: "scope.cascading.securecodebox.io/CIDR4", - operator: "InCIDR", - values: ["{{#list}} attributes.addresses.ip {{/list}}"], - }, - { - key: "scope.cascading.securecodebox.io/CIDR6", - operator: "InCIDR", - values: ["{{#list}} attributes.addresses.ip {{/list}}"], + test("Throws error if IPv6 address is invalid even if scope is in IPv4", () => { + annotations = { + "scope.cascading.securecodebox.io/cidr": "10.0.0.0/16", } - ] - } - - const finding = { - attributes: { - addresses: [ - { - "ip": "192.168.178.42" - }, + scopeLimiter.allOf = [ { - "ip": "2001:0:ce49:7601:e866:efff:62c3:fefe" - }, + key: "scope.cascading.securecodebox.io/cidr", + operator: "InCIDR", + values: ["2001:0:ce49:7601:e866:efff:62c3"], + } ] - } - }; - const cascadedScans = isInScope( - scopeLimiter, - annotations, - finding, - {}, - ); + expect(cascadedScans).toThrowError("Incorrect number of groups found"); + }); + }); - expect(cascadedScans).toBe(false); -}); - -test("Test v6 constraint without v6 address present does not block the scope", () => { - const annotations = { - "scope.cascading.securecodebox.io/CIDR4": "127.0.0.0/8", - "scope.cascading.securecodebox.io/CIDR6": "2001:0:ce49:7601:e866:efff:62c3:fffe/16", - } - const scopeLimiter = { - validOnMissingRender: false, - allOf: [ - { - key: "scope.cascading.securecodebox.io/CIDR4", - operator: "InCIDR", - values: ["{{#list}} attributes.addresses.ip {{/list}}"], - }, - { - key: "scope.cascading.securecodebox.io/CIDR6", - operator: "InCIDR", - values: ["{{#list}} attributes.addresses.ip {{/list}}"], + describe("SubdomainOf", function () { + test("matches if is subdomain", () => { + annotations = { + "scope.cascading.securecodebox.io/domain": "example.com", } - ] - } - - const finding = { - attributes: { - addresses: [ + scopeLimiter.allOf = [ { - "ip": "192.168.178.42" - }, + key: "scope.cascading.securecodebox.io/domain", + operator: "SubdomainOf", + values: ["subdomain.example.com"], + } ] - } - }; + expect(cascadedScans()).toBe(true); + }); - const cascadedScans = isInScope( - scopeLimiter, - annotations, - finding, - {}, - ); + test("does not match if is not subdomain", () => { + annotations = { + "scope.cascading.securecodebox.io/domain": "example.com", + } + scopeLimiter.allOf = [ + { + key: "scope.cascading.securecodebox.io/domain", + operator: "SubdomainOf", + values: ["notexample.com"], + } + ] + expect(cascadedScans()).toBe(false); + }); - expect(cascadedScans).toBe(false); -}); + test("matches if is the domain itself", () => { + annotations = { + "scope.cascading.securecodebox.io/domain": "example.com", + } + scopeLimiter.allOf = [ + { + key: "scope.cascading.securecodebox.io/domain", + operator: "SubdomainOf", + values: ["example.com"], + } + ] + expect(cascadedScans()).toBe(true); + }); -test("Test templating list with invalid keys", () => { - const annotations = { - "scope.cascading.securecodebox.io/CIDR": "127.0.0.0/8", - } - const scopeLimiter = { - validOnMissingRender: false, - allOf: [ - { - key: "scope.cascading.securecodebox.io/CIDR", - operator: "InCIDR", - values: ["{{#list}}attributes.randomkey.ip{{/list}}"], + test("matches if providing a sub-sub domain of a sub-domain", () => { + annotations = { + "scope.cascading.securecodebox.io/domain": "www.example.com", } - ] - } + scopeLimiter.allOf = [ + { + key: "scope.cascading.securecodebox.io/domain", + operator: "SubdomainOf", + values: ["test.www.example.com"], + } + ] + expect(cascadedScans()).toBe(true); + }); - const cascadedScans = isInScope( - scopeLimiter, - annotations, - {}, - {}, - ); + test("matches if providing a deep subdomain of a deep subdomain", () => { + annotations = { + "scope.cascading.securecodebox.io/domain": "a.b.c.d.e.example.com", + } + scopeLimiter.allOf = [ + { + key: "scope.cascading.securecodebox.io/domain", + operator: "SubdomainOf", + values: ["z.a.b.c.d.e.example.com"], + } + ] + expect(cascadedScans()).toBe(true); + }); - expect(cascadedScans).toBe(false); -}); + test("does not match even if differences are deep in the subdomain tree", () => { + annotations = { + "scope.cascading.securecodebox.io/domain": "a.b.c.d.e.example.com", + } + scopeLimiter.allOf = [ + { + key: "scope.cascading.securecodebox.io/domain", + operator: "SubdomainOf", + values: ["z.b.c.d.e.example.com"], + } + ] + expect(cascadedScans()).toBe(false); + }); -test("Test templating list with too short key", () => { - const annotations = { - "scope.cascading.securecodebox.io/CIDR": "127.0.0.0/8", - } - const scopeLimiter = { - validOnMissingRender: false, - allOf: [ + test("does not match if providing a sub domain of a different sub-domain", () => { + annotations = { + "scope.cascading.securecodebox.io/domain": "www.example.com", + } + scopeLimiter.anyOf = [ + { + key: "scope.cascading.securecodebox.io/domain", + operator: "SubdomainOf", + values: ["test.example.com"], + } + ] + expect(cascadedScans()).toBe(false); + }); + }); +}) + +describe("ScopeLimiter", function () { + it("does not match if one of selector types does not match", () => { + annotations = { + "scope.cascading.securecodebox.io/domains": "example.com", + } + scopeLimiter.allOf = [ { - key: "scope.cascading.securecodebox.io/CIDR", - operator: "InCIDR", - values: ["{{#list}}attributes{{/list}}"], + key: "scope.cascading.securecodebox.io/domains", + operator: "Contains", + values: ["example.com"], } ] - } - - const cascadedScans = () => isInScope( - scopeLimiter, - annotations, - {}, - {}, - ); - - expect(cascadedScans).toThrowError("Invalid list key 'attributes'. List key must be at least 2 levels deep. E.g. 'attributes.addresses.ip'"); -}); - -test("Test templating list of strings", () => { - const annotations = { - "scope.cascading.securecodebox.io/domain": "example.com", - } - const scopeLimiter = { - validOnMissingRender: false, - allOf: [ + scopeLimiter.noneOf = [ { - key: "scope.cascading.securecodebox.io/domain", - operator: "SubdomainOf", - values: ["{{attributes.domains}}"], + key: "scope.cascading.securecodebox.io/domains", + operator: "Contains", + values: ["example.com"], } ] - } - - const finding = { - attributes: { - domains: ["example.com", "subdomain.example.com"], - } - }; - - const cascadedScans = isInScope( - scopeLimiter, - annotations, - finding, - {}, - ); - - expect(cascadedScans).toBe(true); -}); + expect(cascadedScans()).toBe(false); + }); +}) diff --git a/hooks/cascading-scans/hook/scope-limiter.ts b/hooks/cascading-scans/hook/scope-limiter.ts index 8886236696..40f598fa1c 100644 --- a/hooks/cascading-scans/hook/scope-limiter.ts +++ b/hooks/cascading-scans/hook/scope-limiter.ts @@ -99,19 +99,37 @@ export function isInScope( const delimiter = ";;;;" let rendered = Mustache.render(mapped, { ...finding, - // Custom Mustache list function to select attributes inside a list. - // returns a string containing a list delimited by `delimiter` defined above. - "list": function () { + // These custom mustache functions all return a string containing a list delimited by `delimiter` defined above. + "keyinobjectlist": function () { + // Select attributes inside a list of objects return function (text, render) { - const path = text.trim().split("."); - if (path.length < 2) { - throw new Error(`Invalid list key '${text}'. List key must be at least 2 levels deep. E.g. 'attributes.addresses.ip'`) + text = text.trim(); + const path = text.split("."); + if (path.length < 3) { + throw new Error(`Invalid list key '${text}'. List key must be at least 3 levels deep. E.g. 'attributes.addresses.ip'`) } const listKey = path.slice(0, path.length - 1).join("."); const objectKey = path.pop(); return render(`{{#${listKey}}}{{${objectKey}}}${delimiter}{{/${listKey}}}`); } - } + }, + "list": function () { + // Select a complete list + return function (text, render) { + text = text.trim(); + const path = text.split("."); + if (path.length < 2) { + throw new Error(`Invalid list key '${text}'. List key must be at least 2 levels deep. E.g. 'attributes.addresses'`) + } + return render(`{{#${text}}}{{.}}${delimiter}{{/${text}}}`); + } + }, + "split": function () { + // Split an existing list by comma + return function (text, render) { + return render(text).trim().replace(",", delimiter); + } + }, } ); // If the final render includes a delimiter, unpack the rendered string to an actual list @@ -134,9 +152,9 @@ export function isInScope( // All the different scope limiter fields must match (i.e. results of `allOf`, `anyOf`, `noneOf` are ANDed). // If one of those fields is not declared, regard it as matched. return [ - scopeLimiter.allOf !== undefined ? scopeLimiter.allOf.every(validateRequirement) : true, - scopeLimiter.anyOf !== undefined ? scopeLimiter.anyOf.some(validateRequirement) : true, - scopeLimiter.noneOf !== undefined ? !scopeLimiter.noneOf.some(validateRequirement) : true, + scopeLimiter.allOf !== undefined && scopeLimiter.allOf.length > 0 ? scopeLimiter.allOf.every(validateRequirement) : true, + scopeLimiter.anyOf !== undefined && scopeLimiter.anyOf.length > 0 ? scopeLimiter.anyOf.some(validateRequirement) : true, + scopeLimiter.noneOf !== undefined && scopeLimiter.noneOf.length > 0 ? !scopeLimiter.noneOf.some(validateRequirement) : true, ].every(entry => entry === true); } From e162d92b1cc1a1c1add8ad951c2f06996b68cb05 Mon Sep 17 00:00:00 2001 From: Jop Zitman Date: Mon, 29 Nov 2021 14:11:11 +0100 Subject: [PATCH 36/54] Replace all `test` by `it` Signed-off-by: Jop Zitman --- .../hook/scope-limiter.test.js | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/hooks/cascading-scans/hook/scope-limiter.test.js b/hooks/cascading-scans/hook/scope-limiter.test.js index 0b06ee3b59..1a2d460d0f 100644 --- a/hooks/cascading-scans/hook/scope-limiter.test.js +++ b/hooks/cascading-scans/hook/scope-limiter.test.js @@ -46,7 +46,7 @@ it("Requirement key must start with 'scope.cascading.securecodebox.io/'", () => expect(cascadedScans).toThrowError("key 'engagement.scope/domains' is invalid: key does not start with 'scope.cascading.securecodebox.io/'"); }); -test("Requirement key must map to an annotation", () => { +it("Requirement key must map to an annotation", () => { scopeLimiter.allOf = [ { key: "scope.cascading.securecodebox.io/domain", @@ -94,7 +94,7 @@ describe("Templating", function () { }); describe("validOnMissingRender", function () { - test("does not match if mapping is not available: validOnMissingRender false", () => { + it("does not match if mapping is not available: validOnMissingRender false", () => { annotations = { "scope.cascading.securecodebox.io/domains": "example.com,subdomain.example.com", } @@ -113,7 +113,7 @@ describe("Templating", function () { }) describe("aliases", function () { - test("matches using templates populated with finding and a mapped selector", () => { + it("matches using templates populated with finding and a mapped selector", () => { annotations = { "scope.cascading.securecodebox.io/domains": "example.com,subdomain.example.com", } @@ -130,7 +130,7 @@ describe("Templating", function () { expect(cascadedScans()).toBe(true); }); - test("Matches if mapping is not available: validOnMissingRender true", () => { + it("Matches if mapping is not available: validOnMissingRender true", () => { annotations = { "scope.cascading.securecodebox.io/domains": "example.com,subdomain.example.com", } @@ -152,7 +152,7 @@ describe("Templating", function () { describe("lists", function () { describe("list", function () { - test("matches with list of strings", () => { + it("matches with list of strings", () => { annotations = { "scope.cascading.securecodebox.io/domain": "example.com", } @@ -173,7 +173,7 @@ describe("Templating", function () { expect(cascadedScans()).toBe(true); }); - test("fails with too short key", () => { + it("fails with too short key", () => { annotations = { "scope.cascading.securecodebox.io/CIDR": "127.0.0.0/8", } @@ -192,7 +192,7 @@ describe("Templating", function () { }) describe("split", function () { - test("matches on simple string", () => { + it("matches on simple string", () => { annotations = { "scope.cascading.securecodebox.io/domains": "example.com", } @@ -206,7 +206,7 @@ describe("Templating", function () { expect(cascadedScans()).toBe(true); }); - test("matches on template", () => { + it("matches on template", () => { annotations = { "scope.cascading.securecodebox.io/domains": "example.com", } @@ -229,7 +229,7 @@ describe("Templating", function () { }) describe("keyinobjectlist", function () { - test("matches if templating key is present in all list entries", () => { + it("matches if templating key is present in all list entries", () => { annotations = { "scope.cascading.securecodebox.io/CIDR": "127.0.0.0/8", } @@ -257,7 +257,7 @@ describe("Templating", function () { expect(cascadedScans()).toBe(true); }); - test("does not match if list with invalid keys", () => { + it("does not match if list with invalid keys", () => { annotations = { "scope.cascading.securecodebox.io/CIDR": "127.0.0.0/8", } @@ -274,7 +274,7 @@ describe("Templating", function () { expect(cascadedScans()).toBe(false); }); - test("does not match if templating key is not present in all list entries", () => { + it("does not match if templating key is not present in all list entries", () => { annotations = { "scope.cascading.securecodebox.io/CIDR": "127.0.0.0/8", } @@ -305,7 +305,7 @@ describe("Templating", function () { expect(cascadedScans()).toBe(false); }); - test("matches if validOnMissingRender is set and templating key is not present in all list entries", () => { + it("matches if validOnMissingRender is set and templating key is not present in all list entries", () => { annotations = { "scope.cascading.securecodebox.io/CIDR": "127.0.0.0/8", } @@ -373,7 +373,7 @@ describe("Operator", function () { expect(cascadedScans()).toBe(true); }); - test("does not match if ip not in subnet", () => { + it("does not match if ip not in subnet", () => { annotations = { "scope.cascading.securecodebox.io/cidr": "10.0.0.0/32", } @@ -388,7 +388,7 @@ describe("Operator", function () { expect(cascadedScans()).toBe(false); }); - test("matches if ip in subnet (IPv6)", () => { + it("matches if ip in subnet (IPv6)", () => { annotations = { "scope.cascading.securecodebox.io/cidr": "2001:0:ce49:7601:e866:efff:62c3:fffe/16", } @@ -403,7 +403,7 @@ describe("Operator", function () { expect(cascadedScans()).toBe(true); }); - test("matches if there is an IPv4/6 mismatch", () => { + it("matches if there is an IPv4/6 mismatch", () => { annotations = { "scope.cascading.securecodebox.io/cidr": "2001:0:ce49:7601:e866:efff:62c3:fffe/16", } @@ -418,7 +418,7 @@ describe("Operator", function () { expect(cascadedScans()).toBe(true); }); - test("does not match if there is an IPv4/6 mismatch AND an out-of-scope IPv4/6 match", () => { + it("does not match if there is an IPv4/6 mismatch AND an out-of-scope IPv4/6 match", () => { annotations = { "scope.cascading.securecodebox.io/CIDR4": "127.0.0.0/8", "scope.cascading.securecodebox.io/CIDR6": "2001:0:ce49:7601:e866:efff:62c3:fffe/16", @@ -438,7 +438,7 @@ describe("Operator", function () { expect(cascadedScans()).toBe(false); }); - test("does not match if there exist out-of-scope matched IPv4/6 entries", () => { + it("does not match if there exist out-of-scope matched IPv4/6 entries", () => { annotations = { "scope.cascading.securecodebox.io/CIDR4": "127.0.0.0/8", "scope.cascading.securecodebox.io/CIDR6": "2001:0:ce49:7601:e866:efff:62c3:fffe/16", @@ -458,7 +458,7 @@ describe("Operator", function () { expect(cascadedScans()).toBe(false); }); - test("matches if there exist only in-scope matched IPv4/6 entries", () => { + it("matches if there exist only in-scope matched IPv4/6 entries", () => { annotations = { "scope.cascading.securecodebox.io/CIDR4": "127.0.0.0/8", "scope.cascading.securecodebox.io/CIDR6": "2001:0:ce49:7601:e866:efff:62c3:fffe/16", @@ -478,7 +478,7 @@ describe("Operator", function () { expect(cascadedScans()).toBe(true); }); - test("throws error if IPv4 address is invalid even if scope is in IPv6", () => { + it("throws error if IPv4 address is invalid even if scope is in IPv6", () => { annotations = { "scope.cascading.securecodebox.io/cidr": "2001:0:ce49:7601:e866:efff:62c3:fffe/16", } @@ -493,7 +493,7 @@ describe("Operator", function () { expect(cascadedScans).toThrowError("Bad characters detected in address: .."); }); - test("Throws error if IPv6 address is invalid even if scope is in IPv4", () => { + it("Throws error if IPv6 address is invalid even if scope is in IPv4", () => { annotations = { "scope.cascading.securecodebox.io/cidr": "10.0.0.0/16", } @@ -510,7 +510,7 @@ describe("Operator", function () { }); describe("SubdomainOf", function () { - test("matches if is subdomain", () => { + it("matches if is subdomain", () => { annotations = { "scope.cascading.securecodebox.io/domain": "example.com", } @@ -524,7 +524,7 @@ describe("Operator", function () { expect(cascadedScans()).toBe(true); }); - test("does not match if is not subdomain", () => { + it("does not match if is not subdomain", () => { annotations = { "scope.cascading.securecodebox.io/domain": "example.com", } @@ -538,7 +538,7 @@ describe("Operator", function () { expect(cascadedScans()).toBe(false); }); - test("matches if is the domain itself", () => { + it("matches if is the domain itself", () => { annotations = { "scope.cascading.securecodebox.io/domain": "example.com", } @@ -552,7 +552,7 @@ describe("Operator", function () { expect(cascadedScans()).toBe(true); }); - test("matches if providing a sub-sub domain of a sub-domain", () => { + it("matches if providing a sub-sub domain of a sub-domain", () => { annotations = { "scope.cascading.securecodebox.io/domain": "www.example.com", } @@ -566,7 +566,7 @@ describe("Operator", function () { expect(cascadedScans()).toBe(true); }); - test("matches if providing a deep subdomain of a deep subdomain", () => { + it("matches if providing a deep subdomain of a deep subdomain", () => { annotations = { "scope.cascading.securecodebox.io/domain": "a.b.c.d.e.example.com", } @@ -580,7 +580,7 @@ describe("Operator", function () { expect(cascadedScans()).toBe(true); }); - test("does not match even if differences are deep in the subdomain tree", () => { + it("does not match even if differences are deep in the subdomain tree", () => { annotations = { "scope.cascading.securecodebox.io/domain": "a.b.c.d.e.example.com", } @@ -594,7 +594,7 @@ describe("Operator", function () { expect(cascadedScans()).toBe(false); }); - test("does not match if providing a sub domain of a different sub-domain", () => { + it("does not match if providing a sub domain of a different sub-domain", () => { annotations = { "scope.cascading.securecodebox.io/domain": "www.example.com", } From 73a3748f23e76f18c8ee48020e778119821aeb6d Mon Sep 17 00:00:00 2001 From: Jop Zitman Date: Mon, 29 Nov 2021 14:15:17 +0100 Subject: [PATCH 37/54] Rename `cascadedScans` to `isInScope` in test suite Signed-off-by: Jop Zitman --- .../hook/scope-limiter.test.js | 70 +++++++++---------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/hooks/cascading-scans/hook/scope-limiter.test.js b/hooks/cascading-scans/hook/scope-limiter.test.js index 1a2d460d0f..ef77e24b28 100644 --- a/hooks/cascading-scans/hook/scope-limiter.test.js +++ b/hooks/cascading-scans/hook/scope-limiter.test.js @@ -2,14 +2,14 @@ // // SPDX-License-Identifier: Apache-2.0 -const { isInScope } = require("./scope-limiter"); +const { isInScope: isInScopeInternal } = require("./scope-limiter"); let scopeLimiter = undefined let annotations = undefined let finding = undefined let scopeLimiterAliases = undefined -const cascadedScans = () => isInScope( +const isInScope = () => isInScopeInternal( scopeLimiter, annotations, finding, @@ -43,7 +43,7 @@ it("Requirement key must start with 'scope.cascading.securecodebox.io/'", () => values: ["{{attributes.hostname}}"], } ] - expect(cascadedScans).toThrowError("key 'engagement.scope/domains' is invalid: key does not start with 'scope.cascading.securecodebox.io/'"); + expect(isInScope).toThrowError("key 'engagement.scope/domains' is invalid: key does not start with 'scope.cascading.securecodebox.io/'"); }); it("Requirement key must map to an annotation", () => { @@ -61,7 +61,7 @@ it("Requirement key must map to an annotation", () => { } }; - expect(cascadedScans).toThrowError("using operator 'In': the referenced annotation may not be undefined"); + expect(isInScope).toThrowError("using operator 'In': the referenced annotation may not be undefined"); }); describe("Templating", function () { @@ -76,7 +76,7 @@ describe("Templating", function () { values: ["{{attributes.hostname}}"], } ] - expect(cascadedScans).toThrowError("using operator 'Contains': the referenced annotation may not be undefined"); + expect(isInScope).toThrowError("using operator 'Contains': the referenced annotation may not be undefined"); }); it("supports requirement value", () => { @@ -90,7 +90,7 @@ describe("Templating", function () { values: ["{{attributes.hostname}}"], } ] - expect(cascadedScans()).toBe(true); + expect(isInScope()).toBe(true); }); describe("validOnMissingRender", function () { @@ -108,7 +108,7 @@ describe("Templating", function () { finding = {} - expect(cascadedScans()).toBe(false); + expect(isInScope()).toBe(false); }); }) @@ -127,7 +127,7 @@ describe("Templating", function () { scopeLimiterAliases = { "hostname": "{{attributes.hostname}}", } - expect(cascadedScans()).toBe(true); + expect(isInScope()).toBe(true); }); it("Matches if mapping is not available: validOnMissingRender true", () => { @@ -145,7 +145,7 @@ describe("Templating", function () { ] finding = {} - expect(cascadedScans()).toBe(true); + expect(isInScope()).toBe(true); }); }) @@ -170,7 +170,7 @@ describe("Templating", function () { } }; - expect(cascadedScans()).toBe(true); + expect(isInScope()).toBe(true); }); it("fails with too short key", () => { @@ -187,7 +187,7 @@ describe("Templating", function () { finding = {} - expect(cascadedScans).toThrowError("Invalid list key 'attributes'. List key must be at least 2 levels deep. E.g. 'attributes.addresses'"); + expect(isInScope).toThrowError("Invalid list key 'attributes'. List key must be at least 2 levels deep. E.g. 'attributes.addresses'"); }); }) @@ -203,7 +203,7 @@ describe("Templating", function () { values: ["{{#split}}subdomain.example.com,www.example.com{{/split}}"], } ] - expect(cascadedScans()).toBe(true); + expect(isInScope()).toBe(true); }); it("matches on template", () => { @@ -224,7 +224,7 @@ describe("Templating", function () { } } - expect(cascadedScans()).toBe(true) + expect(isInScope()).toBe(true) }); }) @@ -254,7 +254,7 @@ describe("Templating", function () { } }; - expect(cascadedScans()).toBe(true); + expect(isInScope()).toBe(true); }); it("does not match if list with invalid keys", () => { @@ -271,7 +271,7 @@ describe("Templating", function () { finding = {} - expect(cascadedScans()).toBe(false); + expect(isInScope()).toBe(false); }); it("does not match if templating key is not present in all list entries", () => { @@ -302,7 +302,7 @@ describe("Templating", function () { } }; - expect(cascadedScans()).toBe(false); + expect(isInScope()).toBe(false); }); it("matches if validOnMissingRender is set and templating key is not present in all list entries", () => { @@ -334,7 +334,7 @@ describe("Templating", function () { } }; - expect(cascadedScans()).toBe(true); + expect(isInScope()).toBe(true); }); }) @@ -354,7 +354,7 @@ describe("Operator", function () { values: ["example.com"], } ] - expect(cascadedScans()).toBe(false); + expect(isInScope()).toBe(false); }); }); @@ -370,7 +370,7 @@ describe("Operator", function () { values: ["10.0.1.0"], } ] - expect(cascadedScans()).toBe(true); + expect(isInScope()).toBe(true); }); it("does not match if ip not in subnet", () => { @@ -385,7 +385,7 @@ describe("Operator", function () { } ] - expect(cascadedScans()).toBe(false); + expect(isInScope()).toBe(false); }); it("matches if ip in subnet (IPv6)", () => { @@ -400,7 +400,7 @@ describe("Operator", function () { } ] - expect(cascadedScans()).toBe(true); + expect(isInScope()).toBe(true); }); it("matches if there is an IPv4/6 mismatch", () => { @@ -415,7 +415,7 @@ describe("Operator", function () { } ] - expect(cascadedScans()).toBe(true); + expect(isInScope()).toBe(true); }); it("does not match if there is an IPv4/6 mismatch AND an out-of-scope IPv4/6 match", () => { @@ -435,7 +435,7 @@ describe("Operator", function () { values: ["192.168.178.42"], } ] - expect(cascadedScans()).toBe(false); + expect(isInScope()).toBe(false); }); it("does not match if there exist out-of-scope matched IPv4/6 entries", () => { @@ -455,7 +455,7 @@ describe("Operator", function () { values: ["192.168.178.42", "2001:0:ce49:7601:e866:efff:62c3:fefe"], } ] - expect(cascadedScans()).toBe(false); + expect(isInScope()).toBe(false); }); it("matches if there exist only in-scope matched IPv4/6 entries", () => { @@ -475,7 +475,7 @@ describe("Operator", function () { values: ["127.0.0.5", "2001:0:ce49:7601:e866:efff:62c3:fefe"], } ] - expect(cascadedScans()).toBe(true); + expect(isInScope()).toBe(true); }); it("throws error if IPv4 address is invalid even if scope is in IPv6", () => { @@ -490,7 +490,7 @@ describe("Operator", function () { } ] - expect(cascadedScans).toThrowError("Bad characters detected in address: .."); + expect(isInScope).toThrowError("Bad characters detected in address: .."); }); it("Throws error if IPv6 address is invalid even if scope is in IPv4", () => { @@ -505,7 +505,7 @@ describe("Operator", function () { } ] - expect(cascadedScans).toThrowError("Incorrect number of groups found"); + expect(isInScope).toThrowError("Incorrect number of groups found"); }); }); @@ -521,7 +521,7 @@ describe("Operator", function () { values: ["subdomain.example.com"], } ] - expect(cascadedScans()).toBe(true); + expect(isInScope()).toBe(true); }); it("does not match if is not subdomain", () => { @@ -535,7 +535,7 @@ describe("Operator", function () { values: ["notexample.com"], } ] - expect(cascadedScans()).toBe(false); + expect(isInScope()).toBe(false); }); it("matches if is the domain itself", () => { @@ -549,7 +549,7 @@ describe("Operator", function () { values: ["example.com"], } ] - expect(cascadedScans()).toBe(true); + expect(isInScope()).toBe(true); }); it("matches if providing a sub-sub domain of a sub-domain", () => { @@ -563,7 +563,7 @@ describe("Operator", function () { values: ["test.www.example.com"], } ] - expect(cascadedScans()).toBe(true); + expect(isInScope()).toBe(true); }); it("matches if providing a deep subdomain of a deep subdomain", () => { @@ -577,7 +577,7 @@ describe("Operator", function () { values: ["z.a.b.c.d.e.example.com"], } ] - expect(cascadedScans()).toBe(true); + expect(isInScope()).toBe(true); }); it("does not match even if differences are deep in the subdomain tree", () => { @@ -591,7 +591,7 @@ describe("Operator", function () { values: ["z.b.c.d.e.example.com"], } ] - expect(cascadedScans()).toBe(false); + expect(isInScope()).toBe(false); }); it("does not match if providing a sub domain of a different sub-domain", () => { @@ -605,7 +605,7 @@ describe("Operator", function () { values: ["test.example.com"], } ] - expect(cascadedScans()).toBe(false); + expect(isInScope()).toBe(false); }); }); }) @@ -629,6 +629,6 @@ describe("ScopeLimiter", function () { values: ["example.com"], } ] - expect(cascadedScans()).toBe(false); + expect(isInScope()).toBe(false); }); }) From b56f3ff0abf5073047fc62069156d478577bff04 Mon Sep 17 00:00:00 2001 From: Jop Zitman Date: Mon, 29 Nov 2021 14:31:19 +0100 Subject: [PATCH 38/54] Add more `Contains` tests Signed-off-by: Jop Zitman --- .../hook/scope-limiter.test.js | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/hooks/cascading-scans/hook/scope-limiter.test.js b/hooks/cascading-scans/hook/scope-limiter.test.js index ef77e24b28..9d29bb4a99 100644 --- a/hooks/cascading-scans/hook/scope-limiter.test.js +++ b/hooks/cascading-scans/hook/scope-limiter.test.js @@ -343,9 +343,23 @@ describe("Templating", function () { describe("Operator", function () { describe("Contains", function () { - it("does not match if selector should not match", () => { + it("matches if value is in annotation list", () => { annotations = { - "scope.cascading.securecodebox.io/domains": "subdomain.example.com", + "scope.cascading.securecodebox.io/domains": "subdomain.example.com,www.example.com", + } + scopeLimiter.allOf = [ + { + key: "scope.cascading.securecodebox.io/domains", + operator: "Contains", + values: ["subdomain.example.com"], + } + ] + expect(isInScope()).toBe(true); + }); + + it("does not match if value is not in annotation list", () => { + annotations = { + "scope.cascading.securecodebox.io/domains": "subdomain.example.com,www.example.com", } scopeLimiter.allOf = [ { @@ -356,6 +370,20 @@ describe("Operator", function () { ] expect(isInScope()).toBe(false); }); + + it("does not match if one of the values is not in annotation list", () => { + annotations = { + "scope.cascading.securecodebox.io/domains": "subdomain.example.com,www.example.com", + } + scopeLimiter.allOf = [ + { + key: "scope.cascading.securecodebox.io/domains", + operator: "Contains", + values: ["subdomain.example.com","example.com"], + } + ] + expect(isInScope()).toBe(false); + }); }); describe("InCIDR", function () { From c90379ddbfe00c991534b71d1a5c0cbd25ac5af1 Mon Sep 17 00:00:00 2001 From: Jop Zitman Date: Mon, 29 Nov 2021 14:37:07 +0100 Subject: [PATCH 39/54] Add `In` tests Signed-off-by: Jop Zitman --- .../hook/scope-limiter.test.js | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/hooks/cascading-scans/hook/scope-limiter.test.js b/hooks/cascading-scans/hook/scope-limiter.test.js index 9d29bb4a99..500c9dcf9d 100644 --- a/hooks/cascading-scans/hook/scope-limiter.test.js +++ b/hooks/cascading-scans/hook/scope-limiter.test.js @@ -342,6 +342,36 @@ describe("Templating", function () { }) describe("Operator", function () { + describe("In", function () { + it("matches if annotation is in value list", () => { + annotations = { + "scope.cascading.securecodebox.io/domains": "www.example.com", + } + scopeLimiter.allOf = [ + { + key: "scope.cascading.securecodebox.io/domains", + operator: "In", + values: ["subdomain.example.com", "www.example.com"], + } + ] + expect(isInScope()).toBe(true); + }); + + it("does not match if annotation is not in value list", () => { + annotations = { + "scope.cascading.securecodebox.io/domains": "www.example.com", + } + scopeLimiter.allOf = [ + { + key: "scope.cascading.securecodebox.io/domains", + operator: "In", + values: ["subdomain.example.com", "example.com"], + } + ] + expect(isInScope()).toBe(false); + }); + }) + describe("Contains", function () { it("matches if value is in annotation list", () => { annotations = { From ff081c035d67818dfcc9b43d65686a91bb6a2c6d Mon Sep 17 00:00:00 2001 From: Jop Zitman Date: Mon, 29 Nov 2021 15:22:02 +0100 Subject: [PATCH 40/54] Add trailing delimiter to internal split Signed-off-by: Jop Zitman --- hooks/cascading-scans/hook/scope-limiter.test.js | 15 +++++++++++++++ hooks/cascading-scans/hook/scope-limiter.ts | 7 ++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/hooks/cascading-scans/hook/scope-limiter.test.js b/hooks/cascading-scans/hook/scope-limiter.test.js index 500c9dcf9d..80f2e69abe 100644 --- a/hooks/cascading-scans/hook/scope-limiter.test.js +++ b/hooks/cascading-scans/hook/scope-limiter.test.js @@ -226,6 +226,21 @@ describe("Templating", function () { expect(isInScope()).toBe(true) }); + + it("does not ignore the last entry in the split list", () => { + annotations = { + "scope.cascading.securecodebox.io/domains": "example.com", + } + scopeLimiter.allOf = [ + { + key: "scope.cascading.securecodebox.io/domains", + operator: "SubdomainOf", + values: ["{{#split}}example.com,some.otherdomain.com{{/split}}"], + } + ] + + expect(isInScope()).toBe(false) + }); }) describe("keyinobjectlist", function () { diff --git a/hooks/cascading-scans/hook/scope-limiter.ts b/hooks/cascading-scans/hook/scope-limiter.ts index 40f598fa1c..e9b92daafd 100644 --- a/hooks/cascading-scans/hook/scope-limiter.ts +++ b/hooks/cascading-scans/hook/scope-limiter.ts @@ -127,7 +127,12 @@ export function isInScope( "split": function () { // Split an existing list by comma return function (text, render) { - return render(text).trim().replace(",", delimiter); + const result = render(text).trim().replace(",", delimiter); + if (result === "") { + return result; + } else { + return result.concat(delimiter) + } } }, } From e4f0324073f0bb5433427fa4ea251b07ed8b27f3 Mon Sep 17 00:00:00 2001 From: Jop Zitman Date: Mon, 29 Nov 2021 15:52:15 +0100 Subject: [PATCH 41/54] Rename `keyinobjectlist` -> `pickValues` and `list` -> `asList` Signed-off-by: Jop Zitman --- hooks/cascading-scans/hook/scope-limiter.test.js | 12 ++++++------ hooks/cascading-scans/hook/scope-limiter.ts | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/hooks/cascading-scans/hook/scope-limiter.test.js b/hooks/cascading-scans/hook/scope-limiter.test.js index 80f2e69abe..7b7b1eee50 100644 --- a/hooks/cascading-scans/hook/scope-limiter.test.js +++ b/hooks/cascading-scans/hook/scope-limiter.test.js @@ -160,7 +160,7 @@ describe("Templating", function () { { key: "scope.cascading.securecodebox.io/domain", operator: "SubdomainOf", - values: ["{{#list}}attributes.domains{{/list}}"], + values: ["{{#asList}}attributes.domains{{/asList}}"], } ] @@ -181,7 +181,7 @@ describe("Templating", function () { { key: "scope.cascading.securecodebox.io/CIDR", operator: "InCIDR", - values: ["{{#list}}attributes{{/list}}"], + values: ["{{#asList}}attributes{{/asList}}"], } ] @@ -252,7 +252,7 @@ describe("Templating", function () { { key: "scope.cascading.securecodebox.io/CIDR", operator: "InCIDR", - values: ["{{#keyinobjectlist}}attributes.addresses.ip{{/keyinobjectlist}}"], + values: ["{{#pickValues}}attributes.addresses.ip{{/pickValues}}"], } ] @@ -280,7 +280,7 @@ describe("Templating", function () { { key: "scope.cascading.securecodebox.io/CIDR", operator: "InCIDR", - values: ["{{#keyinobjectlist}}attributes.randomkey.ip{{/keyinobjectlist}}"], + values: ["{{#pickValues}}attributes.randomkey.ip{{/pickValues}}"], } ] @@ -297,7 +297,7 @@ describe("Templating", function () { { key: "scope.cascading.securecodebox.io/CIDR", operator: "InCIDR", - values: ["{{#keyinobjectlist}}attributes.addresses.ip{{/keyinobjectlist}}"], + values: ["{{#pickValues}}attributes.addresses.ip{{/pickValues}}"], } ] @@ -329,7 +329,7 @@ describe("Templating", function () { { key: "scope.cascading.securecodebox.io/CIDR", operator: "InCIDR", - values: ["{{#keyinobjectlist}}attributes.addresses.ip{{/keyinobjectlist}}"], + values: ["{{#pickValues}}attributes.addresses.ip{{/pickValues}}"], } ] diff --git a/hooks/cascading-scans/hook/scope-limiter.ts b/hooks/cascading-scans/hook/scope-limiter.ts index e9b92daafd..56c0ffed9d 100644 --- a/hooks/cascading-scans/hook/scope-limiter.ts +++ b/hooks/cascading-scans/hook/scope-limiter.ts @@ -100,7 +100,7 @@ export function isInScope( let rendered = Mustache.render(mapped, { ...finding, // These custom mustache functions all return a string containing a list delimited by `delimiter` defined above. - "keyinobjectlist": function () { + "pickValues": function () { // Select attributes inside a list of objects return function (text, render) { text = text.trim(); @@ -113,7 +113,7 @@ export function isInScope( return render(`{{#${listKey}}}{{${objectKey}}}${delimiter}{{/${listKey}}}`); } }, - "list": function () { + "asList": function () { // Select a complete list return function (text, render) { text = text.trim(); From 2500c0c31299de990c2840cb04af3ab28fff5e49 Mon Sep 17 00:00:00 2001 From: Max Maass Date: Tue, 30 Nov 2021 08:47:35 +0100 Subject: [PATCH 42/54] Add failing test case for trailing comma Signed-off-by: Max Maass --- hooks/cascading-scans/hook/scope-limiter.test.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/hooks/cascading-scans/hook/scope-limiter.test.js b/hooks/cascading-scans/hook/scope-limiter.test.js index 7b7b1eee50..d5e3716370 100644 --- a/hooks/cascading-scans/hook/scope-limiter.test.js +++ b/hooks/cascading-scans/hook/scope-limiter.test.js @@ -241,6 +241,21 @@ describe("Templating", function () { expect(isInScope()).toBe(false) }); + + it("does not create extra empty entry for trailing comma", () => { + annotations = { + "scope.cascading.securecodebox.io/domains": "example.com", + } + scopeLimiter.allOf = [ + { + key: "scope.cascading.securecodebox.io/domains", + operator: "SubdomainOf", + values: ["{{#split}}example.com,test.example.com,{{/split}}"], + } + ] + + expect(isInScope()).toBe(true) + }); }) describe("keyinobjectlist", function () { From edf7161fa6361168f9bd057290e7196cbf2f1ab5 Mon Sep 17 00:00:00 2001 From: Jop Zitman Date: Tue, 30 Nov 2021 09:54:49 +0100 Subject: [PATCH 43/54] Add more tests for trailing comma in `list` Signed-off-by: Jop Zitman --- .../hook/scope-limiter.test.js | 83 ++++++++++++++++++- hooks/cascading-scans/hook/scope-limiter.ts | 4 +- 2 files changed, 83 insertions(+), 4 deletions(-) diff --git a/hooks/cascading-scans/hook/scope-limiter.test.js b/hooks/cascading-scans/hook/scope-limiter.test.js index d5e3716370..56cb7344d2 100644 --- a/hooks/cascading-scans/hook/scope-limiter.test.js +++ b/hooks/cascading-scans/hook/scope-limiter.test.js @@ -242,7 +242,7 @@ describe("Templating", function () { expect(isInScope()).toBe(false) }); - it("does not create extra empty entry for trailing comma", () => { + it("does not create extra empty entry for trailing comma (matching limiter)", () => { annotations = { "scope.cascading.securecodebox.io/domains": "example.com", } @@ -256,7 +256,86 @@ describe("Templating", function () { expect(isInScope()).toBe(true) }); - }) + + it("does not create extra empty entry for trailing comma (non-matching limiter)", () => { + annotations = { + "scope.cascading.securecodebox.io/domains": "example.com", + } + scopeLimiter.allOf = [ + { + key: "scope.cascading.securecodebox.io/domains", + operator: "SubdomainOf", + values: ["{{#split}}example.com,notexample.com,{{/split}}"], + } + ] + + expect(isInScope()).toBe(false) + scopeLimiter.validOnMissingRender = true + expect(isInScope()).toBe(false) + }); + + it("does not create extra empty entry for trailing comma (non-matching limiter from template)", () => { + annotations = { + "scope.cascading.securecodebox.io/domains": "example.com", + } + scopeLimiter.allOf = [ + { + key: "scope.cascading.securecodebox.io/domains", + operator: "SubdomainOf", + values: ["{{#split}}{{#attributes.addresses}}{{domain}},{{/attributes.addresses}}{{/split}}"], + } + ] + + finding = { + attributes: { + addresses: [ + { + "domain": "example.com" + }, + { + "domain": "notexample.com" + } + ] + } + }; + + // Fails because 'notexample.com' is not a subdomain of example.com + expect(isInScope()).toBe(false) + scopeLimiter.validOnMissingRender = true + expect(isInScope()).toBe(false) + }); + + it("respects validOnMissingRender", () => { + annotations = { + "scope.cascading.securecodebox.io/domains": "example.com", + } + scopeLimiter.allOf = [ + { + key: "scope.cascading.securecodebox.io/domains", + operator: "SubdomainOf", + values: ["{{#split}}{{#attributes.addresses}}{{domain}},{{/attributes.addresses}}{{/split}}"], + } + ] + + finding = { + attributes: { + addresses: [ + { + "domain": "example.com" + }, + { + "typo": "www.example.com" + } + ] + } + }; + + // Fails because 'typo' wasn't templated + expect(isInScope()).toBe(false) + scopeLimiter.validOnMissingRender = true + expect(isInScope()).toBe(true) + }); + }); describe("keyinobjectlist", function () { it("matches if templating key is present in all list entries", () => { diff --git a/hooks/cascading-scans/hook/scope-limiter.ts b/hooks/cascading-scans/hook/scope-limiter.ts index 56c0ffed9d..72b38b825d 100644 --- a/hooks/cascading-scans/hook/scope-limiter.ts +++ b/hooks/cascading-scans/hook/scope-limiter.ts @@ -127,8 +127,8 @@ export function isInScope( "split": function () { // Split an existing list by comma return function (text, render) { - const result = render(text).trim().replace(",", delimiter); - if (result === "") { + const result = render(text).trim().replaceAll(",", delimiter); + if (result === "" || result.endsWith(delimiter)) { return result; } else { return result.concat(delimiter) From b2285c2e279350b8f0e074e53f1142920b2ba859 Mon Sep 17 00:00:00 2001 From: Jop Zitman Date: Tue, 30 Nov 2021 09:56:15 +0100 Subject: [PATCH 44/54] Rename `asList` to `getValues` Signed-off-by: Jop Zitman --- hooks/cascading-scans/hook/scope-limiter.test.js | 4 ++-- hooks/cascading-scans/hook/scope-limiter.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/hooks/cascading-scans/hook/scope-limiter.test.js b/hooks/cascading-scans/hook/scope-limiter.test.js index 56cb7344d2..95adb0ba18 100644 --- a/hooks/cascading-scans/hook/scope-limiter.test.js +++ b/hooks/cascading-scans/hook/scope-limiter.test.js @@ -160,7 +160,7 @@ describe("Templating", function () { { key: "scope.cascading.securecodebox.io/domain", operator: "SubdomainOf", - values: ["{{#asList}}attributes.domains{{/asList}}"], + values: ["{{#getValues}}attributes.domains{{/getValues}}"], } ] @@ -181,7 +181,7 @@ describe("Templating", function () { { key: "scope.cascading.securecodebox.io/CIDR", operator: "InCIDR", - values: ["{{#asList}}attributes{{/asList}}"], + values: ["{{#getValues}}attributes{{/getValues}}"], } ] diff --git a/hooks/cascading-scans/hook/scope-limiter.ts b/hooks/cascading-scans/hook/scope-limiter.ts index 72b38b825d..f0958a10f7 100644 --- a/hooks/cascading-scans/hook/scope-limiter.ts +++ b/hooks/cascading-scans/hook/scope-limiter.ts @@ -113,7 +113,7 @@ export function isInScope( return render(`{{#${listKey}}}{{${objectKey}}}${delimiter}{{/${listKey}}}`); } }, - "asList": function () { + "getValues": function () { // Select a complete list return function (text, render) { text = text.trim(); From 051b3ae50c7654b49575a383ce6c4163c8e06ea8 Mon Sep 17 00:00:00 2001 From: Max Maass Date: Tue, 30 Nov 2021 15:10:17 +0100 Subject: [PATCH 45/54] Use replace with RegEx instead of replaceAll Compatibility with NodeJS 14.X. Signed-off-by: Max Maass --- .../hook/scope-limiter.test.js | 30 +++++++++++++++++++ hooks/cascading-scans/hook/scope-limiter.ts | 5 +++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/hooks/cascading-scans/hook/scope-limiter.test.js b/hooks/cascading-scans/hook/scope-limiter.test.js index 95adb0ba18..151c08f1fe 100644 --- a/hooks/cascading-scans/hook/scope-limiter.test.js +++ b/hooks/cascading-scans/hook/scope-limiter.test.js @@ -242,6 +242,36 @@ describe("Templating", function () { expect(isInScope()).toBe(false) }); + it("works for longer lists", () => { + annotations = { + "scope.cascading.securecodebox.io/domains": "example.com", + } + scopeLimiter.allOf = [ + { + key: "scope.cascading.securecodebox.io/domains", + operator: "SubdomainOf", + values: ["{{#split}}example.com,some.example.com,some-other.example.com,yet-another.example.com{{/split}}"], + } + ] + + expect(isInScope()).toBe(true) + }); + + it("also considers entries far back in the list", () => { + annotations = { + "scope.cascading.securecodebox.io/domains": "example.com", + } + scopeLimiter.allOf = [ + { + key: "scope.cascading.securecodebox.io/domains", + operator: "SubdomainOf", + values: ["{{#split}}example.com,some.example.com,some-other.example.com,but.anotherwebsite.aswell.com{{/split}}"], + } + ] + + expect(isInScope()).toBe(false) + }); + it("does not create extra empty entry for trailing comma (matching limiter)", () => { annotations = { "scope.cascading.securecodebox.io/domains": "example.com", diff --git a/hooks/cascading-scans/hook/scope-limiter.ts b/hooks/cascading-scans/hook/scope-limiter.ts index f0958a10f7..a55e3e2203 100644 --- a/hooks/cascading-scans/hook/scope-limiter.ts +++ b/hooks/cascading-scans/hook/scope-limiter.ts @@ -127,7 +127,10 @@ export function isInScope( "split": function () { // Split an existing list by comma return function (text, render) { - const result = render(text).trim().replaceAll(",", delimiter); + // We are using a regular expression of the comma delimiter instead of a straight comma because + // NodeJS 14.X only replaces the first occurence when using the latter, and the + // replaceAll function is only available starting with NodeJS 15. + const result = render(text).trim().replace(/,/g, delimiter); if (result === "" || result.endsWith(delimiter)) { return result; } else { From d4152880e7ec57aaea632cf1f47e8877894c47eb Mon Sep 17 00:00:00 2001 From: Max Maass Date: Tue, 30 Nov 2021 15:14:02 +0100 Subject: [PATCH 46/54] Support comma with trailing space for split Signed-off-by: Max Maass --- hooks/cascading-scans/hook/scope-limiter.test.js | 15 +++++++++++++++ hooks/cascading-scans/hook/scope-limiter.ts | 4 +++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/hooks/cascading-scans/hook/scope-limiter.test.js b/hooks/cascading-scans/hook/scope-limiter.test.js index 151c08f1fe..fee8ce411c 100644 --- a/hooks/cascading-scans/hook/scope-limiter.test.js +++ b/hooks/cascading-scans/hook/scope-limiter.test.js @@ -272,6 +272,21 @@ describe("Templating", function () { expect(isInScope()).toBe(false) }); + it("can handle spaces between entries", () => { + annotations = { + "scope.cascading.securecodebox.io/domains": "example.com", + } + scopeLimiter.allOf = [ + { + key: "scope.cascading.securecodebox.io/domains", + operator: "SubdomainOf", + values: ["{{#split}}example.com, some.example.com, some-other.example.com, yet-another.example.com{{/split}}"], + } + ] + + expect(isInScope()).toBe(true) + }); + it("does not create extra empty entry for trailing comma (matching limiter)", () => { annotations = { "scope.cascading.securecodebox.io/domains": "example.com", diff --git a/hooks/cascading-scans/hook/scope-limiter.ts b/hooks/cascading-scans/hook/scope-limiter.ts index a55e3e2203..dcfe94d9eb 100644 --- a/hooks/cascading-scans/hook/scope-limiter.ts +++ b/hooks/cascading-scans/hook/scope-limiter.ts @@ -130,7 +130,9 @@ export function isInScope( // We are using a regular expression of the comma delimiter instead of a straight comma because // NodeJS 14.X only replaces the first occurence when using the latter, and the // replaceAll function is only available starting with NodeJS 15. - const result = render(text).trim().replace(/,/g, delimiter); + // First replace comma with trailing space in case the list is specified as "entry1, entry2". + // Then replace any leftover commas without a space, in case the list format is "entry1,entry2". + const result = render(text).trim().replace(/, /g, delimiter).replace(/,/g, delimiter); if (result === "" || result.endsWith(delimiter)) { return result; } else { From 2a3733d408ab3097f63dd6ae8606ed6b86cef20b Mon Sep 17 00:00:00 2001 From: Max Maass Date: Tue, 30 Nov 2021 16:02:16 +0100 Subject: [PATCH 47/54] Rename getValues to asList Signed-off-by: Max Maass --- hooks/cascading-scans/hook/scope-limiter.test.js | 4 ++-- hooks/cascading-scans/hook/scope-limiter.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/hooks/cascading-scans/hook/scope-limiter.test.js b/hooks/cascading-scans/hook/scope-limiter.test.js index fee8ce411c..2b5b1262d4 100644 --- a/hooks/cascading-scans/hook/scope-limiter.test.js +++ b/hooks/cascading-scans/hook/scope-limiter.test.js @@ -160,7 +160,7 @@ describe("Templating", function () { { key: "scope.cascading.securecodebox.io/domain", operator: "SubdomainOf", - values: ["{{#getValues}}attributes.domains{{/getValues}}"], + values: ["{{#asList}}attributes.domains{{/asList}}"], } ] @@ -181,7 +181,7 @@ describe("Templating", function () { { key: "scope.cascading.securecodebox.io/CIDR", operator: "InCIDR", - values: ["{{#getValues}}attributes{{/getValues}}"], + values: ["{{#asList}}attributes{{/asList}}"], } ] diff --git a/hooks/cascading-scans/hook/scope-limiter.ts b/hooks/cascading-scans/hook/scope-limiter.ts index dcfe94d9eb..4d66bbbec2 100644 --- a/hooks/cascading-scans/hook/scope-limiter.ts +++ b/hooks/cascading-scans/hook/scope-limiter.ts @@ -113,7 +113,7 @@ export function isInScope( return render(`{{#${listKey}}}{{${objectKey}}}${delimiter}{{/${listKey}}}`); } }, - "getValues": function () { + "asList": function () { // Select a complete list return function (text, render) { text = text.trim(); From 5c5dc6f4d0b0fed2ca307f4b31a5814357b0cd6f Mon Sep 17 00:00:00 2001 From: Max Maass Date: Tue, 30 Nov 2021 16:03:02 +0100 Subject: [PATCH 48/54] Rename pickValues to getValues Signed-off-by: Max Maass --- hooks/cascading-scans/hook/scope-limiter.test.js | 8 ++++---- hooks/cascading-scans/hook/scope-limiter.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/hooks/cascading-scans/hook/scope-limiter.test.js b/hooks/cascading-scans/hook/scope-limiter.test.js index 2b5b1262d4..0bdbd6c3a0 100644 --- a/hooks/cascading-scans/hook/scope-limiter.test.js +++ b/hooks/cascading-scans/hook/scope-limiter.test.js @@ -391,7 +391,7 @@ describe("Templating", function () { { key: "scope.cascading.securecodebox.io/CIDR", operator: "InCIDR", - values: ["{{#pickValues}}attributes.addresses.ip{{/pickValues}}"], + values: ["{{#getValues}}attributes.addresses.ip{{/getValues}}"], } ] @@ -419,7 +419,7 @@ describe("Templating", function () { { key: "scope.cascading.securecodebox.io/CIDR", operator: "InCIDR", - values: ["{{#pickValues}}attributes.randomkey.ip{{/pickValues}}"], + values: ["{{#getValues}}attributes.randomkey.ip{{/getValues}}"], } ] @@ -436,7 +436,7 @@ describe("Templating", function () { { key: "scope.cascading.securecodebox.io/CIDR", operator: "InCIDR", - values: ["{{#pickValues}}attributes.addresses.ip{{/pickValues}}"], + values: ["{{#getValues}}attributes.addresses.ip{{/getValues}}"], } ] @@ -468,7 +468,7 @@ describe("Templating", function () { { key: "scope.cascading.securecodebox.io/CIDR", operator: "InCIDR", - values: ["{{#pickValues}}attributes.addresses.ip{{/pickValues}}"], + values: ["{{#getValues}}attributes.addresses.ip{{/getValues}}"], } ] diff --git a/hooks/cascading-scans/hook/scope-limiter.ts b/hooks/cascading-scans/hook/scope-limiter.ts index 4d66bbbec2..510218d5f7 100644 --- a/hooks/cascading-scans/hook/scope-limiter.ts +++ b/hooks/cascading-scans/hook/scope-limiter.ts @@ -100,7 +100,7 @@ export function isInScope( let rendered = Mustache.render(mapped, { ...finding, // These custom mustache functions all return a string containing a list delimited by `delimiter` defined above. - "pickValues": function () { + "getValues": function () { // Select attributes inside a list of objects return function (text, render) { text = text.trim(); From 31110aa7ea9c16656ef9022896b1d9e65e395775 Mon Sep 17 00:00:00 2001 From: Max Maass Date: Tue, 30 Nov 2021 16:57:25 +0100 Subject: [PATCH 49/54] Add more test cases to increase coverage Signed-off-by: Max Maass --- .../hook/scope-limiter.test.js | 143 +++++++++++++++++- hooks/cascading-scans/hook/scope-limiter.ts | 3 +- 2 files changed, 142 insertions(+), 4 deletions(-) diff --git a/hooks/cascading-scans/hook/scope-limiter.test.js b/hooks/cascading-scans/hook/scope-limiter.test.js index 0bdbd6c3a0..0f384b1ec3 100644 --- a/hooks/cascading-scans/hook/scope-limiter.test.js +++ b/hooks/cascading-scans/hook/scope-limiter.test.js @@ -65,6 +65,19 @@ it("Requirement key must map to an annotation", () => { }); describe("Templating", function () { + it("fails if the values are undefined", () => { + annotations = { + "scope.cascading.securecodebox.io/example.com": "example.com" + } + scopeLimiter.allOf = [ + { + key: "scope.cascading.securecodebox.io/{{attributes.hostname}}", + operator: "Contains", + } + ] + expect(isInScope()).toThrowError("the values field may not be undefined"); + }); + it("does not support requirement key", () => { annotations = { "scope.cascading.securecodebox.io/example.com": "example.com" @@ -151,7 +164,7 @@ describe("Templating", function () { }) describe("lists", function () { - describe("list", function () { + describe("asList", function () { it("matches with list of strings", () => { annotations = { "scope.cascading.securecodebox.io/domain": "example.com", @@ -382,7 +395,7 @@ describe("Templating", function () { }); }); - describe("keyinobjectlist", function () { + describe("getValues", function () { it("matches if templating key is present in all list entries", () => { annotations = { "scope.cascading.securecodebox.io/CIDR": "127.0.0.0/8", @@ -459,6 +472,34 @@ describe("Templating", function () { expect(isInScope()).toBe(false); }); + it("throws when the provided key is not at least 3 levels deep", () => { + annotations = { + "scope.cascading.securecodebox.io/CIDR": "127.0.0.0/8", + } + scopeLimiter.allOf = [ + { + key: "scope.cascading.securecodebox.io/CIDR", + operator: "InCIDR", + values: ["{{#getValues}}attributes.addresses{{/getValues}}"], + } + ] + + finding = { + attributes: { + addresses: [ + { + "ip": "127.0.0.1" + }, + { + "ip": "fe80::4eb3:e128:53cc:5722" + }, + ] + } + }; + + expect(isInScope).toThrowError("Invalid list key 'attributes.addresses'. List key must be at least 3 levels deep. E.g. 'attributes.addresses.ip'"); + }); + it("matches if validOnMissingRender is set and templating key is not present in all list entries", () => { annotations = { "scope.cascading.securecodebox.io/CIDR": "127.0.0.0/8", @@ -722,6 +763,48 @@ describe("Operator", function () { }); describe("SubdomainOf", function () { + it("does not match if annotation domain is not a domain", () => { + annotations = { + "scope.cascading.securecodebox.io/domain": "I am not a domain", + } + scopeLimiter.allOf = [ + { + key: "scope.cascading.securecodebox.io/domain", + operator: "SubdomainOf", + values: ["subdomain.example.com"], + } + ] + expect(isInScope).toThrowError("I am not a domain is an invalid domain name"); + }); + + it("does not match if finding domain is not a domain", () => { + annotations = { + "scope.cascading.securecodebox.io/domain": "example.com", + } + scopeLimiter.allOf = [ + { + key: "scope.cascading.securecodebox.io/domain", + operator: "SubdomainOf", + values: ["I am not a domain"], + } + ] + expect(isInScope).toThrowError("I am not a domain is an invalid domain name"); + }); + + it("does not match if target domain is deeper nested than the finding domain", () => { + annotations = { + "scope.cascading.securecodebox.io/domain": "some.subdomain.of.example.com", + } + scopeLimiter.allOf = [ + { + key: "scope.cascading.securecodebox.io/domain", + operator: "SubdomainOf", + values: ["example.com"], + } + ] + expect(isInScope()).toBe(false); + }); + it("matches if is subdomain", () => { annotations = { "scope.cascading.securecodebox.io/domain": "example.com", @@ -843,4 +926,60 @@ describe("ScopeLimiter", function () { ] expect(isInScope()).toBe(false); }); + + it("ANDs together results from multiple different limiter classes and fails if one fails", () => { + annotations = { + "scope.cascading.securecodebox.io/domains": "example.com", + "scope.cascading.securecodebox.io/cidr4": "8.8.8.8/8", + "scope.cascading.securecodebox.io/cidr6": "2001:0:ce49:7601:e866:efff:62c3:fffe/16", + } + scopeLimiter.allOf = [ + { + key: "scope.cascading.securecodebox.io/domains", + operator: "SubdomainOf", + values: ["subdomain.example.com"], + } + ] + scopeLimiter.anyOf = [ + { + key: "scope.cascading.securecodebox.io/cidr4", + operator: "InCIDR", + values: ["127.0.0.1", "fe80::1"], + }, + { + key: "scope.cascading.securecodebox.io/cidr6", + operator: "InCIDR", + values: ["127.0.0.1", "fe80::1"], + } + ] + expect(isInScope()).toBe(false); + }); + + it("ANDs together results from multiple different limiter classes", () => { + annotations = { + "scope.cascading.securecodebox.io/domains": "example.com", + "scope.cascading.securecodebox.io/cidr4": "127.0.0.1/8", + "scope.cascading.securecodebox.io/cidr6": "2001:0:ce49:7601:e866:efff:62c3:fffe/16", + } + scopeLimiter.allOf = [ + { + key: "scope.cascading.securecodebox.io/domains", + operator: "SubdomainOf", + values: ["subdomain.example.com"], + } + ] + scopeLimiter.anyOf = [ + { + key: "scope.cascading.securecodebox.io/cidr4", + operator: "InCIDR", + values: ["127.0.0.1", "fe80::1"], + }, + { + key: "scope.cascading.securecodebox.io/cidr6", + operator: "InCIDR", + values: ["127.0.0.1", "fe80::1"], + } + ] + expect(isInScope()).toBe(true); + }); }) diff --git a/hooks/cascading-scans/hook/scope-limiter.ts b/hooks/cascading-scans/hook/scope-limiter.ts index 510218d5f7..7126e4059f 100644 --- a/hooks/cascading-scans/hook/scope-limiter.ts +++ b/hooks/cascading-scans/hook/scope-limiter.ts @@ -309,8 +309,7 @@ function operatorSubdomainOf({scopeAnnotationValue, findingValues}: Operands): b takeRight(findingDomain.labels, scopeAnnotationDomain.labels.length) ); } - console.log(`${findingValue} is an invalid domain name`) - return false; + throw new Error(`${findingValue} is an invalid domain name`); }) } else { throw new Error(`${scopeAnnotationValue} is an invalid domain name`); From e1e797e2a2f2712e025a11f382f1a570f0417540 Mon Sep 17 00:00:00 2001 From: Jop Zitman Date: Tue, 30 Nov 2021 17:09:26 +0100 Subject: [PATCH 50/54] Make `Values` field required Signed-off-by: Jop Zitman --- hooks/cascading-scans/hook/scope-limiter.test.js | 2 +- hooks/cascading-scans/hook/scope-limiter.ts | 12 +++++++----- operator/apis/execution/v1/scan_types.go | 3 +-- .../cascading.securecodebox.io_cascadingrules.yaml | 3 +++ .../crd/bases/execution.securecodebox.io_scans.yaml | 3 +++ .../execution.securecodebox.io_scheduledscans.yaml | 3 +++ .../cascading.securecodebox.io_cascadingrules.yaml | 3 +++ operator/crds/execution.securecodebox.io_scans.yaml | 3 +++ .../execution.securecodebox.io_scheduledscans.yaml | 3 +++ 9 files changed, 27 insertions(+), 8 deletions(-) diff --git a/hooks/cascading-scans/hook/scope-limiter.test.js b/hooks/cascading-scans/hook/scope-limiter.test.js index 0f384b1ec3..eef5d94dbe 100644 --- a/hooks/cascading-scans/hook/scope-limiter.test.js +++ b/hooks/cascading-scans/hook/scope-limiter.test.js @@ -75,7 +75,7 @@ describe("Templating", function () { operator: "Contains", } ] - expect(isInScope()).toThrowError("the values field may not be undefined"); + expect(isInScope).toThrowError("the values field may not be undefined"); }); it("does not support requirement key", () => { diff --git a/hooks/cascading-scans/hook/scope-limiter.ts b/hooks/cascading-scans/hook/scope-limiter.ts index 7126e4059f..25ab2526ac 100644 --- a/hooks/cascading-scans/hook/scope-limiter.ts +++ b/hooks/cascading-scans/hook/scope-limiter.ts @@ -59,6 +59,11 @@ export function isInScope( } const scopeAnnotationValue = scanAnnotations[key]; + // Possible redundant check as the field is required by CRD + if (values === undefined) { + throw new Error("the values field may not be undefined") + } + // Template the user values input using Mustache const findingValues = values.map(templateValue); // If one of the user values couldn't be rendered, fallback to user-defined behaviour @@ -179,7 +184,7 @@ interface OperatorFunctions { } // This validator ensures that neither the scope annotation nor the finding values can be undefined -const defaultValidator: OperatorFunctions["validator"] = props => validate(props, false, false); +const defaultValidator: OperatorFunctions["validator"] = props => validate(props, false); const operatorFunctions: { [key in ScopeLimiterRequirementOperator]: OperatorFunctions } = { [ScopeLimiterRequirementOperator.In]: { @@ -216,13 +221,10 @@ const operatorFunctions: { [key in ScopeLimiterRequirementOperator]: OperatorFun }, } -function validate({scopeAnnotationValue, findingValues}: Operands, scopeAnnotationValueUndefinedAllowed, findingValuesUndefinedAllowed) { +function validate({scopeAnnotationValue, findingValues}: Operands, scopeAnnotationValueUndefinedAllowed) { if (!scopeAnnotationValueUndefinedAllowed && scopeAnnotationValue === undefined) { throw new Error(`the referenced annotation may not be undefined`) } - if (!findingValuesUndefinedAllowed && findingValues === undefined) { - throw new Error(`the values field may not be undefined`) - } } /** diff --git a/operator/apis/execution/v1/scan_types.go b/operator/apis/execution/v1/scan_types.go index a0fdcc3bc8..14fcc361c3 100644 --- a/operator/apis/execution/v1/scan_types.go +++ b/operator/apis/execution/v1/scan_types.go @@ -85,8 +85,7 @@ type ScopeLimiterRequirement struct { // operator represents a key's relationship to a set of values. Operator string `json:"operator" protobuf:"bytes,2,opt,name=operator"` // values is an array of string values. - // +optional - Values []string `json:"values,omitempty" protobuf:"bytes,3,rep,name=values"` + Values []string `json:"values" protobuf:"bytes,3,rep,name=values"` } // ScanSpec defines the desired state of Scan diff --git a/operator/config/crd/bases/cascading.securecodebox.io_cascadingrules.yaml b/operator/config/crd/bases/cascading.securecodebox.io_cascadingrules.yaml index 3c12d03190..068a4c3d3a 100644 --- a/operator/config/crd/bases/cascading.securecodebox.io_cascadingrules.yaml +++ b/operator/config/crd/bases/cascading.securecodebox.io_cascadingrules.yaml @@ -204,6 +204,7 @@ spec: required: - key - operator + - values type: object type: array anyOf: @@ -230,6 +231,7 @@ spec: required: - key - operator + - values type: object type: array noneOf: @@ -256,6 +258,7 @@ spec: required: - key - operator + - values type: object type: array validOnMissingRender: diff --git a/operator/config/crd/bases/execution.securecodebox.io_scans.yaml b/operator/config/crd/bases/execution.securecodebox.io_scans.yaml index 820a5c9455..ac11ba308b 100644 --- a/operator/config/crd/bases/execution.securecodebox.io_scans.yaml +++ b/operator/config/crd/bases/execution.securecodebox.io_scans.yaml @@ -165,6 +165,7 @@ spec: required: - key - operator + - values type: object type: array anyOf: @@ -191,6 +192,7 @@ spec: required: - key - operator + - values type: object type: array noneOf: @@ -217,6 +219,7 @@ spec: required: - key - operator + - values type: object type: array validOnMissingRender: diff --git a/operator/config/crd/bases/execution.securecodebox.io_scheduledscans.yaml b/operator/config/crd/bases/execution.securecodebox.io_scheduledscans.yaml index c5111a0cc5..3e6f12c06b 100644 --- a/operator/config/crd/bases/execution.securecodebox.io_scheduledscans.yaml +++ b/operator/config/crd/bases/execution.securecodebox.io_scheduledscans.yaml @@ -186,6 +186,7 @@ spec: required: - key - operator + - values type: object type: array anyOf: @@ -212,6 +213,7 @@ spec: required: - key - operator + - values type: object type: array noneOf: @@ -238,6 +240,7 @@ spec: required: - key - operator + - values type: object type: array validOnMissingRender: diff --git a/operator/crds/cascading.securecodebox.io_cascadingrules.yaml b/operator/crds/cascading.securecodebox.io_cascadingrules.yaml index 3c12d03190..068a4c3d3a 100644 --- a/operator/crds/cascading.securecodebox.io_cascadingrules.yaml +++ b/operator/crds/cascading.securecodebox.io_cascadingrules.yaml @@ -204,6 +204,7 @@ spec: required: - key - operator + - values type: object type: array anyOf: @@ -230,6 +231,7 @@ spec: required: - key - operator + - values type: object type: array noneOf: @@ -256,6 +258,7 @@ spec: required: - key - operator + - values type: object type: array validOnMissingRender: diff --git a/operator/crds/execution.securecodebox.io_scans.yaml b/operator/crds/execution.securecodebox.io_scans.yaml index 820a5c9455..ac11ba308b 100644 --- a/operator/crds/execution.securecodebox.io_scans.yaml +++ b/operator/crds/execution.securecodebox.io_scans.yaml @@ -165,6 +165,7 @@ spec: required: - key - operator + - values type: object type: array anyOf: @@ -191,6 +192,7 @@ spec: required: - key - operator + - values type: object type: array noneOf: @@ -217,6 +219,7 @@ spec: required: - key - operator + - values type: object type: array validOnMissingRender: diff --git a/operator/crds/execution.securecodebox.io_scheduledscans.yaml b/operator/crds/execution.securecodebox.io_scheduledscans.yaml index c5111a0cc5..3e6f12c06b 100644 --- a/operator/crds/execution.securecodebox.io_scheduledscans.yaml +++ b/operator/crds/execution.securecodebox.io_scheduledscans.yaml @@ -186,6 +186,7 @@ spec: required: - key - operator + - values type: object type: array anyOf: @@ -212,6 +213,7 @@ spec: required: - key - operator + - values type: object type: array noneOf: @@ -238,6 +240,7 @@ spec: required: - key - operator + - values type: object type: array validOnMissingRender: From af6807528c4a7d13fe3185767e45093a513a1a18 Mon Sep 17 00:00:00 2001 From: Jop Zitman Date: Tue, 30 Nov 2021 19:16:24 +0100 Subject: [PATCH 51/54] Test lists in alias Signed-off-by: Jop Zitman --- .../hook/scope-limiter.test.js | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/hooks/cascading-scans/hook/scope-limiter.test.js b/hooks/cascading-scans/hook/scope-limiter.test.js index eef5d94dbe..659f0c8e26 100644 --- a/hooks/cascading-scans/hook/scope-limiter.test.js +++ b/hooks/cascading-scans/hook/scope-limiter.test.js @@ -143,6 +143,28 @@ describe("Templating", function () { expect(isInScope()).toBe(true); }); + it("templates custom list functions in aliases", () => { + annotations = { + "scope.cascading.securecodebox.io/domains": "example.com,subdomain.example.com", + } + scopeLimiter.allOf = [ + { + key: "scope.cascading.securecodebox.io/domains", + operator: "Contains", + values: ["{{{$.hostname}}}"], + } + ] + finding = { + attributes: { + hostname: ["notexample.com", "example.com"], + } + } + scopeLimiterAliases = { + "hostname": "{{#asList}}attributes.hostname{{/asList}}", + } + expect(isInScope()).toBe(false); + }); + it("Matches if mapping is not available: validOnMissingRender true", () => { annotations = { "scope.cascading.securecodebox.io/domains": "example.com,subdomain.example.com", From 27d6e6060f0241da14aacec10ff6a608e2f09324 Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach Date: Tue, 7 Dec 2021 12:48:56 +0100 Subject: [PATCH 52/54] Merge branch 'main' into reverse-matches Signed-off-by: Jannik Hollenbach --- .github/workflows/scb-bot.yaml | 4 +- docs/adr/adr_0010.md | 91 +++ hooks/cascading-scans/hook/hook.test.js | 458 ++++++++++++ hooks/cascading-scans/hook/hook.ts | 29 +- hooks/cascading-scans/hook/scan-helpers.ts | 7 + .../execution/v1/parsedefinition_types.go | 5 + operator/apis/execution/v1/scan_types.go | 14 + .../execution/v1/scancompletionhook_types.go | 4 + .../execution/v1/zz_generated.deepcopy.go | 36 + ...ading.securecodebox.io_cascadingrules.yaml | 673 +++++++++++++++++- ...ion.securecodebox.io_parsedefinitions.yaml | 636 ++++++++++++++++- ....securecodebox.io_scancompletionhooks.yaml | 636 ++++++++++++++++- .../execution.securecodebox.io_scans.yaml | 646 ++++++++++++++++- .../execution.securecodebox.io_scantypes.yaml | 3 - ...ution.securecodebox.io_scheduledscans.yaml | 673 +++++++++++++++++- .../execution/scans/hook_reconciler.go | 15 + .../execution/scans/parse_reconciler.go | 14 + .../execution/scans/scan_reconciler.go | 11 + ...ading.securecodebox.io_cascadingrules.yaml | 670 +++++++++++++++++ ...ion.securecodebox.io_parsedefinitions.yaml | 636 ++++++++++++++++- ....securecodebox.io_scancompletionhooks.yaml | 633 ++++++++++++++++ .../execution.securecodebox.io_scans.yaml | 643 +++++++++++++++++ ...ution.securecodebox.io_scheduledscans.yaml | 670 +++++++++++++++++ operator/internal/telemetry/telemetry.go | 5 + .../angularjs-csti-scanner/.helm-docs.gotmpl | 8 +- scanners/angularjs-csti-scanner/README.md | 12 +- .../scan-website-with-options/scan.yaml | 2 +- .../angularjs-csti-scanner/scanner/Dockerfile | 18 +- .../angularjs-csti-scanner/scanner/wrapper.sh | 8 +- .../angularjs-csti-scanner-scan-type.yaml | 2 +- scanners/angularjs-csti-scanner/values.yaml | 2 +- 31 files changed, 7216 insertions(+), 48 deletions(-) create mode 100644 docs/adr/adr_0010.md diff --git a/.github/workflows/scb-bot.yaml b/.github/workflows/scb-bot.yaml index c4c13a9c8f..76f37b25a3 100644 --- a/.github/workflows/scb-bot.yaml +++ b/.github/workflows/scb-bot.yaml @@ -61,7 +61,7 @@ jobs: upgrade=$(echo $release| tr -d "v") fi fi - + echo $upgrade echo release=$upgrade >> $GITHUB_ENV @@ -81,7 +81,7 @@ jobs: # Fetches changelog and filters out any issue references run: | changelog=$(curl -sL ${{env.versionApi}} | jq -r ".body") - echo releaseChangelog="$changelog" >> $GITHUB_ENV + echo 'releaseChangelog=$changelog' >> $GITHUB_ENV - name: Upgrade Scanner Helm Chart if: ${{ env.release != env.local && env.prExists == 0 && env.release != null}} diff --git a/docs/adr/adr_0010.md b/docs/adr/adr_0010.md new file mode 100644 index 0000000000..00a3d3b93a --- /dev/null +++ b/docs/adr/adr_0010.md @@ -0,0 +1,91 @@ + + +# ADR-0010: Custom Inheritance Behavior for Affinity and Tolerations + +| | | +|----------------|----------| +| **Status**: | ACCEPTED | +| **Date**: | 2021-11-22 | +| **Author(s)**: | Max Maass | + +## Context + +Kubernetes-based cloud environments allow controlling which nodes are used for which workloads through two mechanisms: [Affinity][affinity] and [Tolerations][tolerations]. +These can be selected by setting them in the Kubernetes job. + +### Problem 1: Sources of Configuration + +The secureCodeBox operator is managing Kubernetes jobs for us, and templating their values from the provided `Scan` (for a concrete scan), `ScanType` (for a type of scan, e.g. nmap), `ParseDefinition` (for the parser related to a scan), or `ScanCompletionHook` (for a hook) into the job itself. +Since they each pull information from different places, supporting affinity and tolerations for all of them requires adding them in multiple locations. +Generally, such settings are pulled into the job from one of two (or in some cases three) places: +- The helm values (configured via the values.yaml during install of the `ScanType`, `ParseDefinition` or `ScanCompletionHook`) +- The `Scan` specification for the running scan +- The `CascadingScan` specification when creating a cascaded scan (or, more precisely: both the `Scan` spec of the parent scan and whatever information is given in the `ScanSpec` of the `CascadingScan`) + +Specified as a table, this is where values for the different jobs scheduled by the operator normally come from: + +| Job type | Helm values | `Scan.spec` | `CascadingScan.spec.scanSpec` | +|----------|-------------|-------------|-------------------------------| +| Scan | ✅ | ✅ | ✅ | +| Parse | ✅ | |  | +| Hook | ✅ | |  | + +This presents us with a problem: All three job types should be configurable with an affinity and tolerations, but two of them only read their relevant configuration from the helm values (provided during install). +This makes it impossible to ensure that all jobs triggered by a single `Scan` use a specific affinity or toleration (which may be different from the default). +We can address this issue in two ways: + +#### Option 1: Accept And Move On +One option is to accept that this is the case and leave it unchanged. +This is unsatifactory, as affinity and tolerations are a powerful tool, not only for controlling the cost of cloud deployments (by using cheaper node types, like preemptible nodes), but also for other aspects like controlling the geographic location of nodes, the presence of special node features, and other aspects. +In some cases, there may be no one valid default value for a single `ScanType` that is correct for all `Scan`s using it. +Additionally, for other features that can have defaults set by the helm values, it is possible to add to these defaults using fields in the `Scan` definition. + +#### Option 2: Use Affinity and Tolerations from Scan in all Jobs +The other option is to deviate from the usual method of setting values for the jobs by making all three types of jobs (scans, parsers and hooks) use the affinity and tolerations defined in the `Scan` (_if_ it defines them), and fall back to the defaults from the Helm values otherwise. +This allows the user to specify affinity and tolerations in one place (the `Scan`), and be confident that any jobs started by the `Scan` will use the same affinity and toleration settings. +The downside is that now, values set in the `Scan` will influence the execution of parsers and even hooks, which is different from the behavior of the system in other places, where settings on a `Scan` will not impact the hooks and parsers. + +To summarize, this would make the table look as follows: + +| Job type | Helm values | `Scan.spec` | `CascadingScan.spec.scanSpec` | +|----------|-------------|-------------|-------------------------------| +| Scan | ✅ | ✅ | ✅ | +| Parse | ✅ | ✅ |  | +| Hook | ✅ | ✅ |  | + +(There are no checkmarks on the `CascadingScan.spec.scanSpec` column for parser and hook because it is merged into the `Scan.spec` when creating the cascaded scan. From there, it will influence the parser and hook the same way it would if it had been directly added to the parent `Scan`.) + +### Problem 2: Merging vs. Replacing Defaults +Normally, defaults set in the helm values are merged with any additional values provided in the `Scan`, and the same merging behavior governs combining the `Scan.spec` of the triggering scan with the `CascadingScan.spec.scanSpec` of a cascading scan (assuming inheritance is enabled). +However, since the [affinity is defined as a deeply nested dictionary][affinity], merging is both technically challenging and may lead to unexpected results. +In the worst case, it can lead to an invalid configuration, or one that is impossible to schedule because of conflicting requirements. +It is thus advisable to replace any default affinity with one that is specified in the `Scan.spec` (for Helm values) or the `CascadingScan.spec.scanSpec` (for cascaded scans), instead of attempting to merge them. +However, this raises the question of how to handle tolerations. + +#### Option 1: Consistency With Other Values +One option is to have it behave in the same way it is done for labels, environmental variables, etc.: merging the default with the values provided in the `Scan`. +The downside of this approach is that it is purely "additive": It is never possible to create a set of tolerations that does not include the default. +Additionally, it is inconsistent with the behavior of the affinity, which is the most closely related feature. + +### Option 2: Consistency with Affinity +The alternative is to replace instead of merge for the tolerations as well. +This is inconsistent with the other values, but it ensures that affinity and tolerations behave the same. + +## Decision +For the first problem, we choose to use Option 2: Affinity and tolerations defined in the `Scan` will be used by all jobs related to this scan. +They will also be inherited by default, although a special cascading scan flag can be used to disable this (`inheritAffinity` / `inheritTolerations`, as per the standard naming scheme). +If no values are defined in the Scan, the default values from the Helm install are used. + +For the second problem, we choose Option 2 as well: Both affinity and tolerations will have the more specific value (`Scan.spec` for scans, `CascadingScan.spec.scanSpec` for cascaded scans) replace the more general value (Helm values for scans, `Scan.spec` of the parent scan for cascaded scans), assuming the more specific value is set. +If the value is not set (i.e., the key is omitted in the configuration), the more general value is used. +If the more specific key is set to an empty value (`[]` for tolerations, `{}` for affinity), it will still replace the more general value and thus remove any affinity or toleration that was previously configured. + +## Consequences +This decision leads to a system that is less concerned with consistency in behavior between unrelated features (e.g., tolerations and environmental variables), and more interested in consistency between related features (affinity and tolerations) and convenience for the operator (changing the scheduling behavior of parsers and hooks from the scan definition). + +[affinity]: https://kubernetes.io/docs/tasks/configure-pod-container/assign-pods-nodes-using-node-affinity/ +[tolerations]: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/ diff --git a/hooks/cascading-scans/hook/hook.test.js b/hooks/cascading-scans/hook/hook.test.js index d45edadb9d..84d28b321a 100644 --- a/hooks/cascading-scans/hook/hook.test.js +++ b/hooks/cascading-scans/hook/hook.test.js @@ -115,6 +115,7 @@ test("Should create subsequent scans for open HTTPS ports (NMAP findings)", () = ], }, "spec": Object { + "affinity": undefined, "cascades": Object {}, "env": Array [], "hookSelector": Object {}, @@ -124,6 +125,7 @@ test("Should create subsequent scans for open HTTPS ports (NMAP findings)", () = "foobar.com:443", ], "scanType": "sslyze", + "tolerations": undefined, "volumeMounts": Array [], "volumes": Array [], }, @@ -259,6 +261,7 @@ test("Should not crash when the annotations are not set", () => { ], }, "spec": Object { + "affinity": undefined, "cascades": Object {}, "env": Array [], "hookSelector": Object {}, @@ -268,6 +271,7 @@ test("Should not crash when the annotations are not set", () => { "foobar.com:443", ], "scanType": "sslyze", + "tolerations": undefined, "volumeMounts": Array [], "volumes": Array [], }, @@ -395,6 +399,7 @@ test("Should allow wildcards in cascadingRules", () => { ], }, "spec": Object { + "affinity": undefined, "cascades": Object {}, "env": Array [], "hookSelector": Object {}, @@ -404,6 +409,7 @@ test("Should allow wildcards in cascadingRules", () => { "foobar.com:8443", ], "scanType": "sslyze", + "tolerations": undefined, "volumeMounts": Array [], "volumes": Array [], }, @@ -1176,6 +1182,7 @@ test("Templating should apply to environment variables", () => { ], }, "spec": Object { + "affinity": undefined, "cascades": Object {}, "env": Array [ Object { @@ -1190,6 +1197,7 @@ test("Templating should apply to environment variables", () => { "foobar.com:443", ], "scanType": "sslyze", + "tolerations": undefined, "volumeMounts": Array [ Object { "mountPath": "/test", @@ -1299,6 +1307,7 @@ test("Templating should apply to initContainer commands", () => { ], }, "spec": Object { + "affinity": undefined, "cascades": Object {}, "env": Array [], "hookSelector": Object {}, @@ -1325,6 +1334,7 @@ test("Templating should apply to initContainer commands", () => { "foobar.com:443", ], "scanType": "sslyze", + "tolerations": undefined, "volumeMounts": Array [ Object { "mountPath": "/test", @@ -1435,6 +1445,7 @@ test("Templating should apply to initContainer environment variables", () => { ], }, "spec": Object { + "affinity": undefined, "cascades": Object {}, "env": Array [], "hookSelector": Object {}, @@ -1464,6 +1475,7 @@ test("Templating should apply to initContainer environment variables", () => { "foobar.com:443", ], "scanType": "sslyze", + "tolerations": undefined, "volumeMounts": Array [ Object { "mountPath": "/test", @@ -1573,6 +1585,7 @@ test("Templating should not break special encoding (http://...) when using tripl ], }, "spec": Object { + "affinity": undefined, "cascades": Object {}, "env": Array [], "hookSelector": Object {}, @@ -1599,6 +1612,7 @@ test("Templating should not break special encoding (http://...) when using tripl "https://github.com/secureCodeBox/secureCodeBox", ], "scanType": "sslyze", + "tolerations": undefined, "volumeMounts": Array [ Object { "mountPath": "/test", @@ -1761,6 +1775,450 @@ test("should not merge hookSelector into cascaded scan if inheritHookSelector is `); }); +test("should copy tolerations and affinity into cascaded scan if one is set and label is unset", () => { + const findings = [ + { + name: "Port 443 is open", + category: "Open Port", + attributes: { + state: "open", + hostname: "foobar.com", + port: 443, + service: "https" + } + } + ]; + + parentScan.spec.affinity = { + nodeAffinity: { + requiredDuringSchedulingIgnoredDuringExecution: { + nodeSelectorTerms: [ + { + matchExpressions: [ + { + key: "disktype", + operator: "In", + values: [ + "ssd" + ] + } + ] + } + ] + } + } + } + + parentScan.spec.tolerations = [ + { + key: "key1", + operator: "Equal", + value: "test", + effect: "NoSchedule" + } + ] + + const cascadedScans = getCascadingScans( + parentScan, + findings, + sslyzeCascadingRules + ); + + const cascadedScan = cascadedScans[0]; + + expect(cascadedScan.spec.affinity).toMatchInlineSnapshot(` + Object { + "nodeAffinity": Object { + "requiredDuringSchedulingIgnoredDuringExecution": Object { + "nodeSelectorTerms": Array [ + Object { + "matchExpressions": Array [ + Object { + "key": "disktype", + "operator": "In", + "values": Array [ + "ssd", + ], + }, + ], + }, + ], + }, + }, + } + `); + + expect(cascadedScan.spec.tolerations).toMatchInlineSnapshot(` + Array [ + Object { + "effect": "NoSchedule", + "key": "key1", + "operator": "Equal", + "value": "test", + }, + ] + `); +}); + +test("should not copy tolerations and affinity into cascaded scan if label disables it", () => { + parentScan.spec.cascades.inheritAffinity = false; + parentScan.spec.cascades.inheritTolerations = false; + const findings = [ + { + name: "Port 443 is open", + category: "Open Port", + attributes: { + state: "open", + hostname: "foobar.com", + port: 443, + service: "https" + } + } + ]; + + parentScan.spec.affinity = { + nodeAffinity: { + requiredDuringSchedulingIgnoredDuringExecution: { + nodeSelectorTerms: [ + { + matchExpressions: [ + { + key: "disktype", + operator: "In", + values: [ + "ssd" + ] + } + ] + } + ] + } + } + } + + parentScan.spec.tolerations = [ + { + key: "key1", + operator: "Equal", + value: "test", + effect: "NoSchedule" + } + ] + + const cascadedScans = getCascadingScans( + parentScan, + findings, + sslyzeCascadingRules + ); + + const cascadedScan = cascadedScans[0]; + + expect(cascadedScan.spec.affinity).toMatchInlineSnapshot(`undefined`); + + expect(cascadedScan.spec.tolerations).toMatchInlineSnapshot(`undefined`); +}); + +test("should merge tolerations and replace affinity in cascaded scan if cascading spec sets new ones", () => { + const findings = [ + { + name: "Port 443 is open", + category: "Open Port", + attributes: { + state: "open", + hostname: "foobar.com", + port: 443, + service: "https" + } + } + ]; + + parentScan.spec.affinity = { + nodeAffinity: { + requiredDuringSchedulingIgnoredDuringExecution: { + nodeSelectorTerms: [ + { + matchExpressions: [ + { + key: "disktype", + operator: "In", + values: [ + "ssd" + ] + } + ] + } + ] + } + } + } + + parentScan.spec.tolerations = [ + { + key: "key1", + operator: "Equal", + value: "test", + effect: "NoSchedule" + } + ] + + sslyzeCascadingRules[0].spec.scanSpec.tolerations = [ + { + key: "key2", + operator: "Equal", + value: "test-2", + effect: "NoSchedule", + } + ]; + + sslyzeCascadingRules[0].spec.scanSpec.affinity = { + nodeAffinity: { + requiredDuringSchedulingIgnoredDuringExecution: { + nodeSelectorTerms: [ + { + matchExpressions: [ + { + key: "network", + operator: "In", + values: [ + "10g" + ] + } + ] + } + ] + } + } + } + + const cascadedScans = getCascadingScans( + parentScan, + findings, + sslyzeCascadingRules + ); + + const cascadedScan = cascadedScans[0]; + + // New values will completely replace the old values, not be merged + expect(cascadedScan.spec.affinity).toMatchInlineSnapshot(` + Object { + "nodeAffinity": Object { + "requiredDuringSchedulingIgnoredDuringExecution": Object { + "nodeSelectorTerms": Array [ + Object { + "matchExpressions": Array [ + Object { + "key": "network", + "operator": "In", + "values": Array [ + "10g", + ], + }, + ], + }, + ], + }, + }, + } + `); + + expect(cascadedScan.spec.tolerations).toMatchInlineSnapshot(` + Array [ + Object { + "effect": "NoSchedule", + "key": "key1", + "operator": "Equal", + "value": "test", + }, + Object { + "effect": "NoSchedule", + "key": "key2", + "operator": "Equal", + "value": "test-2", + }, + ] + `); +}); + +test("should not set affinity or tolerations to undefined if they are defined to be an empty map / list in upstream scan", () => { + const findings = [ + { + name: "Port 443 is open", + category: "Open Port", + attributes: { + state: "open", + hostname: "foobar.com", + port: 443, + service: "https" + } + } + ]; + + parentScan.spec.affinity = {} + + parentScan.spec.tolerations = [] + + const cascadedScans = getCascadingScans( + parentScan, + findings, + sslyzeCascadingRules + ); + + const cascadedScan = cascadedScans[0]; + + // New values will completely replace the old values, not be merged + expect(cascadedScan.spec.affinity).toMatchInlineSnapshot(`Object {}`); + + expect(cascadedScan.spec.tolerations).toMatchInlineSnapshot(`Array []`); +}); + +test("Should not set affinity or tolerations to undefined if they are defined to be an empty map / list in cascading ScanSpec", () => { + const findings = [ + { + name: "Port 443 is open", + category: "Open Port", + attributes: { + state: "open", + hostname: "foobar.com", + port: 443, + service: "https" + } + } + ]; + + sslyzeCascadingRules[0].spec.scanSpec.tolerations = []; + + sslyzeCascadingRules[0].spec.scanSpec.affinity = {}; + + const cascadedScans = getCascadingScans( + parentScan, + findings, + sslyzeCascadingRules + ); + + const cascadedScan = cascadedScans[0]; + + // New values will completely replace the old values, not be merged + expect(cascadedScan.spec.affinity).toMatchInlineSnapshot(`Object {}`); + + expect(cascadedScan.spec.tolerations).toMatchInlineSnapshot(`Array []`); +}); + +test("should only use tolerations and affinity of cascaded scan if inheritance is disabled", () => { + parentScan.spec.cascades.inheritAffinity = false; + parentScan.spec.cascades.inheritTolerations = false; + const findings = [ + { + name: "Port 443 is open", + category: "Open Port", + attributes: { + state: "open", + hostname: "foobar.com", + port: 443, + service: "https" + } + } + ]; + + parentScan.spec.affinity = { + nodeAffinity: { + requiredDuringSchedulingIgnoredDuringExecution: { + nodeSelectorTerms: [ + { + matchExpressions: [ + { + key: "disktype", + operator: "In", + values: [ + "ssd" + ] + } + ] + } + ] + } + } + } + + parentScan.spec.tolerations = [ + { + key: "key1", + operator: "Equal", + value: "test", + effect: "NoSchedule" + } + ] + + sslyzeCascadingRules[0].spec.scanSpec.tolerations = [ + { + key: "key2", + operator: "Equal", + value: "test-2", + effect: "NoSchedule", + } + ]; + + sslyzeCascadingRules[0].spec.scanSpec.affinity = { + nodeAffinity: { + requiredDuringSchedulingIgnoredDuringExecution: { + nodeSelectorTerms: [ + { + matchExpressions: [ + { + key: "network", + operator: "In", + values: [ + "10g" + ] + } + ] + } + ] + } + } + } + + const cascadedScans = getCascadingScans( + parentScan, + findings, + sslyzeCascadingRules + ); + + const cascadedScan = cascadedScans[0]; + + expect(cascadedScan.spec.affinity).toMatchInlineSnapshot(` + Object { + "nodeAffinity": Object { + "requiredDuringSchedulingIgnoredDuringExecution": Object { + "nodeSelectorTerms": Array [ + Object { + "matchExpressions": Array [ + Object { + "key": "network", + "operator": "In", + "values": Array [ + "10g", + ], + }, + ], + }, + ], + }, + }, + } + `); + + expect(cascadedScan.spec.tolerations).toMatchInlineSnapshot(` + Array [ + Object { + "effect": "NoSchedule", + "key": "key2", + "operator": "Equal", + "value": "test-2", + }, + ] + `); +}); + test("should purge cascaded scan spec from parent scan", () => { parentScan.spec.cascades.inheritEnv = true parentScan.spec.cascades.inheritVolumes = true diff --git a/hooks/cascading-scans/hook/hook.ts b/hooks/cascading-scans/hook/hook.ts index 56eb85b6d5..71005cdece 100644 --- a/hooks/cascading-scans/hook/hook.ts +++ b/hooks/cascading-scans/hook/hook.ts @@ -153,7 +153,7 @@ function getCascadingScan( let { scanType, parameters } = cascadingRule.spec.scanSpec; - let { annotations, labels, env, volumes, volumeMounts, initContainers, hookSelector } = mergeCascadingRuleWithScan(parentScan, cascadingRule); + let { annotations, labels, env, volumes, volumeMounts, initContainers, hookSelector, affinity, tolerations } = mergeCascadingRuleWithScan(parentScan, cascadingRule); let cascadingChain = getScanChain(parentScan); @@ -194,6 +194,8 @@ function getCascadingScan( volumes, volumeMounts, initContainers, + tolerations, + affinity, } }; } @@ -203,9 +205,28 @@ function mergeCascadingRuleWithScan( cascadingRule: CascadingRule ) { const { scanAnnotations, scanLabels } = cascadingRule.spec; - let { env = [], volumes = [], volumeMounts = [], initContainers = [], hookSelector = {} } = cascadingRule.spec.scanSpec; - let { inheritAnnotations, inheritLabels, inheritEnv, inheritVolumes, inheritInitContainers, inheritHookSelector } = scan.spec.cascades; + let { env = [], volumes = [], volumeMounts = [], initContainers = [], hookSelector = {}, affinity, tolerations } = cascadingRule.spec.scanSpec; + let { inheritAnnotations, inheritLabels, inheritEnv, inheritVolumes, inheritInitContainers, inheritHookSelector, inheritAffinity = true, inheritTolerations = true} = scan.spec.cascades; + + // We have to use a slightly complicated logic for inheriting / setting the tolerations and affinity to work around some + // limitations in the operator. The goal is to avoid setting anything to an empty list [] or empty map {} if the keys are actually + // missing in the specification, as this will lead to issues in the operator when pulling in default values from the templates of the + // scanners. So, we are taking a bit more care to make sure that the value stays undefined (and thus nil in Go) unless someone explicitly + // specified an empty list or map. + let selectedTolerations = undefined; + if (tolerations !== undefined) { + selectedTolerations = mergeInheritedArray(scan.spec.tolerations, tolerations, inheritTolerations); + } else if (inheritTolerations) { + selectedTolerations = scan.spec.tolerations; + } + let selectedAffinity = undefined; + if (affinity !== undefined) { + selectedAffinity = affinity + } else if (inheritAffinity) { + selectedAffinity = scan.spec.affinity; + } + return { annotations: mergeInheritedMap(scan.metadata.annotations, scanAnnotations, inheritAnnotations), labels: mergeInheritedMap(scan.metadata.labels, scanLabels, inheritLabels), @@ -214,6 +235,8 @@ function mergeCascadingRuleWithScan( volumeMounts: mergeInheritedArray(scan.spec.volumeMounts, volumeMounts, inheritVolumes), initContainers: mergeInheritedArray(scan.spec.initContainers, initContainers, inheritInitContainers), hookSelector: mergeInheritedSelector(scan.spec.hookSelector, hookSelector, inheritHookSelector), + affinity: selectedAffinity, + tolerations: selectedTolerations } } diff --git a/hooks/cascading-scans/hook/scan-helpers.ts b/hooks/cascading-scans/hook/scan-helpers.ts index 19b07532b4..e82d52dfb8 100644 --- a/hooks/cascading-scans/hook/scan-helpers.ts +++ b/hooks/cascading-scans/hook/scan-helpers.ts @@ -65,6 +65,8 @@ export interface ScanSpec { volumeMounts?: Array; initContainers?: Array; hookSelector?: LabelSelector; + tolerations?: Array; + affinity?: k8s.V1Toleration; } export interface ScopeLimiter { @@ -82,6 +84,8 @@ export interface CascadingInheritance { inheritVolumes: boolean, inheritInitContainers: boolean, inheritHookSelector: boolean, + inheritAffinity: boolean, + inheritTolerations: boolean, } export interface ScanStatus { @@ -103,6 +107,9 @@ export function mergeInheritedMap(parentProps, ruleProps, inherit: boolean = tru if (!inherit) { parentProps = {}; } + if (ruleProps === undefined) { + return parentProps; + } return { ...parentProps, ...ruleProps // ruleProps overwrites any duplicate keys from parentProps diff --git a/operator/apis/execution/v1/parsedefinition_types.go b/operator/apis/execution/v1/parsedefinition_types.go index d0c9422502..651bdaf0d6 100644 --- a/operator/apis/execution/v1/parsedefinition_types.go +++ b/operator/apis/execution/v1/parsedefinition_types.go @@ -36,6 +36,11 @@ type ParseDefinitionSpec struct { Volumes []corev1.Volume `json:"volumes,omitempty"` // VolumeMounts allows to specify volume mounts for the parser container. VolumeMounts []corev1.VolumeMount `json:"volumeMounts,omitempty"` + + // Affinity allows to specify a node affinity, to control on which nodes you want a parser to run. See: https://kubernetes.io/docs/tasks/configure-pod-container/assign-pods-nodes-using-node-affinity/ + Affinity *corev1.Affinity `json:"affinity,omitempty"` + // Tolerations are a different way to control on which nodes your parser is executed. See https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/ + Tolerations []corev1.Toleration `json:"tolerations,omitempty"` } // ParseDefinitionStatus defines the observed state of ParseDefinition diff --git a/operator/apis/execution/v1/scan_types.go b/operator/apis/execution/v1/scan_types.go index 14fcc361c3..4ffb641ba7 100644 --- a/operator/apis/execution/v1/scan_types.go +++ b/operator/apis/execution/v1/scan_types.go @@ -48,6 +48,16 @@ type CascadeSpec struct { // +kubebuilder:default=false InheritHookSelector bool `json:"inheritHookSelector"` + // InheritAffinity defines whether cascading scans should inherit affinity from the parent scan. + // +optional + // +kubebuilder:default=true + InheritAffinity bool `json:"inheritAffinity"` + + // InheritTolerations defines whether cascading scans should inherit tolerations from the parent scan. + // +optional + // +kubebuilder:default=true + InheritTolerations bool `json:"inheritTolerations"` + // 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. @@ -112,6 +122,10 @@ type ScanSpec struct { VolumeMounts []corev1.VolumeMount `json:"volumeMounts,omitempty"` // InitContainers allows to specify init containers for the scan container, to pre-load data into them. InitContainers []corev1.Container `json:"initContainers,omitempty"` + // Affinity allows to specify a node affinity, to control on which nodes you want a scan to run. See: https://kubernetes.io/docs/tasks/configure-pod-container/assign-pods-nodes-using-node-affinity/ + Affinity *corev1.Affinity `json:"affinity,omitempty"` + // Tolerations are a different way to control on which nodes your scan is executed. See https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/ + Tolerations []corev1.Toleration `json:"tolerations,omitempty"` Cascades *CascadeSpec `json:"cascades,omitempty"` } diff --git a/operator/apis/execution/v1/scancompletionhook_types.go b/operator/apis/execution/v1/scancompletionhook_types.go index d7beb87ee8..3ad72c8938 100644 --- a/operator/apis/execution/v1/scancompletionhook_types.go +++ b/operator/apis/execution/v1/scancompletionhook_types.go @@ -48,6 +48,10 @@ type ScanCompletionHookSpec struct { Volumes []corev1.Volume `json:"volumes,omitempty"` // VolumeMounts allows to specify volume mounts for the hooks container. VolumeMounts []corev1.VolumeMount `json:"volumeMounts,omitempty"` + // Affinity allows to specify a node affinity, to control on which nodes you want a hook to run. See: https://kubernetes.io/docs/tasks/configure-pod-container/assign-pods-nodes-using-node-affinity/ + Affinity *corev1.Affinity `json:"affinity,omitempty"` + // Tolerations are a different way to control on which nodes your hook is executed. See https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/ + Tolerations []corev1.Toleration `json:"tolerations,omitempty"` // ServiceAccountName Name of the serviceAccount Name used. Should only be used if your hook needs specifc RBAC Access. Otherwise the hook is run using a "scan-completion-hook" service account. The service account should have at least "get" rights on scans.execution.securecodebox.io, and "get" & "patch" scans.execution.securecodebox.io/status ServiceAccountName *string `json:"serviceAccountName,omitempty"` diff --git a/operator/apis/execution/v1/zz_generated.deepcopy.go b/operator/apis/execution/v1/zz_generated.deepcopy.go index e182c5817d..c686b2dcd8 100644 --- a/operator/apis/execution/v1/zz_generated.deepcopy.go +++ b/operator/apis/execution/v1/zz_generated.deepcopy.go @@ -213,6 +213,18 @@ func (in *ParseDefinitionSpec) DeepCopyInto(out *ParseDefinitionSpec) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.Affinity != nil { + in, out := &in.Affinity, &out.Affinity + *out = new(corev1.Affinity) + (*in).DeepCopyInto(*out) + } + if in.Tolerations != nil { + in, out := &in.Tolerations, &out.Tolerations + *out = make([]corev1.Toleration, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ParseDefinitionSpec. @@ -355,6 +367,18 @@ func (in *ScanCompletionHookSpec) DeepCopyInto(out *ScanCompletionHookSpec) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.Affinity != nil { + in, out := &in.Affinity, &out.Affinity + *out = new(corev1.Affinity) + (*in).DeepCopyInto(*out) + } + if in.Tolerations != nil { + in, out := &in.Tolerations, &out.Tolerations + *out = make([]corev1.Toleration, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } if in.ServiceAccountName != nil { in, out := &in.ServiceAccountName, &out.ServiceAccountName *out = new(string) @@ -465,6 +489,18 @@ func (in *ScanSpec) DeepCopyInto(out *ScanSpec) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.Affinity != nil { + in, out := &in.Affinity, &out.Affinity + *out = new(corev1.Affinity) + (*in).DeepCopyInto(*out) + } + if in.Tolerations != nil { + in, out := &in.Tolerations, &out.Tolerations + *out = make([]corev1.Toleration, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } if in.Cascades != nil { in, out := &in.Cascades, &out.Cascades *out = new(CascadeSpec) diff --git a/operator/config/crd/bases/cascading.securecodebox.io_cascadingrules.yaml b/operator/config/crd/bases/cascading.securecodebox.io_cascadingrules.yaml index 068a4c3d3a..c6e60f4784 100644 --- a/operator/config/crd/bases/cascading.securecodebox.io_cascadingrules.yaml +++ b/operator/config/crd/bases/cascading.securecodebox.io_cascadingrules.yaml @@ -1,6 +1,3 @@ -# SPDX-FileCopyrightText: 2021 iteratec GmbH -# -# SPDX-License-Identifier: Apache-2.0 --- apiVersion: apiextensions.k8s.io/v1 @@ -100,10 +97,633 @@ spec: scanSpec: description: ScanSpec defines how the cascaded scan should look like properties: + affinity: + description: 'Affinity allows to specify a node affinity, to control + on which nodes you want a scan to run. See: https://kubernetes.io/docs/tasks/configure-pod-container/assign-pods-nodes-using-node-affinity/' + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for + the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods + to nodes that satisfy the affinity expressions specified + by this field, but it may choose a node that violates + one or more of the expressions. The node that is most + preferred is the one with the greatest sum of weights, + i.e. for each node that meets all of the scheduling + requirements (resource request, requiredDuringScheduling + affinity expressions, etc.), compute a sum by iterating + through the elements of this field and adding "weight" + to the sum if the node matches the corresponding matchExpressions; + the node(s) with the highest sum are the most preferred. + items: + description: An empty preferred scheduling term matches + all objects with implicit weight 0 (i.e. it's a no-op). + A null preferred scheduling term matches no objects + (i.e. is also a no-op). + properties: + preference: + description: A node selector term, associated with + the corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: 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. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: 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. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + weight: + description: Weight associated with matching the + corresponding nodeSelectorTerm, in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by + this field are not met at scheduling time, the pod will + not be scheduled onto the node. If the affinity requirements + specified by this field cease to be met at some point + during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from + its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. + The terms are ORed. + items: + description: A null or empty node selector term + matches no objects. The requirements of them are + ANDed. The TopologySelectorTerm type implements + a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: 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. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: 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. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + type: array + required: + - nodeSelectorTerms + type: object + type: object + podAffinity: + description: Describes pod affinity scheduling rules (e.g. + co-locate this pod in the same node, zone, etc. as some + other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods + to nodes that satisfy the affinity expressions specified + by this field, but it may choose a node that violates + one or more of the expressions. The node that is most + preferred is the one with the greatest sum of weights, + i.e. for each node that meets all of the scheduling + requirements (resource request, requiredDuringScheduling + affinity expressions, etc.), compute a sum by iterating + through the elements of this field and adding "weight" + to the sum if the node has pods which matches the corresponding + podAffinityTerm; the node(s) with the highest sum are + the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + 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 + namespaces: + description: namespaces specifies which namespaces + the labelSelector applies to (matches against); + null or empty list means "this pod's namespace" + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the + pods matching the labelSelector in the specified + namespaces, where co-located is defined as + running on a node whose value of the label + with key topologyKey matches that of any node + on which any of the selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching the + corresponding podAffinityTerm, in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by + this field are not met at scheduling time, the pod will + not be scheduled onto the node. If the affinity requirements + specified by this field cease to be met at some point + during pod execution (e.g. due to a pod label update), + the system may or may not try to eventually evict the + pod from its node. When there are multiple elements, + the lists of nodes corresponding to each podAffinityTerm + are intersected, i.e. all terms must be satisfied. + items: + description: Defines a set of pods (namely those matching + the labelSelector relative to the given namespace(s)) + that this pod should be co-located (affinity) or not + co-located (anti-affinity) with, where co-located + is defined as running on a node whose value of the + label with key matches that of any node + on which a pod of the set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + 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 + namespaces: + description: namespaces specifies which namespaces + the labelSelector applies to (matches against); + null or empty list means "this pod's namespace" + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods + matching the labelSelector in the specified namespaces, + where co-located is defined as running on a node + whose value of the label with key topologyKey + matches that of any node on which any of the selected + pods is running. Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules + (e.g. avoid putting this pod in the same node, zone, etc. + as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods + to nodes that satisfy the anti-affinity expressions + specified by this field, but it may choose a node that + violates one or more of the expressions. The node that + is most preferred is the one with the greatest sum of + weights, i.e. for each node that meets all of the scheduling + requirements (resource request, requiredDuringScheduling + anti-affinity expressions, etc.), compute a sum by iterating + through the elements of this field and adding "weight" + to the sum if the node has pods which matches the corresponding + podAffinityTerm; the node(s) with the highest sum are + the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + 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 + namespaces: + description: namespaces specifies which namespaces + the labelSelector applies to (matches against); + null or empty list means "this pod's namespace" + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the + pods matching the labelSelector in the specified + namespaces, where co-located is defined as + running on a node whose value of the label + with key topologyKey matches that of any node + on which any of the selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching the + corresponding podAffinityTerm, in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the anti-affinity requirements specified + by this field are not met at scheduling time, the pod + will not be scheduled onto the node. If the anti-affinity + requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod + label update), the system may or may not try to eventually + evict the pod from its node. When there are multiple + elements, the lists of nodes corresponding to each podAffinityTerm + are intersected, i.e. all terms must be satisfied. + items: + description: Defines a set of pods (namely those matching + the labelSelector relative to the given namespace(s)) + that this pod should be co-located (affinity) or not + co-located (anti-affinity) with, where co-located + is defined as running on a node whose value of the + label with key matches that of any node + on which a pod of the set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + 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 + namespaces: + description: namespaces specifies which namespaces + the labelSelector applies to (matches against); + null or empty list means "this pod's namespace" + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods + matching the labelSelector in the specified namespaces, + where co-located is defined as running on a node + whose value of the label with key topologyKey + matches that of any node on which any of the selected + pods is running. Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object cascades: description: CascadeSpec describes how and when cascading scans should be generated. properties: + inheritAffinity: + default: true + description: InheritAffinity defines whether cascading scans + should inherit affinity from the parent scan. + type: boolean inheritAnnotations: default: true description: InheritAnnotations defines whether cascading @@ -130,6 +750,11 @@ spec: description: InheritLabels defines whether cascading scans should inherit labels from the parent scan type: boolean + inheritTolerations: + default: true + description: InheritTolerations defines whether cascading + scans should inherit tolerations from the parent scan. + type: boolean inheritVolumes: default: false description: InheritVolumes defines whether cascading scans @@ -1542,6 +2167,48 @@ spec: scanType: description: The name of the scanType which should be started. type: string + tolerations: + description: Tolerations are a different way to control on which + nodes your scan is executed. See https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/ + items: + description: The pod this Toleration is attached to tolerates + any taint that matches the triple using + the matching operator . + properties: + effect: + description: Effect indicates the taint effect to match. + Empty means match all taint effects. When specified, allowed + values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: Key is the taint key that the toleration applies + to. Empty means match all taint keys. If the key is empty, + operator must be Exists; this combination means to match + all values and all keys. + type: string + operator: + description: Operator represents a key's relationship to + the value. Valid operators are Exists and Equal. Defaults + to Equal. Exists is equivalent to wildcard for value, + so that a pod can tolerate all taints of a particular + category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period of + time the toleration (which must be of effect NoExecute, + otherwise this field is ignored) tolerates the taint. + By default, it is not set, which means tolerate the taint + forever (do not evict). Zero and negative values will + be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: Value is the taint value the toleration matches + to. If the operator is Exists, the value should be empty, + otherwise just a regular string. + type: string + type: object + type: array volumeMounts: description: VolumeMounts allows to specify volume mounts for the scan container. diff --git a/operator/config/crd/bases/execution.securecodebox.io_parsedefinitions.yaml b/operator/config/crd/bases/execution.securecodebox.io_parsedefinitions.yaml index 4473217c8d..e1145c50d7 100644 --- a/operator/config/crd/bases/execution.securecodebox.io_parsedefinitions.yaml +++ b/operator/config/crd/bases/execution.securecodebox.io_parsedefinitions.yaml @@ -1,6 +1,3 @@ -# SPDX-FileCopyrightText: 2021 iteratec GmbH -# -# SPDX-License-Identifier: Apache-2.0 --- apiVersion: apiextensions.k8s.io/v1 @@ -44,6 +41,598 @@ spec: spec: description: ParseDefinitionSpec defines the desired state of ParseDefinition properties: + affinity: + description: 'Affinity allows to specify a node affinity, to control + on which nodes you want a parser to run. See: https://kubernetes.io/docs/tasks/configure-pod-container/assign-pods-nodes-using-node-affinity/' + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for the + pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to + nodes that satisfy the affinity expressions specified by + this field, but it may choose a node that violates one or + more of the expressions. The node that is most preferred + is the one with the greatest sum of weights, i.e. for each + node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, + etc.), compute a sum by iterating through the elements of + this field and adding "weight" to the sum if the node matches + the corresponding matchExpressions; the node(s) with the + highest sum are the most preferred. + items: + description: An empty preferred scheduling term matches + all objects with implicit weight 0 (i.e. it's a no-op). + A null preferred scheduling term matches no objects (i.e. + is also a no-op). + properties: + preference: + description: A node selector term, associated with the + corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: A node selector requirement is a + selector that contains values, a key, and an + operator that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators are + In, NotIn, Exists, DoesNotExist. Gt, and + Lt. + type: string + values: + description: 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. If the operator is Gt or + Lt, the values array must have a single + element, which will be interpreted as an + integer. This array is replaced during a + strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: A node selector requirement is a + selector that contains values, a key, and an + operator that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators are + In, NotIn, Exists, DoesNotExist. Gt, and + Lt. + type: string + values: + description: 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. If the operator is Gt or + Lt, the values array must have a single + element, which will be interpreted as an + integer. This array is replaced during a + strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + weight: + description: Weight associated with matching the corresponding + nodeSelectorTerm, in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by this + field are not met at scheduling time, the pod will not be + scheduled onto the node. If the affinity requirements specified + by this field cease to be met at some point during pod execution + (e.g. due to an update), the system may or may not try to + eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. + The terms are ORed. + items: + description: A null or empty node selector term matches + no objects. The requirements of them are ANDed. The + TopologySelectorTerm type implements a subset of the + NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: A node selector requirement is a + selector that contains values, a key, and an + operator that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators are + In, NotIn, Exists, DoesNotExist. Gt, and + Lt. + type: string + values: + description: 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. If the operator is Gt or + Lt, the values array must have a single + element, which will be interpreted as an + integer. This array is replaced during a + strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: A node selector requirement is a + selector that contains values, a key, and an + operator that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators are + In, NotIn, Exists, DoesNotExist. Gt, and + Lt. + type: string + values: + description: 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. If the operator is Gt or + Lt, the values array must have a single + element, which will be interpreted as an + integer. This array is replaced during a + strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + type: array + required: + - nodeSelectorTerms + type: object + type: object + podAffinity: + description: Describes pod affinity scheduling rules (e.g. co-locate + this pod in the same node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to + nodes that satisfy the affinity expressions specified by + this field, but it may choose a node that violates one or + more of the expressions. The node that is most preferred + is the one with the greatest sum of weights, i.e. for each + node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, + etc.), compute a sum by iterating through the elements of + this field and adding "weight" to the sum if the node has + pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + 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 + namespaces: + description: namespaces specifies which namespaces + the labelSelector applies to (matches against); + null or empty list means "this pod's namespace" + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods + matching the labelSelector in the specified namespaces, + where co-located is defined as running on a node + whose value of the label with key topologyKey + matches that of any node on which any of the selected + pods is running. Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching the corresponding + podAffinityTerm, in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by this + field are not met at scheduling time, the pod will not be + scheduled onto the node. If the affinity requirements specified + by this field cease to be met at some point during pod execution + (e.g. due to a pod label update), the system may or may + not try to eventually evict the pod from its node. When + there are multiple elements, the lists of nodes corresponding + to each podAffinityTerm are intersected, i.e. all terms + must be satisfied. + items: + description: Defines a set of pods (namely those matching + the labelSelector relative to the given namespace(s)) + that this pod should be co-located (affinity) or not co-located + (anti-affinity) with, where co-located is defined as running + on a node whose value of the label with key + matches that of any node on which a pod of the set of + pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + 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 + namespaces: + description: namespaces specifies which namespaces the + labelSelector applies to (matches against); null or + empty list means "this pod's namespace" + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where + co-located is defined as running on a node whose value + of the label with key topologyKey matches that of + any node on which any of the selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules (e.g. + avoid putting this pod in the same node, zone, etc. as some + other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to + nodes that satisfy the anti-affinity expressions specified + by this field, but it may choose a node that violates one + or more of the expressions. The node that is most preferred + is the one with the greatest sum of weights, i.e. for each + node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, + etc.), compute a sum by iterating through the elements of + this field and adding "weight" to the sum if the node has + pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + 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 + namespaces: + description: namespaces specifies which namespaces + the labelSelector applies to (matches against); + null or empty list means "this pod's namespace" + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods + matching the labelSelector in the specified namespaces, + where co-located is defined as running on a node + whose value of the label with key topologyKey + matches that of any node on which any of the selected + pods is running. Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching the corresponding + podAffinityTerm, in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the anti-affinity requirements specified by + this field are not met at scheduling time, the pod will + not be scheduled onto the node. If the anti-affinity requirements + specified by this field cease to be met at some point during + pod execution (e.g. due to a pod label update), the system + may or may not try to eventually evict the pod from its + node. When there are multiple elements, the lists of nodes + corresponding to each podAffinityTerm are intersected, i.e. + all terms must be satisfied. + items: + description: Defines a set of pods (namely those matching + the labelSelector relative to the given namespace(s)) + that this pod should be co-located (affinity) or not co-located + (anti-affinity) with, where co-located is defined as running + on a node whose value of the label with key + matches that of any node on which a pod of the set of + pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + 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 + namespaces: + description: namespaces specifies which namespaces the + labelSelector applies to (matches against); null or + empty list means "this pod's namespace" + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where + co-located is defined as running on a node whose value + of the label with key topologyKey matches that of + any node on which any of the selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object env: description: Env allows to specify environment vars for the parser container. @@ -173,6 +762,47 @@ spec: additionalProperties: type: string type: object + tolerations: + description: Tolerations are a different way to control on which nodes + your parser is executed. See https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/ + items: + description: The pod this Toleration is attached to tolerates any + taint that matches the triple using the matching + operator . + properties: + effect: + description: Effect indicates the taint effect to match. Empty + means match all taint effects. When specified, allowed values + are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: Key is the taint key that the toleration applies + to. Empty means match all taint keys. If the key is empty, + operator must be Exists; this combination means to match all + values and all keys. + type: string + operator: + description: Operator represents a key's relationship to the + value. Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod + can tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period of time + the toleration (which must be of effect NoExecute, otherwise + this field is ignored) tolerates the taint. By default, it + is not set, which means tolerate the taint forever (do not + evict). Zero and negative values will be treated as 0 (evict + immediately) by the system. + format: int64 + type: integer + value: + description: Value is the taint value the toleration matches + to. If the operator is Exists, the value should be empty, + otherwise just a regular string. + type: string + type: object + type: array ttlSecondsAfterFinished: description: TTLSecondsAfterFinished configures the ttlSecondsAfterFinished field for the created parse job diff --git a/operator/config/crd/bases/execution.securecodebox.io_scancompletionhooks.yaml b/operator/config/crd/bases/execution.securecodebox.io_scancompletionhooks.yaml index 2179549b24..896c54691c 100644 --- a/operator/config/crd/bases/execution.securecodebox.io_scancompletionhooks.yaml +++ b/operator/config/crd/bases/execution.securecodebox.io_scancompletionhooks.yaml @@ -1,6 +1,3 @@ -# SPDX-FileCopyrightText: 2021 iteratec GmbH -# -# SPDX-License-Identifier: Apache-2.0 --- apiVersion: apiextensions.k8s.io/v1 @@ -53,6 +50,598 @@ spec: spec: description: ScanCompletionHookSpec defines the desired state of ScanCompletionHook properties: + affinity: + description: 'Affinity allows to specify a node affinity, to control + on which nodes you want a hook to run. See: https://kubernetes.io/docs/tasks/configure-pod-container/assign-pods-nodes-using-node-affinity/' + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for the + pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to + nodes that satisfy the affinity expressions specified by + this field, but it may choose a node that violates one or + more of the expressions. The node that is most preferred + is the one with the greatest sum of weights, i.e. for each + node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, + etc.), compute a sum by iterating through the elements of + this field and adding "weight" to the sum if the node matches + the corresponding matchExpressions; the node(s) with the + highest sum are the most preferred. + items: + description: An empty preferred scheduling term matches + all objects with implicit weight 0 (i.e. it's a no-op). + A null preferred scheduling term matches no objects (i.e. + is also a no-op). + properties: + preference: + description: A node selector term, associated with the + corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: A node selector requirement is a + selector that contains values, a key, and an + operator that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators are + In, NotIn, Exists, DoesNotExist. Gt, and + Lt. + type: string + values: + description: 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. If the operator is Gt or + Lt, the values array must have a single + element, which will be interpreted as an + integer. This array is replaced during a + strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: A node selector requirement is a + selector that contains values, a key, and an + operator that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators are + In, NotIn, Exists, DoesNotExist. Gt, and + Lt. + type: string + values: + description: 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. If the operator is Gt or + Lt, the values array must have a single + element, which will be interpreted as an + integer. This array is replaced during a + strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + weight: + description: Weight associated with matching the corresponding + nodeSelectorTerm, in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by this + field are not met at scheduling time, the pod will not be + scheduled onto the node. If the affinity requirements specified + by this field cease to be met at some point during pod execution + (e.g. due to an update), the system may or may not try to + eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. + The terms are ORed. + items: + description: A null or empty node selector term matches + no objects. The requirements of them are ANDed. The + TopologySelectorTerm type implements a subset of the + NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: A node selector requirement is a + selector that contains values, a key, and an + operator that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators are + In, NotIn, Exists, DoesNotExist. Gt, and + Lt. + type: string + values: + description: 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. If the operator is Gt or + Lt, the values array must have a single + element, which will be interpreted as an + integer. This array is replaced during a + strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: A node selector requirement is a + selector that contains values, a key, and an + operator that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators are + In, NotIn, Exists, DoesNotExist. Gt, and + Lt. + type: string + values: + description: 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. If the operator is Gt or + Lt, the values array must have a single + element, which will be interpreted as an + integer. This array is replaced during a + strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + type: array + required: + - nodeSelectorTerms + type: object + type: object + podAffinity: + description: Describes pod affinity scheduling rules (e.g. co-locate + this pod in the same node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to + nodes that satisfy the affinity expressions specified by + this field, but it may choose a node that violates one or + more of the expressions. The node that is most preferred + is the one with the greatest sum of weights, i.e. for each + node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, + etc.), compute a sum by iterating through the elements of + this field and adding "weight" to the sum if the node has + pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + 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 + namespaces: + description: namespaces specifies which namespaces + the labelSelector applies to (matches against); + null or empty list means "this pod's namespace" + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods + matching the labelSelector in the specified namespaces, + where co-located is defined as running on a node + whose value of the label with key topologyKey + matches that of any node on which any of the selected + pods is running. Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching the corresponding + podAffinityTerm, in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by this + field are not met at scheduling time, the pod will not be + scheduled onto the node. If the affinity requirements specified + by this field cease to be met at some point during pod execution + (e.g. due to a pod label update), the system may or may + not try to eventually evict the pod from its node. When + there are multiple elements, the lists of nodes corresponding + to each podAffinityTerm are intersected, i.e. all terms + must be satisfied. + items: + description: Defines a set of pods (namely those matching + the labelSelector relative to the given namespace(s)) + that this pod should be co-located (affinity) or not co-located + (anti-affinity) with, where co-located is defined as running + on a node whose value of the label with key + matches that of any node on which a pod of the set of + pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + 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 + namespaces: + description: namespaces specifies which namespaces the + labelSelector applies to (matches against); null or + empty list means "this pod's namespace" + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where + co-located is defined as running on a node whose value + of the label with key topologyKey matches that of + any node on which any of the selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules (e.g. + avoid putting this pod in the same node, zone, etc. as some + other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to + nodes that satisfy the anti-affinity expressions specified + by this field, but it may choose a node that violates one + or more of the expressions. The node that is most preferred + is the one with the greatest sum of weights, i.e. for each + node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, + etc.), compute a sum by iterating through the elements of + this field and adding "weight" to the sum if the node has + pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + 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 + namespaces: + description: namespaces specifies which namespaces + the labelSelector applies to (matches against); + null or empty list means "this pod's namespace" + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods + matching the labelSelector in the specified namespaces, + where co-located is defined as running on a node + whose value of the label with key topologyKey + matches that of any node on which any of the selected + pods is running. Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching the corresponding + podAffinityTerm, in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the anti-affinity requirements specified by + this field are not met at scheduling time, the pod will + not be scheduled onto the node. If the anti-affinity requirements + specified by this field cease to be met at some point during + pod execution (e.g. due to a pod label update), the system + may or may not try to eventually evict the pod from its + node. When there are multiple elements, the lists of nodes + corresponding to each podAffinityTerm are intersected, i.e. + all terms must be satisfied. + items: + description: Defines a set of pods (namely those matching + the labelSelector relative to the given namespace(s)) + that this pod should be co-located (affinity) or not co-located + (anti-affinity) with, where co-located is defined as running + on a node whose value of the label with key + matches that of any node on which a pod of the set of + pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + 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 + namespaces: + description: namespaces specifies which namespaces the + labelSelector applies to (matches against); null or + empty list means "this pod's namespace" + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where + co-located is defined as running on a node whose value + of the label with key topologyKey matches that of + any node on which any of the selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object env: description: Env allows to specify environment vars for the hooks container. @@ -192,6 +781,47 @@ spec: The service account should have at least "get" rights on scans.execution.securecodebox.io, and "get" & "patch" scans.execution.securecodebox.io/status type: string + tolerations: + description: Tolerations are a different way to control on which nodes + your hook is executed. See https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/ + items: + description: The pod this Toleration is attached to tolerates any + taint that matches the triple using the matching + operator . + properties: + effect: + description: Effect indicates the taint effect to match. Empty + means match all taint effects. When specified, allowed values + are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: Key is the taint key that the toleration applies + to. Empty means match all taint keys. If the key is empty, + operator must be Exists; this combination means to match all + values and all keys. + type: string + operator: + description: Operator represents a key's relationship to the + value. Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod + can tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period of time + the toleration (which must be of effect NoExecute, otherwise + this field is ignored) tolerates the taint. By default, it + is not set, which means tolerate the taint forever (do not + evict). Zero and negative values will be treated as 0 (evict + immediately) by the system. + format: int64 + type: integer + value: + description: Value is the taint value the toleration matches + to. If the operator is Exists, the value should be empty, + otherwise just a regular string. + type: string + type: object + type: array ttlSecondsAfterFinished: description: TTLSecondsAfterFinished configures the ttlSecondsAfterFinished field for the created hook job diff --git a/operator/config/crd/bases/execution.securecodebox.io_scans.yaml b/operator/config/crd/bases/execution.securecodebox.io_scans.yaml index ac11ba308b..01ae4e9712 100644 --- a/operator/config/crd/bases/execution.securecodebox.io_scans.yaml +++ b/operator/config/crd/bases/execution.securecodebox.io_scans.yaml @@ -1,6 +1,3 @@ -# SPDX-FileCopyrightText: 2021 iteratec GmbH -# -# SPDX-License-Identifier: Apache-2.0 --- apiVersion: apiextensions.k8s.io/v1 @@ -62,10 +59,607 @@ spec: spec: description: ScanSpec defines the desired state of Scan properties: + affinity: + description: 'Affinity allows to specify a node affinity, to control + on which nodes you want a scan to run. See: https://kubernetes.io/docs/tasks/configure-pod-container/assign-pods-nodes-using-node-affinity/' + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for the + pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to + nodes that satisfy the affinity expressions specified by + this field, but it may choose a node that violates one or + more of the expressions. The node that is most preferred + is the one with the greatest sum of weights, i.e. for each + node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, + etc.), compute a sum by iterating through the elements of + this field and adding "weight" to the sum if the node matches + the corresponding matchExpressions; the node(s) with the + highest sum are the most preferred. + items: + description: An empty preferred scheduling term matches + all objects with implicit weight 0 (i.e. it's a no-op). + A null preferred scheduling term matches no objects (i.e. + is also a no-op). + properties: + preference: + description: A node selector term, associated with the + corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: A node selector requirement is a + selector that contains values, a key, and an + operator that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators are + In, NotIn, Exists, DoesNotExist. Gt, and + Lt. + type: string + values: + description: 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. If the operator is Gt or + Lt, the values array must have a single + element, which will be interpreted as an + integer. This array is replaced during a + strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: A node selector requirement is a + selector that contains values, a key, and an + operator that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators are + In, NotIn, Exists, DoesNotExist. Gt, and + Lt. + type: string + values: + description: 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. If the operator is Gt or + Lt, the values array must have a single + element, which will be interpreted as an + integer. This array is replaced during a + strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + weight: + description: Weight associated with matching the corresponding + nodeSelectorTerm, in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by this + field are not met at scheduling time, the pod will not be + scheduled onto the node. If the affinity requirements specified + by this field cease to be met at some point during pod execution + (e.g. due to an update), the system may or may not try to + eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. + The terms are ORed. + items: + description: A null or empty node selector term matches + no objects. The requirements of them are ANDed. The + TopologySelectorTerm type implements a subset of the + NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: A node selector requirement is a + selector that contains values, a key, and an + operator that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators are + In, NotIn, Exists, DoesNotExist. Gt, and + Lt. + type: string + values: + description: 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. If the operator is Gt or + Lt, the values array must have a single + element, which will be interpreted as an + integer. This array is replaced during a + strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: A node selector requirement is a + selector that contains values, a key, and an + operator that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators are + In, NotIn, Exists, DoesNotExist. Gt, and + Lt. + type: string + values: + description: 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. If the operator is Gt or + Lt, the values array must have a single + element, which will be interpreted as an + integer. This array is replaced during a + strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + type: array + required: + - nodeSelectorTerms + type: object + type: object + podAffinity: + description: Describes pod affinity scheduling rules (e.g. co-locate + this pod in the same node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to + nodes that satisfy the affinity expressions specified by + this field, but it may choose a node that violates one or + more of the expressions. The node that is most preferred + is the one with the greatest sum of weights, i.e. for each + node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, + etc.), compute a sum by iterating through the elements of + this field and adding "weight" to the sum if the node has + pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + 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 + namespaces: + description: namespaces specifies which namespaces + the labelSelector applies to (matches against); + null or empty list means "this pod's namespace" + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods + matching the labelSelector in the specified namespaces, + where co-located is defined as running on a node + whose value of the label with key topologyKey + matches that of any node on which any of the selected + pods is running. Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching the corresponding + podAffinityTerm, in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by this + field are not met at scheduling time, the pod will not be + scheduled onto the node. If the affinity requirements specified + by this field cease to be met at some point during pod execution + (e.g. due to a pod label update), the system may or may + not try to eventually evict the pod from its node. When + there are multiple elements, the lists of nodes corresponding + to each podAffinityTerm are intersected, i.e. all terms + must be satisfied. + items: + description: Defines a set of pods (namely those matching + the labelSelector relative to the given namespace(s)) + that this pod should be co-located (affinity) or not co-located + (anti-affinity) with, where co-located is defined as running + on a node whose value of the label with key + matches that of any node on which a pod of the set of + pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + 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 + namespaces: + description: namespaces specifies which namespaces the + labelSelector applies to (matches against); null or + empty list means "this pod's namespace" + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where + co-located is defined as running on a node whose value + of the label with key topologyKey matches that of + any node on which any of the selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules (e.g. + avoid putting this pod in the same node, zone, etc. as some + other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to + nodes that satisfy the anti-affinity expressions specified + by this field, but it may choose a node that violates one + or more of the expressions. The node that is most preferred + is the one with the greatest sum of weights, i.e. for each + node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, + etc.), compute a sum by iterating through the elements of + this field and adding "weight" to the sum if the node has + pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + 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 + namespaces: + description: namespaces specifies which namespaces + the labelSelector applies to (matches against); + null or empty list means "this pod's namespace" + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods + matching the labelSelector in the specified namespaces, + where co-located is defined as running on a node + whose value of the label with key topologyKey + matches that of any node on which any of the selected + pods is running. Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching the corresponding + podAffinityTerm, in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the anti-affinity requirements specified by + this field are not met at scheduling time, the pod will + not be scheduled onto the node. If the anti-affinity requirements + specified by this field cease to be met at some point during + pod execution (e.g. due to a pod label update), the system + may or may not try to eventually evict the pod from its + node. When there are multiple elements, the lists of nodes + corresponding to each podAffinityTerm are intersected, i.e. + all terms must be satisfied. + items: + description: Defines a set of pods (namely those matching + the labelSelector relative to the given namespace(s)) + that this pod should be co-located (affinity) or not co-located + (anti-affinity) with, where co-located is defined as running + on a node whose value of the label with key + matches that of any node on which a pod of the set of + pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + 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 + namespaces: + description: namespaces specifies which namespaces the + labelSelector applies to (matches against); null or + empty list means "this pod's namespace" + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where + co-located is defined as running on a node whose value + of the label with key topologyKey matches that of + any node on which any of the selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object cascades: description: CascadeSpec describes how and when cascading scans should be generated. properties: + inheritAffinity: + default: true + description: InheritAffinity defines whether cascading scans should + inherit affinity from the parent scan. + type: boolean inheritAnnotations: default: true description: InheritAnnotations defines whether cascading scans @@ -92,6 +686,11 @@ spec: description: InheritLabels defines whether cascading scans should inherit labels from the parent scan type: boolean + inheritTolerations: + default: true + description: InheritTolerations defines whether cascading scans + should inherit tolerations from the parent scan. + type: boolean inheritVolumes: default: false description: InheritVolumes defines whether cascading scans should @@ -1473,6 +2072,47 @@ spec: scanType: description: The name of the scanType which should be started. type: string + tolerations: + description: Tolerations are a different way to control on which nodes + your scan is executed. See https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/ + items: + description: The pod this Toleration is attached to tolerates any + taint that matches the triple using the matching + operator . + properties: + effect: + description: Effect indicates the taint effect to match. Empty + means match all taint effects. When specified, allowed values + are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: Key is the taint key that the toleration applies + to. Empty means match all taint keys. If the key is empty, + operator must be Exists; this combination means to match all + values and all keys. + type: string + operator: + description: Operator represents a key's relationship to the + value. Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod + can tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period of time + the toleration (which must be of effect NoExecute, otherwise + this field is ignored) tolerates the taint. By default, it + is not set, which means tolerate the taint forever (do not + evict). Zero and negative values will be treated as 0 (evict + immediately) by the system. + format: int64 + type: integer + value: + description: Value is the taint value the toleration matches + to. If the operator is Exists, the value should be empty, + otherwise just a regular string. + type: string + type: object + type: array volumeMounts: description: VolumeMounts allows to specify volume mounts for the scan container. diff --git a/operator/config/crd/bases/execution.securecodebox.io_scantypes.yaml b/operator/config/crd/bases/execution.securecodebox.io_scantypes.yaml index b152d06a11..a5457b382e 100644 --- a/operator/config/crd/bases/execution.securecodebox.io_scantypes.yaml +++ b/operator/config/crd/bases/execution.securecodebox.io_scantypes.yaml @@ -1,6 +1,3 @@ -# SPDX-FileCopyrightText: 2021 iteratec GmbH -# -# SPDX-License-Identifier: Apache-2.0 --- apiVersion: apiextensions.k8s.io/v1 diff --git a/operator/config/crd/bases/execution.securecodebox.io_scheduledscans.yaml b/operator/config/crd/bases/execution.securecodebox.io_scheduledscans.yaml index 3e6f12c06b..7f58229ca2 100644 --- a/operator/config/crd/bases/execution.securecodebox.io_scheduledscans.yaml +++ b/operator/config/crd/bases/execution.securecodebox.io_scheduledscans.yaml @@ -1,6 +1,3 @@ -# SPDX-FileCopyrightText: 2021 iteratec GmbH -# -# SPDX-License-Identifier: Apache-2.0 --- apiVersion: apiextensions.k8s.io/v1 @@ -82,10 +79,633 @@ spec: scanSpec: description: ScanSpec describes the scan which should be started regularly properties: + affinity: + description: 'Affinity allows to specify a node affinity, to control + on which nodes you want a scan to run. See: https://kubernetes.io/docs/tasks/configure-pod-container/assign-pods-nodes-using-node-affinity/' + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for + the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods + to nodes that satisfy the affinity expressions specified + by this field, but it may choose a node that violates + one or more of the expressions. The node that is most + preferred is the one with the greatest sum of weights, + i.e. for each node that meets all of the scheduling + requirements (resource request, requiredDuringScheduling + affinity expressions, etc.), compute a sum by iterating + through the elements of this field and adding "weight" + to the sum if the node matches the corresponding matchExpressions; + the node(s) with the highest sum are the most preferred. + items: + description: An empty preferred scheduling term matches + all objects with implicit weight 0 (i.e. it's a no-op). + A null preferred scheduling term matches no objects + (i.e. is also a no-op). + properties: + preference: + description: A node selector term, associated with + the corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: 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. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: 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. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + weight: + description: Weight associated with matching the + corresponding nodeSelectorTerm, in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by + this field are not met at scheduling time, the pod will + not be scheduled onto the node. If the affinity requirements + specified by this field cease to be met at some point + during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from + its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. + The terms are ORed. + items: + description: A null or empty node selector term + matches no objects. The requirements of them are + ANDed. The TopologySelectorTerm type implements + a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: 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. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: 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. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + type: array + required: + - nodeSelectorTerms + type: object + type: object + podAffinity: + description: Describes pod affinity scheduling rules (e.g. + co-locate this pod in the same node, zone, etc. as some + other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods + to nodes that satisfy the affinity expressions specified + by this field, but it may choose a node that violates + one or more of the expressions. The node that is most + preferred is the one with the greatest sum of weights, + i.e. for each node that meets all of the scheduling + requirements (resource request, requiredDuringScheduling + affinity expressions, etc.), compute a sum by iterating + through the elements of this field and adding "weight" + to the sum if the node has pods which matches the corresponding + podAffinityTerm; the node(s) with the highest sum are + the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + 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 + namespaces: + description: namespaces specifies which namespaces + the labelSelector applies to (matches against); + null or empty list means "this pod's namespace" + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the + pods matching the labelSelector in the specified + namespaces, where co-located is defined as + running on a node whose value of the label + with key topologyKey matches that of any node + on which any of the selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching the + corresponding podAffinityTerm, in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by + this field are not met at scheduling time, the pod will + not be scheduled onto the node. If the affinity requirements + specified by this field cease to be met at some point + during pod execution (e.g. due to a pod label update), + the system may or may not try to eventually evict the + pod from its node. When there are multiple elements, + the lists of nodes corresponding to each podAffinityTerm + are intersected, i.e. all terms must be satisfied. + items: + description: Defines a set of pods (namely those matching + the labelSelector relative to the given namespace(s)) + that this pod should be co-located (affinity) or not + co-located (anti-affinity) with, where co-located + is defined as running on a node whose value of the + label with key matches that of any node + on which a pod of the set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + 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 + namespaces: + description: namespaces specifies which namespaces + the labelSelector applies to (matches against); + null or empty list means "this pod's namespace" + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods + matching the labelSelector in the specified namespaces, + where co-located is defined as running on a node + whose value of the label with key topologyKey + matches that of any node on which any of the selected + pods is running. Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules + (e.g. avoid putting this pod in the same node, zone, etc. + as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods + to nodes that satisfy the anti-affinity expressions + specified by this field, but it may choose a node that + violates one or more of the expressions. The node that + is most preferred is the one with the greatest sum of + weights, i.e. for each node that meets all of the scheduling + requirements (resource request, requiredDuringScheduling + anti-affinity expressions, etc.), compute a sum by iterating + through the elements of this field and adding "weight" + to the sum if the node has pods which matches the corresponding + podAffinityTerm; the node(s) with the highest sum are + the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + 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 + namespaces: + description: namespaces specifies which namespaces + the labelSelector applies to (matches against); + null or empty list means "this pod's namespace" + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the + pods matching the labelSelector in the specified + namespaces, where co-located is defined as + running on a node whose value of the label + with key topologyKey matches that of any node + on which any of the selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching the + corresponding podAffinityTerm, in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the anti-affinity requirements specified + by this field are not met at scheduling time, the pod + will not be scheduled onto the node. If the anti-affinity + requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod + label update), the system may or may not try to eventually + evict the pod from its node. When there are multiple + elements, the lists of nodes corresponding to each podAffinityTerm + are intersected, i.e. all terms must be satisfied. + items: + description: Defines a set of pods (namely those matching + the labelSelector relative to the given namespace(s)) + that this pod should be co-located (affinity) or not + co-located (anti-affinity) with, where co-located + is defined as running on a node whose value of the + label with key matches that of any node + on which a pod of the set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + 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 + namespaces: + description: namespaces specifies which namespaces + the labelSelector applies to (matches against); + null or empty list means "this pod's namespace" + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods + matching the labelSelector in the specified namespaces, + where co-located is defined as running on a node + whose value of the label with key topologyKey + matches that of any node on which any of the selected + pods is running. Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object cascades: description: CascadeSpec describes how and when cascading scans should be generated. properties: + inheritAffinity: + default: true + description: InheritAffinity defines whether cascading scans + should inherit affinity from the parent scan. + type: boolean inheritAnnotations: default: true description: InheritAnnotations defines whether cascading @@ -112,6 +732,11 @@ spec: description: InheritLabels defines whether cascading scans should inherit labels from the parent scan type: boolean + inheritTolerations: + default: true + description: InheritTolerations defines whether cascading + scans should inherit tolerations from the parent scan. + type: boolean inheritVolumes: default: false description: InheritVolumes defines whether cascading scans @@ -1524,6 +2149,48 @@ spec: scanType: description: The name of the scanType which should be started. type: string + tolerations: + description: Tolerations are a different way to control on which + nodes your scan is executed. See https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/ + items: + description: The pod this Toleration is attached to tolerates + any taint that matches the triple using + the matching operator . + properties: + effect: + description: Effect indicates the taint effect to match. + Empty means match all taint effects. When specified, allowed + values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: Key is the taint key that the toleration applies + to. Empty means match all taint keys. If the key is empty, + operator must be Exists; this combination means to match + all values and all keys. + type: string + operator: + description: Operator represents a key's relationship to + the value. Valid operators are Exists and Equal. Defaults + to Equal. Exists is equivalent to wildcard for value, + so that a pod can tolerate all taints of a particular + category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period of + time the toleration (which must be of effect NoExecute, + otherwise this field is ignored) tolerates the taint. + By default, it is not set, which means tolerate the taint + forever (do not evict). Zero and negative values will + be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: Value is the taint value the toleration matches + to. If the operator is Exists, the value should be empty, + otherwise just a regular string. + type: string + type: object + type: array volumeMounts: description: VolumeMounts allows to specify volume mounts for the scan container. diff --git a/operator/controllers/execution/scans/hook_reconciler.go b/operator/controllers/execution/scans/hook_reconciler.go index e49a36bc39..5466cee7f6 100644 --- a/operator/controllers/execution/scans/hook_reconciler.go +++ b/operator/controllers/execution/scans/hook_reconciler.go @@ -402,6 +402,21 @@ func (r *ScanReconciler) createJobForHook(hook *executionv1.ScanCompletionHook, hook.Spec.Volumes..., ) + // Set affinity from Scan, if one is set. Otherwise keep value from template + if scan.Spec.Affinity != nil { + job.Spec.Template.Spec.Affinity = scan.Spec.Affinity + } else { + job.Spec.Template.Spec.Affinity = hook.Spec.Affinity + } + + // Replace tolerations from template with those from the scan, if specified. + // Otherwise, stick to those from the template + if scan.Spec.Tolerations != nil { + job.Spec.Template.Spec.Tolerations = scan.Spec.Tolerations + } else { + job.Spec.Template.Spec.Tolerations = hook.Spec.Tolerations + } + if err := ctrl.SetControllerReference(scan, job, r.Scheme); err != nil { r.Log.Error(err, "Unable to set controllerReference on job", "job", job) return "", err diff --git a/operator/controllers/execution/scans/parse_reconciler.go b/operator/controllers/execution/scans/parse_reconciler.go index 6d5c0d110f..d5fbbc37c3 100644 --- a/operator/controllers/execution/scans/parse_reconciler.go +++ b/operator/controllers/execution/scans/parse_reconciler.go @@ -178,6 +178,20 @@ func (r *ScanReconciler) startParser(scan *executionv1.Scan) error { parseDefinition.Spec.Volumes..., ) + // Set affinity based on scan, if defined, or parseDefinition if not overriden by scan + if scan.Spec.Affinity != nil { + job.Spec.Template.Spec.Affinity = scan.Spec.Affinity + } else { + job.Spec.Template.Spec.Affinity = parseDefinition.Spec.Affinity + } + + // Set tolerations, either from parseDefinition or from scan + if scan.Spec.Tolerations != nil { + job.Spec.Template.Spec.Tolerations = scan.Spec.Tolerations + } else { + job.Spec.Template.Spec.Tolerations = parseDefinition.Spec.Tolerations + } + r.Log.V(8).Info("Configuring customCACerts for Parser") injectCustomCACertsIfConfigured(job) diff --git a/operator/controllers/execution/scans/scan_reconciler.go b/operator/controllers/execution/scans/scan_reconciler.go index 58569ead87..41b46ebf90 100644 --- a/operator/controllers/execution/scans/scan_reconciler.go +++ b/operator/controllers/execution/scans/scan_reconciler.go @@ -350,6 +350,17 @@ func (r *ScanReconciler) constructJobForScan(scan *executionv1.Scan, scanType *e scan.Spec.InitContainers..., ) + // Set affinity from ScanTemplate + if scan.Spec.Affinity != nil { + job.Spec.Template.Spec.Affinity = scan.Spec.Affinity + } + + // Replace (not merge!) tolerations from template with those specified in the scan job, if there are any. + // (otherwise keep those from the template) + if scan.Spec.Tolerations != nil { + job.Spec.Template.Spec.Tolerations = scan.Spec.Tolerations + } + // Using command over args job.Spec.Template.Spec.Containers[0].Command = command job.Spec.Template.Spec.Containers[0].Args = nil diff --git a/operator/crds/cascading.securecodebox.io_cascadingrules.yaml b/operator/crds/cascading.securecodebox.io_cascadingrules.yaml index 068a4c3d3a..334ef50049 100644 --- a/operator/crds/cascading.securecodebox.io_cascadingrules.yaml +++ b/operator/crds/cascading.securecodebox.io_cascadingrules.yaml @@ -100,10 +100,633 @@ spec: scanSpec: description: ScanSpec defines how the cascaded scan should look like properties: + affinity: + description: 'Affinity allows to specify a node affinity, to control + on which nodes you want a scan to run. See: https://kubernetes.io/docs/tasks/configure-pod-container/assign-pods-nodes-using-node-affinity/' + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for + the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods + to nodes that satisfy the affinity expressions specified + by this field, but it may choose a node that violates + one or more of the expressions. The node that is most + preferred is the one with the greatest sum of weights, + i.e. for each node that meets all of the scheduling + requirements (resource request, requiredDuringScheduling + affinity expressions, etc.), compute a sum by iterating + through the elements of this field and adding "weight" + to the sum if the node matches the corresponding matchExpressions; + the node(s) with the highest sum are the most preferred. + items: + description: An empty preferred scheduling term matches + all objects with implicit weight 0 (i.e. it's a no-op). + A null preferred scheduling term matches no objects + (i.e. is also a no-op). + properties: + preference: + description: A node selector term, associated with + the corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: 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. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: 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. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + weight: + description: Weight associated with matching the + corresponding nodeSelectorTerm, in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by + this field are not met at scheduling time, the pod will + not be scheduled onto the node. If the affinity requirements + specified by this field cease to be met at some point + during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from + its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. + The terms are ORed. + items: + description: A null or empty node selector term + matches no objects. The requirements of them are + ANDed. The TopologySelectorTerm type implements + a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: 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. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: 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. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + type: array + required: + - nodeSelectorTerms + type: object + type: object + podAffinity: + description: Describes pod affinity scheduling rules (e.g. + co-locate this pod in the same node, zone, etc. as some + other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods + to nodes that satisfy the affinity expressions specified + by this field, but it may choose a node that violates + one or more of the expressions. The node that is most + preferred is the one with the greatest sum of weights, + i.e. for each node that meets all of the scheduling + requirements (resource request, requiredDuringScheduling + affinity expressions, etc.), compute a sum by iterating + through the elements of this field and adding "weight" + to the sum if the node has pods which matches the corresponding + podAffinityTerm; the node(s) with the highest sum are + the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + 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 + namespaces: + description: namespaces specifies which namespaces + the labelSelector applies to (matches against); + null or empty list means "this pod's namespace" + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the + pods matching the labelSelector in the specified + namespaces, where co-located is defined as + running on a node whose value of the label + with key topologyKey matches that of any node + on which any of the selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching the + corresponding podAffinityTerm, in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by + this field are not met at scheduling time, the pod will + not be scheduled onto the node. If the affinity requirements + specified by this field cease to be met at some point + during pod execution (e.g. due to a pod label update), + the system may or may not try to eventually evict the + pod from its node. When there are multiple elements, + the lists of nodes corresponding to each podAffinityTerm + are intersected, i.e. all terms must be satisfied. + items: + description: Defines a set of pods (namely those matching + the labelSelector relative to the given namespace(s)) + that this pod should be co-located (affinity) or not + co-located (anti-affinity) with, where co-located + is defined as running on a node whose value of the + label with key matches that of any node + on which a pod of the set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + 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 + namespaces: + description: namespaces specifies which namespaces + the labelSelector applies to (matches against); + null or empty list means "this pod's namespace" + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods + matching the labelSelector in the specified namespaces, + where co-located is defined as running on a node + whose value of the label with key topologyKey + matches that of any node on which any of the selected + pods is running. Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules + (e.g. avoid putting this pod in the same node, zone, etc. + as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods + to nodes that satisfy the anti-affinity expressions + specified by this field, but it may choose a node that + violates one or more of the expressions. The node that + is most preferred is the one with the greatest sum of + weights, i.e. for each node that meets all of the scheduling + requirements (resource request, requiredDuringScheduling + anti-affinity expressions, etc.), compute a sum by iterating + through the elements of this field and adding "weight" + to the sum if the node has pods which matches the corresponding + podAffinityTerm; the node(s) with the highest sum are + the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + 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 + namespaces: + description: namespaces specifies which namespaces + the labelSelector applies to (matches against); + null or empty list means "this pod's namespace" + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the + pods matching the labelSelector in the specified + namespaces, where co-located is defined as + running on a node whose value of the label + with key topologyKey matches that of any node + on which any of the selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching the + corresponding podAffinityTerm, in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the anti-affinity requirements specified + by this field are not met at scheduling time, the pod + will not be scheduled onto the node. If the anti-affinity + requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod + label update), the system may or may not try to eventually + evict the pod from its node. When there are multiple + elements, the lists of nodes corresponding to each podAffinityTerm + are intersected, i.e. all terms must be satisfied. + items: + description: Defines a set of pods (namely those matching + the labelSelector relative to the given namespace(s)) + that this pod should be co-located (affinity) or not + co-located (anti-affinity) with, where co-located + is defined as running on a node whose value of the + label with key matches that of any node + on which a pod of the set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + 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 + namespaces: + description: namespaces specifies which namespaces + the labelSelector applies to (matches against); + null or empty list means "this pod's namespace" + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods + matching the labelSelector in the specified namespaces, + where co-located is defined as running on a node + whose value of the label with key topologyKey + matches that of any node on which any of the selected + pods is running. Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object cascades: description: CascadeSpec describes how and when cascading scans should be generated. properties: + inheritAffinity: + default: true + description: InheritAffinity defines whether cascading scans + should inherit affinity from the parent scan. + type: boolean inheritAnnotations: default: true description: InheritAnnotations defines whether cascading @@ -130,6 +753,11 @@ spec: description: InheritLabels defines whether cascading scans should inherit labels from the parent scan type: boolean + inheritTolerations: + default: true + description: InheritTolerations defines whether cascading + scans should inherit tolerations from the parent scan. + type: boolean inheritVolumes: default: false description: InheritVolumes defines whether cascading scans @@ -1542,6 +2170,48 @@ spec: scanType: description: The name of the scanType which should be started. type: string + tolerations: + description: Tolerations are a different way to control on which + nodes your scan is executed. See https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/ + items: + description: The pod this Toleration is attached to tolerates + any taint that matches the triple using + the matching operator . + properties: + effect: + description: Effect indicates the taint effect to match. + Empty means match all taint effects. When specified, allowed + values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: Key is the taint key that the toleration applies + to. Empty means match all taint keys. If the key is empty, + operator must be Exists; this combination means to match + all values and all keys. + type: string + operator: + description: Operator represents a key's relationship to + the value. Valid operators are Exists and Equal. Defaults + to Equal. Exists is equivalent to wildcard for value, + so that a pod can tolerate all taints of a particular + category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period of + time the toleration (which must be of effect NoExecute, + otherwise this field is ignored) tolerates the taint. + By default, it is not set, which means tolerate the taint + forever (do not evict). Zero and negative values will + be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: Value is the taint value the toleration matches + to. If the operator is Exists, the value should be empty, + otherwise just a regular string. + type: string + type: object + type: array volumeMounts: description: VolumeMounts allows to specify volume mounts for the scan container. diff --git a/operator/crds/execution.securecodebox.io_parsedefinitions.yaml b/operator/crds/execution.securecodebox.io_parsedefinitions.yaml index 4473217c8d..e1145c50d7 100644 --- a/operator/crds/execution.securecodebox.io_parsedefinitions.yaml +++ b/operator/crds/execution.securecodebox.io_parsedefinitions.yaml @@ -1,6 +1,3 @@ -# SPDX-FileCopyrightText: 2021 iteratec GmbH -# -# SPDX-License-Identifier: Apache-2.0 --- apiVersion: apiextensions.k8s.io/v1 @@ -44,6 +41,598 @@ spec: spec: description: ParseDefinitionSpec defines the desired state of ParseDefinition properties: + affinity: + description: 'Affinity allows to specify a node affinity, to control + on which nodes you want a parser to run. See: https://kubernetes.io/docs/tasks/configure-pod-container/assign-pods-nodes-using-node-affinity/' + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for the + pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to + nodes that satisfy the affinity expressions specified by + this field, but it may choose a node that violates one or + more of the expressions. The node that is most preferred + is the one with the greatest sum of weights, i.e. for each + node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, + etc.), compute a sum by iterating through the elements of + this field and adding "weight" to the sum if the node matches + the corresponding matchExpressions; the node(s) with the + highest sum are the most preferred. + items: + description: An empty preferred scheduling term matches + all objects with implicit weight 0 (i.e. it's a no-op). + A null preferred scheduling term matches no objects (i.e. + is also a no-op). + properties: + preference: + description: A node selector term, associated with the + corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: A node selector requirement is a + selector that contains values, a key, and an + operator that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators are + In, NotIn, Exists, DoesNotExist. Gt, and + Lt. + type: string + values: + description: 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. If the operator is Gt or + Lt, the values array must have a single + element, which will be interpreted as an + integer. This array is replaced during a + strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: A node selector requirement is a + selector that contains values, a key, and an + operator that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators are + In, NotIn, Exists, DoesNotExist. Gt, and + Lt. + type: string + values: + description: 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. If the operator is Gt or + Lt, the values array must have a single + element, which will be interpreted as an + integer. This array is replaced during a + strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + weight: + description: Weight associated with matching the corresponding + nodeSelectorTerm, in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by this + field are not met at scheduling time, the pod will not be + scheduled onto the node. If the affinity requirements specified + by this field cease to be met at some point during pod execution + (e.g. due to an update), the system may or may not try to + eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. + The terms are ORed. + items: + description: A null or empty node selector term matches + no objects. The requirements of them are ANDed. The + TopologySelectorTerm type implements a subset of the + NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: A node selector requirement is a + selector that contains values, a key, and an + operator that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators are + In, NotIn, Exists, DoesNotExist. Gt, and + Lt. + type: string + values: + description: 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. If the operator is Gt or + Lt, the values array must have a single + element, which will be interpreted as an + integer. This array is replaced during a + strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: A node selector requirement is a + selector that contains values, a key, and an + operator that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators are + In, NotIn, Exists, DoesNotExist. Gt, and + Lt. + type: string + values: + description: 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. If the operator is Gt or + Lt, the values array must have a single + element, which will be interpreted as an + integer. This array is replaced during a + strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + type: array + required: + - nodeSelectorTerms + type: object + type: object + podAffinity: + description: Describes pod affinity scheduling rules (e.g. co-locate + this pod in the same node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to + nodes that satisfy the affinity expressions specified by + this field, but it may choose a node that violates one or + more of the expressions. The node that is most preferred + is the one with the greatest sum of weights, i.e. for each + node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, + etc.), compute a sum by iterating through the elements of + this field and adding "weight" to the sum if the node has + pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + 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 + namespaces: + description: namespaces specifies which namespaces + the labelSelector applies to (matches against); + null or empty list means "this pod's namespace" + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods + matching the labelSelector in the specified namespaces, + where co-located is defined as running on a node + whose value of the label with key topologyKey + matches that of any node on which any of the selected + pods is running. Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching the corresponding + podAffinityTerm, in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by this + field are not met at scheduling time, the pod will not be + scheduled onto the node. If the affinity requirements specified + by this field cease to be met at some point during pod execution + (e.g. due to a pod label update), the system may or may + not try to eventually evict the pod from its node. When + there are multiple elements, the lists of nodes corresponding + to each podAffinityTerm are intersected, i.e. all terms + must be satisfied. + items: + description: Defines a set of pods (namely those matching + the labelSelector relative to the given namespace(s)) + that this pod should be co-located (affinity) or not co-located + (anti-affinity) with, where co-located is defined as running + on a node whose value of the label with key + matches that of any node on which a pod of the set of + pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + 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 + namespaces: + description: namespaces specifies which namespaces the + labelSelector applies to (matches against); null or + empty list means "this pod's namespace" + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where + co-located is defined as running on a node whose value + of the label with key topologyKey matches that of + any node on which any of the selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules (e.g. + avoid putting this pod in the same node, zone, etc. as some + other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to + nodes that satisfy the anti-affinity expressions specified + by this field, but it may choose a node that violates one + or more of the expressions. The node that is most preferred + is the one with the greatest sum of weights, i.e. for each + node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, + etc.), compute a sum by iterating through the elements of + this field and adding "weight" to the sum if the node has + pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + 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 + namespaces: + description: namespaces specifies which namespaces + the labelSelector applies to (matches against); + null or empty list means "this pod's namespace" + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods + matching the labelSelector in the specified namespaces, + where co-located is defined as running on a node + whose value of the label with key topologyKey + matches that of any node on which any of the selected + pods is running. Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching the corresponding + podAffinityTerm, in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the anti-affinity requirements specified by + this field are not met at scheduling time, the pod will + not be scheduled onto the node. If the anti-affinity requirements + specified by this field cease to be met at some point during + pod execution (e.g. due to a pod label update), the system + may or may not try to eventually evict the pod from its + node. When there are multiple elements, the lists of nodes + corresponding to each podAffinityTerm are intersected, i.e. + all terms must be satisfied. + items: + description: Defines a set of pods (namely those matching + the labelSelector relative to the given namespace(s)) + that this pod should be co-located (affinity) or not co-located + (anti-affinity) with, where co-located is defined as running + on a node whose value of the label with key + matches that of any node on which a pod of the set of + pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + 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 + namespaces: + description: namespaces specifies which namespaces the + labelSelector applies to (matches against); null or + empty list means "this pod's namespace" + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where + co-located is defined as running on a node whose value + of the label with key topologyKey matches that of + any node on which any of the selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object env: description: Env allows to specify environment vars for the parser container. @@ -173,6 +762,47 @@ spec: additionalProperties: type: string type: object + tolerations: + description: Tolerations are a different way to control on which nodes + your parser is executed. See https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/ + items: + description: The pod this Toleration is attached to tolerates any + taint that matches the triple using the matching + operator . + properties: + effect: + description: Effect indicates the taint effect to match. Empty + means match all taint effects. When specified, allowed values + are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: Key is the taint key that the toleration applies + to. Empty means match all taint keys. If the key is empty, + operator must be Exists; this combination means to match all + values and all keys. + type: string + operator: + description: Operator represents a key's relationship to the + value. Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod + can tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period of time + the toleration (which must be of effect NoExecute, otherwise + this field is ignored) tolerates the taint. By default, it + is not set, which means tolerate the taint forever (do not + evict). Zero and negative values will be treated as 0 (evict + immediately) by the system. + format: int64 + type: integer + value: + description: Value is the taint value the toleration matches + to. If the operator is Exists, the value should be empty, + otherwise just a regular string. + type: string + type: object + type: array ttlSecondsAfterFinished: description: TTLSecondsAfterFinished configures the ttlSecondsAfterFinished field for the created parse job diff --git a/operator/crds/execution.securecodebox.io_scancompletionhooks.yaml b/operator/crds/execution.securecodebox.io_scancompletionhooks.yaml index 2179549b24..6f27ae2885 100644 --- a/operator/crds/execution.securecodebox.io_scancompletionhooks.yaml +++ b/operator/crds/execution.securecodebox.io_scancompletionhooks.yaml @@ -53,6 +53,598 @@ spec: spec: description: ScanCompletionHookSpec defines the desired state of ScanCompletionHook properties: + affinity: + description: 'Affinity allows to specify a node affinity, to control + on which nodes you want a hook to run. See: https://kubernetes.io/docs/tasks/configure-pod-container/assign-pods-nodes-using-node-affinity/' + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for the + pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to + nodes that satisfy the affinity expressions specified by + this field, but it may choose a node that violates one or + more of the expressions. The node that is most preferred + is the one with the greatest sum of weights, i.e. for each + node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, + etc.), compute a sum by iterating through the elements of + this field and adding "weight" to the sum if the node matches + the corresponding matchExpressions; the node(s) with the + highest sum are the most preferred. + items: + description: An empty preferred scheduling term matches + all objects with implicit weight 0 (i.e. it's a no-op). + A null preferred scheduling term matches no objects (i.e. + is also a no-op). + properties: + preference: + description: A node selector term, associated with the + corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: A node selector requirement is a + selector that contains values, a key, and an + operator that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators are + In, NotIn, Exists, DoesNotExist. Gt, and + Lt. + type: string + values: + description: 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. If the operator is Gt or + Lt, the values array must have a single + element, which will be interpreted as an + integer. This array is replaced during a + strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: A node selector requirement is a + selector that contains values, a key, and an + operator that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators are + In, NotIn, Exists, DoesNotExist. Gt, and + Lt. + type: string + values: + description: 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. If the operator is Gt or + Lt, the values array must have a single + element, which will be interpreted as an + integer. This array is replaced during a + strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + weight: + description: Weight associated with matching the corresponding + nodeSelectorTerm, in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by this + field are not met at scheduling time, the pod will not be + scheduled onto the node. If the affinity requirements specified + by this field cease to be met at some point during pod execution + (e.g. due to an update), the system may or may not try to + eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. + The terms are ORed. + items: + description: A null or empty node selector term matches + no objects. The requirements of them are ANDed. The + TopologySelectorTerm type implements a subset of the + NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: A node selector requirement is a + selector that contains values, a key, and an + operator that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators are + In, NotIn, Exists, DoesNotExist. Gt, and + Lt. + type: string + values: + description: 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. If the operator is Gt or + Lt, the values array must have a single + element, which will be interpreted as an + integer. This array is replaced during a + strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: A node selector requirement is a + selector that contains values, a key, and an + operator that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators are + In, NotIn, Exists, DoesNotExist. Gt, and + Lt. + type: string + values: + description: 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. If the operator is Gt or + Lt, the values array must have a single + element, which will be interpreted as an + integer. This array is replaced during a + strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + type: array + required: + - nodeSelectorTerms + type: object + type: object + podAffinity: + description: Describes pod affinity scheduling rules (e.g. co-locate + this pod in the same node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to + nodes that satisfy the affinity expressions specified by + this field, but it may choose a node that violates one or + more of the expressions. The node that is most preferred + is the one with the greatest sum of weights, i.e. for each + node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, + etc.), compute a sum by iterating through the elements of + this field and adding "weight" to the sum if the node has + pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + 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 + namespaces: + description: namespaces specifies which namespaces + the labelSelector applies to (matches against); + null or empty list means "this pod's namespace" + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods + matching the labelSelector in the specified namespaces, + where co-located is defined as running on a node + whose value of the label with key topologyKey + matches that of any node on which any of the selected + pods is running. Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching the corresponding + podAffinityTerm, in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by this + field are not met at scheduling time, the pod will not be + scheduled onto the node. If the affinity requirements specified + by this field cease to be met at some point during pod execution + (e.g. due to a pod label update), the system may or may + not try to eventually evict the pod from its node. When + there are multiple elements, the lists of nodes corresponding + to each podAffinityTerm are intersected, i.e. all terms + must be satisfied. + items: + description: Defines a set of pods (namely those matching + the labelSelector relative to the given namespace(s)) + that this pod should be co-located (affinity) or not co-located + (anti-affinity) with, where co-located is defined as running + on a node whose value of the label with key + matches that of any node on which a pod of the set of + pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + 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 + namespaces: + description: namespaces specifies which namespaces the + labelSelector applies to (matches against); null or + empty list means "this pod's namespace" + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where + co-located is defined as running on a node whose value + of the label with key topologyKey matches that of + any node on which any of the selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules (e.g. + avoid putting this pod in the same node, zone, etc. as some + other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to + nodes that satisfy the anti-affinity expressions specified + by this field, but it may choose a node that violates one + or more of the expressions. The node that is most preferred + is the one with the greatest sum of weights, i.e. for each + node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, + etc.), compute a sum by iterating through the elements of + this field and adding "weight" to the sum if the node has + pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + 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 + namespaces: + description: namespaces specifies which namespaces + the labelSelector applies to (matches against); + null or empty list means "this pod's namespace" + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods + matching the labelSelector in the specified namespaces, + where co-located is defined as running on a node + whose value of the label with key topologyKey + matches that of any node on which any of the selected + pods is running. Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching the corresponding + podAffinityTerm, in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the anti-affinity requirements specified by + this field are not met at scheduling time, the pod will + not be scheduled onto the node. If the anti-affinity requirements + specified by this field cease to be met at some point during + pod execution (e.g. due to a pod label update), the system + may or may not try to eventually evict the pod from its + node. When there are multiple elements, the lists of nodes + corresponding to each podAffinityTerm are intersected, i.e. + all terms must be satisfied. + items: + description: Defines a set of pods (namely those matching + the labelSelector relative to the given namespace(s)) + that this pod should be co-located (affinity) or not co-located + (anti-affinity) with, where co-located is defined as running + on a node whose value of the label with key + matches that of any node on which a pod of the set of + pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + 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 + namespaces: + description: namespaces specifies which namespaces the + labelSelector applies to (matches against); null or + empty list means "this pod's namespace" + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where + co-located is defined as running on a node whose value + of the label with key topologyKey matches that of + any node on which any of the selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object env: description: Env allows to specify environment vars for the hooks container. @@ -192,6 +784,47 @@ spec: The service account should have at least "get" rights on scans.execution.securecodebox.io, and "get" & "patch" scans.execution.securecodebox.io/status type: string + tolerations: + description: Tolerations are a different way to control on which nodes + your hook is executed. See https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/ + items: + description: The pod this Toleration is attached to tolerates any + taint that matches the triple using the matching + operator . + properties: + effect: + description: Effect indicates the taint effect to match. Empty + means match all taint effects. When specified, allowed values + are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: Key is the taint key that the toleration applies + to. Empty means match all taint keys. If the key is empty, + operator must be Exists; this combination means to match all + values and all keys. + type: string + operator: + description: Operator represents a key's relationship to the + value. Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod + can tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period of time + the toleration (which must be of effect NoExecute, otherwise + this field is ignored) tolerates the taint. By default, it + is not set, which means tolerate the taint forever (do not + evict). Zero and negative values will be treated as 0 (evict + immediately) by the system. + format: int64 + type: integer + value: + description: Value is the taint value the toleration matches + to. If the operator is Exists, the value should be empty, + otherwise just a regular string. + type: string + type: object + type: array ttlSecondsAfterFinished: description: TTLSecondsAfterFinished configures the ttlSecondsAfterFinished field for the created hook job diff --git a/operator/crds/execution.securecodebox.io_scans.yaml b/operator/crds/execution.securecodebox.io_scans.yaml index ac11ba308b..c27a0383ea 100644 --- a/operator/crds/execution.securecodebox.io_scans.yaml +++ b/operator/crds/execution.securecodebox.io_scans.yaml @@ -62,10 +62,607 @@ spec: spec: description: ScanSpec defines the desired state of Scan properties: + affinity: + description: 'Affinity allows to specify a node affinity, to control + on which nodes you want a scan to run. See: https://kubernetes.io/docs/tasks/configure-pod-container/assign-pods-nodes-using-node-affinity/' + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for the + pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to + nodes that satisfy the affinity expressions specified by + this field, but it may choose a node that violates one or + more of the expressions. The node that is most preferred + is the one with the greatest sum of weights, i.e. for each + node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, + etc.), compute a sum by iterating through the elements of + this field and adding "weight" to the sum if the node matches + the corresponding matchExpressions; the node(s) with the + highest sum are the most preferred. + items: + description: An empty preferred scheduling term matches + all objects with implicit weight 0 (i.e. it's a no-op). + A null preferred scheduling term matches no objects (i.e. + is also a no-op). + properties: + preference: + description: A node selector term, associated with the + corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: A node selector requirement is a + selector that contains values, a key, and an + operator that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators are + In, NotIn, Exists, DoesNotExist. Gt, and + Lt. + type: string + values: + description: 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. If the operator is Gt or + Lt, the values array must have a single + element, which will be interpreted as an + integer. This array is replaced during a + strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: A node selector requirement is a + selector that contains values, a key, and an + operator that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators are + In, NotIn, Exists, DoesNotExist. Gt, and + Lt. + type: string + values: + description: 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. If the operator is Gt or + Lt, the values array must have a single + element, which will be interpreted as an + integer. This array is replaced during a + strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + weight: + description: Weight associated with matching the corresponding + nodeSelectorTerm, in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by this + field are not met at scheduling time, the pod will not be + scheduled onto the node. If the affinity requirements specified + by this field cease to be met at some point during pod execution + (e.g. due to an update), the system may or may not try to + eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. + The terms are ORed. + items: + description: A null or empty node selector term matches + no objects. The requirements of them are ANDed. The + TopologySelectorTerm type implements a subset of the + NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: A node selector requirement is a + selector that contains values, a key, and an + operator that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators are + In, NotIn, Exists, DoesNotExist. Gt, and + Lt. + type: string + values: + description: 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. If the operator is Gt or + Lt, the values array must have a single + element, which will be interpreted as an + integer. This array is replaced during a + strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: A node selector requirement is a + selector that contains values, a key, and an + operator that relates the key and values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators are + In, NotIn, Exists, DoesNotExist. Gt, and + Lt. + type: string + values: + description: 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. If the operator is Gt or + Lt, the values array must have a single + element, which will be interpreted as an + integer. This array is replaced during a + strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + type: array + required: + - nodeSelectorTerms + type: object + type: object + podAffinity: + description: Describes pod affinity scheduling rules (e.g. co-locate + this pod in the same node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to + nodes that satisfy the affinity expressions specified by + this field, but it may choose a node that violates one or + more of the expressions. The node that is most preferred + is the one with the greatest sum of weights, i.e. for each + node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, + etc.), compute a sum by iterating through the elements of + this field and adding "weight" to the sum if the node has + pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + 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 + namespaces: + description: namespaces specifies which namespaces + the labelSelector applies to (matches against); + null or empty list means "this pod's namespace" + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods + matching the labelSelector in the specified namespaces, + where co-located is defined as running on a node + whose value of the label with key topologyKey + matches that of any node on which any of the selected + pods is running. Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching the corresponding + podAffinityTerm, in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by this + field are not met at scheduling time, the pod will not be + scheduled onto the node. If the affinity requirements specified + by this field cease to be met at some point during pod execution + (e.g. due to a pod label update), the system may or may + not try to eventually evict the pod from its node. When + there are multiple elements, the lists of nodes corresponding + to each podAffinityTerm are intersected, i.e. all terms + must be satisfied. + items: + description: Defines a set of pods (namely those matching + the labelSelector relative to the given namespace(s)) + that this pod should be co-located (affinity) or not co-located + (anti-affinity) with, where co-located is defined as running + on a node whose value of the label with key + matches that of any node on which a pod of the set of + pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + 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 + namespaces: + description: namespaces specifies which namespaces the + labelSelector applies to (matches against); null or + empty list means "this pod's namespace" + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where + co-located is defined as running on a node whose value + of the label with key topologyKey matches that of + any node on which any of the selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules (e.g. + avoid putting this pod in the same node, zone, etc. as some + other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods to + nodes that satisfy the anti-affinity expressions specified + by this field, but it may choose a node that violates one + or more of the expressions. The node that is most preferred + is the one with the greatest sum of weights, i.e. for each + node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, + etc.), compute a sum by iterating through the elements of + this field and adding "weight" to the sum if the node has + pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + 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 + namespaces: + description: namespaces specifies which namespaces + the labelSelector applies to (matches against); + null or empty list means "this pod's namespace" + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods + matching the labelSelector in the specified namespaces, + where co-located is defined as running on a node + whose value of the label with key topologyKey + matches that of any node on which any of the selected + pods is running. Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching the corresponding + podAffinityTerm, in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the anti-affinity requirements specified by + this field are not met at scheduling time, the pod will + not be scheduled onto the node. If the anti-affinity requirements + specified by this field cease to be met at some point during + pod execution (e.g. due to a pod label update), the system + may or may not try to eventually evict the pod from its + node. When there are multiple elements, the lists of nodes + corresponding to each podAffinityTerm are intersected, i.e. + all terms must be satisfied. + items: + description: Defines a set of pods (namely those matching + the labelSelector relative to the given namespace(s)) + that this pod should be co-located (affinity) or not co-located + (anti-affinity) with, where co-located is defined as running + on a node whose value of the label with key + matches that of any node on which a pod of the set of + pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + 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 + namespaces: + description: namespaces specifies which namespaces the + labelSelector applies to (matches against); null or + empty list means "this pod's namespace" + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where + co-located is defined as running on a node whose value + of the label with key topologyKey matches that of + any node on which any of the selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object cascades: description: CascadeSpec describes how and when cascading scans should be generated. properties: + inheritAffinity: + default: true + description: InheritAffinity defines whether cascading scans should + inherit affinity from the parent scan. + type: boolean inheritAnnotations: default: true description: InheritAnnotations defines whether cascading scans @@ -92,6 +689,11 @@ spec: description: InheritLabels defines whether cascading scans should inherit labels from the parent scan type: boolean + inheritTolerations: + default: true + description: InheritTolerations defines whether cascading scans + should inherit tolerations from the parent scan. + type: boolean inheritVolumes: default: false description: InheritVolumes defines whether cascading scans should @@ -1473,6 +2075,47 @@ spec: scanType: description: The name of the scanType which should be started. type: string + tolerations: + description: Tolerations are a different way to control on which nodes + your scan is executed. See https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/ + items: + description: The pod this Toleration is attached to tolerates any + taint that matches the triple using the matching + operator . + properties: + effect: + description: Effect indicates the taint effect to match. Empty + means match all taint effects. When specified, allowed values + are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: Key is the taint key that the toleration applies + to. Empty means match all taint keys. If the key is empty, + operator must be Exists; this combination means to match all + values and all keys. + type: string + operator: + description: Operator represents a key's relationship to the + value. Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod + can tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period of time + the toleration (which must be of effect NoExecute, otherwise + this field is ignored) tolerates the taint. By default, it + is not set, which means tolerate the taint forever (do not + evict). Zero and negative values will be treated as 0 (evict + immediately) by the system. + format: int64 + type: integer + value: + description: Value is the taint value the toleration matches + to. If the operator is Exists, the value should be empty, + otherwise just a regular string. + type: string + type: object + type: array volumeMounts: description: VolumeMounts allows to specify volume mounts for the scan container. diff --git a/operator/crds/execution.securecodebox.io_scheduledscans.yaml b/operator/crds/execution.securecodebox.io_scheduledscans.yaml index 3e6f12c06b..4b900fb472 100644 --- a/operator/crds/execution.securecodebox.io_scheduledscans.yaml +++ b/operator/crds/execution.securecodebox.io_scheduledscans.yaml @@ -82,10 +82,633 @@ spec: scanSpec: description: ScanSpec describes the scan which should be started regularly properties: + affinity: + description: 'Affinity allows to specify a node affinity, to control + on which nodes you want a scan to run. See: https://kubernetes.io/docs/tasks/configure-pod-container/assign-pods-nodes-using-node-affinity/' + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for + the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods + to nodes that satisfy the affinity expressions specified + by this field, but it may choose a node that violates + one or more of the expressions. The node that is most + preferred is the one with the greatest sum of weights, + i.e. for each node that meets all of the scheduling + requirements (resource request, requiredDuringScheduling + affinity expressions, etc.), compute a sum by iterating + through the elements of this field and adding "weight" + to the sum if the node matches the corresponding matchExpressions; + the node(s) with the highest sum are the most preferred. + items: + description: An empty preferred scheduling term matches + all objects with implicit weight 0 (i.e. it's a no-op). + A null preferred scheduling term matches no objects + (i.e. is also a no-op). + properties: + preference: + description: A node selector term, associated with + the corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: 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. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: 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. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + weight: + description: Weight associated with matching the + corresponding nodeSelectorTerm, in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by + this field are not met at scheduling time, the pod will + not be scheduled onto the node. If the affinity requirements + specified by this field cease to be met at some point + during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from + its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. + The terms are ORed. + items: + description: A null or empty node selector term + matches no objects. The requirements of them are + ANDed. The TopologySelectorTerm type implements + a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements + by node's labels. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: 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. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchFields: + description: A list of node selector requirements + by node's fields. + items: + description: A node selector requirement is + a selector that contains values, a key, + and an operator that relates the key and + values. + properties: + key: + description: The label key that the selector + applies to. + type: string + operator: + description: Represents a key's relationship + to a set of values. Valid operators + are In, NotIn, Exists, DoesNotExist. + Gt, and Lt. + type: string + values: + description: 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. If the + operator is Gt or Lt, the values array + must have a single element, which will + be interpreted as an integer. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + type: object + type: array + required: + - nodeSelectorTerms + type: object + type: object + podAffinity: + description: Describes pod affinity scheduling rules (e.g. + co-locate this pod in the same node, zone, etc. as some + other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods + to nodes that satisfy the affinity expressions specified + by this field, but it may choose a node that violates + one or more of the expressions. The node that is most + preferred is the one with the greatest sum of weights, + i.e. for each node that meets all of the scheduling + requirements (resource request, requiredDuringScheduling + affinity expressions, etc.), compute a sum by iterating + through the elements of this field and adding "weight" + to the sum if the node has pods which matches the corresponding + podAffinityTerm; the node(s) with the highest sum are + the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + 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 + namespaces: + description: namespaces specifies which namespaces + the labelSelector applies to (matches against); + null or empty list means "this pod's namespace" + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the + pods matching the labelSelector in the specified + namespaces, where co-located is defined as + running on a node whose value of the label + with key topologyKey matches that of any node + on which any of the selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching the + corresponding podAffinityTerm, in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the affinity requirements specified by + this field are not met at scheduling time, the pod will + not be scheduled onto the node. If the affinity requirements + specified by this field cease to be met at some point + during pod execution (e.g. due to a pod label update), + the system may or may not try to eventually evict the + pod from its node. When there are multiple elements, + the lists of nodes corresponding to each podAffinityTerm + are intersected, i.e. all terms must be satisfied. + items: + description: Defines a set of pods (namely those matching + the labelSelector relative to the given namespace(s)) + that this pod should be co-located (affinity) or not + co-located (anti-affinity) with, where co-located + is defined as running on a node whose value of the + label with key matches that of any node + on which a pod of the set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + 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 + namespaces: + description: namespaces specifies which namespaces + the labelSelector applies to (matches against); + null or empty list means "this pod's namespace" + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods + matching the labelSelector in the specified namespaces, + where co-located is defined as running on a node + whose value of the label with key topologyKey + matches that of any node on which any of the selected + pods is running. Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules + (e.g. avoid putting this pod in the same node, zone, etc. + as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: The scheduler will prefer to schedule pods + to nodes that satisfy the anti-affinity expressions + specified by this field, but it may choose a node that + violates one or more of the expressions. The node that + is most preferred is the one with the greatest sum of + weights, i.e. for each node that meets all of the scheduling + requirements (resource request, requiredDuringScheduling + anti-affinity expressions, etc.), compute a sum by iterating + through the elements of this field and adding "weight" + to the sum if the node has pods which matches the corresponding + podAffinityTerm; the node(s) with the highest sum are + the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm + fields are added per-node to find the most preferred + node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + 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 + namespaces: + description: namespaces specifies which namespaces + the labelSelector applies to (matches against); + null or empty list means "this pod's namespace" + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the + pods matching the labelSelector in the specified + namespaces, where co-located is defined as + running on a node whose value of the label + with key topologyKey matches that of any node + on which any of the selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: weight associated with matching the + corresponding podAffinityTerm, in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + description: If the anti-affinity requirements specified + by this field are not met at scheduling time, the pod + will not be scheduled onto the node. If the anti-affinity + requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod + label update), the system may or may not try to eventually + evict the pod from its node. When there are multiple + elements, the lists of nodes corresponding to each podAffinityTerm + are intersected, i.e. all terms must be satisfied. + items: + description: Defines a set of pods (namely those matching + the labelSelector relative to the given namespace(s)) + that this pod should be co-located (affinity) or not + co-located (anti-affinity) with, where co-located + is defined as running on a node whose value of the + label with key matches that of any node + on which a pod of the set of pods is running + properties: + labelSelector: + description: A label query over a set of resources, + in this case pods. + 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 + namespaces: + description: namespaces specifies which namespaces + the labelSelector applies to (matches against); + null or empty list means "this pod's namespace" + items: + type: string + type: array + topologyKey: + description: This pod should be co-located (affinity) + or not co-located (anti-affinity) with the pods + matching the labelSelector in the specified namespaces, + where co-located is defined as running on a node + whose value of the label with key topologyKey + matches that of any node on which any of the selected + pods is running. Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + type: object + type: object cascades: description: CascadeSpec describes how and when cascading scans should be generated. properties: + inheritAffinity: + default: true + description: InheritAffinity defines whether cascading scans + should inherit affinity from the parent scan. + type: boolean inheritAnnotations: default: true description: InheritAnnotations defines whether cascading @@ -112,6 +735,11 @@ spec: description: InheritLabels defines whether cascading scans should inherit labels from the parent scan type: boolean + inheritTolerations: + default: true + description: InheritTolerations defines whether cascading + scans should inherit tolerations from the parent scan. + type: boolean inheritVolumes: default: false description: InheritVolumes defines whether cascading scans @@ -1524,6 +2152,48 @@ spec: scanType: description: The name of the scanType which should be started. type: string + tolerations: + description: Tolerations are a different way to control on which + nodes your scan is executed. See https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/ + items: + description: The pod this Toleration is attached to tolerates + any taint that matches the triple using + the matching operator . + properties: + effect: + description: Effect indicates the taint effect to match. + Empty means match all taint effects. When specified, allowed + values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: Key is the taint key that the toleration applies + to. Empty means match all taint keys. If the key is empty, + operator must be Exists; this combination means to match + all values and all keys. + type: string + operator: + description: Operator represents a key's relationship to + the value. Valid operators are Exists and Equal. Defaults + to Equal. Exists is equivalent to wildcard for value, + so that a pod can tolerate all taints of a particular + category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period of + time the toleration (which must be of effect NoExecute, + otherwise this field is ignored) tolerates the taint. + By default, it is not set, which means tolerate the taint + forever (do not evict). Zero and negative values will + be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: Value is the taint value the toleration matches + to. If the operator is Exists, the value should be empty, + otherwise just a regular string. + type: string + type: object + type: array volumeMounts: description: VolumeMounts allows to specify volume mounts for the scan container. diff --git a/operator/internal/telemetry/telemetry.go b/operator/internal/telemetry/telemetry.go index c66c8185a6..dfc95384b0 100644 --- a/operator/internal/telemetry/telemetry.go +++ b/operator/internal/telemetry/telemetry.go @@ -26,6 +26,7 @@ var telemetryInterval = 24 * time.Hour var officialScanTypes map[string]bool = map[string]bool{ "amass": true, "angularjs-csti-scanner": true, + "cmseek": true, "git-repo-scanner": true, "gitleaks": true, "kube-hunter": true, @@ -33,10 +34,14 @@ var officialScanTypes map[string]bool = map[string]bool{ "ncrack": true, "nikto": true, "nmap": true, + "nuclei": true, "screenshooter": true, + "semgrep": true, "ssh-scan": true, "sslyze": true, "trivy": true, + "typo3scan": true, + "whatweb": true, "wpscan": true, "zap-baseline-scan": true, "zap-api-scan": true, diff --git a/scanners/angularjs-csti-scanner/.helm-docs.gotmpl b/scanners/angularjs-csti-scanner/.helm-docs.gotmpl index ba4b8a9f13..dbafe0325b 100644 --- a/scanners/angularjs-csti-scanner/.helm-docs.gotmpl +++ b/scanners/angularjs-csti-scanner/.helm-docs.gotmpl @@ -65,7 +65,7 @@ Optional arguments: Because *acstis* does not provide command line arguments for configuring the sent requests, you have to mount a config map into the scan container on a specific location. Your additional config map should be - mounted to `/acstis/config/acstis-config.py`. For example create a config map: + mounted to `/home/angularjscsti/acstis/config/acstis-config.py`. For example create a config map: ```bash kubectl create configmap --from-file /path/to/my/acstis-config.py acstis-config @@ -74,13 +74,13 @@ kubectl create configmap --from-file /path/to/my/acstis-config.py acstis-config Then, mount it into the container: ```yaml - volumes: + volumes: - name: "acstis-config" configMap: name: "acstis-config" - volumeMounts: + volumeMounts: - name: "acstis-config" - mountPath: "/acstis/config/" + mountPath: "/home/angularjscsti/acstis/config/" ``` #### Configuration options in *acstis-config.py* diff --git a/scanners/angularjs-csti-scanner/README.md b/scanners/angularjs-csti-scanner/README.md index 3b1f77e407..0b69684590 100644 --- a/scanners/angularjs-csti-scanner/README.md +++ b/scanners/angularjs-csti-scanner/README.md @@ -83,7 +83,7 @@ Kubernetes: `>=v1.11.0-0` Because *acstis* does not provide command line arguments for configuring the sent requests, you have to mount a config map into the scan container on a specific location. Your additional config map should be - mounted to `/acstis/config/acstis-config.py`. For example create a config map: + mounted to `/home/angularjscsti/acstis/config/acstis-config.py`. For example create a config map: ```bash kubectl create configmap --from-file /path/to/my/acstis-config.py acstis-config @@ -92,13 +92,13 @@ kubectl create configmap --from-file /path/to/my/acstis-config.py acstis-config Then, mount it into the container: ```yaml - volumes: + volumes: - name: "acstis-config" configMap: name: "acstis-config" - volumeMounts: + volumeMounts: - name: "acstis-config" - mountPath: "/acstis/config/" + mountPath: "/home/angularjscsti/acstis/config/" ``` #### Configuration options in *acstis-config.py* @@ -186,11 +186,11 @@ options.scope.request_methods = [ | scanner.image.tag | string | `nil` | defaults to the charts appVersion | | scanner.nameAppend | string | `nil` | append a string to the default scantype name. | | scanner.resources | object | `{}` | CPU/memory resource requests/limits (see: https://kubernetes.io/docs/tasks/configure-pod-container/assign-memory-resource/, https://kubernetes.io/docs/tasks/configure-pod-container/assign-cpu-resource/) | -| scanner.securityContext | object | `{"allowPrivilegeEscalation":false,"capabilities":{"drop":["all"]},"privileged":false,"readOnlyRootFilesystem":true,"runAsNonRoot":true}` | Optional securityContext set on scanner container (see: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/) | +| scanner.securityContext | object | `{"allowPrivilegeEscalation":false,"capabilities":{"drop":["all"]},"privileged":false,"readOnlyRootFilesystem":false,"runAsNonRoot":true}` | Optional securityContext set on scanner container (see: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/) | | scanner.securityContext.allowPrivilegeEscalation | bool | `false` | Ensure that users privileges cannot be escalated | | scanner.securityContext.capabilities.drop[0] | string | `"all"` | This drops all linux privileges from the container. | | scanner.securityContext.privileged | bool | `false` | Ensures that the scanner container is not run in privileged mode | -| scanner.securityContext.readOnlyRootFilesystem | bool | `true` | Prevents write access to the containers file system | +| scanner.securityContext.readOnlyRootFilesystem | bool | `false` | Prevents write access to the containers file system | | scanner.securityContext.runAsNonRoot | bool | `true` | Enforces that the scanner image is run as a non root user | | scanner.ttlSecondsAfterFinished | string | `nil` | seconds after which the kubernetes job for the scanner will be deleted. Requires the Kubernetes TTLAfterFinished controller: https://kubernetes.io/docs/concepts/workloads/controllers/ttlafterfinished/ | diff --git a/scanners/angularjs-csti-scanner/examples/scan-website-with-options/scan.yaml b/scanners/angularjs-csti-scanner/examples/scan-website-with-options/scan.yaml index a324cabf0e..639473cb7f 100644 --- a/scanners/angularjs-csti-scanner/examples/scan-website-with-options/scan.yaml +++ b/scanners/angularjs-csti-scanner/examples/scan-website-with-options/scan.yaml @@ -21,4 +21,4 @@ spec: name: "acstis-config" volumeMounts: - name: "acstis-config" - mountPath: "/acstis/config/" + mountPath: "/home/angularjscsti/acstis/config/" diff --git a/scanners/angularjs-csti-scanner/scanner/Dockerfile b/scanners/angularjs-csti-scanner/scanner/Dockerfile index 59dfe9aaa1..42196d885f 100644 --- a/scanners/angularjs-csti-scanner/scanner/Dockerfile +++ b/scanners/angularjs-csti-scanner/scanner/Dockerfile @@ -4,8 +4,16 @@ FROM python:3.6-alpine ARG scannerVersion -COPY acstis-script.py /acstis/acstis-script.py -COPY wrapper.sh /wrapper.sh -RUN apk add --update --no-cache g++ gcc libxslt-dev -RUN pip install https://github.com/tijme/angularjs-csti-scanner/archive/$scannerVersion.zip -ENTRYPOINT [ "sh", "/wrapper.sh" ] + +RUN apk add --update --no-cache g++ gcc libxslt-dev +RUN adduser -S -H -u 1001 angularjscsti + +COPY acstis-script.py /home/angularjscsti/acstis/acstis-script.py +COPY wrapper.sh /home/angularjscsti/wrapper.sh + +RUN pip install https://github.com/tijme/angularjs-csti-scanner/archive/$scannerVersion.zip \ + && chown -R angularjscsti /home/angularjscsti + +USER 1001 + +ENTRYPOINT [ "sh", "/home/angularjscsti/wrapper.sh" ] diff --git a/scanners/angularjs-csti-scanner/scanner/wrapper.sh b/scanners/angularjs-csti-scanner/scanner/wrapper.sh index 5a653dbb10..b8d5d2b29d 100644 --- a/scanners/angularjs-csti-scanner/scanner/wrapper.sh +++ b/scanners/angularjs-csti-scanner/scanner/wrapper.sh @@ -3,13 +3,13 @@ # SPDX-License-Identifier: Apache-2.0 # If acstis config exists paste it into the acstis script -if [ -f /acstis/config/acstis-config.py ]; then +if [ -f /home/angularjscsti/acstis/config/acstis-config.py ]; then echo "Insert acstis-config file into acstis script" - awk '{$1=$1}1' /acstis/config/acstis-config.py | # Trim start end end spaces of each line of the config + awk '{$1=$1}1' /home/angularjscsti/acstis/config/acstis-config.py | # Trim start end end spaces of each line of the config awk -v x=4 '{printf "%" x "s%s\n", "", $0}' | # Add indentation of 4 to every line - sed -i '/#INSERT CUSTOM OPTIONS HERE/ r /dev/stdin' /acstis/acstis-script.py # Insert config into script + sed -i '/#INSERT CUSTOM OPTIONS HERE/ r /dev/stdin' /home/angularjscsti/acstis/acstis-script.py # Insert config into script fi -python /acstis/acstis-script.py $@ +python /home/angularjscsti/acstis/acstis-script.py $@ # If no finding occurred generate a empty file for the lurker if [ ! -f /home/securecodebox/findings.log ]; then diff --git a/scanners/angularjs-csti-scanner/templates/angularjs-csti-scanner-scan-type.yaml b/scanners/angularjs-csti-scanner/templates/angularjs-csti-scanner-scan-type.yaml index 470f3b7ba8..abd46bd9ca 100644 --- a/scanners/angularjs-csti-scanner/templates/angularjs-csti-scanner-scan-type.yaml +++ b/scanners/angularjs-csti-scanner/templates/angularjs-csti-scanner-scan-type.yaml @@ -28,7 +28,7 @@ spec: imagePullPolicy: {{ .Values.scanner.image.pullPolicy }} command: - "sh" - - "/wrapper.sh" + - "/home/angularjscsti/wrapper.sh" - "-vrl" - "/home/securecodebox/findings.log" resources: diff --git a/scanners/angularjs-csti-scanner/values.yaml b/scanners/angularjs-csti-scanner/values.yaml index 33a90a01b6..fb43a38a54 100644 --- a/scanners/angularjs-csti-scanner/values.yaml +++ b/scanners/angularjs-csti-scanner/values.yaml @@ -67,7 +67,7 @@ scanner: # scanner.securityContext.runAsNonRoot -- Enforces that the scanner image is run as a non root user runAsNonRoot: true # scanner.securityContext.readOnlyRootFilesystem -- Prevents write access to the containers file system - readOnlyRootFilesystem: true + readOnlyRootFilesystem: false # scanner.securityContext.allowPrivilegeEscalation -- Ensure that users privileges cannot be escalated allowPrivilegeEscalation: false # scanner.securityContext.privileged -- Ensures that the scanner container is not run in privileged mode From 4e906b3b17d43e750ea276f9b3d53f1debd76e78 Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach Date: Wed, 8 Dec 2021 10:57:38 +0100 Subject: [PATCH 53/54] Update use of `getCascadingScans` function Signed-off-by: Jannik Hollenbach --- hooks/cascading-scans/hook/hook.test.js | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/hooks/cascading-scans/hook/hook.test.js b/hooks/cascading-scans/hook/hook.test.js index 84d28b321a..481d72892f 100644 --- a/hooks/cascading-scans/hook/hook.test.js +++ b/hooks/cascading-scans/hook/hook.test.js @@ -1821,7 +1821,9 @@ test("should copy tolerations and affinity into cascaded scan if one is set and const cascadedScans = getCascadingScans( parentScan, findings, - sslyzeCascadingRules + sslyzeCascadingRules, + undefined, + parseDefinition, ); const cascadedScan = cascadedScans[0]; @@ -1908,7 +1910,9 @@ test("should not copy tolerations and affinity into cascaded scan if label disab const cascadedScans = getCascadingScans( parentScan, findings, - sslyzeCascadingRules + sslyzeCascadingRules, + undefined, + parseDefinition, ); const cascadedScan = cascadedScans[0]; @@ -1993,7 +1997,9 @@ test("should merge tolerations and replace affinity in cascaded scan if cascadin const cascadedScans = getCascadingScans( parentScan, findings, - sslyzeCascadingRules + sslyzeCascadingRules, + undefined, + parseDefinition, ); const cascadedScan = cascadedScans[0]; @@ -2060,7 +2066,9 @@ test("should not set affinity or tolerations to undefined if they are defined to const cascadedScans = getCascadingScans( parentScan, findings, - sslyzeCascadingRules + sslyzeCascadingRules, + undefined, + parseDefinition, ); const cascadedScan = cascadedScans[0]; @@ -2092,7 +2100,9 @@ test("Should not set affinity or tolerations to undefined if they are defined to const cascadedScans = getCascadingScans( parentScan, findings, - sslyzeCascadingRules + sslyzeCascadingRules, + undefined, + parseDefinition, ); const cascadedScan = cascadedScans[0]; @@ -2180,7 +2190,9 @@ test("should only use tolerations and affinity of cascaded scan if inheritance i const cascadedScans = getCascadingScans( parentScan, findings, - sslyzeCascadingRules + sslyzeCascadingRules, + undefined, + parseDefinition, ); const cascadedScan = cascadedScans[0]; From 2dabb0fb8cf30ae2a2cf6ea3458f4c7026360dd4 Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach Date: Wed, 8 Dec 2021 10:58:43 +0100 Subject: [PATCH 54/54] Update snapshots Signed-off-by: Jannik Hollenbach --- hooks/cascading-scans/hook/hook.test.js | 1436 ++++++++++++----------- 1 file changed, 733 insertions(+), 703 deletions(-) diff --git a/hooks/cascading-scans/hook/hook.test.js b/hooks/cascading-scans/hook/hook.test.js index 481d72892f..719a814cd4 100644 --- a/hooks/cascading-scans/hook/hook.test.js +++ b/hooks/cascading-scans/hook/hook.test.js @@ -3,10 +3,10 @@ // SPDX-License-Identifier: Apache-2.0 const { getCascadingScans } = require("./hook"); -const {LabelSelectorRequirementOperator} = require("./kubernetes-label-selector"); const { - ScopeLimiterRequirementOperator, -} = require("./scope-limiter"); + LabelSelectorRequirementOperator, +} = require("./kubernetes-label-selector"); +const { ScopeLimiterRequirementOperator } = require("./scope-limiter"); let parentScan = undefined; let sslyzeCascadingRules = undefined; @@ -18,27 +18,27 @@ beforeEach(() => { kind: "Scan", metadata: { name: "nmap-foobar.com", - annotations: {} + annotations: {}, }, spec: { scanType: "nmap", parameters: "foobar.com", - cascades: {} - } + cascades: {}, + }, }; parseDefinition = { meta: {}, spec: { scopeLimiterAliases: {}, }, - } + }; sslyzeCascadingRules = [ { apiVersion: "cascading.securecodebox.io/v1", kind: "CascadingRule", metadata: { - name: "tls-scans" + name: "tls-scans", }, spec: { matches: { @@ -47,23 +47,23 @@ beforeEach(() => { category: "Open Port", attributes: { port: 443, - service: "https" - } + service: "https", + }, }, { category: "Open Port", attributes: { - service: "https" - } - } - ] + service: "https", + }, + }, + ], }, scanSpec: { scanType: "sslyze", - parameters: ["--regular", "{{$.hostOrIP}}:{{attributes.port}}"] - } - } - } + parameters: ["--regular", "{{$.hostOrIP}}:{{attributes.port}}"], + }, + }, + }, ]; }); @@ -76,9 +76,9 @@ test("Should create subsequent scans for open HTTPS ports (NMAP findings)", () = state: "open", hostname: "foobar.com", port: 443, - service: "https" - } - } + service: "https", + }, + }, ]; const cascadedScans = getCascadingScans( @@ -86,7 +86,7 @@ test("Should create subsequent scans for open HTTPS ports (NMAP findings)", () = findings, sslyzeCascadingRules, undefined, - parseDefinition, + parseDefinition ); expect(cascadedScans).toMatchInlineSnapshot(` @@ -143,14 +143,20 @@ test("Should create no subsequent scans if there are no rules", () => { state: "open", hostname: "foobar.com", port: 443, - service: "https" - } - } + service: "https", + }, + }, ]; const cascadingRules = []; - const cascadedScans = getCascadingScans(parentScan, findings, cascadingRules, undefined, parseDefinition); + const cascadedScans = getCascadingScans( + parentScan, + findings, + cascadingRules, + undefined, + parseDefinition + ); expect(cascadedScans).toMatchInlineSnapshot(`Array []`); }); @@ -167,9 +173,9 @@ test("Should not try to do magic to the scan name if its something random", () = hostname: undefined, ip_address: "10.42.42.42", port: 443, - service: "https" - } - } + service: "https", + }, + }, ]; const cascadedScans = getCascadingScans( @@ -177,10 +183,12 @@ test("Should not try to do magic to the scan name if its something random", () = findings, sslyzeCascadingRules, undefined, - parseDefinition, + parseDefinition ); - expect(cascadedScans[0].metadata.generateName).toEqual("foobar.com-tls-scans-"); + expect(cascadedScans[0].metadata.generateName).toEqual( + "foobar.com-tls-scans-" + ); }); test("Should not start a new scan when the corresponding cascadingRule is already in the chain", () => { @@ -195,9 +203,9 @@ test("Should not start a new scan when the corresponding cascadingRule is alread state: "open", hostname: "foobar.com", port: 443, - service: "https" - } - } + service: "https", + }, + }, ]; const cascadedScans = getCascadingScans( @@ -205,7 +213,7 @@ test("Should not start a new scan when the corresponding cascadingRule is alread findings, sslyzeCascadingRules, undefined, - parseDefinition, + parseDefinition ); expect(cascadedScans).toMatchInlineSnapshot(`Array []`); @@ -222,9 +230,9 @@ test("Should not crash when the annotations are not set", () => { state: "open", hostname: "foobar.com", port: 443, - service: "https" - } - } + service: "https", + }, + }, ]; const cascadedScans = getCascadingScans( @@ -232,7 +240,7 @@ test("Should not crash when the annotations are not set", () => { findings, sslyzeCascadingRules, undefined, - parseDefinition, + parseDefinition ); expect(cascadedScans).toMatchInlineSnapshot(` @@ -284,8 +292,8 @@ test("Should copy ENV fields from cascadingRule to created scan", () => { sslyzeCascadingRules[0].spec.scanSpec.env = [ { name: "FOOBAR", - valueFrom: { secretKeyRef: { name: "foobar-token", key: "token" } } - } + valueFrom: { secretKeyRef: { name: "foobar-token", key: "token" } }, + }, ]; const findings = [ @@ -296,9 +304,9 @@ test("Should copy ENV fields from cascadingRule to created scan", () => { state: "open", hostname: "foobar.com", port: 443, - service: "https" - } - } + service: "https", + }, + }, ]; const cascadedScans = getCascadingScans( @@ -306,7 +314,7 @@ test("Should copy ENV fields from cascadingRule to created scan", () => { findings, sslyzeCascadingRules, undefined, - parseDefinition, + parseDefinition ); expect(cascadedScans[0].spec.env).toMatchInlineSnapshot(` @@ -330,7 +338,7 @@ test("Should allow wildcards in cascadingRules", () => { apiVersion: "cascading.securecodebox.io/v1", kind: "CascadingRule", metadata: { - name: "tls-scans" + name: "tls-scans", }, spec: { matches: { @@ -339,17 +347,17 @@ test("Should allow wildcards in cascadingRules", () => { category: "Open Port", attributes: { port: 8443, - service: "https*" - } - } - ] + service: "https*", + }, + }, + ], }, scanSpec: { scanType: "sslyze", - parameters: ["--regular", "{{$.hostOrIP}}:{{attributes.port}}"] - } - } - } + parameters: ["--regular", "{{$.hostOrIP}}:{{attributes.port}}"], + }, + }, + }, ]; const findings = [ @@ -360,9 +368,9 @@ test("Should allow wildcards in cascadingRules", () => { state: "open", hostname: "foobar.com", port: 8443, - service: "https-alt" - } - } + service: "https-alt", + }, + }, ]; const cascadedScans = getCascadingScans( @@ -370,7 +378,7 @@ test("Should allow wildcards in cascadingRules", () => { findings, sslyzeCascadingRules, undefined, - parseDefinition, + parseDefinition ); expect(cascadedScans).toMatchInlineSnapshot(` @@ -422,7 +430,7 @@ test("should not copy labels if inheritLabels is set to false", () => { parentScan.metadata.labels = { organization: "OWASP", location: "barcelona", - vlan: "lan" + vlan: "lan", }; parentScan.spec.cascades.inheritLabels = false; @@ -434,9 +442,9 @@ test("should not copy labels if inheritLabels is set to false", () => { state: "open", hostname: "foobar.com", port: 443, - service: "https" - } - } + service: "https", + }, + }, ]; const cascadedScans = getCascadingScans( @@ -444,13 +452,15 @@ test("should not copy labels if inheritLabels is set to false", () => { findings, sslyzeCascadingRules, undefined, - parseDefinition, + parseDefinition ); for (const cascadedScan of cascadedScans) { - expect(Object.entries(parentScan.metadata.labels).every(([label, value]) => - cascadedScan.metadata.labels[label] === value - )).toBe(false) + expect( + Object.entries(parentScan.metadata.labels).every( + ([label, value]) => cascadedScan.metadata.labels[label] === value + ) + ).toBe(false); } }); @@ -458,7 +468,7 @@ test("should copy labels if inheritLabels is not set", () => { parentScan.metadata.labels = { organization: "OWASP", location: "barcelona", - vlan: "lan" + vlan: "lan", }; const findings = [ @@ -469,9 +479,9 @@ test("should copy labels if inheritLabels is not set", () => { state: "open", hostname: "foobar.com", port: 443, - service: "https" - } - } + service: "https", + }, + }, ]; const cascadedScans = getCascadingScans( @@ -479,13 +489,15 @@ test("should copy labels if inheritLabels is not set", () => { findings, sslyzeCascadingRules, undefined, - parseDefinition, + parseDefinition ); for (const cascadedScan of cascadedScans) { - expect(Object.entries(parentScan.metadata.labels).every(([label, value]) => - cascadedScan.metadata.labels[label] === value - )).toBe(true) + expect( + Object.entries(parentScan.metadata.labels).every( + ([label, value]) => cascadedScan.metadata.labels[label] === value + ) + ).toBe(true); } }); @@ -493,7 +505,7 @@ test("should copy labels if inheritLabels is set to true", () => { parentScan.metadata.labels = { organization: "OWASP", location: "barcelona", - vlan: "lan" + vlan: "lan", }; parentScan.spec.cascades.inheritLabels = true; @@ -506,9 +518,9 @@ test("should copy labels if inheritLabels is set to true", () => { state: "open", hostname: "foobar.com", port: 443, - service: "https" - } - } + service: "https", + }, + }, ]; const cascadedScans = getCascadingScans( @@ -516,20 +528,22 @@ test("should copy labels if inheritLabels is set to true", () => { findings, sslyzeCascadingRules, undefined, - parseDefinition, + parseDefinition ); for (const cascadedScan of cascadedScans) { - expect(Object.entries(parentScan.metadata.labels).every(([label, value]) => - cascadedScan.metadata.labels[label] === value - )).toBe(true) + expect( + Object.entries(parentScan.metadata.labels).every( + ([label, value]) => cascadedScan.metadata.labels[label] === value + ) + ).toBe(true); } }); test("should not copy annotations if inheritAnnotations is set to false", () => { parentScan.metadata.annotations = { "defectdojo.securecodebox.io/product-name": "barcelona-network-sca", - "defectdojo.securecodebox.io/engagement-name": "scb-automated-scan" + "defectdojo.securecodebox.io/engagement-name": "scb-automated-scan", }; parentScan.spec.cascades.inheritAnnotations = false; @@ -541,9 +555,9 @@ test("should not copy annotations if inheritAnnotations is set to false", () => state: "open", hostname: "foobar.com", port: 443, - service: "https" - } - } + service: "https", + }, + }, ]; const cascadedScans = getCascadingScans( @@ -551,20 +565,22 @@ test("should not copy annotations if inheritAnnotations is set to false", () => findings, sslyzeCascadingRules, undefined, - parseDefinition, + parseDefinition ); for (const cascadedScan of cascadedScans) { - expect(Object.entries(parentScan.metadata.annotations).every(([label, value]) => - cascadedScan.metadata.annotations[label] === value - )).toBe(false) + expect( + Object.entries(parentScan.metadata.annotations).every( + ([label, value]) => cascadedScan.metadata.annotations[label] === value + ) + ).toBe(false); } }); test("should copy annotations if inheritAnnotations is not set", () => { parentScan.metadata.annotations = { "defectdojo.securecodebox.io/product-name": "barcelona-network-sca", - "defectdojo.securecodebox.io/engagement-name": "scb-automated-scan" + "defectdojo.securecodebox.io/engagement-name": "scb-automated-scan", }; const findings = [ @@ -575,9 +591,9 @@ test("should copy annotations if inheritAnnotations is not set", () => { state: "open", hostname: "foobar.com", port: 443, - service: "https" - } - } + service: "https", + }, + }, ]; const cascadedScans = getCascadingScans( @@ -585,20 +601,22 @@ test("should copy annotations if inheritAnnotations is not set", () => { findings, sslyzeCascadingRules, undefined, - parseDefinition, + parseDefinition ); for (const cascadedScan of cascadedScans) { - expect(Object.entries(parentScan.metadata.annotations).every(([label, value]) => - cascadedScan.metadata.annotations[label] === value - )).toBe(true) + expect( + Object.entries(parentScan.metadata.annotations).every( + ([label, value]) => cascadedScan.metadata.annotations[label] === value + ) + ).toBe(true); } }); test("should copy annotations if inheritAnnotations is set to true", () => { parentScan.metadata.annotations = { "defectdojo.securecodebox.io/product-name": "barcelona-network-sca", - "defectdojo.securecodebox.io/engagement-name": "scb-automated-scan" + "defectdojo.securecodebox.io/engagement-name": "scb-automated-scan", }; parentScan.spec.cascades.inheritAnnotations = true; @@ -610,9 +628,9 @@ test("should copy annotations if inheritAnnotations is set to true", () => { state: "open", hostname: "foobar.com", port: 443, - service: "https" - } - } + service: "https", + }, + }, ]; const cascadedScans = getCascadingScans( @@ -620,21 +638,23 @@ test("should copy annotations if inheritAnnotations is set to true", () => { findings, sslyzeCascadingRules, undefined, - parseDefinition, + parseDefinition ); for (const cascadedScan of cascadedScans) { - expect(Object.entries(parentScan.metadata.annotations).every(([label, value]) => - cascadedScan.metadata.annotations[label] === value - )).toBe(true) + expect( + Object.entries(parentScan.metadata.annotations).every( + ([label, value]) => cascadedScan.metadata.annotations[label] === value + ) + ).toBe(true); } }); test("should copy scanLabels from CascadingRule to cascading scan", () => { sslyzeCascadingRules[0].spec.scanLabels = { k_one: "v_one", - k_two: "v_two" - } + k_two: "v_two", + }; const findings = [ { @@ -644,9 +664,9 @@ test("should copy scanLabels from CascadingRule to cascading scan", () => { state: "open", hostname: "foobar.com", port: 443, - service: "https" - } - } + service: "https", + }, + }, ]; const cascadedScans = getCascadingScans( @@ -654,20 +674,22 @@ test("should copy scanLabels from CascadingRule to cascading scan", () => { findings, sslyzeCascadingRules, undefined, - parseDefinition, + parseDefinition ); - const cascadedScan = cascadedScans[0] - expect(Object.entries(sslyzeCascadingRules[0].spec.scanLabels).every(([label, value]) => - cascadedScan.metadata.labels[label] === value - )).toBe(true) + const cascadedScan = cascadedScans[0]; + expect( + Object.entries(sslyzeCascadingRules[0].spec.scanLabels).every( + ([label, value]) => cascadedScan.metadata.labels[label] === value + ) + ).toBe(true); }); test("should copy scanAnnotations from CascadingRule to cascading scan", () => { sslyzeCascadingRules[0].spec.scanAnnotations = { k_one: "v_one", - k_two: "v_two" - } + k_two: "v_two", + }; const findings = [ { @@ -677,9 +699,9 @@ test("should copy scanAnnotations from CascadingRule to cascading scan", () => { state: "open", hostname: "foobar.com", port: 443, - service: "https" - } - } + service: "https", + }, + }, ]; const cascadedScans = getCascadingScans( @@ -687,27 +709,29 @@ test("should copy scanAnnotations from CascadingRule to cascading scan", () => { findings, sslyzeCascadingRules, undefined, - parseDefinition, + parseDefinition ); - const cascadedScan = cascadedScans[0] - expect(Object.entries(sslyzeCascadingRules[0].spec.scanAnnotations).every(([label, value]) => - cascadedScan.metadata.annotations[label] === value - )).toBe(true) + const cascadedScan = cascadedScans[0]; + expect( + Object.entries(sslyzeCascadingRules[0].spec.scanAnnotations).every( + ([label, value]) => cascadedScan.metadata.annotations[label] === value + ) + ).toBe(true); }); test("should properly parse template values in scanLabels and scanAnnotations", () => { sslyzeCascadingRules[0].spec.scanAnnotations = { k_one: "{{metadata.name}}", k_two: "{{metadata.unknown_property}}", - k_three: "{{$.hostOrIP}}" - } + k_three: "{{$.hostOrIP}}", + }; sslyzeCascadingRules[0].spec.scanLabels = { k_one: "{{metadata.name}}", k_two: "{{metadata.unknown_property}}", - k_three: "{{$.hostOrIP}}" - } + k_three: "{{$.hostOrIP}}", + }; const findings = [ { @@ -717,8 +741,8 @@ test("should properly parse template values in scanLabels and scanAnnotations", state: "open", hostname: "foobar.com", port: 8443, - service: "https" - } + service: "https", + }, }, { name: "Port 443 is open", @@ -727,9 +751,9 @@ test("should properly parse template values in scanLabels and scanAnnotations", state: "open", hostname: "foobar.com", port: 443, - service: "https" - } - } + service: "https", + }, + }, ]; const cascadedScans = getCascadingScans( @@ -737,34 +761,37 @@ test("should properly parse template values in scanLabels and scanAnnotations", findings, sslyzeCascadingRules, sslyzeCascadingRules[0], - parseDefinition, + parseDefinition ); - expect(sslyzeCascadingRules[0].spec.scanSpec.parameters).toEqual(["--regular", "{{$.hostOrIP}}:{{attributes.port}}"]) + expect(sslyzeCascadingRules[0].spec.scanSpec.parameters).toEqual([ + "--regular", + "{{$.hostOrIP}}:{{attributes.port}}", + ]); const { labels, annotations } = cascadedScans[0].metadata; // No snapshots as scanLabels/scanAnnotations can be in any order const labelResults = { - "k_one": "nmap-foobar.com", - "k_two": "", - "k_three": "foobar.com", - } + k_one: "nmap-foobar.com", + k_two: "", + k_three: "foobar.com", + }; - expect(labels).toEqual(labelResults) + expect(labels).toEqual(labelResults); const annotationsResults = { "cascading.securecodebox.io/chain": "tls-scans", "cascading.securecodebox.io/matched-finding": undefined, "cascading.securecodebox.io/parent-scan": "nmap-foobar.com", "securecodebox.io/hook": "cascading-scans", - "k_one": "nmap-foobar.com", - "k_two": "", - "k_three": "foobar.com", + k_one: "nmap-foobar.com", + k_two: "", + k_three: "foobar.com", }; - expect(annotations).toEqual(annotationsResults) -}) + expect(annotations).toEqual(annotationsResults); +}); test("should copy proper finding ID into annotations", () => { const findings = [ @@ -775,9 +802,9 @@ test("should copy proper finding ID into annotations", () => { state: "open", hostname: "foobar.com", port: 12345, - service: "unknown" + service: "unknown", }, - id: "random-id" + id: "random-id", }, { name: "Port 443 is open", @@ -786,10 +813,10 @@ test("should copy proper finding ID into annotations", () => { state: "open", hostname: "foobar.com", port: 443, - service: "https" + service: "https", }, - id: "f0c718bd-9987-42c8-2259-73794e61dd5a" - } + id: "f0c718bd-9987-42c8-2259-73794e61dd5a", + }, ]; const cascadedScans = getCascadingScans( @@ -797,20 +824,23 @@ test("should copy proper finding ID into annotations", () => { findings, sslyzeCascadingRules, undefined, - parseDefinition, + parseDefinition ); - const cascadedScan = cascadedScans[0] - expect(Object.entries(cascadedScan.metadata.annotations).every(([label, value]) => { - if (label === "cascading.securecodebox.io/matched-finding") { - return value === "f0c718bd-9987-42c8-2259-73794e61dd5a"; - } else return true; - } - )).toBe(true) + const cascadedScan = cascadedScans[0]; + expect( + Object.entries(cascadedScan.metadata.annotations).every( + ([label, value]) => { + if (label === "cascading.securecodebox.io/matched-finding") { + return value === "f0c718bd-9987-42c8-2259-73794e61dd5a"; + } else return true; + } + ) + ).toBe(true); }); test("should merge environment variables into cascaded scan", () => { - parentScan.spec.cascades.inheritEnv = true + parentScan.spec.cascades.inheritEnv = true; const findings = [ { name: "Port 443 is open", @@ -819,34 +849,34 @@ test("should merge environment variables into cascaded scan", () => { state: "open", hostname: "foobar.com", port: 443, - service: "https" - } - } + service: "https", + }, + }, ]; parentScan.spec.env = [ { - "name": "parent_environment_variable_name", - "value": "parent_environment_variable_value" - } - ] + name: "parent_environment_variable_name", + value: "parent_environment_variable_value", + }, + ]; sslyzeCascadingRules[0].spec.scanSpec.env = [ { - "name": "rule_environment_variable_name", - "value": "rule_environment_variable_value" - } - ] + name: "rule_environment_variable_name", + value: "rule_environment_variable_value", + }, + ]; const cascadedScans = getCascadingScans( parentScan, findings, sslyzeCascadingRules, undefined, - parseDefinition, + parseDefinition ); - const cascadedScan = cascadedScans[0] + const cascadedScan = cascadedScans[0]; expect(cascadedScan.spec.env).toMatchInlineSnapshot(` Array [ Object { @@ -862,7 +892,7 @@ test("should merge environment variables into cascaded scan", () => { }); test("should merge volumeMounts into cascaded scan", () => { - parentScan.spec.cascades.inheritVolumes = true + parentScan.spec.cascades.inheritVolumes = true; const findings = [ { name: "Port 443 is open", @@ -871,38 +901,38 @@ test("should merge volumeMounts into cascaded scan", () => { state: "open", hostname: "foobar.com", port: 443, - service: "https" - } - } + service: "https", + }, + }, ]; parentScan.spec.volumeMounts = [ { - "mountPath": "/etc/ssl/certs/ca-cert.cer", - "name": "ca-certificate", - "readOnly": true, - "subPath": "ca-cert.cer" - } - ] + mountPath: "/etc/ssl/certs/ca-cert.cer", + name: "ca-certificate", + readOnly: true, + subPath: "ca-cert.cer", + }, + ]; sslyzeCascadingRules[0].spec.scanSpec.volumeMounts = [ { - "mountPath": "/etc/ssl/certs/ca-cert-sslyze.cer", - "name": "ca-certificate-sslyze", - "readOnly": true, - "subPath": "ca-cert-sslyze.cer" - } - ] + mountPath: "/etc/ssl/certs/ca-cert-sslyze.cer", + name: "ca-certificate-sslyze", + readOnly: true, + subPath: "ca-cert-sslyze.cer", + }, + ]; const cascadedScans = getCascadingScans( parentScan, findings, sslyzeCascadingRules, undefined, - parseDefinition, + parseDefinition ); - const cascadedScan = cascadedScans[0] + const cascadedScan = cascadedScans[0]; expect(cascadedScan.spec.volumeMounts).toMatchInlineSnapshot(` Array [ Object { @@ -922,7 +952,7 @@ test("should merge volumeMounts into cascaded scan", () => { }); test("should merge volumes into cascaded scan", () => { - parentScan.spec.cascades.inheritVolumes = true + parentScan.spec.cascades.inheritVolumes = true; const findings = [ { name: "Port 443 is open", @@ -931,35 +961,35 @@ test("should merge volumes into cascaded scan", () => { state: "open", hostname: "foobar.com", port: 443, - service: "https" - } - } + service: "https", + }, + }, ]; parentScan.spec.volumes = [ { - "name": "ca-certificate", - "configMap": { - "name": "ca-certificate" - } - } - ] + name: "ca-certificate", + configMap: { + name: "ca-certificate", + }, + }, + ]; sslyzeCascadingRules[0].spec.scanSpec.volumes = [ { - "name": "ca-certificate-sslyze", - "configMap": { - "name": "ca-certificate-sslyze" - } - } - ] + name: "ca-certificate-sslyze", + configMap: { + name: "ca-certificate-sslyze", + }, + }, + ]; const cascadedScans = getCascadingScans( parentScan, findings, sslyzeCascadingRules, undefined, - parseDefinition, + parseDefinition ); const cascadedScan = cascadedScans[0]; @@ -983,7 +1013,7 @@ test("should merge volumes into cascaded scan", () => { }); test("should merge initContainers into cascaded scan", () => { - parentScan.spec.cascades.inheritInitContainers = true + parentScan.spec.cascades.inheritInitContainers = true; const findings = [ { name: "Port 443 is open", @@ -992,56 +1022,56 @@ test("should merge initContainers into cascaded scan", () => { state: "open", hostname: "foobar.com", port: 443, - service: "https" - } - } + service: "https", + }, + }, ]; parentScan.spec.initContainers = [ { - "name": "test-init", - "image": "bitnami/git", - "command": ["whoami"] - } - ] + name: "test-init", + image: "bitnami/git", + command: ["whoami"], + }, + ]; sslyzeCascadingRules[0].spec.scanSpec.initContainers = [ { - "name": "test-init-2", - "image": "some/hypothetical", - "command": ["echo", "1"] - } - ] + name: "test-init-2", + image: "some/hypothetical", + command: ["echo", "1"], + }, + ]; const cascadedScans = getCascadingScans( parentScan, findings, sslyzeCascadingRules, undefined, - parseDefinition, + parseDefinition ); const cascadedScan = cascadedScans[0]; expect(cascadedScan.spec.initContainers).toMatchInlineSnapshot(` - Array [ - Object { - "command": Array [ - "whoami", - ], - "image": "bitnami/git", - "name": "test-init", - }, - Object { - "command": Array [ - "echo", - "1", - ], - "image": "some/hypothetical", - "name": "test-init-2", - }, - ] - `); + Array [ + Object { + "command": Array [ + "whoami", + ], + "image": "bitnami/git", + "name": "test-init", + }, + Object { + "command": Array [ + "echo", + "1", + ], + "image": "some/hypothetical", + "name": "test-init-2", + }, + ] + `); }); test("should not merge initContainers into cascaded scan if not instructed", () => { @@ -1053,49 +1083,49 @@ test("should not merge initContainers into cascaded scan if not instructed", () state: "open", hostname: "foobar.com", port: 443, - service: "https" - } - } + service: "https", + }, + }, ]; parentScan.spec.initContainers = [ { - "name": "test-init", - "image": "bitnami/git", - "command": ["whoami"] - } - ] + name: "test-init", + image: "bitnami/git", + command: ["whoami"], + }, + ]; sslyzeCascadingRules[0].spec.scanSpec.initContainers = [ { - "name": "test-init-2", - "image": "some/hypothetical", - "command": ["echo", "1"] - } - ] + name: "test-init-2", + image: "some/hypothetical", + command: ["echo", "1"], + }, + ]; const cascadedScans = getCascadingScans( parentScan, findings, sslyzeCascadingRules, undefined, - parseDefinition, + parseDefinition ); const cascadedScan = cascadedScans[0]; expect(cascadedScan.spec.initContainers).toMatchInlineSnapshot(` - Array [ - Object { - "command": Array [ - "echo", - "1", - ], - "image": "some/hypothetical", - "name": "test-init-2", - }, - ] - `); + Array [ + Object { + "command": Array [ + "echo", + "1", + ], + "image": "some/hypothetical", + "name": "test-init-2", + }, + ] + `); }); test("Templating should apply to environment variables", () => { @@ -1142,7 +1172,7 @@ test("Templating should apply to environment variables", () => { parameters: ["--regular", "{{$.hostOrIP}}:{{attributes.port}}"], volumes: [{ name: "test-volume", emptyDir: {} }], volumeMounts: [{ name: "test-volume", mountPath: "/test" }], - env: [{"name": "HostOrIp", "value": "{{$.hostOrIP}}"}], + env: [{ name: "HostOrIp", value: "{{$.hostOrIP}}" }], }, }, }, @@ -1153,7 +1183,7 @@ test("Templating should apply to environment variables", () => { findings, sslyzeCascadingRules, undefined, - parseDefinition, + parseDefinition ); expect(cascadedScans).toMatchInlineSnapshot(` @@ -1278,7 +1308,7 @@ test("Templating should apply to initContainer commands", () => { findings, sslyzeCascadingRules, undefined, - parseDefinition, + parseDefinition ); expect(cascadedScans).toMatchInlineSnapshot(` @@ -1403,7 +1433,7 @@ test("Templating should apply to initContainer environment variables", () => { image: "busybox", command: ["whoami"], volumeMounts: [{ name: "test-volume", mountPath: "/test" }], - env: [{"name": "HostOrIp", "value": "{{$.hostOrIP}}"}], + env: [{ name: "HostOrIp", value: "{{$.hostOrIP}}" }], }, ], }, @@ -1416,7 +1446,7 @@ test("Templating should apply to initContainer environment variables", () => { findings, sslyzeCascadingRules, undefined, - parseDefinition, + parseDefinition ); expect(cascadedScans).toMatchInlineSnapshot(` @@ -1556,7 +1586,7 @@ test("Templating should not break special encoding (http://...) when using tripl findings, sslyzeCascadingRules, undefined, - parseDefinition, + parseDefinition ); expect(cascadedScans).toMatchInlineSnapshot(` @@ -1632,7 +1662,7 @@ 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 + parentScan.spec.cascades.inheritHookSelector = true; const findings = [ { name: "Port 443 is open", @@ -1641,74 +1671,73 @@ test("should merge hookSelector into cascaded scan if inheritHookSelector is ena state: "open", hostname: "foobar.com", port: 443, - service: "https" - } - } + service: "https", + }, + }, ]; - parentScan.spec.hookSelector = {} + 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"] - } - ] + values: ["cascading-scans"], + }, + ]; sslyzeCascadingRules[0].spec.scanSpec.hookSelector = {}; sslyzeCascadingRules[0].spec.scanSpec.hookSelector.matchExpressions = [ { key: "securecodebox.io/name", operator: LabelSelectorRequirementOperator.NotIn, - values: ["cascading-scans"] - } - ] + values: ["cascading-scans"], + }, + ]; sslyzeCascadingRules[0].spec.scanSpec.hookSelector.matchLabels = { "securecodebox.io/internal": "false", - } + }; const cascadedScans = getCascadingScans( parentScan, findings, sslyzeCascadingRules, undefined, - parseDefinition, + parseDefinition ); 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", + "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", - }, - } - `); + "matchLabels": Object { + "securecodebox.io/internal": "false", + }, + } + `); }); - test("should not merge hookSelector into cascaded scan if inheritHookSelector is disabled", () => { - parentScan.spec.cascades.inheritHookSelector = false + parentScan.spec.cascades.inheritHookSelector = false; const findings = [ { name: "Port 443 is open", @@ -1717,62 +1746,62 @@ test("should not merge hookSelector into cascaded scan if inheritHookSelector is state: "open", hostname: "foobar.com", port: 443, - service: "https" - } - } + service: "https", + }, + }, ]; - parentScan.spec.hookSelector = {} + 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"] - } - ] + values: ["cascading-scans"], + }, + ]; sslyzeCascadingRules[0].spec.scanSpec.hookSelector = {}; sslyzeCascadingRules[0].spec.scanSpec.hookSelector.matchExpressions = [ { key: "securecodebox.io/name", operator: LabelSelectorRequirementOperator.NotIn, - values: ["cascading-scans"] - } - ] + values: ["cascading-scans"], + }, + ]; sslyzeCascadingRules[0].spec.scanSpec.hookSelector.matchLabels = { "securecodebox.io/internal": "false", - } + }; const cascadedScans = getCascadingScans( parentScan, findings, sslyzeCascadingRules, undefined, - parseDefinition, + parseDefinition ); const cascadedScan = cascadedScans[0]; expect(cascadedScan.spec.hookSelector).toMatchInlineSnapshot(` - Object { - "matchExpressions": Array [ Object { - "key": "securecodebox.io/name", - "operator": "NotIn", - "values": Array [ - "cascading-scans", + "matchExpressions": Array [ + Object { + "key": "securecodebox.io/name", + "operator": "NotIn", + "values": Array [ + "cascading-scans", + ], + }, ], - }, - ], - "matchLabels": Object { - "securecodebox.io/internal": "false", - }, - } - `); + "matchLabels": Object { + "securecodebox.io/internal": "false", + }, + } + `); }); test("should copy tolerations and affinity into cascaded scan if one is set and label is unset", () => { @@ -1784,82 +1813,80 @@ test("should copy tolerations and affinity into cascaded scan if one is set and state: "open", hostname: "foobar.com", port: 443, - service: "https" - } - } + service: "https", + }, + }, ]; parentScan.spec.affinity = { nodeAffinity: { - requiredDuringSchedulingIgnoredDuringExecution: { + requiredDuringSchedulingIgnoredDuringExecution: { nodeSelectorTerms: [ - { + { matchExpressions: [ { key: "disktype", operator: "In", - values: [ - "ssd" - ] - } - ] - } - ] - } - } - } + values: ["ssd"], + }, + ], + }, + ], + }, + }, + }; parentScan.spec.tolerations = [ { key: "key1", operator: "Equal", value: "test", - effect: "NoSchedule" - } - ] + effect: "NoSchedule", + }, + ]; const cascadedScans = getCascadingScans( parentScan, findings, sslyzeCascadingRules, undefined, - parseDefinition, + parseDefinition ); const cascadedScan = cascadedScans[0]; expect(cascadedScan.spec.affinity).toMatchInlineSnapshot(` - Object { - "nodeAffinity": Object { - "requiredDuringSchedulingIgnoredDuringExecution": Object { - "nodeSelectorTerms": Array [ - Object { - "matchExpressions": Array [ + Object { + "nodeAffinity": Object { + "requiredDuringSchedulingIgnoredDuringExecution": Object { + "nodeSelectorTerms": Array [ Object { - "key": "disktype", - "operator": "In", - "values": Array [ - "ssd", + "matchExpressions": Array [ + Object { + "key": "disktype", + "operator": "In", + "values": Array [ + "ssd", + ], + }, ], }, ], }, - ], - }, - }, - } - `); + }, + } + `); expect(cascadedScan.spec.tolerations).toMatchInlineSnapshot(` - Array [ - Object { - "effect": "NoSchedule", - "key": "key1", - "operator": "Equal", - "value": "test", - }, - ] - `); + Array [ + Object { + "effect": "NoSchedule", + "key": "key1", + "operator": "Equal", + "value": "test", + }, + ] + `); }); test("should not copy tolerations and affinity into cascaded scan if label disables it", () => { @@ -1873,46 +1900,44 @@ test("should not copy tolerations and affinity into cascaded scan if label disab state: "open", hostname: "foobar.com", port: 443, - service: "https" - } - } + service: "https", + }, + }, ]; parentScan.spec.affinity = { nodeAffinity: { - requiredDuringSchedulingIgnoredDuringExecution: { + requiredDuringSchedulingIgnoredDuringExecution: { nodeSelectorTerms: [ - { + { matchExpressions: [ { key: "disktype", operator: "In", - values: [ - "ssd" - ] - } - ] - } - ] - } - } - } + values: ["ssd"], + }, + ], + }, + ], + }, + }, + }; parentScan.spec.tolerations = [ { key: "key1", operator: "Equal", value: "test", - effect: "NoSchedule" - } - ] + effect: "NoSchedule", + }, + ]; const cascadedScans = getCascadingScans( parentScan, findings, sslyzeCascadingRules, undefined, - parseDefinition, + parseDefinition ); const cascadedScan = cascadedScans[0]; @@ -1931,39 +1956,37 @@ test("should merge tolerations and replace affinity in cascaded scan if cascadin state: "open", hostname: "foobar.com", port: 443, - service: "https" - } - } + service: "https", + }, + }, ]; parentScan.spec.affinity = { nodeAffinity: { - requiredDuringSchedulingIgnoredDuringExecution: { + requiredDuringSchedulingIgnoredDuringExecution: { nodeSelectorTerms: [ - { + { matchExpressions: [ { key: "disktype", operator: "In", - values: [ - "ssd" - ] - } - ] - } - ] - } - } - } + values: ["ssd"], + }, + ], + }, + ], + }, + }, + }; parentScan.spec.tolerations = [ { key: "key1", operator: "Equal", value: "test", - effect: "NoSchedule" - } - ] + effect: "NoSchedule", + }, + ]; sslyzeCascadingRules[0].spec.scanSpec.tolerations = [ { @@ -1971,78 +1994,76 @@ test("should merge tolerations and replace affinity in cascaded scan if cascadin operator: "Equal", value: "test-2", effect: "NoSchedule", - } + }, ]; sslyzeCascadingRules[0].spec.scanSpec.affinity = { nodeAffinity: { - requiredDuringSchedulingIgnoredDuringExecution: { + requiredDuringSchedulingIgnoredDuringExecution: { nodeSelectorTerms: [ - { + { matchExpressions: [ { key: "network", operator: "In", - values: [ - "10g" - ] - } - ] - } - ] - } - } - } + values: ["10g"], + }, + ], + }, + ], + }, + }, + }; const cascadedScans = getCascadingScans( parentScan, findings, sslyzeCascadingRules, undefined, - parseDefinition, + parseDefinition ); const cascadedScan = cascadedScans[0]; // New values will completely replace the old values, not be merged expect(cascadedScan.spec.affinity).toMatchInlineSnapshot(` - Object { - "nodeAffinity": Object { - "requiredDuringSchedulingIgnoredDuringExecution": Object { - "nodeSelectorTerms": Array [ - Object { - "matchExpressions": Array [ + Object { + "nodeAffinity": Object { + "requiredDuringSchedulingIgnoredDuringExecution": Object { + "nodeSelectorTerms": Array [ Object { - "key": "network", - "operator": "In", - "values": Array [ - "10g", + "matchExpressions": Array [ + Object { + "key": "network", + "operator": "In", + "values": Array [ + "10g", + ], + }, ], }, ], }, - ], - }, - }, - } - `); + }, + } + `); expect(cascadedScan.spec.tolerations).toMatchInlineSnapshot(` - Array [ - Object { - "effect": "NoSchedule", - "key": "key1", - "operator": "Equal", - "value": "test", - }, - Object { - "effect": "NoSchedule", - "key": "key2", - "operator": "Equal", - "value": "test-2", - }, - ] - `); + Array [ + Object { + "effect": "NoSchedule", + "key": "key1", + "operator": "Equal", + "value": "test", + }, + Object { + "effect": "NoSchedule", + "key": "key2", + "operator": "Equal", + "value": "test-2", + }, + ] + `); }); test("should not set affinity or tolerations to undefined if they are defined to be an empty map / list in upstream scan", () => { @@ -2054,21 +2075,21 @@ test("should not set affinity or tolerations to undefined if they are defined to state: "open", hostname: "foobar.com", port: 443, - service: "https" - } - } + service: "https", + }, + }, ]; - parentScan.spec.affinity = {} + parentScan.spec.affinity = {}; - parentScan.spec.tolerations = [] + parentScan.spec.tolerations = []; const cascadedScans = getCascadingScans( parentScan, findings, sslyzeCascadingRules, undefined, - parseDefinition, + parseDefinition ); const cascadedScan = cascadedScans[0]; @@ -2088,9 +2109,9 @@ test("Should not set affinity or tolerations to undefined if they are defined to state: "open", hostname: "foobar.com", port: 443, - service: "https" - } - } + service: "https", + }, + }, ]; sslyzeCascadingRules[0].spec.scanSpec.tolerations = []; @@ -2102,7 +2123,7 @@ test("Should not set affinity or tolerations to undefined if they are defined to findings, sslyzeCascadingRules, undefined, - parseDefinition, + parseDefinition ); const cascadedScan = cascadedScans[0]; @@ -2124,39 +2145,37 @@ test("should only use tolerations and affinity of cascaded scan if inheritance i state: "open", hostname: "foobar.com", port: 443, - service: "https" - } - } + service: "https", + }, + }, ]; parentScan.spec.affinity = { nodeAffinity: { - requiredDuringSchedulingIgnoredDuringExecution: { + requiredDuringSchedulingIgnoredDuringExecution: { nodeSelectorTerms: [ - { + { matchExpressions: [ { key: "disktype", operator: "In", - values: [ - "ssd" - ] - } - ] - } - ] - } - } - } + values: ["ssd"], + }, + ], + }, + ], + }, + }, + }; parentScan.spec.tolerations = [ { key: "key1", operator: "Equal", value: "test", - effect: "NoSchedule" - } - ] + effect: "NoSchedule", + }, + ]; sslyzeCascadingRules[0].spec.scanSpec.tolerations = [ { @@ -2164,77 +2183,75 @@ test("should only use tolerations and affinity of cascaded scan if inheritance i operator: "Equal", value: "test-2", effect: "NoSchedule", - } + }, ]; sslyzeCascadingRules[0].spec.scanSpec.affinity = { nodeAffinity: { - requiredDuringSchedulingIgnoredDuringExecution: { + requiredDuringSchedulingIgnoredDuringExecution: { nodeSelectorTerms: [ - { + { matchExpressions: [ { key: "network", operator: "In", - values: [ - "10g" - ] - } - ] - } - ] - } - } - } + values: ["10g"], + }, + ], + }, + ], + }, + }, + }; const cascadedScans = getCascadingScans( parentScan, findings, sslyzeCascadingRules, undefined, - parseDefinition, + parseDefinition ); const cascadedScan = cascadedScans[0]; expect(cascadedScan.spec.affinity).toMatchInlineSnapshot(` - Object { - "nodeAffinity": Object { - "requiredDuringSchedulingIgnoredDuringExecution": Object { - "nodeSelectorTerms": Array [ - Object { - "matchExpressions": Array [ + Object { + "nodeAffinity": Object { + "requiredDuringSchedulingIgnoredDuringExecution": Object { + "nodeSelectorTerms": Array [ Object { - "key": "network", - "operator": "In", - "values": Array [ - "10g", + "matchExpressions": Array [ + Object { + "key": "network", + "operator": "In", + "values": Array [ + "10g", + ], + }, ], }, ], }, - ], - }, - }, - } - `); + }, + } + `); expect(cascadedScan.spec.tolerations).toMatchInlineSnapshot(` - Array [ - Object { - "effect": "NoSchedule", - "key": "key2", - "operator": "Equal", - "value": "test-2", - }, - ] - `); + Array [ + Object { + "effect": "NoSchedule", + "key": "key2", + "operator": "Equal", + "value": "test-2", + }, + ] + `); }); test("should purge cascaded scan spec from parent scan", () => { - parentScan.spec.cascades.inheritEnv = true - parentScan.spec.cascades.inheritVolumes = true - parentScan.spec.cascades.inheritHookSelector = true + parentScan.spec.cascades.inheritEnv = true; + parentScan.spec.cascades.inheritVolumes = true; + parentScan.spec.cascades.inheritHookSelector = true; const findings = [ { name: "Port 443 is open", @@ -2243,102 +2260,102 @@ test("should purge cascaded scan spec from parent scan", () => { state: "open", hostname: "foobar.com", port: 443, - service: "https" - } - } + service: "https", + }, + }, ]; parentScan.spec.volumes = [ { - "name": "ca-certificate", - "configMap": { - "name": "ca-certificate" - } - } - ] + name: "ca-certificate", + configMap: { + name: "ca-certificate", + }, + }, + ]; sslyzeCascadingRules[0].spec.scanSpec.volumes = [ { - "name": "ca-certificate-sslyze", - "configMap": { - "name": "ca-certificate-sslyze" - } - } - ] + name: "ca-certificate-sslyze", + configMap: { + name: "ca-certificate-sslyze", + }, + }, + ]; parentScan.spec.volumeMounts = [ { - "mountPath": "/etc/ssl/certs/ca-cert.cer", - "name": "ca-certificate", - "readOnly": true, - "subPath": "ca-cert.cer" - } - ] + mountPath: "/etc/ssl/certs/ca-cert.cer", + name: "ca-certificate", + readOnly: true, + subPath: "ca-cert.cer", + }, + ]; sslyzeCascadingRules[0].spec.scanSpec.volumeMounts = [ { - "mountPath": "/etc/ssl/certs/ca-cert-sslyze.cer", - "name": "ca-certificate-sslyze", - "readOnly": true, - "subPath": "ca-cert-sslyze.cer" - } - ] + mountPath: "/etc/ssl/certs/ca-cert-sslyze.cer", + name: "ca-certificate-sslyze", + readOnly: true, + subPath: "ca-cert-sslyze.cer", + }, + ]; parentScan.spec.env = [ { - "name": "parent_environment_variable_name", - "value": "parent_environment_variable_value" - } - ] + name: "parent_environment_variable_name", + value: "parent_environment_variable_value", + }, + ]; sslyzeCascadingRules[0].spec.scanSpec.env = [ { - "name": "rule_environment_variable_name", - "value": "rule_environment_variable_value" - } - ] + name: "rule_environment_variable_name", + value: "rule_environment_variable_value", + }, + ]; - parentScan.spec.hookSelector = {} + 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"] - } - ] + values: ["cascading-scans"], + }, + ]; sslyzeCascadingRules[0].spec.scanSpec.hookSelector = {}; sslyzeCascadingRules[0].spec.scanSpec.hookSelector.matchExpressions = [ { key: "securecodebox.io/name", operator: LabelSelectorRequirementOperator.NotIn, - values: ["cascading-scans"] - } - ] + values: ["cascading-scans"], + }, + ]; sslyzeCascadingRules[0].spec.scanSpec.hookSelector.matchLabels = { "securecodebox.io/internal": "false", - } + }; const cascadedScans = getCascadingScans( parentScan, findings, sslyzeCascadingRules, undefined, - parseDefinition, + parseDefinition ); - const cascadedScan = cascadedScans[0] + const cascadedScan = cascadedScans[0]; // Create a second cascading rule sslyzeCascadingRules[1] = { apiVersion: "cascading.securecodebox.io/v1", kind: "CascadingRule", metadata: { - name: "tls-scans-second" + name: "tls-scans-second", }, spec: { matches: { @@ -2347,32 +2364,32 @@ test("should purge cascaded scan spec from parent scan", () => { category: "Open Port", attributes: { port: 443, - service: "https" - } + service: "https", + }, }, { category: "Open Port", attributes: { - service: "https" - } - } - ] + service: "https", + }, + }, + ], }, scanSpec: { scanType: "sslyze", - parameters: ["--regular", "{{$.hostOrIP}}:{{attributes.port}}"] - } - } - } + parameters: ["--regular", "{{$.hostOrIP}}:{{attributes.port}}"], + }, + }, + }; - cascadedScan.metadata.name = cascadedScan.metadata.generateName + cascadedScan.metadata.name = cascadedScan.metadata.generateName; const secondCascadedScans = getCascadingScans( cascadedScan, findings, sslyzeCascadingRules, sslyzeCascadingRules[0], // cascaded rule on parent - parseDefinition, + parseDefinition ); const secondCascadedScan = secondCascadedScans[0]; @@ -2384,7 +2401,7 @@ test("should purge cascaded scan spec from parent scan", () => { "value": "parent_environment_variable_value", }, ] - `) + `); expect(secondCascadedScan.spec.volumes).toMatchInlineSnapshot(` Array [ @@ -2395,7 +2412,7 @@ test("should purge cascaded scan spec from parent scan", () => { "name": "ca-certificate", }, ] - `) + `); expect(secondCascadedScan.spec.volumeMounts).toMatchInlineSnapshot(` Array [ @@ -2406,20 +2423,23 @@ test("should purge cascaded scan spec from parent scan", () => { "subPath": "ca-cert.cer", }, ] - `) - - 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 {}`) + `); + + 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", () => { @@ -2431,76 +2451,76 @@ test("should not copy cascaded scan spec from parent scan if inheritance is unde state: "open", hostname: "foobar.com", port: 443, - service: "https" - } - } + service: "https", + }, + }, ]; parentScan.spec.volumes = [ { - "name": "ca-certificate", - "configMap": { - "name": "ca-certificate" - } - } - ] + name: "ca-certificate", + configMap: { + name: "ca-certificate", + }, + }, + ]; sslyzeCascadingRules[0].spec.scanSpec.volumes = [ { - "name": "ca-certificate-sslyze", - "configMap": { - "name": "ca-certificate-sslyze" - } - } - ] + name: "ca-certificate-sslyze", + configMap: { + name: "ca-certificate-sslyze", + }, + }, + ]; parentScan.spec.volumeMounts = [ { - "mountPath": "/etc/ssl/certs/ca-cert.cer", - "name": "ca-certificate", - "readOnly": true, - "subPath": "ca-cert.cer" - } - ] + mountPath: "/etc/ssl/certs/ca-cert.cer", + name: "ca-certificate", + readOnly: true, + subPath: "ca-cert.cer", + }, + ]; sslyzeCascadingRules[0].spec.scanSpec.volumeMounts = [ { - "mountPath": "/etc/ssl/certs/ca-cert-sslyze.cer", - "name": "ca-certificate-sslyze", - "readOnly": true, - "subPath": "ca-cert-sslyze.cer" - } - ] + mountPath: "/etc/ssl/certs/ca-cert-sslyze.cer", + name: "ca-certificate-sslyze", + readOnly: true, + subPath: "ca-cert-sslyze.cer", + }, + ]; parentScan.spec.env = [ { - "name": "parent_environment_variable_name", - "value": "parent_environment_variable_value" - } - ] + name: "parent_environment_variable_name", + value: "parent_environment_variable_value", + }, + ]; sslyzeCascadingRules[0].spec.scanSpec.env = [ { - "name": "rule_environment_variable_name", - "value": "rule_environment_variable_value" - } - ] + name: "rule_environment_variable_name", + value: "rule_environment_variable_value", + }, + ]; const cascadedScans = getCascadingScans( parentScan, findings, sslyzeCascadingRules, undefined, - parseDefinition, + parseDefinition ); - const cascadedScan = cascadedScans[0] + const cascadedScan = cascadedScans[0]; // Create a second cascading rule sslyzeCascadingRules[1] = { apiVersion: "cascading.securecodebox.io/v1", kind: "CascadingRule", metadata: { - name: "tls-scans-second" + name: "tls-scans-second", }, spec: { matches: { @@ -2509,45 +2529,45 @@ test("should not copy cascaded scan spec from parent scan if inheritance is unde category: "Open Port", attributes: { port: 443, - service: "https" - } + service: "https", + }, }, { category: "Open Port", attributes: { - service: "https" - } - } - ] + service: "https", + }, + }, + ], }, scanSpec: { scanType: "sslyze", - parameters: ["--regular", "{{$.hostOrIP}}:{{attributes.port}}"] - } - } - } + parameters: ["--regular", "{{$.hostOrIP}}:{{attributes.port}}"], + }, + }, + }; - cascadedScan.metadata.name = cascadedScan.metadata.generateName + cascadedScan.metadata.name = cascadedScan.metadata.generateName; const secondCascadedScans = getCascadingScans( cascadedScan, findings, sslyzeCascadingRules, sslyzeCascadingRules[0], // cascaded rule on parent - parseDefinition, + parseDefinition ); const secondCascadedScan = secondCascadedScans[0]; - expect(secondCascadedScan.spec.env).toMatchInlineSnapshot(`Array []`) - - expect(secondCascadedScan.spec.volumes).toMatchInlineSnapshot(`Array []`) + expect(secondCascadedScan.spec.env).toMatchInlineSnapshot(`Array []`); - expect(secondCascadedScan.spec.volumeMounts).toMatchInlineSnapshot(`Array []`) + expect(secondCascadedScan.spec.volumes).toMatchInlineSnapshot(`Array []`); + expect(secondCascadedScan.spec.volumeMounts).toMatchInlineSnapshot( + `Array []` + ); }); - test("should append cascading rule to further cascading scan chains", () => { const findings = [ { @@ -2557,9 +2577,9 @@ test("should append cascading rule to further cascading scan chains", () => { state: "open", hostname: "foobar.com", port: 443, - service: "https" - } - } + service: "https", + }, + }, ]; const cascadedScans = getCascadingScans( @@ -2567,16 +2587,16 @@ test("should append cascading rule to further cascading scan chains", () => { findings, sslyzeCascadingRules, undefined, - parseDefinition, + parseDefinition ); - const cascadedScan = cascadedScans[0] + const cascadedScan = cascadedScans[0]; // Create a second cascading rule sslyzeCascadingRules[1] = { apiVersion: "cascading.securecodebox.io/v1", kind: "CascadingRule", metadata: { - name: "tls-scans-second" + name: "tls-scans-second", }, spec: { matches: { @@ -2585,41 +2605,44 @@ test("should append cascading rule to further cascading scan chains", () => { category: "Open Port", attributes: { port: 443, - service: "https" - } + service: "https", + }, }, { category: "Open Port", attributes: { - service: "https" - } - } - ] + service: "https", + }, + }, + ], }, scanSpec: { scanType: "sslyze", - parameters: ["--regular", "{{$.hostOrIP}}:{{attributes.port}}"] - } - } - } + parameters: ["--regular", "{{$.hostOrIP}}:{{attributes.port}}"], + }, + }, + }; - cascadedScan.metadata.name = cascadedScan.metadata.generateName + cascadedScan.metadata.name = cascadedScan.metadata.generateName; const secondCascadedScans = getCascadingScans( cascadedScan, findings, sslyzeCascadingRules, sslyzeCascadingRules[0], // cascaded rule on parent - parseDefinition, + parseDefinition ); const secondCascadedScan = secondCascadedScans[0]; - expect(secondCascadedScan.metadata.annotations["cascading.securecodebox.io/chain"]).toEqual("tls-scans,tls-scans-second") + expect( + secondCascadedScan.metadata.annotations["cascading.securecodebox.io/chain"] + ).toEqual("tls-scans,tls-scans-second"); }); test("should not cascade if scope limiter does not pass", () => { - parentScan.metadata.annotations["scope.cascading.securecodebox.io/ports"] = "80,443"; + parentScan.metadata.annotations["scope.cascading.securecodebox.io/ports"] = + "80,443"; parentScan.spec.cascades.scopeLimiter = { allOf: [ { @@ -2660,7 +2683,7 @@ test("should not cascade if scope limiter does not pass", () => { findings, sslyzeCascadingRules, undefined, - parseDefinition, + parseDefinition ); expect(cascadedScans).toMatchInlineSnapshot(` @@ -2690,6 +2713,7 @@ test("should not cascade if scope limiter does not pass", () => { ], }, "spec": Object { + "affinity": undefined, "cascades": Object { "scopeLimiter": Object { "allOf": Array [ @@ -2711,6 +2735,7 @@ test("should not cascade if scope limiter does not pass", () => { "foobar.com:443", ], "scanType": "sslyze", + "tolerations": undefined, "volumeMounts": Array [], "volumes": Array [], }, @@ -2719,9 +2744,9 @@ test("should not cascade if scope limiter does not pass", () => { `); }); - test("scope annotations should be completely immutable", () => { - parentScan.metadata.annotations["scope.cascading.securecodebox.io/domains"] = "example.com"; + parentScan.metadata.annotations["scope.cascading.securecodebox.io/domains"] = + "example.com"; parentScan.metadata.annotations["not.a.scope.annotation"] = "really"; parentScan.spec.cascades.inheritAnnotations = false; sslyzeCascadingRules[0].spec.scanAnnotations = { @@ -2736,33 +2761,38 @@ test("scope annotations should be completely immutable", () => { state: "open", hostname: "foobar.com", port: 443, - service: "https" - } - } + service: "https", + }, + }, ]; - const cascadedScans = () => getCascadingScans( - parentScan, - findings, - sslyzeCascadingRules, - undefined, - parseDefinition, + const cascadedScans = () => + getCascadingScans( + parentScan, + findings, + sslyzeCascadingRules, + undefined, + parseDefinition + ); + + expect(cascadedScans).toThrowError( + "may not add scope annotation 'scope.cascading.securecodebox.io/domains':'malicious.example.com' in Cascading Rule spec" ); - expect(cascadedScans).toThrowError("may not add scope annotation 'scope.cascading.securecodebox.io/domains':'malicious.example.com' in Cascading Rule spec"); - - delete sslyzeCascadingRules[0].spec.scanAnnotations["scope.cascading.securecodebox.io/domains"] + delete sslyzeCascadingRules[0].spec.scanAnnotations[ + "scope.cascading.securecodebox.io/domains" + ]; - const cascadedScan = cascadedScans()[0] + const cascadedScan = cascadedScans()[0]; expect(cascadedScan.metadata.annotations).toMatchInlineSnapshot(` - Object { - "another.not.a.scope.annotation": "really", - "cascading.securecodebox.io/chain": "tls-scans", - "cascading.securecodebox.io/matched-finding": undefined, - "cascading.securecodebox.io/parent-scan": "nmap-foobar.com", - "scope.cascading.securecodebox.io/domains": "example.com", - "securecodebox.io/hook": "cascading-scans", - } - `) + Object { + "another.not.a.scope.annotation": "really", + "cascading.securecodebox.io/chain": "tls-scans", + "cascading.securecodebox.io/matched-finding": undefined, + "cascading.securecodebox.io/parent-scan": "nmap-foobar.com", + "scope.cascading.securecodebox.io/domains": "example.com", + "securecodebox.io/hook": "cascading-scans", + } + `); });