Skip to content
This repository was archived by the owner on Oct 14, 2020. It is now read-only.

Commit 1b1a297

Browse files
authored
Merge pull request #128 from secureCodeBox/cascading-rules
Add Cascading Rules for Ncrack
2 parents 9c96b03 + 291acc2 commit 1b1a297

File tree

6 files changed

+227
-1
lines changed

6 files changed

+227
-1
lines changed

.github/workflows/ci.yaml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,31 @@ jobs:
454454
helm -n integration-tests install zap ./scanners/zap/ --set="parserImage.tag=sha-$(git rev-parse --short HEAD)"
455455
cd tests/integration/
456456
npx jest --ci --color zap
457+
- name: "cascading Scans Integration Tests"
458+
run: |
459+
# We'll run these in a separate namespace so that only the cascadingRules we want to test will be used
460+
kubectl create namespace cascading-tests
461+
# Install declarative-subsequent-scans hook
462+
helm upgrade --install dssh ./hooks/declarative-subsequent-scans/ -n cascading-tests
463+
# Install nmap
464+
helm -n cascading-tests install nmap ./scanners/nmap/ --set="parserImage.tag=sha-$(git rev-parse --short HEAD)"
465+
# Install ncrack
466+
printf "root\nadmin\n" > users.txt
467+
printf "THEPASSWORDYOUCREATED\n123456\npassword\n" > passwords.txt
468+
kubectl create secret generic --from-file users.txt --from-file passwords.txt ncrack-lists -n cascading-tests
469+
cat <<EOF | helm -n cascading-tests install ncrack ./scanners/ncrack --set="parserImage.tag=sha-$(git rev-parse --short HEAD)" --values -
470+
scannerJob:
471+
extraVolumes:
472+
- name: ncrack-lists
473+
secret:
474+
secretName: ncrack-lists
475+
extraVolumeMounts:
476+
- name: ncrack-lists
477+
mountPath: "/ncrack/"
478+
EOF
479+
# Actually run the tests
480+
cd tests/integration/
481+
npx jest --ci --color cascade
457482
- name: Inspect Post Failure
458483
if: failure()
459484
run: |
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
apiVersion: "cascading.experimental.securecodebox.io/v1"
2+
kind: CascadingRule
3+
metadata:
4+
name: "ncrack-ssh"
5+
labels:
6+
securecodebox.io/invasive: invasive
7+
securecodebox.io/intensive: high
8+
spec:
9+
matches:
10+
anyOf:
11+
- category: "Open Port"
12+
attributes:
13+
port: 22
14+
state: open
15+
- category: "Open Port"
16+
attributes:
17+
service: "ssh"
18+
state: open
19+
scanSpec:
20+
scanType: "ncrack"
21+
parameters:
22+
- -v
23+
- -d10
24+
- -U
25+
- /ncrack/users.txt
26+
- -P
27+
- /ncrack/passwords.txt
28+
- -p
29+
- ssh:{{attributes.port}}
30+
- "{{attributes.ip_address}}"
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# The CascadingRules are not directly in the /templates directory as their curly bracket syntax clashes with helms templates ... :(
2+
# We import them as raw files to avoid these clashes as escaping them is even more messy
3+
{{ range $path, $_ := .Files.Glob "cascading-rules/*" }}
4+
# Include File
5+
{{ $.Files.Get $path }}
6+
# Separate multiple files
7+
---
8+
{{ end }}
9+
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
apiVersion: "execution.experimental.securecodebox.io/v1"
2+
kind: Scan
3+
metadata:
4+
name: "nmap-dummy-ssh"
5+
spec:
6+
scanType: "nmap"
7+
parameters:
8+
# Internal cluster is blocking our ping probes, therefore we skip them
9+
- "-Pn"
10+
# Service Detection enabled
11+
- "-sV"
12+
# Actual Service Address will depend on you cluster and namespace configuration. 🤷‍
13+
- "dummy-ssh.demo-apps.svc"
14+
cascades:
15+
matchLabels:
16+
securecodebox.io/intensive: high

tests/integration/helpers.js

Lines changed: 114 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const k8sCRDApi = kc.makeApiClient(k8s.CustomObjectsApi);
77
const k8sBatchApi = kc.makeApiClient(k8s.BatchV1Api);
88
const k8sPodsApi = kc.makeApiClient(k8s.CoreV1Api);
99

10-
const namespace = "integration-tests";
10+
let namespace = "integration-tests";
1111

1212
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms * 1000));
1313

@@ -109,6 +109,7 @@ async function disasterRecovery(scanName) {
109109
* @returns {scan.findings} returns findings { categories, severities, count }
110110
*/
111111
async function scan(name, scanType, parameters = [], timeout = 180) {
112+
namespace ="integration-tests"
112113
const scanDefinition = {
113114
apiVersion: "execution.experimental.securecodebox.io/v1",
114115
kind: "Scan",
@@ -157,4 +158,116 @@ async function scan(name, scanType, parameters = [], timeout = 180) {
157158
throw new Error("timed out while waiting for scan results");
158159
}
159160

161+
/**
162+
*
163+
* @param {string} name name of the scan. Actual name will be sufixed with a random number to avoid conflicts
164+
* @param {string} scanType type of the scan. Must match the name of a ScanType CRD
165+
* @param {string[]} parameters cli argument to be passed to the scanner
166+
* @param {number} timeout in seconds
167+
* @returns {scan.findings} returns findings { categories, severities, count }
168+
*/
169+
async function cascadingScan(name, scanType, parameters = [], { nameCascade, matchLabels }, timeout = 180) {
170+
namespace = "cascading-tests";
171+
172+
const scanDefinition = {
173+
apiVersion: "execution.experimental.securecodebox.io/v1",
174+
kind: "Scan",
175+
metadata: {
176+
// Use `generateName` instead of name to generate a random sufix and avoid name clashes
177+
generateName: `${name}-`,
178+
},
179+
spec: {
180+
scanType,
181+
parameters,
182+
cascades: {
183+
matchLabels,
184+
}
185+
},
186+
};
187+
188+
const { body } = await k8sCRDApi.createNamespacedCustomObject(
189+
"execution.experimental.securecodebox.io",
190+
"v1",
191+
namespace,
192+
"scans",
193+
scanDefinition
194+
);
195+
196+
const actualName = body.metadata.name;
197+
198+
for (let i = 0; i < timeout; i++) {
199+
await sleep(1);
200+
const { status } = await getScan(actualName);
201+
202+
if (status && status.state === "Done") {
203+
// Wait a couple seconds to give kubernetes more time to update the fields
204+
await sleep(2);
205+
206+
break;
207+
} else if (status && status.state === "Errored") {
208+
console.error("Scan Errored");
209+
await disasterRecovery(actualName);
210+
throw new Error(
211+
`Initial Scan failed with description "${status.errorDescription}"`
212+
);
213+
}
214+
215+
if (i === (timeout - 1)) {
216+
throw new Error(
217+
`Initial Scan timed out failed`
218+
);
219+
}
220+
}
221+
222+
console.log("First Scan finished")
223+
224+
const { body: scans } = await k8sCRDApi.listNamespacedCustomObject(
225+
"execution.experimental.securecodebox.io",
226+
"v1",
227+
namespace,
228+
"scans"
229+
);
230+
231+
let cascadedScan = null;
232+
233+
for (const scan of scans.items) {
234+
if (scan.metadata.annotations && scan.metadata.annotations["cascading.securecodebox.io/chain"] === nameCascade) {
235+
cascadedScan = scan;
236+
break;
237+
}
238+
}
239+
240+
if (cascadedScan === null) {
241+
throw new Error(`Didn't find cascaded Scan for ${nameCascade}`)
242+
}
243+
const actualNameCascade = cascadedScan.metadata.name;
244+
245+
for (let j = 0; j < timeout; j++) {
246+
await sleep(1)
247+
const { status: statusCascade } = await getScan(actualNameCascade);
248+
249+
if (statusCascade && statusCascade.state === "Done") {
250+
await sleep(2);
251+
const { status: statusCascade } = await getScan(actualNameCascade);
252+
253+
await deleteScan(actualName);
254+
await deleteScan(actualNameCascade);
255+
return statusCascade.findings;
256+
} else if (statusCascade && statusCascade.state === "Errored") {
257+
console.error("Scan Errored");
258+
await disasterRecovery(actualName);
259+
await disasterRecovery(actualNameCascade);
260+
throw new Error(
261+
`Cascade Scan failed with description "${statusCascade.errorDescription}"`
262+
);
263+
}
264+
}
265+
console.error("Cascade Scan Timed out!");
266+
await disasterRecovery(actualName);
267+
await disasterRecovery(actualNameCascade);
268+
269+
throw new Error("timed out while waiting for scan results");
270+
}
271+
160272
module.exports.scan = scan;
273+
module.exports.cascadingScan = cascadingScan;
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
const { cascadingScan } = require('../helpers')
2+
3+
test(
4+
"Cascading Scan nmap -> ncrack on dummy-ssh",
5+
async () => {
6+
const { categories, severities, count } = await cascadingScan(
7+
"nmap-dummy-ssh",
8+
"nmap",
9+
["-Pn", "-sV", "dummy-ssh.demo-apps.svc"],
10+
{
11+
nameCascade: "ncrack-ssh",
12+
matchLabels: {
13+
"securecodebox.io/invasive": "invasive",
14+
"securecodebox.io/intensive": "high"
15+
}
16+
},
17+
120
18+
);
19+
20+
expect(count).toBe(1);
21+
expect(categories).toEqual(
22+
{
23+
"Discovered Credentials": 1,
24+
}
25+
);
26+
expect(severities).toEqual(
27+
{
28+
"high": 1,
29+
}
30+
);
31+
},
32+
3 * 60 * 1000
33+
);

0 commit comments

Comments
 (0)