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

Commit ee456ca

Browse files
committed
#33 Restrict query for CascadingRules by the Scans selector
1 parent 0b881c2 commit ee456ca

File tree

6 files changed

+278
-64
lines changed

6 files changed

+278
-64
lines changed

hooks/declarative-subsequent-scans/Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ RUN mkdir -p /home/app
1010
WORKDIR /home/app
1111
COPY package.json package-lock.json ./
1212
RUN npm ci
13-
COPY hook.ts scan-helpers.ts ./
13+
COPY hook.ts scan-helpers.ts kubernetes-label-selector.ts ./
1414
RUN npm run build
1515

1616
FROM scbexperimental/hook-sdk-nodejs:${baseImageTag:-latest}
1717
WORKDIR /home/app/hook-wrapper/hook/
1818
COPY --from=install --chown=app:app /home/app/node_modules/ ./node_modules/
19-
COPY --from=build --chown=app:app /home/app/hook.js /home/app/scan-helpers.js ./
19+
COPY --from=build --chown=app:app /home/app/hook.js /home/app/scan-helpers.js /home/app/kubernetes-label-selector.js ./

hooks/declarative-subsequent-scans/hook.test.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ beforeEach(() => {
1313
},
1414
spec: {
1515
scanType: "nmap",
16-
parameters: "foobar.com"
16+
parameters: "foobar.com",
17+
cascades: {}
1718
}
1819
};
1920

@@ -74,6 +75,7 @@ test("should create subsequent scans for open HTTPS ports (NMAP findings)", () =
7475
expect(cascadedScans).toMatchInlineSnapshot(`
7576
Array [
7677
Object {
78+
"cascades": Object {},
7779
"generatedBy": "tls-scans",
7880
"name": "sslyze-foobar.com-tls-scans",
7981
"parameters": Array [
@@ -132,6 +134,7 @@ test("should not try to do magic to the scan name if its something random", () =
132134
expect(cascadedScans).toMatchInlineSnapshot(`
133135
Array [
134136
Object {
137+
"cascades": Object {},
135138
"generatedBy": "tls-scans",
136139
"name": "foobar.com-tls-scans",
137140
"parameters": Array [

hooks/declarative-subsequent-scans/hook.ts

Lines changed: 14 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,24 @@
11
import { isMatch } from "lodash";
22
import * as Mustache from "mustache";
3-
import * as k8s from "@kubernetes/client-node";
43

54
import {
65
startSubsequentSecureCodeBoxScan,
7-
getCascadingRulesFromCluster,
6+
getCascadingRulesForScan,
7+
// types
8+
Scan,
9+
Finding,
10+
CascadingRule,
11+
ExtendedScanSpec
812
} from "./scan-helpers";
913

10-
interface Finding {
11-
name: string;
12-
location: string;
13-
category: string;
14-
severity: string;
15-
osi_layer: string;
16-
attributes: Map<string, string | number>;
17-
}
18-
19-
interface CascadingRule {
20-
metadata: k8s.V1ObjectMeta;
21-
spec: CascadingRuleSpec;
22-
}
23-
24-
interface CascadingRuleSpec {
25-
matches: Matches;
26-
scanSpec: ScanSpec;
27-
}
28-
29-
interface Matches {
30-
anyOf: Array<Finding>;
31-
}
32-
33-
interface Scan {
34-
metadata: k8s.V1ObjectMeta;
35-
spec: ScanSpec;
36-
}
37-
38-
interface ScanSpec {
39-
scanType: string;
40-
parameters: Array<string>;
41-
}
42-
43-
interface ExtendedScanSpec extends ScanSpec {
44-
// This is the name of the scan. Its not "really" part of the scan spec
45-
// But this makes the object smaller
46-
name: string;
47-
48-
// Indicates which CascadingRule was used to generate the resulting Scan
49-
generatedBy: string;
50-
}
51-
5214
interface HandleArgs {
5315
scan: Scan;
5416
getFindings: () => Array<Finding>;
5517
}
5618

5719
export async function handle({ scan, getFindings }: HandleArgs) {
5820
const findings = await getFindings();
59-
const cascadingRules = await getCascadingRules();
21+
const cascadingRules = await getCascadingRules(scan);
6022

6123
const cascadingScans = getCascadingScans(scan, findings, cascadingRules);
6224

@@ -66,14 +28,14 @@ export async function handle({ scan, getFindings }: HandleArgs) {
6628
parentScan: scan,
6729
generatedBy,
6830
scanType,
69-
parameters,
31+
parameters
7032
});
7133
}
7234
}
7335

74-
async function getCascadingRules(): Promise<Array<CascadingRule>> {
36+
async function getCascadingRules(scan: Scan): Promise<Array<CascadingRule>> {
7537
// Explicit Cast to the proper Type
76-
return <Array<CascadingRule>>await getCascadingRulesFromCluster();
38+
return <Array<CascadingRule>>await getCascadingRulesForScan(scan);
7739
}
7840

7941
/**
@@ -112,7 +74,7 @@ export function getCascadingScans(
11274

11375
for (const finding of findings) {
11476
// Check if one (ore more) of the CascadingRule matchers apply to the finding
115-
const matches = cascadingRule.spec.matches.anyOf.some((matchesRule) =>
77+
const matches = cascadingRule.spec.matches.anyOf.some(matchesRule =>
11678
isMatch(finding, matchesRule)
11779
);
11880

@@ -122,10 +84,11 @@ export function getCascadingScans(
12284
cascadingScans.push({
12385
name: generateCascadingScanName(parentScan, cascadingRule),
12486
scanType: Mustache.render(scanType, finding),
125-
parameters: parameters.map((parameter) =>
87+
parameters: parameters.map(parameter =>
12688
Mustache.render(parameter, finding)
12789
),
128-
generatedBy: cascadingRule.metadata.name,
90+
cascades: null,
91+
generatedBy: cascadingRule.metadata.name
12992
});
13093
}
13194
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
const { generateLabelSelectorString } = require("./kubernetes-label-selector");
2+
3+
test("should generate a empty string if passed an empty object", () => {
4+
expect(generateLabelSelectorString({})).toBe("");
5+
});
6+
7+
test("should generate basic label string for key values selector", () => {
8+
expect(
9+
generateLabelSelectorString({
10+
matchLabels: { environment: "production" }
11+
})
12+
).toBe("environment=production");
13+
14+
expect(
15+
generateLabelSelectorString({
16+
matchLabels: { environment: "testing" }
17+
})
18+
).toBe("environment=testing");
19+
});
20+
21+
test("should generate basic label string for multiple key values selector", () => {
22+
expect(
23+
generateLabelSelectorString({
24+
matchLabels: {
25+
environment: "production",
26+
team: "search"
27+
}
28+
})
29+
).toBe("environment=production,team=search");
30+
31+
expect(
32+
generateLabelSelectorString({
33+
matchLabels: {
34+
environment: "testing",
35+
team: "payment"
36+
}
37+
})
38+
).toBe("environment=testing,team=payment");
39+
});
40+
41+
test("should generate label string for set based expressions", () => {
42+
expect(
43+
generateLabelSelectorString({
44+
matchExpression: [
45+
{
46+
key: "environment",
47+
operator: "In",
48+
values: ["testing", "development"]
49+
}
50+
]
51+
})
52+
).toBe("environment in (testing,development)");
53+
54+
expect(
55+
generateLabelSelectorString({
56+
matchExpression: [
57+
{
58+
key: "environment",
59+
operator: "In",
60+
values: ["development"]
61+
}
62+
]
63+
})
64+
).toBe("environment in (development)");
65+
});
66+
67+
test("should generate label string for set based expressions with multiple entries", () => {
68+
expect(
69+
generateLabelSelectorString({
70+
matchExpression: [
71+
{
72+
key: "environment",
73+
operator: "NotIn",
74+
values: ["production"]
75+
},
76+
{
77+
key: "team",
78+
operator: "In",
79+
values: ["search", "payment"]
80+
}
81+
]
82+
})
83+
).toBe("environment notin (production),team in (search,payment)");
84+
});
85+
86+
test("should generate label string for set based Exists and DoesNotExist operators", () => {
87+
expect(
88+
generateLabelSelectorString({
89+
matchExpression: [
90+
{
91+
key: "environment",
92+
operator: "Exists"
93+
},
94+
{
95+
key: "team",
96+
operator: "DoesNotExist"
97+
}
98+
]
99+
})
100+
).toBe("environment,!team");
101+
});
102+
103+
test("should generate selectors with both expression and labelMatching", () => {
104+
expect(
105+
generateLabelSelectorString({
106+
matchExpression: [
107+
{
108+
key: "environment",
109+
operator: "NotIn",
110+
values: ["production"]
111+
},
112+
{
113+
key: "team",
114+
operator: "In",
115+
values: ["search", "payment"]
116+
},
117+
{
118+
key: "foobar",
119+
operator: "Exists"
120+
},
121+
{
122+
key: "barfoo",
123+
operator: "DoesNotExist"
124+
}
125+
],
126+
matchLabels: {
127+
critical: "true"
128+
}
129+
})
130+
).toBe(
131+
"critical=true,environment notin (production),team in (search,payment),foobar,!barfoo"
132+
);
133+
});
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
export enum LabelSelectorRequirementOperator {
2+
In = "In",
3+
NotIn = "NotIn",
4+
Exists = "Exists",
5+
DoesNotExist = "DoesNotExist"
6+
}
7+
8+
// See: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#labelselectorrequirement-v1-meta
9+
// Re created in TS because the included types suck 😕
10+
export interface LabelSelectorRequirement {
11+
key: string;
12+
values: Array<string>;
13+
14+
operator: LabelSelectorRequirementOperator;
15+
}
16+
17+
export interface LabelSelector {
18+
matchExpression: Array<LabelSelectorRequirement>;
19+
matchLabels: Map<string, string>;
20+
}
21+
22+
// generateLabelSelectorString transforms a kubernetes labelSelector object in to the string representation
23+
export function generateLabelSelectorString({
24+
matchExpression = [],
25+
matchLabels = new Map()
26+
}: LabelSelector): string {
27+
const matchLabelsSelector = Array.from(Object.entries(matchLabels)).map(
28+
([key, values]) => `${key}=${values}`
29+
);
30+
31+
const matchExpressionsSelector = matchExpression.map(
32+
({ key, values, operator }) => {
33+
if (
34+
operator === LabelSelectorRequirementOperator.In ||
35+
operator === LabelSelectorRequirementOperator.NotIn
36+
) {
37+
return `${key} ${operator.toLowerCase()} (${values.join(",")})`;
38+
}
39+
40+
if (operator === LabelSelectorRequirementOperator.Exists) {
41+
return key;
42+
}
43+
if (operator === LabelSelectorRequirementOperator.DoesNotExist) {
44+
return `!${key}`;
45+
}
46+
}
47+
);
48+
49+
return [...matchLabelsSelector, ...matchExpressionsSelector].join(",");
50+
}

0 commit comments

Comments
 (0)