diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 3ab03a0604..ea863501f4 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -54,6 +54,36 @@ jobs: path: ./nvm-node.tar.gz retention-days: 1 + + test-nodejs-scanner-test-helpers: + name: "Unit Test | Node.js Scanner Test Helpers" + needs: + - nvm-setup + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + + - name: Download nvm and Node.js + uses: actions/download-artifact@v4 + with: + name: nvm-node + path: nvm-node + + - name: Extract nvm and Node.js + run: | + tar xzf nvm-node/nvm-node.tar.gz -C $HOME + export NVM_DIR="$HOME/.nvm" + [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" + nvm use + + - name: Install dependencies + working-directory: ./scanners + run: | + npm ci + - name: Test Node.js Scanner Test Helpers + working-directory: ./scanners + run: | + npm run test:helpers k8s-setup: name: "Setup Kind & Kubectl & Helm" runs-on: ubuntu-22.04 diff --git a/scanners/Makefile b/scanners/Makefile index b1524d560a..d671dadc69 100644 --- a/scanners/Makefile +++ b/scanners/Makefile @@ -25,7 +25,7 @@ helm-unit-tests: for directory in ./*; do \ if [ -d "$$directory" ]; then \ dir_name=$$(basename "$$directory"); \ - if [ "$$dir_name" != "coverage" ] && [ "$$dir_name" != "node_modules" ]; then \ + if [ "$$dir_name" != "coverage" ] && [ "$$dir_name" != "node_modules" ] && [ "$$dir_name" != "__snapshots__" ] && [ "$$dir_name" != "__testFiles__" ]; then \ helm unittest "$$directory"; \ fi; \ fi; \ diff --git a/scanners/__snapshots__/helpers.test.js.snap b/scanners/__snapshots__/helpers.test.js.snap new file mode 100644 index 0000000000..44d036305e --- /dev/null +++ b/scanners/__snapshots__/helpers.test.js.snap @@ -0,0 +1,89 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Kubernetes interaction tests cascading scan function should create a cascading scan and return findings on successful completion 1`] = ` +{ + "categories": { + "Discovered Credentials": 1, + }, + "count": 1, + "severities": { + "high": 1, + }, +} +`; + +exports[`Kubernetes interaction tests scan function should create a scan and return findings on successful completion 1`] = ` +{ + "categories": { + "Vulnerability": 24, + }, + "count": 24, + "severities": { + "high": 24, + }, +} +`; + +exports[`Kubernetes interaction tests scan function should create a scan and return findings on successful completion 2`] = ` +[MockFunction] { + "calls": [ + [ + "execution.securecodebox.io", + "v1", + "integration-tests", + "scans", + { + "apiVersion": "execution.securecodebox.io/v1", + "kind": "Scan", + "metadata": { + "generateName": "typo3scan-old-typo3-", + }, + "spec": { + "initContainers": [], + "parameters": [], + "scanType": "typo3scan", + "volumeMounts": [], + "volumes": [], + }, + }, + ], + ], + "results": [ + { + "type": "return", + "value": Promise {}, + }, + ], +} +`; + +exports[`Kubernetes interaction tests scan function should create a scan and return findings on successful completion 3`] = ` +[MockFunction] { + "calls": [ + [ + "execution.securecodebox.io", + "v1", + "integration-tests", + "scans", + "typo3scan-old-typo3-pw8vt", + ], + [ + "execution.securecodebox.io", + "v1", + "integration-tests", + "scans", + "typo3scan-old-typo3-pw8vt", + ], + ], + "results": [ + { + "type": "return", + "value": Promise {}, + }, + { + "type": "return", + "value": Promise {}, + }, + ], +} +`; diff --git a/scanners/__snapshots__/helpers.test.js.snap.license b/scanners/__snapshots__/helpers.test.js.snap.license new file mode 100644 index 0000000000..c95bc37185 --- /dev/null +++ b/scanners/__snapshots__/helpers.test.js.snap.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: the secureCodeBox authors + +SPDX-License-Identifier: Apache-2.0 diff --git a/scanners/__testFiles__/mockCascadingListNamespacedCustomObject.json b/scanners/__testFiles__/mockCascadingListNamespacedCustomObject.json new file mode 100644 index 0000000000..57db132c7d --- /dev/null +++ b/scanners/__testFiles__/mockCascadingListNamespacedCustomObject.json @@ -0,0 +1,399 @@ +{ + "body": { + "apiVersion": "execution.securecodebox.io/v1", + "items": [ + { + "apiVersion": "execution.securecodebox.io/v1", + "kind": "Scan", + "metadata": { + "annotations": { + "cascading.securecodebox.io/chain": "ncrack-ssh", + "cascading.securecodebox.io/matched-finding": "6c32098e-36e0-491a-b40b-1741081bf7c7", + "cascading.securecodebox.io/parent-scan": "nmap-dummy-ssh-7jz42", + "securecodebox.io/hook": "cascading-scans" + }, + "creationTimestamp": "2024-02-02T16:45:30Z", + "finalizers": [ + "s3.storage.securecodebox.io" + ], + "generateName": "ncrack-dummy-ssh-7jz42-ncrack-ssh-", + "generation": 2, + "managedFields": [ + { + "apiVersion": "execution.securecodebox.io/v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:finalizers": { + ".": {}, + "v:\"s3.storage.securecodebox.io\"": {} + } + }, + "f:spec": { + "f:resources": {} + } + }, + "manager": "manager", + "operation": "Update", + "time": "2024-02-02T16:45:30Z" + }, + { + "apiVersion": "execution.securecodebox.io/v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:annotations": { + ".": {}, + "f:cascading.securecodebox.io/chain": {}, + "f:cascading.securecodebox.io/matched-finding": {}, + "f:cascading.securecodebox.io/parent-scan": {}, + "f:securecodebox.io/hook": {} + }, + "f:generateName": {}, + "f:ownerReferences": { + ".": {}, + "k:{\"uid\":\"c887fffa-ddc9-4a7a-9415-7c46cfaa3649\"}": {} + } + }, + "f:spec": { + ".": {}, + "f:cascades": { + ".": {}, + "f:inheritAffinity": {}, + "f:inheritAnnotations": {}, + "f:inheritEnv": {}, + "f:inheritHookSelector": {}, + "f:inheritInitContainers": {}, + "f:inheritLabels": {}, + "f:inheritTolerations": {}, + "f:inheritVolumes": {}, + "f:matchLabels": { + ".": {}, + "f:securecodebox.io/intensive": {}, + "f:securecodebox.io/invasive": {} + }, + "f:scopeLimiter": { + ".": {}, + "f:validOnMissingRender": {} + } + }, + "f:hookSelector": {}, + "f:parameters": {}, + "f:resourceMode": {}, + "f:scanType": {} + } + }, + "manager": "unknown", + "operation": "Update", + "time": "2024-02-02T16:45:30Z" + }, + { + "apiVersion": "execution.securecodebox.io/v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:status": { + ".": {}, + "f:findingDownloadLink": {}, + "f:findingHeadLink": {}, + "f:findings": { + ".": {}, + "f:severities": {} + }, + "f:rawResultDownloadLink": {}, + "f:rawResultFile": {}, + "f:rawResultHeadLink": {}, + "f:rawResultType": {}, + "f:state": {} + } + }, + "manager": "manager", + "operation": "Update", + "subresource": "status", + "time": "2024-02-02T16:45:38Z" + }, + { + "apiVersion": "execution.securecodebox.io/v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:status": { + "f:findings": { + "f:categories": { + ".": {}, + "f:Discovered Credentials": {} + }, + "f:count": {}, + "f:severities": { + "f:high": {}, + "f:informational": {}, + "f:low": {}, + "f:medium": {} + } + } + } + }, + "manager": "unknown", + "operation": "Update", + "subresource": "status", + "time": "2024-02-02T16:45:44Z" + } + ], + "name": "ncrack-dummy-ssh-7jz42-ncrack-ssh-h9fdz", + "namespace": "integration-tests", + "ownerReferences": [ + { + "apiVersion": "execution.securecodebox.io/v1", + "blockOwnerDeletion": true, + "controller": true, + "kind": "Scan", + "name": "nmap-dummy-ssh-7jz42", + "uid": "c887fffa-ddc9-4a7a-9415-7c46cfaa3649" + } + ], + "resourceVersion": "10500", + "uid": "f0816a28-9b61-4e3e-a586-dd8539390264" + }, + "spec": { + "cascades": { + "inheritAffinity": true, + "inheritAnnotations": true, + "inheritEnv": false, + "inheritHookSelector": false, + "inheritInitContainers": false, + "inheritLabels": true, + "inheritTolerations": true, + "inheritVolumes": false, + "matchLabels": { + "securecodebox.io/intensive": "high", + "securecodebox.io/invasive": "invasive" + }, + "scopeLimiter": { + "validOnMissingRender": false + } + }, + "hookSelector": {}, + "parameters": [ + "-v", + "-d10", + "-U", + "/ncrack/users.txt", + "-P", + "/ncrack/passwords.txt", + "-p", + "ssh:22", + "dummy-ssh.demo-targets.svc" + ], + "resourceMode": "namespaceLocal", + "resources": {}, + "scanType": "ncrack" + }, + "status": { + "findingDownloadLink": "http://securecodebox-operator-minio.securecodebox-system.svc.cluster.local:9000/securecodebox/scan-f0816a28-9b61-4e3e-a586-dd8539390264/findings.json?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=admin%2F20240202%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20240202T164530Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=dcde57274c3827a3c0836917f1b2b1ae7e4e50d652284f43fb9ec9bc49a7dea5", + "findingHeadLink": "http://securecodebox-operator-minio.securecodebox-system.svc.cluster.local:9000/securecodebox/scan-f0816a28-9b61-4e3e-a586-dd8539390264/findings.json?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=admin%2F20240202%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20240202T164530Z&X-Amz-Expires=43200&X-Amz-SignedHeaders=host&X-Amz-Signature=635b4b9eed12861b93ee0347845d79e8d1857659a53a9da025f838c9fb000345", + "findings": { + "categories": { + "Discovered Credentials": 1 + }, + "count": 1, + "severities": { + "high": 1, + "informational": 0, + "low": 0, + "medium": 0 + } + }, + "rawResultDownloadLink": "http://securecodebox-operator-minio.securecodebox-system.svc.cluster.local:9000/securecodebox/scan-f0816a28-9b61-4e3e-a586-dd8539390264/ncrack-results.xml?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=admin%2F20240202%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20240202T164530Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=7bff833cd4fbda4e827159842ea4b6fa6f5a16fb0fd767fd669ee210a072e758", + "rawResultFile": "ncrack-results.xml", + "rawResultHeadLink": "http://securecodebox-operator-minio.securecodebox-system.svc.cluster.local:9000/securecodebox/scan-f0816a28-9b61-4e3e-a586-dd8539390264/ncrack-results.xml?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=admin%2F20240202%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20240202T164530Z&X-Amz-Expires=43200&X-Amz-SignedHeaders=host&X-Amz-Signature=da487d933df9c7c6c3b0edcbfe546f521a31bd9f1bd772a61a8bcdeb78b61677", + "rawResultType": "ncrack-xml", + "state": "Parsing" + } + }, + { + "apiVersion": "execution.securecodebox.io/v1", + "kind": "Scan", + "metadata": { + "creationTimestamp": "2024-02-02T16:44:55Z", + "finalizers": [ + "s3.storage.securecodebox.io" + ], + "generateName": "nmap-dummy-ssh-", + "generation": 2, + "managedFields": [ + { + "apiVersion": "execution.securecodebox.io/v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:generateName": {} + }, + "f:spec": { + ".": {}, + "f:cascades": { + ".": {}, + "f:inheritAffinity": {}, + "f:inheritAnnotations": {}, + "f:inheritEnv": {}, + "f:inheritHookSelector": {}, + "f:inheritInitContainers": {}, + "f:inheritLabels": {}, + "f:inheritTolerations": {}, + "f:inheritVolumes": {}, + "f:matchLabels": { + ".": {}, + "f:securecodebox.io/intensive": {}, + "f:securecodebox.io/invasive": {} + } + }, + "f:parameters": {}, + "f:resourceMode": {}, + "f:scanType": {} + } + }, + "manager": "unknown", + "operation": "Update", + "time": "2024-02-02T16:44:55Z" + }, + { + "apiVersion": "execution.securecodebox.io/v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:finalizers": { + ".": {}, + "v:\"s3.storage.securecodebox.io\"": {} + } + }, + "f:spec": { + "f:cascades": { + "f:scopeLimiter": { + ".": {}, + "f:validOnMissingRender": {} + } + }, + "f:resources": {} + } + }, + "manager": "manager", + "operation": "Update", + "time": "2024-02-02T16:44:56Z" + }, + { + "apiVersion": "execution.securecodebox.io/v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:status": { + "f:findings": { + "f:categories": { + ".": {}, + "f:Host": {}, + "f:Open Port": {} + }, + "f:count": {}, + "f:severities": { + "f:informational": {} + } + } + } + }, + "manager": "unknown", + "operation": "Update", + "subresource": "status", + "time": "2024-02-02T16:45:16Z" + }, + { + "apiVersion": "execution.securecodebox.io/v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:status": { + ".": {}, + "f:findingDownloadLink": {}, + "f:findingHeadLink": {}, + "f:findings": { + ".": {}, + "f:severities": {} + }, + "f:orderedHookStatuses": {}, + "f:rawResultDownloadLink": {}, + "f:rawResultFile": {}, + "f:rawResultHeadLink": {}, + "f:rawResultType": {}, + "f:state": {} + } + }, + "manager": "manager", + "operation": "Update", + "subresource": "status", + "time": "2024-02-02T16:45:33Z" + } + ], + "name": "nmap-dummy-ssh-7jz42", + "namespace": "integration-tests", + "resourceVersion": "10454", + "uid": "c887fffa-ddc9-4a7a-9415-7c46cfaa3649" + }, + "spec": { + "cascades": { + "inheritAffinity": true, + "inheritAnnotations": true, + "inheritEnv": false, + "inheritHookSelector": false, + "inheritInitContainers": false, + "inheritLabels": true, + "inheritTolerations": true, + "inheritVolumes": false, + "matchLabels": { + "securecodebox.io/intensive": "high", + "securecodebox.io/invasive": "invasive" + }, + "scopeLimiter": { + "validOnMissingRender": false + } + }, + "parameters": [ + "-Pn", + "-sV", + "dummy-ssh.demo-targets.svc" + ], + "resourceMode": "namespaceLocal", + "resources": {}, + "scanType": "nmap" + }, + "status": { + "findingDownloadLink": "http://securecodebox-operator-minio.securecodebox-system.svc.cluster.local:9000/securecodebox/scan-c887fffa-ddc9-4a7a-9415-7c46cfaa3649/findings.json?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=admin%2F20240202%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20240202T164456Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=a17a3830b745b2f715641ed4e4ee5c561106a79eed6798d294fe693792e4315a", + "findingHeadLink": "http://securecodebox-operator-minio.securecodebox-system.svc.cluster.local:9000/securecodebox/scan-c887fffa-ddc9-4a7a-9415-7c46cfaa3649/findings.json?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=admin%2F20240202%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20240202T164456Z&X-Amz-Expires=43200&X-Amz-SignedHeaders=host&X-Amz-Signature=28127d19e36438aa686249a3e53e8ffb2ac38c02f9a5679127e0fc466b4fedfc", + "findings": { + "categories": { + "Host": 1, + "Open Port": 1 + }, + "count": 2, + "severities": { + "informational": 2 + } + }, + "orderedHookStatuses": [ + [ + { + "hookName": "dssh-cascading-scans", + "jobName": "dssh-cascading-scans-nmap-dummy-ssh-7jz42-8gq5m", + "priority": 0, + "state": "Completed", + "type": "ReadOnly" + } + ] + ], + "rawResultDownloadLink": "http://securecodebox-operator-minio.securecodebox-system.svc.cluster.local:9000/securecodebox/scan-c887fffa-ddc9-4a7a-9415-7c46cfaa3649/nmap-results.xml?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=admin%2F20240202%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20240202T164456Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=13930cec08c3e576709d6620dcd16a0d51c230003c5346c653de643e0096e9a6", + "rawResultFile": "nmap-results.xml", + "rawResultHeadLink": "http://securecodebox-operator-minio.securecodebox-system.svc.cluster.local:9000/securecodebox/scan-c887fffa-ddc9-4a7a-9415-7c46cfaa3649/nmap-results.xml?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=admin%2F20240202%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20240202T164456Z&X-Amz-Expires=43200&X-Amz-SignedHeaders=host&X-Amz-Signature=9cc5a0324f40c250f8402587036e18386f6fbf47a81b431bed8c7af9cddc4c76", + "rawResultType": "nmap-xml", + "state": "Done" + } + } + ], + "kind": "ScanList", + "metadata": { + "continue": "", + "resourceVersion": "10507" + } + } +} \ No newline at end of file diff --git a/scanners/__testFiles__/mockCascadingListNamespacedCustomObject.json.license b/scanners/__testFiles__/mockCascadingListNamespacedCustomObject.json.license new file mode 100644 index 0000000000..c95bc37185 --- /dev/null +++ b/scanners/__testFiles__/mockCascadingListNamespacedCustomObject.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: the secureCodeBox authors + +SPDX-License-Identifier: Apache-2.0 diff --git a/scanners/__testFiles__/mockCascadingScanCreationResponse.json b/scanners/__testFiles__/mockCascadingScanCreationResponse.json new file mode 100644 index 0000000000..5d4f59d527 --- /dev/null +++ b/scanners/__testFiles__/mockCascadingScanCreationResponse.json @@ -0,0 +1,74 @@ +{ + "body": { + "apiVersion": "execution.securecodebox.io/v1", + "kind": "Scan", + "metadata": { + "creationTimestamp": "2024-01-30T13:24:55Z", + "generateName": "nmap-dummy-ssh-", + "generation": 1, + "managedFields": [ + { + "apiVersion": "execution.securecodebox.io/v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:generateName": {} + }, + "f:spec": { + ".": {}, + "f:cascades": { + ".": {}, + "f:inheritAffinity": {}, + "f:inheritAnnotations": {}, + "f:inheritEnv": {}, + "f:inheritHookSelector": {}, + "f:inheritInitContainers": {}, + "f:inheritLabels": {}, + "f:inheritTolerations": {}, + "f:inheritVolumes": {}, + "f:matchLabels": { + ".": {}, + "f:securecodebox.io/intensive": {}, + "f:securecodebox.io/invasive": {} + } + }, + "f:parameters": {}, + "f:resourceMode": {}, + "f:scanType": {} + } + }, + "manager": "unknown", + "operation": "Update", + "time": "2024-01-30T13:24:55Z" + } + ], + "name": "nmap-dummy-ssh-d47zk", + "namespace": "integration-tests", + "resourceVersion": "20493", + "uid": "09f2fd3f-2fb3-4ae4-8347-9cbe23af97b3" + }, + "spec": { + "cascades": { + "inheritAffinity": true, + "inheritAnnotations": true, + "inheritEnv": false, + "inheritHookSelector": false, + "inheritInitContainers": false, + "inheritLabels": true, + "inheritTolerations": true, + "inheritVolumes": false, + "matchLabels": { + "securecodebox.io/intensive": "high", + "securecodebox.io/invasive": "invasive" + } + }, + "parameters": [ + "-Pn", + "-sV", + "dummy-ssh.demo-targets.svc" + ], + "resourceMode": "namespaceLocal", + "scanType": "nmap" + } + } +} \ No newline at end of file diff --git a/scanners/__testFiles__/mockCascadingScanCreationResponse.json.license b/scanners/__testFiles__/mockCascadingScanCreationResponse.json.license new file mode 100644 index 0000000000..c95bc37185 --- /dev/null +++ b/scanners/__testFiles__/mockCascadingScanCreationResponse.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: the secureCodeBox authors + +SPDX-License-Identifier: Apache-2.0 diff --git a/scanners/__testFiles__/mockCascadingScanStatusResponse.json b/scanners/__testFiles__/mockCascadingScanStatusResponse.json new file mode 100644 index 0000000000..1e269c96bb --- /dev/null +++ b/scanners/__testFiles__/mockCascadingScanStatusResponse.json @@ -0,0 +1,214 @@ +{ + "body": { + "apiVersion": "execution.securecodebox.io/v1", + "kind": "Scan", + "metadata": { + "annotations": { + "cascading.securecodebox.io/chain": "ncrack-ssh", + "cascading.securecodebox.io/matched-finding": "ca358271-980b-4f9a-a1b1-92b64f1d2a44", + "cascading.securecodebox.io/parent-scan": "nmap-dummy-ssh-d47zk", + "securecodebox.io/hook": "cascading-scans" + }, + "creationTimestamp": "2024-01-30T13:25:25Z", + "finalizers": [ + "s3.storage.securecodebox.io" + ], + "generateName": "ncrack-dummy-ssh-d47zk-ncrack-ssh-", + "generation": 2, + "managedFields": [ + { + "apiVersion": "execution.securecodebox.io/v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:finalizers": { + ".": {}, + "v:\"s3.storage.securecodebox.io\"": {} + } + }, + "f:spec": { + "f:resources": {} + } + }, + "manager": "manager", + "operation": "Update", + "time": "2024-01-30T13:25:25Z" + }, + { + "apiVersion": "execution.securecodebox.io/v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:annotations": { + ".": {}, + "f:cascading.securecodebox.io/chain": {}, + "f:cascading.securecodebox.io/matched-finding": {}, + "f:cascading.securecodebox.io/parent-scan": {}, + "f:securecodebox.io/hook": {} + }, + "f:generateName": {}, + "f:ownerReferences": { + ".": {}, + "k:{\"uid\":\"09f2fd3f-2fb3-4ae4-8347-9cbe23af97b3\"}": {} + } + }, + "f:spec": { + ".": {}, + "f:cascades": { + ".": {}, + "f:inheritAffinity": {}, + "f:inheritAnnotations": {}, + "f:inheritEnv": {}, + "f:inheritHookSelector": {}, + "f:inheritInitContainers": {}, + "f:inheritLabels": {}, + "f:inheritTolerations": {}, + "f:inheritVolumes": {}, + "f:matchLabels": { + ".": {}, + "f:securecodebox.io/intensive": {}, + "f:securecodebox.io/invasive": {} + }, + "f:scopeLimiter": { + ".": {}, + "f:validOnMissingRender": {} + } + }, + "f:hookSelector": {}, + "f:parameters": {}, + "f:resourceMode": {}, + "f:scanType": {} + } + }, + "manager": "unknown", + "operation": "Update", + "time": "2024-01-30T13:25:25Z" + }, + { + "apiVersion": "execution.securecodebox.io/v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:status": { + "f:findings": { + "f:categories": { + ".": {}, + "f:Discovered Credentials": {} + }, + "f:count": {}, + "f:severities": { + "f:high": {} + } + } + } + }, + "manager": "unknown", + "operation": "Update", + "subresource": "status", + "time": "2024-01-30T13:25:41Z" + }, + { + "apiVersion": "execution.securecodebox.io/v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:status": { + ".": {}, + "f:findingDownloadLink": {}, + "f:findingHeadLink": {}, + "f:findings": { + ".": {}, + "f:severities": {} + }, + "f:orderedHookStatuses": {}, + "f:rawResultDownloadLink": {}, + "f:rawResultFile": {}, + "f:rawResultHeadLink": {}, + "f:rawResultType": {}, + "f:state": {} + } + }, + "manager": "manager", + "operation": "Update", + "subresource": "status", + "time": "2024-01-30T13:25:45Z" + } + ], + "name": "ncrack-dummy-ssh-d47zk-ncrack-ssh-9ht7b", + "namespace": "integration-tests", + "ownerReferences": [ + { + "apiVersion": "execution.securecodebox.io/v1", + "blockOwnerDeletion": true, + "controller": true, + "kind": "Scan", + "name": "nmap-dummy-ssh-d47zk", + "uid": "09f2fd3f-2fb3-4ae4-8347-9cbe23af97b3" + } + ], + "resourceVersion": "20705", + "uid": "c613dd44-626d-44a8-be36-8b71d984068c" + }, + "spec": { + "cascades": { + "inheritAffinity": true, + "inheritAnnotations": true, + "inheritEnv": false, + "inheritHookSelector": false, + "inheritInitContainers": false, + "inheritLabels": true, + "inheritTolerations": true, + "inheritVolumes": false, + "matchLabels": { + "securecodebox.io/intensive": "high", + "securecodebox.io/invasive": "invasive" + }, + "scopeLimiter": { + "validOnMissingRender": false + } + }, + "hookSelector": {}, + "parameters": [ + "-v", + "-d10", + "-U", + "/ncrack/users.txt", + "-P", + "/ncrack/passwords.txt", + "-p", + "ssh:22", + "dummy-ssh.demo-targets.svc" + ], + "resourceMode": "namespaceLocal", + "resources": {}, + "scanType": "ncrack" + }, + "status": { + "findingDownloadLink": "http://securecodebox-operator-minio.securecodebox-system.svc.cluster.local:9000/securecodebox/scan-c613dd44-626d-44a8-be36-8b71d984068c/findings.json?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=admin%2F20240130%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20240130T132525Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=39576bace58a75c3daba5097a26215a7b99ca62de62fb0e3066939c6ea4b17a2", + "findingHeadLink": "http://securecodebox-operator-minio.securecodebox-system.svc.cluster.local:9000/securecodebox/scan-c613dd44-626d-44a8-be36-8b71d984068c/findings.json?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=admin%2F20240130%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20240130T132525Z&X-Amz-Expires=43200&X-Amz-SignedHeaders=host&X-Amz-Signature=eb1546645b611d5f8e43c696ab11a759c0b6257f046425f1747e639ee88c958e", + "findings": { + "categories": { + "Discovered Credentials": 1 + }, + "count": 1, + "severities": { + "high": 1 + } + }, + "orderedHookStatuses": [ + [ + { + "hookName": "dssh-cascading-scans", + "jobName": "dssh-cascading-scans-ncrack-dummy-ssh-d47zk-ncrack-ssh-9h-nbfwd", + "priority": 0, + "state": "InProgress", + "type": "ReadOnly" + } + ] + ], + "rawResultDownloadLink": "http://securecodebox-operator-minio.securecodebox-system.svc.cluster.local:9000/securecodebox/scan-c613dd44-626d-44a8-be36-8b71d984068c/ncrack-results.xml?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=admin%2F20240130%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20240130T132525Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=228bb75956d8b8b9365bfcb030ecf6a57aeeaeb153d985ce9d52eae5bc241ea6", + "rawResultFile": "ncrack-results.xml", + "rawResultHeadLink": "http://securecodebox-operator-minio.securecodebox-system.svc.cluster.local:9000/securecodebox/scan-c613dd44-626d-44a8-be36-8b71d984068c/ncrack-results.xml?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=admin%2F20240130%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20240130T132525Z&X-Amz-Expires=43200&X-Amz-SignedHeaders=host&X-Amz-Signature=a5230e4c81b2315e8aa2339cb3efeb985cd33aed5de02f0e023f161aa98289ec", + "rawResultType": "ncrack-xml", + "state": "Done" + } + } +} \ No newline at end of file diff --git a/scanners/__testFiles__/mockCascadingScanStatusResponse.json.license b/scanners/__testFiles__/mockCascadingScanStatusResponse.json.license new file mode 100644 index 0000000000..c95bc37185 --- /dev/null +++ b/scanners/__testFiles__/mockCascadingScanStatusResponse.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: the secureCodeBox authors + +SPDX-License-Identifier: Apache-2.0 diff --git a/scanners/__testFiles__/mockCascadingScanStatusResponse_Errored.json b/scanners/__testFiles__/mockCascadingScanStatusResponse_Errored.json new file mode 100644 index 0000000000..95152062fc --- /dev/null +++ b/scanners/__testFiles__/mockCascadingScanStatusResponse_Errored.json @@ -0,0 +1,215 @@ +{ + "body": { + "apiVersion": "execution.securecodebox.io/v1", + "kind": "Scan", + "metadata": { + "annotations": { + "cascading.securecodebox.io/chain": "ncrack-ssh", + "cascading.securecodebox.io/matched-finding": "ca358271-980b-4f9a-a1b1-92b64f1d2a44", + "cascading.securecodebox.io/parent-scan": "nmap-dummy-ssh-d47zk", + "securecodebox.io/hook": "cascading-scans" + }, + "creationTimestamp": "2024-01-30T13:25:25Z", + "finalizers": [ + "s3.storage.securecodebox.io" + ], + "generateName": "ncrack-dummy-ssh-d47zk-ncrack-ssh-", + "generation": 2, + "managedFields": [ + { + "apiVersion": "execution.securecodebox.io/v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:finalizers": { + ".": {}, + "v:\"s3.storage.securecodebox.io\"": {} + } + }, + "f:spec": { + "f:resources": {} + } + }, + "manager": "manager", + "operation": "Update", + "time": "2024-01-30T13:25:25Z" + }, + { + "apiVersion": "execution.securecodebox.io/v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:annotations": { + ".": {}, + "f:cascading.securecodebox.io/chain": {}, + "f:cascading.securecodebox.io/matched-finding": {}, + "f:cascading.securecodebox.io/parent-scan": {}, + "f:securecodebox.io/hook": {} + }, + "f:generateName": {}, + "f:ownerReferences": { + ".": {}, + "k:{\"uid\":\"09f2fd3f-2fb3-4ae4-8347-9cbe23af97b3\"}": {} + } + }, + "f:spec": { + ".": {}, + "f:cascades": { + ".": {}, + "f:inheritAffinity": {}, + "f:inheritAnnotations": {}, + "f:inheritEnv": {}, + "f:inheritHookSelector": {}, + "f:inheritInitContainers": {}, + "f:inheritLabels": {}, + "f:inheritTolerations": {}, + "f:inheritVolumes": {}, + "f:matchLabels": { + ".": {}, + "f:securecodebox.io/intensive": {}, + "f:securecodebox.io/invasive": {} + }, + "f:scopeLimiter": { + ".": {}, + "f:validOnMissingRender": {} + } + }, + "f:hookSelector": {}, + "f:parameters": {}, + "f:resourceMode": {}, + "f:scanType": {} + } + }, + "manager": "unknown", + "operation": "Update", + "time": "2024-01-30T13:25:25Z" + }, + { + "apiVersion": "execution.securecodebox.io/v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:status": { + "f:findings": { + "f:categories": { + ".": {}, + "f:Discovered Credentials": {} + }, + "f:count": {}, + "f:severities": { + "f:high": {} + } + } + } + }, + "manager": "unknown", + "operation": "Update", + "subresource": "status", + "time": "2024-01-30T13:25:41Z" + }, + { + "apiVersion": "execution.securecodebox.io/v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:status": { + ".": {}, + "f:findingDownloadLink": {}, + "f:findingHeadLink": {}, + "f:findings": { + ".": {}, + "f:severities": {} + }, + "f:orderedHookStatuses": {}, + "f:rawResultDownloadLink": {}, + "f:rawResultFile": {}, + "f:rawResultHeadLink": {}, + "f:rawResultType": {}, + "f:state": {} + } + }, + "manager": "manager", + "operation": "Update", + "subresource": "status", + "time": "2024-01-30T13:25:45Z" + } + ], + "name": "ncrack-dummy-ssh-d47zk-ncrack-ssh-9ht7b", + "namespace": "integration-tests", + "ownerReferences": [ + { + "apiVersion": "execution.securecodebox.io/v1", + "blockOwnerDeletion": true, + "controller": true, + "kind": "Scan", + "name": "nmap-dummy-ssh-d47zk", + "uid": "09f2fd3f-2fb3-4ae4-8347-9cbe23af97b3" + } + ], + "resourceVersion": "20705", + "uid": "c613dd44-626d-44a8-be36-8b71d984068c" + }, + "spec": { + "cascades": { + "inheritAffinity": true, + "inheritAnnotations": true, + "inheritEnv": false, + "inheritHookSelector": false, + "inheritInitContainers": false, + "inheritLabels": true, + "inheritTolerations": true, + "inheritVolumes": false, + "matchLabels": { + "securecodebox.io/intensive": "high", + "securecodebox.io/invasive": "invasive" + }, + "scopeLimiter": { + "validOnMissingRender": false + } + }, + "hookSelector": {}, + "parameters": [ + "-v", + "-d10", + "-U", + "/ncrack/users.txt", + "-P", + "/ncrack/passwords.txt", + "-p", + "ssh:22", + "dummy-ssh.demo-targets.svc" + ], + "resourceMode": "namespaceLocal", + "resources": {}, + "scanType": "ncrack" + }, + "status": { + "findingDownloadLink": "http://securecodebox-operator-minio.securecodebox-system.svc.cluster.local:9000/securecodebox/scan-c613dd44-626d-44a8-be36-8b71d984068c/findings.json?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=admin%2F20240130%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20240130T132525Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=39576bace58a75c3daba5097a26215a7b99ca62de62fb0e3066939c6ea4b17a2", + "findingHeadLink": "http://securecodebox-operator-minio.securecodebox-system.svc.cluster.local:9000/securecodebox/scan-c613dd44-626d-44a8-be36-8b71d984068c/findings.json?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=admin%2F20240130%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20240130T132525Z&X-Amz-Expires=43200&X-Amz-SignedHeaders=host&X-Amz-Signature=eb1546645b611d5f8e43c696ab11a759c0b6257f046425f1747e639ee88c958e", + "findings": { + "categories": { + "Discovered Credentials": 1 + }, + "count": 1, + "severities": { + "high": 1 + } + }, + "orderedHookStatuses": [ + [ + { + "hookName": "dssh-cascading-scans", + "jobName": "dssh-cascading-scans-ncrack-dummy-ssh-d47zk-ncrack-ssh-9h-nbfwd", + "priority": 0, + "state": "InProgress", + "type": "ReadOnly" + } + ] + ], + "rawResultDownloadLink": "http://securecodebox-operator-minio.securecodebox-system.svc.cluster.local:9000/securecodebox/scan-c613dd44-626d-44a8-be36-8b71d984068c/ncrack-results.xml?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=admin%2F20240130%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20240130T132525Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=228bb75956d8b8b9365bfcb030ecf6a57aeeaeb153d985ce9d52eae5bc241ea6", + "rawResultFile": "ncrack-results.xml", + "rawResultHeadLink": "http://securecodebox-operator-minio.securecodebox-system.svc.cluster.local:9000/securecodebox/scan-c613dd44-626d-44a8-be36-8b71d984068c/ncrack-results.xml?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=admin%2F20240130%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20240130T132525Z&X-Amz-Expires=43200&X-Amz-SignedHeaders=host&X-Amz-Signature=a5230e4c81b2315e8aa2339cb3efeb985cd33aed5de02f0e023f161aa98289ec", + "rawResultType": "ncrack-xml", + "state": "Errored", + "errorDescription": "Mocked Error" + } + } +} \ No newline at end of file diff --git a/scanners/__testFiles__/mockCascadingScanStatusResponse_Errored.json.license b/scanners/__testFiles__/mockCascadingScanStatusResponse_Errored.json.license new file mode 100644 index 0000000000..c95bc37185 --- /dev/null +++ b/scanners/__testFiles__/mockCascadingScanStatusResponse_Errored.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: the secureCodeBox authors + +SPDX-License-Identifier: Apache-2.0 diff --git a/scanners/__testFiles__/mockListNamespacedJobResponse.json b/scanners/__testFiles__/mockListNamespacedJobResponse.json new file mode 100644 index 0000000000..2e56030b7d --- /dev/null +++ b/scanners/__testFiles__/mockListNamespacedJobResponse.json @@ -0,0 +1,13 @@ +{ + "body": { + "apiVersion": "batch/v1", + "items": [], + "kind": "JobList", + "metadata": { + "_continue": "undefined", + "remainingItemCount": "undefined", + "resourceVersion": "6097", + "selfLink": "undefined" + } + } +} \ No newline at end of file diff --git a/scanners/__testFiles__/mockListNamespacedJobResponse.json.license b/scanners/__testFiles__/mockListNamespacedJobResponse.json.license new file mode 100644 index 0000000000..c95bc37185 --- /dev/null +++ b/scanners/__testFiles__/mockListNamespacedJobResponse.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: the secureCodeBox authors + +SPDX-License-Identifier: Apache-2.0 diff --git a/scanners/__testFiles__/mockListNamespacedPodResponse.json b/scanners/__testFiles__/mockListNamespacedPodResponse.json new file mode 100644 index 0000000000..89f33ba2cb --- /dev/null +++ b/scanners/__testFiles__/mockListNamespacedPodResponse.json @@ -0,0 +1,37 @@ +{ + "body": { + "items": [ + { + "metadata": { + "name": "pod-1", + "namespace": "default", + "labels": { + "job-name": "example-job" + } + }, + "spec": { + "containers": [ + { + "name": "container-1", + "image": "nginx:1.7.9", + "ports": [ + { + "containerPort": 80 + } + ] + } + ] + }, + "status": { + "phase": "Running", + "conditions": [ + { + "type": "Ready", + "status": "True" + } + ] + } + } + ] + } +} \ No newline at end of file diff --git a/scanners/__testFiles__/mockListNamespacedPodResponse.json.license b/scanners/__testFiles__/mockListNamespacedPodResponse.json.license new file mode 100644 index 0000000000..c95bc37185 --- /dev/null +++ b/scanners/__testFiles__/mockListNamespacedPodResponse.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: the secureCodeBox authors + +SPDX-License-Identifier: Apache-2.0 diff --git a/scanners/__testFiles__/mockReadNamespacedPodLogResponse.json b/scanners/__testFiles__/mockReadNamespacedPodLogResponse.json new file mode 100644 index 0000000000..9938392287 --- /dev/null +++ b/scanners/__testFiles__/mockReadNamespacedPodLogResponse.json @@ -0,0 +1,6 @@ +{ + "response": { + "status_code": 200, + "body": "u001b[39m\u001b[0m\n\u001b[31mHTTPConnectionPool(host='old-typo3.demo-targets.svc', port=80): Max retries exceeded with url: / (Caused by NameResolutionError(\": Failed to resolve 'old-typo3.demo-targets.svc' ([Errno -2] Name does not resolve)\"))\u001b[39m\n\u001b[31mHTTPConnectionPool(host='old-typo3.demo-targets.svc', port=80): Max retries exceeded with url: /fdhmndovji (Caused by NameResolutionError(\": Failed to resolve 'old-typo3.demo-targets.svc' ([Errno -2] Name does not resolve)\"))\u001b[39m\n\u001b[31m\n[x] It seems that Typo3 is not used on this domain\n\u001b[39m\n" + } +} \ No newline at end of file diff --git a/scanners/__testFiles__/mockReadNamespacedPodLogResponse.json.license b/scanners/__testFiles__/mockReadNamespacedPodLogResponse.json.license new file mode 100644 index 0000000000..c95bc37185 --- /dev/null +++ b/scanners/__testFiles__/mockReadNamespacedPodLogResponse.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: the secureCodeBox authors + +SPDX-License-Identifier: Apache-2.0 diff --git a/scanners/__testFiles__/mockScanCreationResponse.json b/scanners/__testFiles__/mockScanCreationResponse.json new file mode 100644 index 0000000000..ac11cf1996 --- /dev/null +++ b/scanners/__testFiles__/mockScanCreationResponse.json @@ -0,0 +1,50 @@ +{ + "body": { + "apiVersion": "execution.securecodebox.io/v1", + "kind": "Scan", + "metadata": { + "creationTimestamp": "2024-01-16T14:40:59Z", + "generateName": "typo3scan-old-typo3-", + "generation": 1, + "managedFields": [ + { + "apiVersion": "execution.securecodebox.io/v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:generateName": {} + }, + "f:spec": { + ".": {}, + "f:initContainers": {}, + "f:parameters": {}, + "f:resourceMode": {}, + "f:scanType": {}, + "f:volumeMounts": {}, + "f:volumes": {} + } + }, + "manager": "unknown", + "operation": "Update", + "time": "2024-01-16T14:40:59Z" + } + ], + "name": "typo3scan-old-typo3-pw8vt", + "namespace": "integration-tests", + "resourceVersion": "1867", + "uid": "4ebccf10-ac84-4e85-91bc-1e4d60b45697" + }, + "spec": { + "initContainers": [], + "parameters": [ + "-d", + "http://old-typo3.demo-targets.svc", + "--vuln" + ], + "resourceMode": "namespaceLocal", + "scanType": "typo3scan", + "volumeMounts": [], + "volumes": [] + } + } +} \ No newline at end of file diff --git a/scanners/__testFiles__/mockScanCreationResponse.json.license b/scanners/__testFiles__/mockScanCreationResponse.json.license new file mode 100644 index 0000000000..c95bc37185 --- /dev/null +++ b/scanners/__testFiles__/mockScanCreationResponse.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: the secureCodeBox authors + +SPDX-License-Identifier: Apache-2.0 diff --git a/scanners/__testFiles__/mockScanStatusResponse.json b/scanners/__testFiles__/mockScanStatusResponse.json new file mode 100644 index 0000000000..8fac470e9d --- /dev/null +++ b/scanners/__testFiles__/mockScanStatusResponse.json @@ -0,0 +1,90 @@ +{ + "body": { + "apiVersion": "execution.securecodebox.io/v1", + "kind": "Scan", + "metadata": { + "creationTimestamp": "2024-01-16T14:45:28Z", + "finalizers": [ + "s3.storage.securecodebox.io" + ], + "generateName": "typo3scan-old-typo3-", + "generation": 2, + "managedFields": [ + { + "apiVersion": "execution.securecodebox.io/v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:finalizers": { + ".": {}, + "v:\"s3.storage.securecodebox.io\"": {} + } + }, + "f:spec": { + "f:resources": {} + } + }, + "manager": "manager", + "operation": "Update", + "time": "2024-01-16T14:45:28Z" + }, + { + "apiVersion": "execution.securecodebox.io/v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:status": { + ".": {}, + "f:findingDownloadLink": {}, + "f:findingHeadLink": {}, + "f:findings": { + ".": {}, + "f:severities": {} + }, + "f:rawResultDownloadLink": {}, + "f:rawResultFile": {}, + "f:rawResultHeadLink": {}, + "f:rawResultType": {}, + "f:state": {} + } + }, + "manager": "manager", + "operation": "Update", + "subresource": "status", + "time": "2024-01-16T14:45:28Z" + } + ], + "name": "typo3scan-old-typo3-c24gt", + "namespace": "integration-tests", + "resourceVersion": "2399", + "uid": "5d2ef0e2-ad11-4e58-80d0-5168a137da17" + }, + "spec": { + "parameters": [ + "-d", + "http://old-typo3.demo-targets.svc", + "--vuln" + ], + "resourceMode": "namespaceLocal", + "resources": {}, + "scanType": "typo3scan" + }, + "status": { + "findingDownloadLink": "http://securecodebox-operator-minio.securecodebox-system.svc.cluster.local:9000/securecodebox/scan-c4c2b6ae-f8a1-474d-88ec-c739f6e55f56/findings.json?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=admin%2F20240116%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20240116T160849Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=5944f10813f6127d96a3889b06c8256765c3a739db1e23f650ee6ec9ed956b20", + "findingHeadLink": "http://securecodebox-operator-minio.securecodebox-system.svc.cluster.local:9000/securecodebox/scan-c4c2b6ae-f8a1-474d-88ec-c739f6e55f56/findings.json?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=admin%2F20240116%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20240116T160849Z&X-Amz-Expires=43200&X-Amz-SignedHeaders=host&X-Amz-Signature=45833109b8ea8d43df51b535730aece4cec1dbf6557a12acd1b95cdbce1f221e", + "findings": { + "categories": { + "Vulnerability": 24 + }, + "count": 24, + "severities": { + "high": 24 + } + }, + "rawResultDownloadLink": "http://securecodebox-operator-minio.securecodebox-system.svc.cluster.local:9000/securecodebox/scan-c4c2b6ae-f8a1-474d-88ec-c739f6e55f56/typo3scan.json?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=admin%2F20240116%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20240116T160849Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=0a91e1756a42fa40ca9bf1bb448ec36c544dd5af4b76c8bc3e91debbb68e349e", + "rawResultFile": "typo3scan.json", + "rawResultHeadLink": "http://securecodebox-operator-minio.securecodebox-system.svc.cluster.local:9000/securecodebox/scan-c4c2b6ae-f8a1-474d-88ec-c739f6e55f56/typo3scan.json?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=admin%2F20240116%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20240116T160849Z&X-Amz-Expires=43200&X-Amz-SignedHeaders=host&X-Amz-Signature=d09a78fe0e563ac70a75c908319085699894a9eca04d4a32d723862e748e9416", + "rawResultType": "typo3scan-json", + "state": "Done" + } + } +} \ No newline at end of file diff --git a/scanners/__testFiles__/mockScanStatusResponse.json.license b/scanners/__testFiles__/mockScanStatusResponse.json.license new file mode 100644 index 0000000000..c95bc37185 --- /dev/null +++ b/scanners/__testFiles__/mockScanStatusResponse.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: the secureCodeBox authors + +SPDX-License-Identifier: Apache-2.0 diff --git a/scanners/__testFiles__/mockScanStatusResponse_Errored.json b/scanners/__testFiles__/mockScanStatusResponse_Errored.json new file mode 100644 index 0000000000..aa6cc5b63f --- /dev/null +++ b/scanners/__testFiles__/mockScanStatusResponse_Errored.json @@ -0,0 +1,83 @@ +{ + "body": { + "apiVersion": "execution.securecodebox.io/v1", + "kind": "Scan", + "metadata": { + "creationTimestamp": "2024-01-16T14:45:28Z", + "finalizers": [ + "s3.storage.securecodebox.io" + ], + "generateName": "typo3scan-old-typo3-", + "generation": 2, + "managedFields": [ + { + "apiVersion": "execution.securecodebox.io/v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:finalizers": { + ".": {}, + "v:\"s3.storage.securecodebox.io\"": {} + } + }, + "f:spec": { + "f:resources": {} + } + }, + "manager": "manager", + "operation": "Update", + "time": "2024-01-16T14:45:28Z" + }, + { + "apiVersion": "execution.securecodebox.io/v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:status": { + ".": {}, + "f:findingDownloadLink": {}, + "f:findingHeadLink": {}, + "f:findings": { + ".": {}, + "f:severities": {} + }, + "f:rawResultDownloadLink": {}, + "f:rawResultFile": {}, + "f:rawResultHeadLink": {}, + "f:rawResultType": {}, + "f:state": {} + } + }, + "manager": "manager", + "operation": "Update", + "subresource": "status", + "time": "2024-01-16T14:45:28Z" + } + ], + "name": "typo3scan-old-typo3-c24gt", + "namespace": "integration-tests", + "resourceVersion": "2399", + "uid": "5d2ef0e2-ad11-4e58-80d0-5168a137da17" + }, + "spec": { + "parameters": [ + "-d", + "http://old-typo3.demo-targets.svc", + "--vuln" + ], + "resourceMode": "namespaceLocal", + "resources": {}, + "scanType": "typo3scan" + }, + "status": { + "findingDownloadLink": "http://securecodebox-operator-minio.securecodebox-system.svc.cluster.local:9000/securecodebox/scan-c4c2b6ae-f8a1-474d-88ec-c739f6e55f56/findings.json?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=admin%2F20240116%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20240116T160849Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=5944f10813f6127d96a3889b06c8256765c3a739db1e23f650ee6ec9ed956b20", + "findingHeadLink": "http://securecodebox-operator-minio.securecodebox-system.svc.cluster.local:9000/securecodebox/scan-c4c2b6ae-f8a1-474d-88ec-c739f6e55f56/findings.json?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=admin%2F20240116%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20240116T160849Z&X-Amz-Expires=43200&X-Amz-SignedHeaders=host&X-Amz-Signature=45833109b8ea8d43df51b535730aece4cec1dbf6557a12acd1b95cdbce1f221e", + "findings": {}, + "rawResultDownloadLink": "http://securecodebox-operator-minio.securecodebox-system.svc.cluster.local:9000/securecodebox/scan-c4c2b6ae-f8a1-474d-88ec-c739f6e55f56/typo3scan.json?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=admin%2F20240116%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20240116T160849Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=0a91e1756a42fa40ca9bf1bb448ec36c544dd5af4b76c8bc3e91debbb68e349e", + "rawResultFile": "typo3scan.json", + "rawResultHeadLink": "http://securecodebox-operator-minio.securecodebox-system.svc.cluster.local:9000/securecodebox/scan-c4c2b6ae-f8a1-474d-88ec-c739f6e55f56/typo3scan.json?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=admin%2F20240116%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20240116T160849Z&X-Amz-Expires=43200&X-Amz-SignedHeaders=host&X-Amz-Signature=d09a78fe0e563ac70a75c908319085699894a9eca04d4a32d723862e748e9416", + "rawResultType": "typo3scan-json", + "state": "Errored", + "errorDescription": "Mocked Error" + } + } +} \ No newline at end of file diff --git a/scanners/__testFiles__/mockScanStatusResponse_Errored.json.license b/scanners/__testFiles__/mockScanStatusResponse_Errored.json.license new file mode 100644 index 0000000000..c95bc37185 --- /dev/null +++ b/scanners/__testFiles__/mockScanStatusResponse_Errored.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: the secureCodeBox authors + +SPDX-License-Identifier: Apache-2.0 diff --git a/scanners/helpers.js b/scanners/helpers.js index 9087741e8f..5eabca031c 100644 --- a/scanners/helpers.js +++ b/scanners/helpers.js @@ -7,16 +7,27 @@ const k8s = require("@kubernetes/client-node"); const kc = new k8s.KubeConfig(); kc.loadFromDefault(); -const k8sCRDApi = kc.makeApiClient(k8s.CustomObjectsApi); -const k8sBatchApi = kc.makeApiClient(k8s.BatchV1Api); -const k8sPodsApi = kc.makeApiClient(k8s.CoreV1Api); +let k8sCRDApi, k8sBatchApi, k8sPodsApi; +function getKubernetesAPIs() { + if (!k8sCRDApi) { + k8sCRDApi = kc.makeApiClient(k8s.CustomObjectsApi); + } + if (!k8sBatchApi) { + k8sBatchApi = kc.makeApiClient(k8s.BatchV1Api); + } + if (!k8sPodsApi) { + k8sPodsApi = kc.makeApiClient(k8s.CoreV1Api); + } + + return { k8sCRDApi, k8sBatchApi, k8sPodsApi }; +} let namespace = "integration-tests"; const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms * 1000)); -async function deleteScan(name) { - await k8sCRDApi.deleteNamespacedCustomObject( +async function deleteScan(name, k8sApis = getKubernetesAPIs()) { + await k8sApis.k8sCRDApi.deleteNamespacedCustomObject( "execution.securecodebox.io", "v1", namespace, @@ -26,8 +37,8 @@ async function deleteScan(name) { ); } -async function getScan(name) { - const { body: scan } = await k8sCRDApi.getNamespacedCustomObjectStatus( +async function getScan(name, k8sApis = getKubernetesAPIs()) { + const { body: scan } = await k8sApis.k8sCRDApi.getNamespacedCustomObjectStatus( "execution.securecodebox.io", "v1", namespace, @@ -37,11 +48,11 @@ async function getScan(name) { return scan; } -async function displayAllLogsForJob(jobName) { +async function displayAllLogsForJob(jobName, k8sApis = getKubernetesAPIs()) { console.log(`Listing logs for Job '${jobName}':`); const { body: { items: pods }, - } = await k8sPodsApi.listNamespacedPod( + } = await k8sApis.k8sPodsApi.listNamespacedPod( namespace, true, undefined, @@ -61,7 +72,7 @@ async function displayAllLogsForJob(jobName) { for (const container of pod.spec.containers) { try { - const response = await k8sPodsApi.readNamespacedPodLog( + const response = await k8sApis.k8sPodsApi.readNamespacedPodLog( pod.metadata.name, namespace, container.name @@ -77,9 +88,9 @@ async function displayAllLogsForJob(jobName) { } } -async function logJobs() { +async function logJobs(k8sApis = getKubernetesAPIs()) { try { - const { body: jobs } = await k8sBatchApi.listNamespacedJob(namespace); + const { body: jobs } = await k8sApis.k8sBatchApi.listNamespacedJob(namespace); console.log("Logging spec & status of jobs in namespace"); @@ -89,7 +100,7 @@ async function logJobs() { console.log(`Job: '${job.metadata.name}' Status:`); console.log(JSON.stringify(job.status, null, 2)); - await displayAllLogsForJob(job.metadata.name); + await displayAllLogsForJob(job.metadata.name, k8sApis); } } catch (error) { console.error("Failed to list Jobs"); @@ -97,11 +108,11 @@ async function logJobs() { } } -async function disasterRecovery(scanName) { - const scan = await getScan(scanName); +async function disasterRecovery(scanName, k8sApis) { + const scan = await getScan(scanName, k8sApis); console.error("Last Scan State:"); console.dir(scan); - await logJobs(); + await logJobs(k8sApis); } /** @@ -113,10 +124,14 @@ async function disasterRecovery(scanName) { * @param {object[]} volumes definitions for kubernetes volumes that should be used. Optional, useful for initContainers (see below) * @param {object[]} volumeMounts definitions for kubernetes volume mounts that should be used. Optional, useful for initContainers (see below) * @param {object[]} initContainers definitions for initContainers that should be added to the scan job to provision files for the scanner. Optional. + * @param {CRDApi} CRDApi kubernetes api client for CRDs. Optional, will be created if not provided. + * @param {BatchApi} BatchApi kubernetes api client for BatchV1Api. Optional, will be created if not provided. + * @param {PodsApi} PodsApi kubernetes api client for CoreV1Api. Optional, will be created if not provided. * @returns {scan.findings} returns findings { categories, severities, count } */ -async function scan(name, scanType, parameters = [], timeout = 180, volumes = [], volumeMounts = [], initContainers = []) { - namespace ="integration-tests" +async function scan(name, scanType, parameters = [], timeout = 180, volumes = [], volumeMounts = [], + initContainers = [], k8sApis = getKubernetesAPIs()) { + namespace = "integration-tests" const scanDefinition = { apiVersion: "execution.securecodebox.io/v1", kind: "Scan", @@ -132,8 +147,7 @@ async function scan(name, scanType, parameters = [], timeout = 180, volumes = [] initContainers, }, }; - - const { body } = await k8sCRDApi.createNamespacedCustomObject( + const { body } = await k8sApis.k8sCRDApi.createNamespacedCustomObject( "execution.securecodebox.io", "v1", namespace, @@ -145,17 +159,17 @@ async function scan(name, scanType, parameters = [], timeout = 180, volumes = [] for (let i = 0; i < timeout; i++) { await sleep(1); - const { status } = await getScan(actualName); + const { status } = await getScan(actualName, k8sApis); if (status && status.state === "Done") { // Wait a couple seconds to give kubernetes more time to update the fields await sleep(2); - const { status } = await getScan(actualName); - await deleteScan(actualName); + const { status } = await getScan(actualName, k8sApis); + await deleteScan(actualName, k8sApis); return status.findings; } else if (status && status.state === "Errored") { console.error("Scan Errored"); - await disasterRecovery(actualName); + await disasterRecovery(actualName, k8sApis); throw new Error( `Scan failed with description "${status.errorDescription}"` @@ -163,7 +177,7 @@ async function scan(name, scanType, parameters = [], timeout = 180, volumes = [] } } console.error("Scan Timed out!"); - await disasterRecovery(actualName); + await disasterRecovery(actualName, k8sApis); throw new Error("timed out while waiting for scan results"); } @@ -176,9 +190,13 @@ async function scan(name, scanType, parameters = [], timeout = 180, volumes = [] * @param {string} nameCascade name of cascading scan * @param {object} matchLabels set invasive and intensive of cascading scan * @param {number} timeout in seconds + * @param {CRDApi} CRDApi kubernetes api client for CRDs. Optional, will be created if not provided. + * @param {BatchApi} BatchApi kubernetes api client for BatchV1Api. Optional, will be created if not provided. + * @param {PodsApi} PodsApi kubernetes api client for CoreV1Api. Optional, will be created if not provided. + * * @returns {scan.findings} returns findings { categories, severities, count } */ -async function cascadingScan(name, scanType, parameters = [], { nameCascade, matchLabels }, timeout = 180) { +async function cascadingScan(name, scanType, parameters = [], { nameCascade, matchLabels }, timeout = 180, k8sApis = getKubernetesAPIs()) { const scanDefinition = { apiVersion: "execution.securecodebox.io/v1", kind: "Scan", @@ -194,8 +212,8 @@ async function cascadingScan(name, scanType, parameters = [], { nameCascade, mat } }, }; - - const { body } = await k8sCRDApi.createNamespacedCustomObject( + + const { body } = await k8sApis.k8sCRDApi.createNamespacedCustomObject( "execution.securecodebox.io", "v1", namespace, @@ -207,7 +225,7 @@ async function cascadingScan(name, scanType, parameters = [], { nameCascade, mat for (let i = 0; i < timeout; i++) { await sleep(1); - const { status } = await getScan(actualName); + const { status } = await getScan(actualName, k8sApis); if (status && status.state === "Done") { // Wait a couple seconds to give kubernetes more time to update the fields @@ -218,7 +236,7 @@ async function cascadingScan(name, scanType, parameters = [], { nameCascade, mat break; } else if (status && status.state === "Errored") { console.error("Scan Errored"); - await disasterRecovery(actualName); + await disasterRecovery(actualName, k8sApis); throw new Error( `Initial Scan failed with description "${status.errorDescription}"` ); @@ -231,7 +249,7 @@ async function cascadingScan(name, scanType, parameters = [], { nameCascade, mat } } - const { body: scans } = await k8sCRDApi.listNamespacedCustomObject( + const { body: scans } = await k8sApis.k8sCRDApi.listNamespacedCustomObject( "execution.securecodebox.io", "v1", namespace, @@ -255,27 +273,27 @@ async function cascadingScan(name, scanType, parameters = [], { nameCascade, mat for (let j = 0; j < timeout; j++) { await sleep(1) - const { status: statusCascade } = await getScan(actualNameCascade); + const { status: statusCascade } = await getScan(actualNameCascade, k8sApis); if (statusCascade && statusCascade.state === "Done") { await sleep(2); - const { status: statusCascade } = await getScan(actualNameCascade); + const { status: statusCascade } = await getScan(actualNameCascade, k8sApis); - await deleteScan(actualName); - await deleteScan(actualNameCascade); + await deleteScan(actualName, k8sApis); + await deleteScan(actualNameCascade, k8sApis); return statusCascade.findings; } else if (statusCascade && statusCascade.state === "Errored") { console.error("Scan Errored"); - await disasterRecovery(actualName); - await disasterRecovery(actualNameCascade); + await disasterRecovery(actualName, k8sApis); + await disasterRecovery(actualNameCascade, k8sApis); throw new Error( `Cascade Scan failed with description "${statusCascade.errorDescription}"` ); } } console.error("Cascade Scan Timed out!"); - await disasterRecovery(actualName); - await disasterRecovery(actualNameCascade); + await disasterRecovery(actualName, k8sApis); + await disasterRecovery(actualNameCascade, k8sApis); throw new Error("timed out while waiting for scan results"); } diff --git a/scanners/helpers.test.js b/scanners/helpers.test.js new file mode 100644 index 0000000000..0ea0bf2526 --- /dev/null +++ b/scanners/helpers.test.js @@ -0,0 +1,181 @@ +// SPDX-FileCopyrightText: the secureCodeBox authors +// +// SPDX-License-Identifier: Apache-2.0 + +const {scan, cascadingScan} = require("./helpers"); + +jest.setTimeout(10 * 1000); + +describe("Kubernetes interaction tests", () => { + const mockK8sCRDApi = { + createNamespacedCustomObject: jest.fn(), + getNamespacedCustomObjectStatus: jest.fn(), + deleteNamespacedCustomObject: jest.fn(), + listNamespacedCustomObject: jest.fn(), + }; + const mockK8sBatchApi = { + createNamespacedJob: jest.fn(), + deleteNamespacedJob: jest.fn(), + listNamespacedJob: jest.fn(), + }; + + const mockPodsApi = { + listNamespacedPod: jest.fn(), + readNamespacedPodLog: jest.fn(), + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + describe("scan function", () => { + it("should create a scan and return findings on successful completion", async () => { + const mockScanCreationResponse = require("./__testFiles__/mockScanCreationResponse.json"); + const mockScanStatusResponse = require("./__testFiles__/mockScanStatusResponse.json"); + + mockK8sCRDApi.createNamespacedCustomObject.mockResolvedValue( + mockScanCreationResponse + ); + mockK8sCRDApi.getNamespacedCustomObjectStatus.mockResolvedValue( + mockScanStatusResponse + ); + const k8sApi = { k8sCRDApi:mockK8sCRDApi, k8sBatchApi:mockK8sBatchApi, k8sPodsApi:mockPodsApi } + + const findings = await scan( + "typo3scan-old-typo3", + "typo3scan", + [], + 180, + [], + [], + [], + k8sApi + ); + + expect(findings).toBeDefined(); + expect(findings).toMatchSnapshot(); + expect(mockK8sCRDApi.createNamespacedCustomObject).toMatchSnapshot(); + expect(mockK8sCRDApi.getNamespacedCustomObjectStatus).toMatchSnapshot(); + }); + + it("should throw an error if the scan fails", async () => { + const mockScanCreationResponse = require("./__testFiles__/mockScanCreationResponse.json"); + const mockScanStatusResponse_Errored = require("./__testFiles__/mockScanStatusResponse_Errored.json"); + + const mockListNamespacedJobResponse = require("./__testFiles__/mockListNamespacedJobResponse.json"); + + const mockListNamespacedPodResponse = require("./__testFiles__/mockListNamespacedPodResponse.json"); + const mockReadNamespacedPodLogResponse = require("./__testFiles__/mockReadNamespacedPodLogResponse.json"); + + mockK8sCRDApi.createNamespacedCustomObject.mockResolvedValue( + mockScanCreationResponse + ); + mockK8sCRDApi.getNamespacedCustomObjectStatus.mockResolvedValue( + mockScanStatusResponse_Errored + ); + mockK8sBatchApi.listNamespacedJob.mockResolvedValue( + mockListNamespacedJobResponse + ); + mockPodsApi.listNamespacedPod.mockResolvedValue( + mockListNamespacedPodResponse + ); + mockPodsApi.readNamespacedPodLog.mockResolvedValue( + mockReadNamespacedPodLogResponse + ); + + + const k8sApi = { k8sCRDApi:mockK8sCRDApi, k8sBatchApi:mockK8sBatchApi, k8sPodsApi:mockPodsApi } + + return expect(scan( + "typo3scan-old-typo3", + "typo3scan", + [], + 180, + [], + [], + [], + k8sApi + )).rejects.toThrow('Scan failed with description "Mocked Error"'); + + }); + }); + + describe("cascading scan function", () => { + it("should create a cascading scan and return findings on successful completion", async () => { + const mockScanCreationResponse = require("./__testFiles__/mockCascadingScanCreationResponse.json"); + const mockScanStatusResponse = require("./__testFiles__/mockCascadingScanStatusResponse.json"); + const mockListNamespacedCustomObjectResponse = require("./__testFiles__/mockCascadingListNamespacedCustomObject.json"); + + mockK8sCRDApi.createNamespacedCustomObject.mockResolvedValue( + mockScanCreationResponse + ); + mockK8sCRDApi.getNamespacedCustomObjectStatus.mockResolvedValue( + mockScanStatusResponse + ); + + mockK8sCRDApi.listNamespacedCustomObject.mockResolvedValue( + mockListNamespacedCustomObjectResponse + ); + + const k8sApi = { k8sCRDApi:mockK8sCRDApi, k8sBatchApi:mockK8sBatchApi, k8sPodsApi:mockPodsApi } + + const findings = await cascadingScan( + "nmap-dummy-ssh", + "nmap", + ["-Pn", "-sV", "dummy-ssh.demo-targets.svc"], + { + nameCascade: "ncrack-ssh", + matchLabels: { + "securecodebox.io/invasive": "invasive", + "securecodebox.io/intensive": "high", + }, + }, + 180, + k8sApi + ); + + expect(findings).toBeDefined(); + expect(findings).toMatchSnapshot(); + }); + it("should throw an error if the scan fails", async () => { + const mockScanCreationResponse = require("./__testFiles__/mockCascadingScanCreationResponse.json"); + const mockScanStatusResponse_Errored = require("./__testFiles__/mockCascadingScanStatusResponse_Errored.json"); + + const mockListNamespacedJobResponse = require("./__testFiles__/mockListNamespacedJobResponse.json"); + + const mockListNamespacedPodResponse = require("./__testFiles__/mockListNamespacedPodResponse.json"); + const mockReadNamespacedPodLogResponse = require("./__testFiles__/mockReadNamespacedPodLogResponse.json"); + + mockK8sCRDApi.createNamespacedCustomObject.mockResolvedValue( + mockScanCreationResponse + ); + mockK8sCRDApi.getNamespacedCustomObjectStatus.mockResolvedValue( + mockScanStatusResponse_Errored + ); + mockK8sBatchApi.listNamespacedJob.mockResolvedValue( + mockListNamespacedJobResponse + ); + mockPodsApi.listNamespacedPod.mockResolvedValue( + mockListNamespacedPodResponse + ); + mockPodsApi.readNamespacedPodLog.mockResolvedValue( + mockReadNamespacedPodLogResponse + ); + const k8sApi = { k8sCRDApi:mockK8sCRDApi, k8sBatchApi:mockK8sBatchApi, k8sPodsApi:mockPodsApi } + + return expect(cascadingScan( + "nmap-dummy-ssh", + "nmap", + ["-Pn", "-sV", "dummy-ssh.demo-targets.svc"], + { + nameCascade: "ncrack-ssh", + matchLabels: { + "securecodebox.io/invasive": "invasive", + "securecodebox.io/intensive": "high", + }, + }, + 180, + k8sApi + )).rejects.toThrow('Initial Scan failed with description "Mocked Error"'); + }); + }); +}); diff --git a/scanners/package.json b/scanners/package.json index 46d00d9f37..ea8eb7c231 100644 --- a/scanners/package.json +++ b/scanners/package.json @@ -10,7 +10,8 @@ "main": "index.js", "scripts": { "test:unit": "jest --verbose --testPathIgnorePatterns /integration-tests/ --ci --colors --coverage --passWithNoTests", - "test:integration": "jest --verbose --ci --colors --coverage --passWithNoTests" + "test:integration": "jest --verbose --ci --colors --coverage --passWithNoTests", + "test:helpers": "jest helpers.test.js --verbose --ci --colors --coverage --passWithNoTests" }, "keywords": [ "secureCodeBox",