Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
452 changes: 452 additions & 0 deletions hooks/declarative-subsequent-scans/hook.test.js

Large diffs are not rendered by default.

65 changes: 41 additions & 24 deletions hooks/declarative-subsequent-scans/hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
//
// SPDX-License-Identifier: Apache-2.0

import { isMatch, isMatchWith, isString } from "lodash";
import { isMatch, isMatchWith, isString, mapValues } from "lodash";
import { isMatch as wildcardIsMatch } from "matcher";
import * as Mustache from "mustache";

import {
startSubsequentSecureCodeBoxScan,
getCascadingRulesForScan,
getSubsequentScanDefinition,
// types
Scan,
Finding,
Expand All @@ -27,15 +28,18 @@ export async function handle({ scan, getFindings }: HandleArgs) {

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

for (const { name, scanType, parameters, generatedBy, env } of cascadingScans) {
await startSubsequentSecureCodeBoxScan({
for (const { name, scanType, parameters, generatedBy, env, scanLabels, scanAnnotations } of cascadingScans) {
const cascadingScanDefinition = getSubsequentScanDefinition({
name,
parentScan: scan,
generatedBy,
scanType,
parameters,
env,
scanLabels,
scanAnnotations
});
await startSubsequentSecureCodeBoxScan(cascadingScanDefinition);
}
}

Expand Down Expand Up @@ -88,34 +92,47 @@ export function getCascadingScans(
);

if (matches) {
const { scanType, parameters, env } = cascadingRule.spec.scanSpec;

const templateArgs = {
...finding,
// Attribute "$" hold special non finding helper attributes
$: {
hostOrIP:
finding.attributes["hostname"] || finding.attributes["ip_address"]
}
};

cascadingScans.push({
name: generateCascadingScanName(parentScan, cascadingRule),
scanType: Mustache.render(scanType, templateArgs),
parameters: parameters.map(parameter =>
Mustache.render(parameter, templateArgs)
),
cascades: null,
generatedBy: cascadingRule.metadata.name,
env,
});
cascadingScans.push(getCascadingScan(parentScan, finding, cascadingRule))
}
}
}

return cascadingScans;
}

function getCascadingScan(
parentScan: Scan,
finding: Finding,
cascadingRule: CascadingRule
) {
const { scanType, parameters, env } = cascadingRule.spec.scanSpec;

const templateArgs = {
...finding,
...parentScan,
// Attribute "$" hold special non finding helper attributes
$: {
hostOrIP:
finding.attributes["hostname"] || finding.attributes["ip_address"]
}
};

return {
name: generateCascadingScanName(parentScan, cascadingRule),
scanType: Mustache.render(scanType, templateArgs),
parameters: parameters.map(parameter =>
Mustache.render(parameter, templateArgs)
),
cascades: null,
generatedBy: cascadingRule.metadata.name,
env,
scanLabels: cascadingRule.spec.scanLabels === undefined ? {} :
mapValues(cascadingRule.spec.scanLabels, value => Mustache.render(value, templateArgs)),
scanAnnotations: cascadingRule.spec.scanAnnotations === undefined ? {} :
mapValues(cascadingRule.spec.scanAnnotations, value => Mustache.render(value, templateArgs)),
};
}

function generateCascadingScanName(
parentScan: Scan,
cascadingRule: CascadingRule
Expand Down
71 changes: 56 additions & 15 deletions hooks/declarative-subsequent-scans/scan-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ export interface CascadingRule {
export interface CascadingRuleSpec {
matches: Matches;
scanSpec: ScanSpec;
scanLabels: {
[key: string]: string;
};
scanAnnotations: {
[key: string]: string;
};
}

export interface Matches {
Expand All @@ -48,27 +54,59 @@ export interface Scan {
export interface ScanSpec {
scanType: string;
parameters: Array<string>;
cascades: LabelSelector;
cascades: LabelSelector & CascadingInheritance;
env?: Array<k8s.V1EnvVar>;
}

export interface CascadingInheritance {
inheritLabels: boolean,
inheritAnnotations: boolean
}

export interface ExtendedScanSpec extends ScanSpec {
// This is the name of the scan. Its not "really" part of the scan spec
// But this makes the object smaller
name: string;

// Indicates which CascadingRule was used to generate the resulting Scan
generatedBy: string;

// Additional label to be added to the resulting scan
scanLabels: {
[key: string]: string;
};

// Additional annotations to be added to the resulting scan
scanAnnotations: {
[key: string]: string;
};
}

export async function startSubsequentSecureCodeBoxScan({
name,
parentScan,
scanType,
parameters,
generatedBy,
env,
}) {
export function getSubsequentScanDefinition({
name,
parentScan,
scanType,
parameters,
generatedBy,
env,
scanLabels,
scanAnnotations
}) {
function mergeInherited(parentProps, ruleProps, inherit: boolean = true) {
if (!inherit) {
parentProps = {};
}
return {
...parentProps,
...ruleProps // ruleProps overwrites any duplicate keys from parentProps
}
}

let annotations = mergeInherited(
parentScan.metadata.annotations, scanAnnotations, parentScan.spec.cascades.inheritAnnotations);
let labels = mergeInherited(
parentScan.metadata.labels, scanLabels, parentScan.spec.cascades.inheritLabels);

let cascadingChain: Array<string> = [];

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

const scanDefinition = {
return {
apiVersion: "execution.securecodebox.io/v1",
kind: "Scan",
metadata: {
generateName: `${name}-`,
labels: {
...parentScan.metadata.labels
...labels
},
annotations: {
"securecodebox.io/hook": "declarative-subsequent-scans",
"cascading.securecodebox.io/parent-scan": parentScan.metadata.name,
"cascading.securecodebox.io/chain": [
...cascadingChain,
generatedBy
].join(",")
].join(","),
...annotations,
},
ownerReferences: [
{
Expand All @@ -111,8 +150,10 @@ export async function startSubsequentSecureCodeBoxScan({
env,
}
};
}

console.log(`Starting Scan ${name}`);
export async function startSubsequentSecureCodeBoxScan(scan: Scan) {
console.log(`Starting Scan ${scan.metadata.name}`);

try {
// Submitting the Scan to the kubernetes api
Expand All @@ -121,11 +162,11 @@ export async function startSubsequentSecureCodeBoxScan({
"v1",
namespace,
"scans",
scanDefinition,
scan,
"false"
);
} catch (error) {
console.error(`Failed to start Scan ${name}`);
console.error(`Failed to start Scan ${scan.metadata.name}`);
console.error(error);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@
@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.")
@javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen", date = "2021-03-26T19:41:39.505Z[Etc/UTC]")
public class V1ScanSpecCascades {
public static final String SERIALIZED_NAME_INHERIT_LABELS = "inheritLabels";
@SerializedName(SERIALIZED_NAME_INHERIT_LABELS)
private boolean inheritLabels = true;

public static final String SERIALIZED_NAME_INHERIT_ANNOTATIONS = "inheritAnnotations";
@SerializedName(SERIALIZED_NAME_INHERIT_ANNOTATIONS)
private boolean inheritAnnotations = true;

public static final String SERIALIZED_NAME_MATCH_EXPRESSIONS = "matchExpressions";
@SerializedName(SERIALIZED_NAME_MATCH_EXPRESSIONS)
private List<V1ScanSpecCascadesMatchExpressions> matchExpressions = null;
Expand All @@ -47,6 +55,21 @@ public class V1ScanSpecCascades {
@SerializedName(SERIALIZED_NAME_MATCH_LABELS)
private Map<String, String> matchLabels = null;

public boolean inheritsLabels() {
return inheritsAnnotations();
}

public void setInheritLabels(boolean inheritLabels) {
this.inheritLabels = inheritLabels;
}

public boolean inheritsAnnotations() {
return inheritAnnotations;
}

public void setInheritAnnotations(boolean inheritAnnotations) {
this.inheritAnnotations = inheritAnnotations;
}

public V1ScanSpecCascades matchExpressions(List<V1ScanSpecCascadesMatchExpressions> matchExpressions) {

Expand Down Expand Up @@ -120,7 +143,9 @@ public boolean equals(Object o) {
}
V1ScanSpecCascades v1ScanSpecCascades = (V1ScanSpecCascades) o;
return Objects.equals(this.matchExpressions, v1ScanSpecCascades.matchExpressions) &&
Objects.equals(this.matchLabels, v1ScanSpecCascades.matchLabels);
Objects.equals(this.matchLabels, v1ScanSpecCascades.matchLabels) &&
this.inheritLabels == v1ScanSpecCascades.inheritLabels &&
this.inheritAnnotations == v1ScanSpecCascades.inheritAnnotations;
}

@Override
Expand All @@ -133,6 +158,8 @@ public int hashCode() {
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("class V1ScanSpecCascades {\n");
sb.append(" inheritLabels: ").append(String.valueOf(inheritLabels)).append("\n");
sb.append(" inheritAnnotations: ").append(String.valueOf(inheritAnnotations)).append("\n");
sb.append(" matchExpressions: ").append(toIndentedString(matchExpressions)).append("\n");
sb.append(" matchLabels: ").append(toIndentedString(matchLabels)).append("\n");
sb.append("}");
Expand Down
9 changes: 9 additions & 0 deletions operator/apis/cascading/v1/cascadingrule_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@ type CascadingRuleSpec struct {

// Matches defines to which findings the CascadingRule should apply
Matches Matches `json:"matches"`

// ScanLabels define additional labels for cascading scans
// +optional
ScanLabels map[string]string `json:"scanLabels"`

// ScanAnnotations define additional annotations for cascading scans
// +optional
ScanAnnotations map[string]string `json:"scanAnnotations"`

// ScanSpec defines how the cascaded scan should look like
ScanSpec executionv1.ScanSpec `json:"scanSpec"`
}
Expand Down
14 changes: 14 additions & 0 deletions operator/apis/cascading/v1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 21 additions & 1 deletion operator/apis/execution/v1/scan_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,26 @@ import (
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.

// CascadeSpec describes how and when cascading scans should be generated.
type CascadeSpec struct {
// InheritLabels defines whether cascading scans should inherit labels from the parent scan
// +optional
InheritLabels bool `json:"inheritLabels,omitempty"`

// InheritAnnotations defines whether cascading scans should inherit annotations from the parent scan
// +optional
InheritAnnotations bool `json:"inheritAnnotations,omitempty"`

// matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels
// map is equivalent to an element of matchExpressions, whose key field is "key", the
// operator is "In", and the values array contains only "value". The requirements are ANDed.
// +optional
MatchLabels map[string]string `json:"matchLabels,omitempty" protobuf:"bytes,1,rep,name=matchLabels"`
// matchExpressions is a list of label selector requirements. The requirements are ANDed.
// +optional
MatchExpressions []metav1.LabelSelectorRequirement `json:"matchExpressions,omitempty" protobuf:"bytes,2,rep,name=matchExpressions"`
}

// ScanSpec defines the desired state of Scan
type ScanSpec struct {
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
Expand All @@ -30,7 +50,7 @@ type ScanSpec struct {
// VolumeMounts allows to specify volume mounts for the scan container.
VolumeMounts []corev1.VolumeMount `json:"volumeMounts,omitempty"`

Cascades *metav1.LabelSelector `json:"cascades,omitempty"`
Cascades *CascadeSpec `json:"cascades,omitempty"`
}

// ScanStatus defines the observed state of Scan
Expand Down
Loading