From 579571c98111c41fde818c10b93f2de0296b7d59 Mon Sep 17 00:00:00 2001 From: SebieF Date: Fri, 2 Oct 2020 10:30:48 +0200 Subject: [PATCH 1/8] Cascading Rules Examples Nmap/Ncrack --- scanners/ncrack/cascading-rules/crackssh.yaml | 22 +++++++++++++++++++ .../nmap/examples/dummy-ssh-cascade/scan.yaml | 16 ++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 scanners/ncrack/cascading-rules/crackssh.yaml create mode 100644 scanners/nmap/examples/dummy-ssh-cascade/scan.yaml diff --git a/scanners/ncrack/cascading-rules/crackssh.yaml b/scanners/ncrack/cascading-rules/crackssh.yaml new file mode 100644 index 00000000..77ea0413 --- /dev/null +++ b/scanners/ncrack/cascading-rules/crackssh.yaml @@ -0,0 +1,22 @@ +apiVersion: "cascading.experimental.securecodebox.io/v1" +kind: CascadingRule +metadata: + name: "ncrack-ssh" + labels: + securecodebox.io/invasive: invasive + securecodebox.io/intensive: high +spec: + matches: + anyOf: + - category: "Open Port" + attributes: + port: 22 + state: open + scanSpec: + scanType: "ncrack" + parameters: + - -v + - -d10 + - --user=root,admin + - --pass=abcdef,THEPASSWORDYOUCREATED,12345 + - ssh://"{{location}}" diff --git a/scanners/nmap/examples/dummy-ssh-cascade/scan.yaml b/scanners/nmap/examples/dummy-ssh-cascade/scan.yaml new file mode 100644 index 00000000..1f05d84b --- /dev/null +++ b/scanners/nmap/examples/dummy-ssh-cascade/scan.yaml @@ -0,0 +1,16 @@ +apiVersion: "execution.experimental.securecodebox.io/v1" +kind: Scan +metadata: + name: "nmap-dummy-ssh" +spec: + scanType: "nmap" + parameters: + # Internal cluster is blocking our ping probes, therefore we skip them + - "-Pn" + # Service Detection enabled + - "-sV" + # Actual Service Address will depend on you cluster and namespace configuration. 🤷‍ + - "dummy-ssh.demo-apps.svc" + cascades: + matchLabels: + securecodebox.io/intensive: high From 256c24192f24aa5a355c6ed9a34b6b0a9ba42c52 Mon Sep 17 00:00:00 2001 From: SebieF Date: Fri, 2 Oct 2020 11:11:51 +0200 Subject: [PATCH 2/8] Cascading Integration Test - Test --- tests/integration/helpers.js | 121 ++++++++++++++++++++ tests/integration/scanner/cascading-test.js | 31 +++++ 2 files changed, 152 insertions(+) create mode 100644 tests/integration/scanner/cascading-test.js diff --git a/tests/integration/helpers.js b/tests/integration/helpers.js index 7781a7cd..71432e35 100644 --- a/tests/integration/helpers.js +++ b/tests/integration/helpers.js @@ -157,4 +157,125 @@ async function scan(name, scanType, parameters = [], timeout = 180) { throw new Error("timed out while waiting for scan results"); } +/** + * + * @param {string} name name of the scan. Actual name will be sufixed with a random number to avoid conflicts + * @param {string} scanType type of the scan. Must match the name of a ScanType CRD + * @param {string[]} parameters cli argument to be passed to the scanner + * @param {number} timeout in seconds + * @returns {scan.findings} returns findings { categories, severities, count } + */ +async function cascadingScan(name, scanType, parameters = [], nameCascade, scanTypeCascade, parametersCascade, intensive, invasive, timeout = 180) { + const cascadingDefinition = { + apiVersion: "cascading.experimental.securecodebox.io/v1", + kind: "CascadingRule", + metadata: { + generateName: `${nameCascade}-`, + labels: { + invasive, + intensive, + }, + spec: { + matches: { + anyOf: { + category: "Open Port", + attributes: { + port: 22, + state: "open" + } + } + }, + scanSpec: { + scanTypeCascade, + parametersCascade + } + } + } + }; + + + const scanDefinition = { + apiVersion: "execution.experimental.securecodebox.io/v1", + kind: "Scan", + metadata: { + // Use `generateName` instead of name to generate a random sufix and avoid name clashes + generateName: `${name}-`, + }, + spec: { + scanType, + parameters, + }, + cascades: { + matchLabels: { + intensive + } + } + }; + + const { bodyCascade } = await k8sCRDApi.createNamespacedCustomObject( + "cascading.experimental.securecodebox.io", + "v1", + namespace, + "scans", + cascadingDefinition + ); + + const { body } = await k8sCRDApi.createNamespacedCustomObject( + "execution.experimental.securecodebox.io", + "v1", + namespace, + "scans", + scanDefinition + ); + + const actualName = body.metadata.name; + const actualNameCascade = bodyCascade.metadata.name; + + for (let i = 0; i < timeout; i++) { + await sleep(1); + const { status } = await getScan(actualName); + + if (status && status.state === "Done") { + // Wait a couple seconds to give kubernetes more time to update the fields + await sleep(2); + + for(let j = 0; j < timeout; j++) { + await sleep(1) + const { statusCascade } = await getScan(actualNameCascade); + if (statusCascade && statusCascade.state === "Done") { + await sleep(2); + const { statusCascade } = await getScan(actualNameCascade); + + await deleteScan(actualName); + await deleteScan(actualNameCascade); + return statusCascade.findings; + } else if (statusCascade && statusCascade.state === "Errored") { + console.error("Scan Errored"); + await disasterRecovery(actualNameCascade); + throw new Error( + `Cascade Scan failed with description "${statusCascade.errorDescription}"` + ); + } + console.error("Cascade Scan Timed out!"); + await disasterRecovery(actualNameCascade); + + throw new Error("timed out while waiting for cascading scan results"); + } + + } else if (status && status.state === "Errored") { + console.error("Scan Errored"); + await disasterRecovery(actualName); + + throw new Error( + `Scan failed with description "${status.errorDescription}"` + ); + } + } + console.error("Scan Timed out!"); + await disasterRecovery(actualName); + + throw new Error("timed out while waiting for scan results"); +} + module.exports.scan = scan; +module.exports.cascadingScan = cascadingScan; diff --git a/tests/integration/scanner/cascading-test.js b/tests/integration/scanner/cascading-test.js new file mode 100644 index 00000000..8e8376e8 --- /dev/null +++ b/tests/integration/scanner/cascading-test.js @@ -0,0 +1,31 @@ +const { cascadingScan } = require('../helpers') + +test( + "Cascading Scan nmap -> ncrack on dummy-ssh", + async () => { + const { categories, severities, count } = await cascadingScan( + "nmap-dummy-ssh", + "nmap", + ["-Pn", "-sV", "dummy-ssh.demo-apps.svc"], + "ncrack-ssh", + "ncrack", + ["-v", "--user=root,admin", "--pass=THEPASSWORDYOUCREATED,12345", "ssh://{{location}}"], + "high", + "invasive", + 120 + ); + + expect(count).toBe(1); + expect(categories).toEqual( + { + "Discovered Credentials": 1, + } + ); + expect(severities).toEqual( + { + "high": 1, + } + ); + }, + 3 * 60 * 1000 +); From 027f26dd73acb08de38a3762367848981ca319f3 Mon Sep 17 00:00:00 2001 From: SebieF Date: Fri, 2 Oct 2020 11:19:08 +0200 Subject: [PATCH 3/8] Updated CI.yaml with Cascading Integration Test --- .github/workflows/ci.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b5135898..7e91f4c9 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -440,6 +440,10 @@ jobs: helm -n integration-tests install zap ./scanners/zap/ --set="parserImage.tag=sha-$(git rev-parse --short HEAD)" cd tests/integration/ npx jest --ci --color zap + - name: "Cascading Scans Integration Tests" + run: | + cd tests/integration/ + npx jest --ci --color cascading-test - name: Inspect Post Failure if: failure() run: | From 9f3e650c250fb9989ff8e97c1256d298e35a2fec Mon Sep 17 00:00:00 2001 From: SebieF Date: Fri, 2 Oct 2020 11:37:40 +0200 Subject: [PATCH 4/8] Fixed Filenames --- .github/workflows/ci.yaml | 2 +- .../integration/scanner/{cascading-test.js => cascade.test.js} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename tests/integration/scanner/{cascading-test.js => cascade.test.js} (100%) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 7e91f4c9..438b0feb 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -443,7 +443,7 @@ jobs: - name: "Cascading Scans Integration Tests" run: | cd tests/integration/ - npx jest --ci --color cascading-test + npx jest --ci --color cascade - name: Inspect Post Failure if: failure() run: | diff --git a/tests/integration/scanner/cascading-test.js b/tests/integration/scanner/cascade.test.js similarity index 100% rename from tests/integration/scanner/cascading-test.js rename to tests/integration/scanner/cascade.test.js From c6fddbfc91b0ce472871c7d0f00a15258ce6ddd2 Mon Sep 17 00:00:00 2001 From: SebieF Date: Fri, 2 Oct 2020 13:57:04 +0200 Subject: [PATCH 5/8] Fixed ncrack ssh cascading rules --- scanners/ncrack/cascading-rules/crackssh.yaml | 14 +++++++++++--- scanners/ncrack/templates/cascading-rules.yaml | 8 ++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 scanners/ncrack/templates/cascading-rules.yaml diff --git a/scanners/ncrack/cascading-rules/crackssh.yaml b/scanners/ncrack/cascading-rules/crackssh.yaml index 77ea0413..a9b0de57 100644 --- a/scanners/ncrack/cascading-rules/crackssh.yaml +++ b/scanners/ncrack/cascading-rules/crackssh.yaml @@ -12,11 +12,19 @@ spec: attributes: port: 22 state: open + - category: "Open Port" + attributes: + service: "ssh" + state: open scanSpec: scanType: "ncrack" parameters: - -v - -d10 - - --user=root,admin - - --pass=abcdef,THEPASSWORDYOUCREATED,12345 - - ssh://"{{location}}" + - -U + - /ncrack/users.txt + - -P + - /ncrack/passwords.txt + - -p + - ssh:{{attributes.port}} + - "{{attributes.ip_address}}" diff --git a/scanners/ncrack/templates/cascading-rules.yaml b/scanners/ncrack/templates/cascading-rules.yaml new file mode 100644 index 00000000..ce556e7d --- /dev/null +++ b/scanners/ncrack/templates/cascading-rules.yaml @@ -0,0 +1,8 @@ +# The CascadingRules are not directly in the /templates directory as their curly bracket syntax clashes with helms templates ... :( +# We import them as raw files to avoid these clashes as escaping them is even more messy +{{ range $path, $_ := .Files.Glob "cascading-rules/*" }} +# Include File +{{ $.Files.Get $path }} +# Separate multiple files +--- +{{ end }} \ No newline at end of file From 3de41bb00e8278a021895798b2caa29d42091ec7 Mon Sep 17 00:00:00 2001 From: SebieF Date: Fri, 2 Oct 2020 15:14:13 +0200 Subject: [PATCH 6/8] Cascading Rules Integration Tests --- .github/workflows/ci.yaml | 23 +++- tests/integration/helpers.js | 129 ++++++++++------------ tests/integration/scanner/cascade.test.js | 32 +++--- 3 files changed, 100 insertions(+), 84 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 438b0feb..45f4a7fa 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -440,8 +440,29 @@ jobs: helm -n integration-tests install zap ./scanners/zap/ --set="parserImage.tag=sha-$(git rev-parse --short HEAD)" cd tests/integration/ npx jest --ci --color zap - - name: "Cascading Scans Integration Tests" + - name: "cascading Scans Integration Tests" run: | + # We'll run these in a separate namespace so that only the cascadingRules we want to test will be used + kubectl create namespace cascading-tests + # Install declarative-subsequent-scans hook + helm upgrade --install dssh ./hooks/declarative-subsequent-scans/ -n cascading-tests + # Install nmap + helm -n cascading-tests install nmap ./scanners/nmap/ --set="parserImage.tag=sha-$(git rev-parse --short HEAD)" + # Install ncrack + printf "root\nadmin\n" > users.txt + printf "THEPASSWORDYOUCREATED\n123456\npassword\n" > passwords.txt + kubectl create secret generic --from-file users.txt --from-file passwords.txt ncrack-lists -n cascading-tests + cat < new Promise((resolve) => setTimeout(resolve, ms * 1000)); @@ -109,6 +109,7 @@ async function disasterRecovery(scanName) { * @returns {scan.findings} returns findings { categories, severities, count } */ async function scan(name, scanType, parameters = [], timeout = 180) { + namespace ="integration-tests" const scanDefinition = { apiVersion: "execution.experimental.securecodebox.io/v1", kind: "Scan", @@ -165,34 +166,8 @@ async function scan(name, scanType, parameters = [], timeout = 180) { * @param {number} timeout in seconds * @returns {scan.findings} returns findings { categories, severities, count } */ -async function cascadingScan(name, scanType, parameters = [], nameCascade, scanTypeCascade, parametersCascade, intensive, invasive, timeout = 180) { - const cascadingDefinition = { - apiVersion: "cascading.experimental.securecodebox.io/v1", - kind: "CascadingRule", - metadata: { - generateName: `${nameCascade}-`, - labels: { - invasive, - intensive, - }, - spec: { - matches: { - anyOf: { - category: "Open Port", - attributes: { - port: 22, - state: "open" - } - } - }, - scanSpec: { - scanTypeCascade, - parametersCascade - } - } - } - }; - +async function cascadingScan(name, scanType, parameters = [], { nameCascade, matchLabels }, timeout = 180) { + namespace = "cascading-tests"; const scanDefinition = { apiVersion: "execution.experimental.securecodebox.io/v1", @@ -204,22 +179,12 @@ async function cascadingScan(name, scanType, parameters = [], nameCascade, scanT spec: { scanType, parameters, + cascades: { + matchLabels, + } }, - cascades: { - matchLabels: { - intensive - } - } }; - const { bodyCascade } = await k8sCRDApi.createNamespacedCustomObject( - "cascading.experimental.securecodebox.io", - "v1", - namespace, - "scans", - cascadingDefinition - ); - const { body } = await k8sCRDApi.createNamespacedCustomObject( "execution.experimental.securecodebox.io", "v1", @@ -229,7 +194,6 @@ async function cascadingScan(name, scanType, parameters = [], nameCascade, scanT ); const actualName = body.metadata.name; - const actualNameCascade = bodyCascade.metadata.name; for (let i = 0; i < timeout; i++) { await sleep(1); @@ -238,41 +202,70 @@ async function cascadingScan(name, scanType, parameters = [], nameCascade, scanT if (status && status.state === "Done") { // Wait a couple seconds to give kubernetes more time to update the fields await sleep(2); - - for(let j = 0; j < timeout; j++) { - await sleep(1) - const { statusCascade } = await getScan(actualNameCascade); - if (statusCascade && statusCascade.state === "Done") { - await sleep(2); - const { statusCascade } = await getScan(actualNameCascade); - - await deleteScan(actualName); - await deleteScan(actualNameCascade); - return statusCascade.findings; - } else if (statusCascade && statusCascade.state === "Errored") { - console.error("Scan Errored"); - await disasterRecovery(actualNameCascade); - throw new Error( - `Cascade Scan failed with description "${statusCascade.errorDescription}"` - ); - } - console.error("Cascade Scan Timed out!"); - await disasterRecovery(actualNameCascade); - - throw new Error("timed out while waiting for cascading scan results"); - } + break; } else if (status && status.state === "Errored") { console.error("Scan Errored"); await disasterRecovery(actualName); + throw new Error( + `Initial Scan failed with description "${status.errorDescription}"` + ); + } + if (i === (timeout - 1)) { throw new Error( - `Scan failed with description "${status.errorDescription}"` + `Initial Scan timed out failed` ); } } - console.error("Scan Timed out!"); + + console.log("First Scan finished") + + const { body: scans } = await k8sCRDApi.listNamespacedCustomObject( + "execution.experimental.securecodebox.io", + "v1", + namespace, + namespace, + "scans" + ) + + let cascadedScan = null; + + for (const scan of scans.items) { + if (scan.metadata.annotations && scan.metadata.annotations["cascading.securecodebox.io/chain"] === nameCascade) { + cascadedScan = scan; + break; + } + } + + if (cascadedScan === null) { + throw new Error(`Didn't find cascaded Scan for ${nameCascade}`) + } + const actualNameCascade = cascadedScan.metadata.name; + + for (let j = 0; j < timeout; j++) { + await sleep(1) + const { status: statusCascade } = await getScan(actualNameCascade); + + if (statusCascade && statusCascade.state === "Done") { + await sleep(2); + const { status: statusCascade } = await getScan(actualNameCascade); + + await deleteScan(actualName); + await deleteScan(actualNameCascade); + return statusCascade.findings; + } else if (statusCascade && statusCascade.state === "Errored") { + console.error("Scan Errored"); + await disasterRecovery(actualName); + await disasterRecovery(actualNameCascade); + throw new Error( + `Cascade Scan failed with description "${statusCascade.errorDescription}"` + ); + } + } + console.error("Cascade Scan Timed out!"); await disasterRecovery(actualName); + await disasterRecovery(actualNameCascade); throw new Error("timed out while waiting for scan results"); } diff --git a/tests/integration/scanner/cascade.test.js b/tests/integration/scanner/cascade.test.js index 8e8376e8..b87c9df9 100644 --- a/tests/integration/scanner/cascade.test.js +++ b/tests/integration/scanner/cascade.test.js @@ -7,25 +7,27 @@ test( "nmap-dummy-ssh", "nmap", ["-Pn", "-sV", "dummy-ssh.demo-apps.svc"], - "ncrack-ssh", - "ncrack", - ["-v", "--user=root,admin", "--pass=THEPASSWORDYOUCREATED,12345", "ssh://{{location}}"], - "high", - "invasive", + { + nameCascade: "ncrack-ssh", + matchLabels: { + "securecodebox.io/invasive": "invasive", + "securecodebox.io/intensive": "high" + } + }, 120 ); expect(count).toBe(1); - expect(categories).toEqual( - { - "Discovered Credentials": 1, - } - ); - expect(severities).toEqual( - { - "high": 1, - } - ); + expect(categories).toEqual( + { + "Discovered Credentials": 1, + } + ); + expect(severities).toEqual( + { + "high": 1, + } + ); }, 3 * 60 * 1000 ); From f90b51a49437b4d7442e687356dd9fcf7749a687 Mon Sep 17 00:00:00 2001 From: SebieF Date: Fri, 2 Oct 2020 15:30:28 +0200 Subject: [PATCH 7/8] Bugfix helpers.js --- tests/integration/helpers.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/integration/helpers.js b/tests/integration/helpers.js index fba2aa12..72ba0bed 100644 --- a/tests/integration/helpers.js +++ b/tests/integration/helpers.js @@ -168,7 +168,7 @@ async function scan(name, scanType, parameters = [], timeout = 180) { */ async function cascadingScan(name, scanType, parameters = [], { nameCascade, matchLabels }, timeout = 180) { namespace = "cascading-tests"; - + const scanDefinition = { apiVersion: "execution.experimental.securecodebox.io/v1", kind: "Scan", @@ -225,9 +225,8 @@ async function cascadingScan(name, scanType, parameters = [], { nameCascade, mat "execution.experimental.securecodebox.io", "v1", namespace, - namespace, "scans" - ) + ); let cascadedScan = null; From 291acc26baeb807de033e03c29e2fd2516c4efbf Mon Sep 17 00:00:00 2001 From: Yannik Fuhrmeister Date: Mon, 5 Oct 2020 10:39:28 +0200 Subject: [PATCH 8/8] Add Newline at EOF --- scanners/ncrack/templates/cascading-rules.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scanners/ncrack/templates/cascading-rules.yaml b/scanners/ncrack/templates/cascading-rules.yaml index ce556e7d..2c373ab8 100644 --- a/scanners/ncrack/templates/cascading-rules.yaml +++ b/scanners/ncrack/templates/cascading-rules.yaml @@ -5,4 +5,5 @@ {{ $.Files.Get $path }} # Separate multiple files --- -{{ end }} \ No newline at end of file +{{ end }} +