Skip to content

Commit 1211070

Browse files
authored
Merge pull request #430 from EndPositive/cascading_scans_custom_annotations
🕸 Extend the Cascading-Scans Hook to generate custom labels or annotations for subsequent scans
2 parents 1d9ca6a + cea7ed2 commit 1211070

14 files changed

Lines changed: 735 additions & 68 deletions

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

Lines changed: 452 additions & 0 deletions
Large diffs are not rendered by default.

hooks/declarative-subsequent-scans/hook.ts

Lines changed: 41 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@
22
//
33
// SPDX-License-Identifier: Apache-2.0
44

5-
import { isMatch, isMatchWith, isString } from "lodash";
5+
import { isMatch, isMatchWith, isString, mapValues } from "lodash";
66
import { isMatch as wildcardIsMatch } from "matcher";
77
import * as Mustache from "mustache";
88

99
import {
1010
startSubsequentSecureCodeBoxScan,
1111
getCascadingRulesForScan,
12+
getSubsequentScanDefinition,
1213
// types
1314
Scan,
1415
Finding,
@@ -27,15 +28,18 @@ export async function handle({ scan, getFindings }: HandleArgs) {
2728

2829
const cascadingScans = getCascadingScans(scan, findings, cascadingRules);
2930

30-
for (const { name, scanType, parameters, generatedBy, env } of cascadingScans) {
31-
await startSubsequentSecureCodeBoxScan({
31+
for (const { name, scanType, parameters, generatedBy, env, scanLabels, scanAnnotations } of cascadingScans) {
32+
const cascadingScanDefinition = getSubsequentScanDefinition({
3233
name,
3334
parentScan: scan,
3435
generatedBy,
3536
scanType,
3637
parameters,
3738
env,
39+
scanLabels,
40+
scanAnnotations
3841
});
42+
await startSubsequentSecureCodeBoxScan(cascadingScanDefinition);
3943
}
4044
}
4145

@@ -88,34 +92,47 @@ export function getCascadingScans(
8892
);
8993

9094
if (matches) {
91-
const { scanType, parameters, env } = cascadingRule.spec.scanSpec;
92-
93-
const templateArgs = {
94-
...finding,
95-
// Attribute "$" hold special non finding helper attributes
96-
$: {
97-
hostOrIP:
98-
finding.attributes["hostname"] || finding.attributes["ip_address"]
99-
}
100-
};
101-
102-
cascadingScans.push({
103-
name: generateCascadingScanName(parentScan, cascadingRule),
104-
scanType: Mustache.render(scanType, templateArgs),
105-
parameters: parameters.map(parameter =>
106-
Mustache.render(parameter, templateArgs)
107-
),
108-
cascades: null,
109-
generatedBy: cascadingRule.metadata.name,
110-
env,
111-
});
95+
cascadingScans.push(getCascadingScan(parentScan, finding, cascadingRule))
11296
}
11397
}
11498
}
11599

116100
return cascadingScans;
117101
}
118102

103+
function getCascadingScan(
104+
parentScan: Scan,
105+
finding: Finding,
106+
cascadingRule: CascadingRule
107+
) {
108+
const { scanType, parameters, env } = cascadingRule.spec.scanSpec;
109+
110+
const templateArgs = {
111+
...finding,
112+
...parentScan,
113+
// Attribute "$" hold special non finding helper attributes
114+
$: {
115+
hostOrIP:
116+
finding.attributes["hostname"] || finding.attributes["ip_address"]
117+
}
118+
};
119+
120+
return {
121+
name: generateCascadingScanName(parentScan, cascadingRule),
122+
scanType: Mustache.render(scanType, templateArgs),
123+
parameters: parameters.map(parameter =>
124+
Mustache.render(parameter, templateArgs)
125+
),
126+
cascades: null,
127+
generatedBy: cascadingRule.metadata.name,
128+
env,
129+
scanLabels: cascadingRule.spec.scanLabels === undefined ? {} :
130+
mapValues(cascadingRule.spec.scanLabels, value => Mustache.render(value, templateArgs)),
131+
scanAnnotations: cascadingRule.spec.scanAnnotations === undefined ? {} :
132+
mapValues(cascadingRule.spec.scanAnnotations, value => Mustache.render(value, templateArgs)),
133+
};
134+
}
135+
119136
function generateCascadingScanName(
120137
parentScan: Scan,
121138
cascadingRule: CascadingRule

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

Lines changed: 56 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ export interface CascadingRule {
3434
export interface CascadingRuleSpec {
3535
matches: Matches;
3636
scanSpec: ScanSpec;
37+
scanLabels: {
38+
[key: string]: string;
39+
};
40+
scanAnnotations: {
41+
[key: string]: string;
42+
};
3743
}
3844

3945
export interface Matches {
@@ -48,27 +54,59 @@ export interface Scan {
4854
export interface ScanSpec {
4955
scanType: string;
5056
parameters: Array<string>;
51-
cascades: LabelSelector;
57+
cascades: LabelSelector & CascadingInheritance;
5258
env?: Array<k8s.V1EnvVar>;
5359
}
5460

61+
export interface CascadingInheritance {
62+
inheritLabels: boolean,
63+
inheritAnnotations: boolean
64+
}
65+
5566
export interface ExtendedScanSpec extends ScanSpec {
5667
// This is the name of the scan. Its not "really" part of the scan spec
5768
// But this makes the object smaller
5869
name: string;
5970

6071
// Indicates which CascadingRule was used to generate the resulting Scan
6172
generatedBy: string;
73+
74+
// Additional label to be added to the resulting scan
75+
scanLabels: {
76+
[key: string]: string;
77+
};
78+
79+
// Additional annotations to be added to the resulting scan
80+
scanAnnotations: {
81+
[key: string]: string;
82+
};
6283
}
6384

64-
export async function startSubsequentSecureCodeBoxScan({
65-
name,
66-
parentScan,
67-
scanType,
68-
parameters,
69-
generatedBy,
70-
env,
71-
}) {
85+
export function getSubsequentScanDefinition({
86+
name,
87+
parentScan,
88+
scanType,
89+
parameters,
90+
generatedBy,
91+
env,
92+
scanLabels,
93+
scanAnnotations
94+
}) {
95+
function mergeInherited(parentProps, ruleProps, inherit: boolean = true) {
96+
if (!inherit) {
97+
parentProps = {};
98+
}
99+
return {
100+
...parentProps,
101+
...ruleProps // ruleProps overwrites any duplicate keys from parentProps
102+
}
103+
}
104+
105+
let annotations = mergeInherited(
106+
parentScan.metadata.annotations, scanAnnotations, parentScan.spec.cascades.inheritAnnotations);
107+
let labels = mergeInherited(
108+
parentScan.metadata.labels, scanLabels, parentScan.spec.cascades.inheritLabels);
109+
72110
let cascadingChain: Array<string> = [];
73111

74112
if (parentScan.metadata.annotations && parentScan.metadata.annotations["cascading.securecodebox.io/chain"]) {
@@ -77,21 +115,22 @@ export async function startSubsequentSecureCodeBoxScan({
77115
].split(",");
78116
}
79117

80-
const scanDefinition = {
118+
return {
81119
apiVersion: "execution.securecodebox.io/v1",
82120
kind: "Scan",
83121
metadata: {
84122
generateName: `${name}-`,
85123
labels: {
86-
...parentScan.metadata.labels
124+
...labels
87125
},
88126
annotations: {
89127
"securecodebox.io/hook": "declarative-subsequent-scans",
90128
"cascading.securecodebox.io/parent-scan": parentScan.metadata.name,
91129
"cascading.securecodebox.io/chain": [
92130
...cascadingChain,
93131
generatedBy
94-
].join(",")
132+
].join(","),
133+
...annotations,
95134
},
96135
ownerReferences: [
97136
{
@@ -111,8 +150,10 @@ export async function startSubsequentSecureCodeBoxScan({
111150
env,
112151
}
113152
};
153+
}
114154

115-
console.log(`Starting Scan ${name}`);
155+
export async function startSubsequentSecureCodeBoxScan(scan: Scan) {
156+
console.log(`Starting Scan ${scan.metadata.name}`);
116157

117158
try {
118159
// Submitting the Scan to the kubernetes api
@@ -121,11 +162,11 @@ export async function startSubsequentSecureCodeBoxScan({
121162
"v1",
122163
namespace,
123164
"scans",
124-
scanDefinition,
165+
scan,
125166
"false"
126167
);
127168
} catch (error) {
128-
console.error(`Failed to start Scan ${name}`);
169+
console.error(`Failed to start Scan ${scan.metadata.name}`);
129170
console.error(error);
130171
}
131172
}

hooks/persistence-defectdojo/src/main/java/io/securecodebox/models/V1ScanSpecCascades.java

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,14 @@
3939
@ApiModel(description = "A label selector is a label query over a set of resources. The result of matchLabels and matchExpressions are ANDed. An empty label selector matches all objects. A null label selector matches no objects.")
4040
@javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen", date = "2021-03-26T19:41:39.505Z[Etc/UTC]")
4141
public class V1ScanSpecCascades {
42+
public static final String SERIALIZED_NAME_INHERIT_LABELS = "inheritLabels";
43+
@SerializedName(SERIALIZED_NAME_INHERIT_LABELS)
44+
private boolean inheritLabels = true;
45+
46+
public static final String SERIALIZED_NAME_INHERIT_ANNOTATIONS = "inheritAnnotations";
47+
@SerializedName(SERIALIZED_NAME_INHERIT_ANNOTATIONS)
48+
private boolean inheritAnnotations = true;
49+
4250
public static final String SERIALIZED_NAME_MATCH_EXPRESSIONS = "matchExpressions";
4351
@SerializedName(SERIALIZED_NAME_MATCH_EXPRESSIONS)
4452
private List<V1ScanSpecCascadesMatchExpressions> matchExpressions = null;
@@ -47,6 +55,21 @@ public class V1ScanSpecCascades {
4755
@SerializedName(SERIALIZED_NAME_MATCH_LABELS)
4856
private Map<String, String> matchLabels = null;
4957

58+
public boolean inheritsLabels() {
59+
return inheritsAnnotations();
60+
}
61+
62+
public void setInheritLabels(boolean inheritLabels) {
63+
this.inheritLabels = inheritLabels;
64+
}
65+
66+
public boolean inheritsAnnotations() {
67+
return inheritAnnotations;
68+
}
69+
70+
public void setInheritAnnotations(boolean inheritAnnotations) {
71+
this.inheritAnnotations = inheritAnnotations;
72+
}
5073

5174
public V1ScanSpecCascades matchExpressions(List<V1ScanSpecCascadesMatchExpressions> matchExpressions) {
5275

@@ -120,7 +143,9 @@ public boolean equals(Object o) {
120143
}
121144
V1ScanSpecCascades v1ScanSpecCascades = (V1ScanSpecCascades) o;
122145
return Objects.equals(this.matchExpressions, v1ScanSpecCascades.matchExpressions) &&
123-
Objects.equals(this.matchLabels, v1ScanSpecCascades.matchLabels);
146+
Objects.equals(this.matchLabels, v1ScanSpecCascades.matchLabels) &&
147+
this.inheritLabels == v1ScanSpecCascades.inheritLabels &&
148+
this.inheritAnnotations == v1ScanSpecCascades.inheritAnnotations;
124149
}
125150

126151
@Override
@@ -133,6 +158,8 @@ public int hashCode() {
133158
public String toString() {
134159
StringBuilder sb = new StringBuilder();
135160
sb.append("class V1ScanSpecCascades {\n");
161+
sb.append(" inheritLabels: ").append(String.valueOf(inheritLabels)).append("\n");
162+
sb.append(" inheritAnnotations: ").append(String.valueOf(inheritAnnotations)).append("\n");
136163
sb.append(" matchExpressions: ").append(toIndentedString(matchExpressions)).append("\n");
137164
sb.append(" matchLabels: ").append(toIndentedString(matchLabels)).append("\n");
138165
sb.append("}");

operator/apis/cascading/v1/cascadingrule_types.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,15 @@ type CascadingRuleSpec struct {
2020

2121
// Matches defines to which findings the CascadingRule should apply
2222
Matches Matches `json:"matches"`
23+
24+
// ScanLabels define additional labels for cascading scans
25+
// +optional
26+
ScanLabels map[string]string `json:"scanLabels"`
27+
28+
// ScanAnnotations define additional annotations for cascading scans
29+
// +optional
30+
ScanAnnotations map[string]string `json:"scanAnnotations"`
31+
2332
// ScanSpec defines how the cascaded scan should look like
2433
ScanSpec executionv1.ScanSpec `json:"scanSpec"`
2534
}

operator/apis/cascading/v1/zz_generated.deepcopy.go

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

operator/apis/execution/v1/scan_types.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,26 @@ import (
1212
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
1313
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
1414

15+
// CascadeSpec describes how and when cascading scans should be generated.
16+
type CascadeSpec struct {
17+
// InheritLabels defines whether cascading scans should inherit labels from the parent scan
18+
// +optional
19+
InheritLabels bool `json:"inheritLabels,omitempty"`
20+
21+
// InheritAnnotations defines whether cascading scans should inherit annotations from the parent scan
22+
// +optional
23+
InheritAnnotations bool `json:"inheritAnnotations,omitempty"`
24+
25+
// matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels
26+
// map is equivalent to an element of matchExpressions, whose key field is "key", the
27+
// operator is "In", and the values array contains only "value". The requirements are ANDed.
28+
// +optional
29+
MatchLabels map[string]string `json:"matchLabels,omitempty" protobuf:"bytes,1,rep,name=matchLabels"`
30+
// matchExpressions is a list of label selector requirements. The requirements are ANDed.
31+
// +optional
32+
MatchExpressions []metav1.LabelSelectorRequirement `json:"matchExpressions,omitempty" protobuf:"bytes,2,rep,name=matchExpressions"`
33+
}
34+
1535
// ScanSpec defines the desired state of Scan
1636
type ScanSpec struct {
1737
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
@@ -30,7 +50,7 @@ type ScanSpec struct {
3050
// VolumeMounts allows to specify volume mounts for the scan container.
3151
VolumeMounts []corev1.VolumeMount `json:"volumeMounts,omitempty"`
3252

33-
Cascades *metav1.LabelSelector `json:"cascades,omitempty"`
53+
Cascades *CascadeSpec `json:"cascades,omitempty"`
3454
}
3555

3656
// ScanStatus defines the observed state of Scan

0 commit comments

Comments
 (0)