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

Commit d3f24d5

Browse files
committed
#33 Add loop prevention to stop endless scan cycles
1 parent b4da1ee commit d3f24d5

File tree

3 files changed

+116
-80
lines changed

3 files changed

+116
-80
lines changed
Lines changed: 73 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,55 @@
11
const { getCascadingScans } = require("./hook");
22

33
let parentScan = undefined;
4+
let sslyzeCascadingRules = undefined;
45

56
beforeEach(() => {
67
parentScan = {
78
apiVersion: "execution.experimental.securecodebox.io/v1",
89
kind: "Scan",
910
metadata: {
1011
name: "nmap-foobar.com",
12+
annotations: {}
1113
},
1214
spec: {
1315
scanType: "nmap",
14-
parameters: "foobar.com",
15-
},
16+
parameters: "foobar.com"
17+
}
1618
};
17-
});
1819

19-
const sslyzeCascadingRules = [
20-
{
21-
apiVersion: "cascading.experimental.securecodebox.io/v1",
22-
kind: "CascadingRule",
23-
metadata: {
24-
name: "tls-scans",
25-
},
26-
spec: {
27-
matches: {
28-
anyOf: [
29-
{
30-
category: "Open Port",
31-
attributes: {
32-
port: 443,
33-
service: "https",
34-
},
35-
},
36-
{
37-
category: "Open Port",
38-
attributes: {
39-
service: "https",
40-
},
41-
},
42-
],
43-
},
44-
scanSpec: {
45-
scanType: "sslyze",
46-
parameters: ["--regular", "{{attributes.hostname}}"],
20+
sslyzeCascadingRules = [
21+
{
22+
apiVersion: "cascading.experimental.securecodebox.io/v1",
23+
kind: "CascadingRule",
24+
metadata: {
25+
name: "tls-scans"
4726
},
48-
},
49-
},
50-
];
27+
spec: {
28+
matches: {
29+
anyOf: [
30+
{
31+
category: "Open Port",
32+
attributes: {
33+
port: 443,
34+
service: "https"
35+
}
36+
},
37+
{
38+
category: "Open Port",
39+
attributes: {
40+
service: "https"
41+
}
42+
}
43+
]
44+
},
45+
scanSpec: {
46+
scanType: "sslyze",
47+
parameters: ["--regular", "{{attributes.hostname}}"]
48+
}
49+
}
50+
}
51+
];
52+
});
5153

5254
test("should create subsequent scans for open HTTPS ports (NMAP findings)", () => {
5355
const findings = [
@@ -58,9 +60,9 @@ test("should create subsequent scans for open HTTPS ports (NMAP findings)", () =
5860
state: "open",
5961
hostname: "foobar.com",
6062
port: 443,
61-
service: "https",
62-
},
63-
},
63+
service: "https"
64+
}
65+
}
6466
];
6567

6668
const cascadedScans = getCascadingScans(
@@ -72,6 +74,7 @@ test("should create subsequent scans for open HTTPS ports (NMAP findings)", () =
7274
expect(cascadedScans).toMatchInlineSnapshot(`
7375
Array [
7476
Object {
77+
"generatedBy": "tls-scans",
7578
"name": "sslyze-foobar.com-tls-scans",
7679
"parameters": Array [
7780
"--regular",
@@ -92,9 +95,9 @@ test("Should create no subsequent scans if there are no rules", () => {
9295
state: "open",
9396
hostname: "foobar.com",
9497
port: 443,
95-
service: "https",
96-
},
97-
},
98+
service: "https"
99+
}
100+
}
98101
];
99102

100103
const cascadingRules = [];
@@ -115,9 +118,9 @@ test("should not try to do magic to the scan name if its something random", () =
115118
state: "open",
116119
hostname: "foobar.com",
117120
port: 443,
118-
service: "https",
119-
},
120-
},
121+
service: "https"
122+
}
123+
}
121124
];
122125

123126
const cascadedScans = getCascadingScans(
@@ -129,6 +132,7 @@ test("should not try to do magic to the scan name if its something random", () =
129132
expect(cascadedScans).toMatchInlineSnapshot(`
130133
Array [
131134
Object {
135+
"generatedBy": "tls-scans",
132136
"name": "foobar.com-tls-scans",
133137
"parameters": Array [
134138
"--regular",
@@ -139,3 +143,29 @@ test("should not try to do magic to the scan name if its something random", () =
139143
]
140144
`);
141145
});
146+
147+
test("should not start scan when the cascadingrule for it is already in the chain", () => {
148+
parentScan.metadata.annotations["cascading.securecodebox.io/chain"] =
149+
sslyzeCascadingRules[0].metadata.name;
150+
151+
const findings = [
152+
{
153+
name: "Port 443 is open",
154+
category: "Open Port",
155+
attributes: {
156+
state: "open",
157+
hostname: "foobar.com",
158+
port: 443,
159+
service: "https"
160+
}
161+
}
162+
];
163+
164+
const cascadedScans = getCascadingScans(
165+
parentScan,
166+
findings,
167+
sslyzeCascadingRules
168+
);
169+
170+
expect(cascadedScans).toMatchInlineSnapshot(`Array []`);
171+
});

hooks/declarative-subsequent-scans/hook.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,29 @@ export function getCascadingScans(
8787
): Array<ExtendedScanSpec> {
8888
const cascadingScans: Array<ExtendedScanSpec> = [];
8989

90+
const cascadingRuleChain = new Set<string>();
91+
92+
// Get the current Scan Chain (meaning which CascadingRules were used to start this scan and its parents) and convert it to a set, which makes it easier to query.
93+
if (parentScan.metadata.annotations["cascading.securecodebox.io/chain"]) {
94+
const chainElements = parentScan.metadata.annotations[
95+
"cascading.securecodebox.io/chain"
96+
].split(",");
97+
98+
for (const element of chainElements) {
99+
cascadingRuleChain.add(element);
100+
}
101+
}
102+
90103
for (const cascadingRule of cascadingRules) {
104+
// Check if the Same CascadingRule was already applied in the Cascading Chain
105+
// If it has already been used skip this rule as it could potentially lead to loops
106+
if (cascadingRuleChain.has(cascadingRule.metadata.name)) {
107+
console.log(
108+
`Skipping Rule "${cascadingRule.metadata.name}" as it was already applied in this chain.`
109+
);
110+
continue;
111+
}
112+
91113
for (const finding of findings) {
92114
// Check if one (ore more) of the CascadingRule matchers apply to the finding
93115
const matches = cascadingRule.spec.matches.anyOf.some((matchesRule) =>

hooks/declarative-subsequent-scans/scan-helpers.ts

Lines changed: 21 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,21 @@ kc.loadFromDefault();
66

77
const k8sApiCRD = kc.makeApiClient(k8s.CustomObjectsApi);
88

9-
async function startSubsequentSecureCodeBoxScan({
9+
export async function startSubsequentSecureCodeBoxScan({
1010
name,
1111
parentScan,
1212
scanType,
1313
parameters,
1414
generatedBy,
1515
}) {
16+
let cascadingChain: Array<string> = [];
17+
18+
if (parentScan.metadata.annotations["cascading.securecodebox.io/chain"]) {
19+
cascadingChain = parentScan.metadata.annotations[
20+
"cascading.securecodebox.io/chain"
21+
].split(",");
22+
}
23+
1624
const scanDefinition = {
1725
apiVersion: "execution.experimental.securecodebox.io/v1",
1826
kind: "Scan",
@@ -23,8 +31,11 @@ async function startSubsequentSecureCodeBoxScan({
2331
},
2432
annotations: {
2533
"securecodebox.io/hook": "declarative-subsequent-scans",
26-
"securecodebox.io/parent-scan": parentScan.metadata.name,
27-
"cascading.securecodebox.io/generated-by": generatedBy,
34+
"cascading.securecodebox.io/parent-scan": parentScan.metadata.name,
35+
"cascading.securecodebox.io/chain": [
36+
...cascadingChain,
37+
generatedBy,
38+
].join(","),
2839
},
2940
ownerReferences: [
3041
{
@@ -43,6 +54,8 @@ async function startSubsequentSecureCodeBoxScan({
4354
},
4455
};
4556

57+
console.log(`Starting Scan ${name}`);
58+
4659
try {
4760
// Starting another subsequent sslyze scan based on the nmap results
4861
// found at: https://github.com/kubernetes-client/javascript/blob/79736b9a608c18d818de61a6b44503a08ea3a78f/src/gen/api/customObjectsApi.ts#L209
@@ -60,50 +73,21 @@ async function startSubsequentSecureCodeBoxScan({
6073
}
6174
}
6275

63-
module.exports.startSubsequentSecureCodeBoxScan = startSubsequentSecureCodeBoxScan;
64-
65-
async function getCascadingRulesFromCluster() {
76+
export async function getCascadingRulesFromCluster() {
6677
try {
6778
const namespace = process.env["NAMESPACE"];
68-
const { body } = await k8sApiCRD.listNamespacedCustomObject(
79+
const response: any = await k8sApiCRD.listNamespacedCustomObject(
6980
"cascading.experimental.securecodebox.io",
7081
"v1",
7182
namespace,
7283
"cascadingrules"
7384
);
74-
console.log("got CascadingRules");
75-
console.log(body);
76-
console.log(JSON.stringify(body));
77-
return body.items;
85+
86+
console.log(`Fetched ${response.body.items.length} CascadingRules`);
87+
return response.body.items;
7888
} catch (err) {
7989
console.error("Failed to get CascadingRules from the kubernetes api");
8090
console.error(err);
8191
process.exit(1);
8292
}
8393
}
84-
module.exports.getCascadingRulesFromCluster = getCascadingRulesFromCluster;
85-
86-
enum LabelSelectorRequirementOperator {
87-
In,
88-
NotIn,
89-
Exists,
90-
DoesNotExist,
91-
}
92-
93-
// See: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#labelselectorrequirement-v1-meta
94-
// Re created in TS because the included types suck 😕
95-
interface LabelSelectorRequirement {
96-
key: string;
97-
values: string;
98-
99-
operator: LabelSelectorRequirementOperator;
100-
}
101-
102-
function generateLabelSelectorString(
103-
matchExpression: Array<LabelSelectorRequirement>,
104-
matchLabels: Map<string, string>
105-
): string {
106-
// Convert matchLabels to matchExpression syntax
107-
matchExpression;
108-
return "";
109-
}

0 commit comments

Comments
 (0)