From c335e98df55bc113e7460d6041249634b2742640 Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach Date: Fri, 9 Oct 2020 10:40:14 +0200 Subject: [PATCH 01/38] Initial Kubeaudit Implementation Co-authored-by: Sebastian Franz --- scanners/kubeaudit/.helmignore | 5 + scanners/kubeaudit/Chart.yaml | 20 ++ .../kubeaudit/examples/juice-shop/scan.yaml | 9 + scanners/kubeaudit/parser/Dockerfile | 4 + .../parser/__snapshots__/parser.test.js.snap | 205 ++++++++++++++++++ .../parser/__testFiles__/juice-shop.jsonl | 24 ++ scanners/kubeaudit/parser/parser.js | 112 ++++++++++ scanners/kubeaudit/parser/parser.test.js | 18 ++ scanners/kubeaudit/scanner/Dockerfile | 23 ++ scanners/kubeaudit/scanner/wrapper.sh | 1 + .../templates/kubeaudit-parse-definition.yaml | 7 + .../kubeaudit/templates/kubeaudit-rbac.yaml | 48 ++++ .../templates/kubeaudit-scan-type.yaml | 29 +++ scanners/kubeaudit/values.yaml | 8 + 14 files changed, 513 insertions(+) create mode 100644 scanners/kubeaudit/.helmignore create mode 100644 scanners/kubeaudit/Chart.yaml create mode 100644 scanners/kubeaudit/examples/juice-shop/scan.yaml create mode 100644 scanners/kubeaudit/parser/Dockerfile create mode 100644 scanners/kubeaudit/parser/__snapshots__/parser.test.js.snap create mode 100644 scanners/kubeaudit/parser/__testFiles__/juice-shop.jsonl create mode 100644 scanners/kubeaudit/parser/parser.js create mode 100644 scanners/kubeaudit/parser/parser.test.js create mode 100644 scanners/kubeaudit/scanner/Dockerfile create mode 100644 scanners/kubeaudit/scanner/wrapper.sh create mode 100644 scanners/kubeaudit/templates/kubeaudit-parse-definition.yaml create mode 100644 scanners/kubeaudit/templates/kubeaudit-rbac.yaml create mode 100644 scanners/kubeaudit/templates/kubeaudit-scan-type.yaml create mode 100644 scanners/kubeaudit/values.yaml diff --git a/scanners/kubeaudit/.helmignore b/scanners/kubeaudit/.helmignore new file mode 100644 index 0000000000..2b6e53d709 --- /dev/null +++ b/scanners/kubeaudit/.helmignore @@ -0,0 +1,5 @@ +.DS_Store + +parser/ +scanner/ +examples/ \ No newline at end of file diff --git a/scanners/kubeaudit/Chart.yaml b/scanners/kubeaudit/Chart.yaml new file mode 100644 index 0000000000..e9a07c9c49 --- /dev/null +++ b/scanners/kubeaudit/Chart.yaml @@ -0,0 +1,20 @@ +apiVersion: v2 +name: kubeaudit +description: A Helm chart for the kubeaudit security scanner that integrates with the secureCodeBox. + +type: application +version: latest +appVersion: 0.11.5 + +keywords: + - security + - kubeaudit + - scanner + - secureCodeBox +home: https://www.securecodebox.io/scanners/kubeaudit +icon: https://www.securecodebox.io/scannerIcons/kubeaudit.svg +sources: + - https://github.com/secureCodeBox/secureCodeBox +maintainers: + - name: iteratec GmbH + email: security@iteratec.com diff --git a/scanners/kubeaudit/examples/juice-shop/scan.yaml b/scanners/kubeaudit/examples/juice-shop/scan.yaml new file mode 100644 index 0000000000..fdfe3e0571 --- /dev/null +++ b/scanners/kubeaudit/examples/juice-shop/scan.yaml @@ -0,0 +1,9 @@ +apiVersion: "execution.securecodebox.io/v1" +kind: Scan +metadata: + name: "kubeaudit-juiceshop" +spec: + scanType: "kubeaudit" + parameters: + - "-n" + - "juice-shop" diff --git a/scanners/kubeaudit/parser/Dockerfile b/scanners/kubeaudit/parser/Dockerfile new file mode 100644 index 0000000000..5925068437 --- /dev/null +++ b/scanners/kubeaudit/parser/Dockerfile @@ -0,0 +1,4 @@ +ARG baseImageTag +FROM securecodebox/parser-sdk-nodejs:${baseImageTag:-latest} +WORKDIR /home/app/parser-wrapper/parser/ +COPY --chown=app:app ./parser.js ./parser.js diff --git a/scanners/kubeaudit/parser/__snapshots__/parser.test.js.snap b/scanners/kubeaudit/parser/__snapshots__/parser.test.js.snap new file mode 100644 index 0000000000..4ddc2d976d --- /dev/null +++ b/scanners/kubeaudit/parser/__snapshots__/parser.test.js.snap @@ -0,0 +1,205 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`example parser parses empty json to zero findings 1`] = ` +Array [ + Object { + "attributes": Object {}, + "category": "Automounted ServiceAccount Token", + "description": "Default service account with token mounted. automountServiceAccountToken should be set to 'false' or a non-default service account should be used.", + "location": null, + "name": "Default ServiceAccount uses Automounted Service Account Token", + "osi_layer": "NOT_APPLICABLE", + "severity": "LOW", + }, + Object { + "attributes": Object { + "capability": "AUDIT_WRITE", + "container": "juice-shop", + }, + "category": "Capability Not Dropped", + "description": "Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.", + "location": "container://juice-shop", + "name": "Capability 'AUDIT_WRITE' Not Dropped", + "osi_layer": "NOT_APPLICABLE", + "severity": "LOW", + }, + Object { + "attributes": Object { + "capability": "CHOWN", + "container": "juice-shop", + }, + "category": "Capability Not Dropped", + "description": "Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.", + "location": "container://juice-shop", + "name": "Capability 'CHOWN' Not Dropped", + "osi_layer": "NOT_APPLICABLE", + "severity": "LOW", + }, + Object { + "attributes": Object { + "capability": "DAC_OVERRIDE", + "container": "juice-shop", + }, + "category": "Capability Not Dropped", + "description": "Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.", + "location": "container://juice-shop", + "name": "Capability 'DAC_OVERRIDE' Not Dropped", + "osi_layer": "NOT_APPLICABLE", + "severity": "LOW", + }, + Object { + "attributes": Object { + "capability": "FOWNER", + "container": "juice-shop", + }, + "category": "Capability Not Dropped", + "description": "Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.", + "location": "container://juice-shop", + "name": "Capability 'FOWNER' Not Dropped", + "osi_layer": "NOT_APPLICABLE", + "severity": "LOW", + }, + Object { + "attributes": Object { + "capability": "FSETID", + "container": "juice-shop", + }, + "category": "Capability Not Dropped", + "description": "Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.", + "location": "container://juice-shop", + "name": "Capability 'FSETID' Not Dropped", + "osi_layer": "NOT_APPLICABLE", + "severity": "LOW", + }, + Object { + "attributes": Object { + "capability": "KILL", + "container": "juice-shop", + }, + "category": "Capability Not Dropped", + "description": "Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.", + "location": "container://juice-shop", + "name": "Capability 'KILL' Not Dropped", + "osi_layer": "NOT_APPLICABLE", + "severity": "LOW", + }, + Object { + "attributes": Object { + "capability": "MKNOD", + "container": "juice-shop", + }, + "category": "Capability Not Dropped", + "description": "Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.", + "location": "container://juice-shop", + "name": "Capability 'MKNOD' Not Dropped", + "osi_layer": "NOT_APPLICABLE", + "severity": "LOW", + }, + Object { + "attributes": Object { + "capability": "NET_BIND_SERVICE", + "container": "juice-shop", + }, + "category": "Capability Not Dropped", + "description": "Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.", + "location": "container://juice-shop", + "name": "Capability 'NET_BIND_SERVICE' Not Dropped", + "osi_layer": "NOT_APPLICABLE", + "severity": "LOW", + }, + Object { + "attributes": Object { + "capability": "NET_RAW", + "container": "juice-shop", + }, + "category": "Capability Not Dropped", + "description": "Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.", + "location": "container://juice-shop", + "name": "Capability 'NET_RAW' Not Dropped", + "osi_layer": "NOT_APPLICABLE", + "severity": "LOW", + }, + Object { + "attributes": Object { + "capability": "SETFCAP", + "container": "juice-shop", + }, + "category": "Capability Not Dropped", + "description": "Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.", + "location": "container://juice-shop", + "name": "Capability 'SETFCAP' Not Dropped", + "osi_layer": "NOT_APPLICABLE", + "severity": "LOW", + }, + Object { + "attributes": Object { + "capability": "SETGID", + "container": "juice-shop", + }, + "category": "Capability Not Dropped", + "description": "Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.", + "location": "container://juice-shop", + "name": "Capability 'SETGID' Not Dropped", + "osi_layer": "NOT_APPLICABLE", + "severity": "LOW", + }, + Object { + "attributes": Object { + "capability": "SETPCAP", + "container": "juice-shop", + }, + "category": "Capability Not Dropped", + "description": "Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.", + "location": "container://juice-shop", + "name": "Capability 'SETPCAP' Not Dropped", + "osi_layer": "NOT_APPLICABLE", + "severity": "LOW", + }, + Object { + "attributes": Object { + "capability": "SETUID", + "container": "juice-shop", + }, + "category": "Capability Not Dropped", + "description": "Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.", + "location": "container://juice-shop", + "name": "Capability 'SETUID' Not Dropped", + "osi_layer": "NOT_APPLICABLE", + "severity": "LOW", + }, + Object { + "attributes": Object { + "capability": "SYS_CHROOT", + "container": "juice-shop", + }, + "category": "Capability Not Dropped", + "description": "Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.", + "location": "container://juice-shop", + "name": "Capability 'SYS_CHROOT' Not Dropped", + "osi_layer": "NOT_APPLICABLE", + "severity": "LOW", + }, + Object { + "attributes": Object { + "container": "juice-shop", + }, + "category": "Non Root User Not Enforced", + "description": "runAsNonRoot is not set in container SecurityContext nor the PodSecurityContext. It should be set to 'true' in at least one of the two.", + "location": "container://juice-shop", + "name": "NonRoot User not enforced for Container", + "osi_layer": "NOT_APPLICABLE", + "severity": "MEDIUM", + }, + Object { + "attributes": Object { + "container": "juice-shop", + }, + "category": "Non ReadOnly Root Filesystem", + "description": "readOnlyRootFilesystem is not set in container SecurityContext. It should be set to 'true'.", + "location": "container://juice-shop", + "name": "Container Uses a non ReadOnly Root Filesystem", + "osi_layer": "NOT_APPLICABLE", + "severity": "LOW", + }, +] +`; diff --git a/scanners/kubeaudit/parser/__testFiles__/juice-shop.jsonl b/scanners/kubeaudit/parser/__testFiles__/juice-shop.jsonl new file mode 100644 index 0000000000..4e9dfcf0c3 --- /dev/null +++ b/scanners/kubeaudit/parser/__testFiles__/juice-shop.jsonl @@ -0,0 +1,24 @@ +time="2020-07-22T14:41:53Z" level=info msg="Running inside cluster, using the cluster config" +time="2020-07-22T14:41:53Z" level=error msg="namespaces \"juice-shop\" is forbidden: User \"system:serviceaccount:juice-shop:kube-audit-yolo\" cannot list resource \"namespaces\" in API group \"\" at the cluster scope" +{"AuditResultName":"AppArmorAnnotationMissing","Container":"juice-shop","MissingAnnotation":"container.apparmor.security.beta.kubernetes.io/juice-shop","level":"error","msg":"AppArmor annotation missing. The annotation 'container.apparmor.security.beta.kubernetes.io/juice-shop' should be added.","time":"2020-07-22T14:41:53Z"} +{"AuditResultName":"AutomountServiceAccountTokenTrueAndDefaultSA","level":"error","msg":"Default service account with token mounted. automountServiceAccountToken should be set to 'false' or a non-default service account should be used.","time":"2020-07-22T14:41:53Z"} +{"AuditResultName":"CapabilityNotDropped","Capability":"AUDIT_WRITE","Container":"juice-shop","level":"error","msg":"Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.","time":"2020-07-22T14:41:53Z"} +{"AuditResultName":"CapabilityNotDropped","Capability":"CHOWN","Container":"juice-shop","level":"error","msg":"Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.","time":"2020-07-22T14:41:53Z"} +{"AuditResultName":"CapabilityNotDropped","Capability":"DAC_OVERRIDE","Container":"juice-shop","level":"error","msg":"Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.","time":"2020-07-22T14:41:53Z"} +{"AuditResultName":"CapabilityNotDropped","Capability":"FOWNER","Container":"juice-shop","level":"error","msg":"Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.","time":"2020-07-22T14:41:53Z"} +{"AuditResultName":"CapabilityNotDropped","Capability":"FSETID","Container":"juice-shop","level":"error","msg":"Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.","time":"2020-07-22T14:41:53Z"} +{"AuditResultName":"CapabilityNotDropped","Capability":"KILL","Container":"juice-shop","level":"error","msg":"Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.","time":"2020-07-22T14:41:53Z"} +{"AuditResultName":"CapabilityNotDropped","Capability":"MKNOD","Container":"juice-shop","level":"error","msg":"Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.","time":"2020-07-22T14:41:53Z"} +{"AuditResultName":"CapabilityNotDropped","Capability":"NET_BIND_SERVICE","Container":"juice-shop","level":"error","msg":"Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.","time":"2020-07-22T14:41:53Z"} +{"AuditResultName":"CapabilityNotDropped","Capability":"NET_RAW","Container":"juice-shop","level":"error","msg":"Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.","time":"2020-07-22T14:41:53Z"} +{"AuditResultName":"CapabilityNotDropped","Capability":"SETFCAP","Container":"juice-shop","level":"error","msg":"Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.","time":"2020-07-22T14:41:53Z"} +{"AuditResultName":"CapabilityNotDropped","Capability":"SETGID","Container":"juice-shop","level":"error","msg":"Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.","time":"2020-07-22T14:41:53Z"} +{"AuditResultName":"CapabilityNotDropped","Capability":"SETPCAP","Container":"juice-shop","level":"error","msg":"Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.","time":"2020-07-22T14:41:53Z"} +{"AuditResultName":"CapabilityNotDropped","Capability":"SETUID","Container":"juice-shop","level":"error","msg":"Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.","time":"2020-07-22T14:41:53Z"} +{"AuditResultName":"CapabilityNotDropped","Capability":"SYS_CHROOT","Container":"juice-shop","level":"error","msg":"Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.","time":"2020-07-22T14:41:53Z"} +{"AuditResultName":"LimitsNotSet","Container":"juice-shop","level":"warning","msg":"Resource limits not set.","time":"2020-07-22T14:41:53Z"} +{"AuditResultName":"RunAsNonRootPSCNilCSCNil","Container":"juice-shop","level":"error","msg":"runAsNonRoot is not set in container SecurityContext nor the PodSecurityContext. It should be set to 'true' in at least one of the two.","time":"2020-07-22T14:41:53Z"} +{"AuditResultName":"AllowPrivilegeEscalationNil","Container":"juice-shop","level":"error","msg":"allowPrivilegeEscalation not set which allows privilege escalation. It should be set to 'false'.","time":"2020-07-22T14:41:53Z"} +{"AuditResultName":"PrivilegedNil","Container":"juice-shop","level":"warning","msg":"privileged is not set in container SecurityContext. Privileged defaults to 'false' but it should be explicitly set to 'false'.","time":"2020-07-22T14:41:53Z"} +{"AuditResultName":"ReadOnlyRootFilesystemNil","Container":"juice-shop","level":"error","msg":"readOnlyRootFilesystem is not set in container SecurityContext. It should be set to 'true'.","time":"2020-07-22T14:41:53Z"} +{"AuditResultName":"SeccompAnnotationMissing","MissingAnnotation":"seccomp.security.alpha.kubernetes.io/pod","level":"error","msg":"Seccomp annotation is missing. The annotation seccomp.security.alpha.kubernetes.io/pod: runtime/default should be added.","time":"2020-07-22T14:41:53Z"} \ No newline at end of file diff --git a/scanners/kubeaudit/parser/parser.js b/scanners/kubeaudit/parser/parser.js new file mode 100644 index 0000000000..6cd9363bcd --- /dev/null +++ b/scanners/kubeaudit/parser/parser.js @@ -0,0 +1,112 @@ +function createDropCapabilityFinding({ Capability, Container, msg }) { + return { + name: `Capability '${Capability}' Not Dropped`, + description: msg, + category: "Capability Not Dropped", + location: `container://${Container}`, + osi_layer: "NOT_APPLICABLE", + severity: "LOW", + attributes: { + capability: Capability, + container: Container, + }, + }; +} + +function createNonRootFsFinding({ Container, msg }) { + return { + name: `Container Uses a non ReadOnly Root Filesystem`, + description: msg, + category: "Non ReadOnly Root Filesystem", + location: `container://${Container}`, + osi_layer: "NOT_APPLICABLE", + severity: "LOW", + attributes: { + container: Container, + }, + }; +} + +function createPrivilegedContainerFinding({ Container, msg }) { + return { + name: `Container using Privileged Flag`, + description: msg, + category: "Privileged Container", + location: `container://${Container}`, + osi_layer: "NOT_APPLICABLE", + severity: "HIGH", + attributes: { + container: Container, + }, + }; +} + +function createAutomountedServiceAccountTokenFinding({ msg }) { + return { + name: `Default ServiceAccount uses Automounted Service Account Token`, + description: msg, + category: "Automounted ServiceAccount Token", + location: null, + osi_layer: "NOT_APPLICABLE", + severity: "LOW", + attributes: {}, + }; +} + +function createNonRootUserNotEnforcedFinding({ msg, Container }) { + return { + name: `NonRoot User not enforced for Container`, + description: msg, + category: "Non Root User Not Enforced", + location: `container://${Container}`, + osi_layer: "NOT_APPLICABLE", + severity: "MEDIUM", + attributes: { + container: Container, + }, + }; +} + +async function parse(fileContent) { + return fileContent + .split("\n") + .filter(Boolean) + .filter((line) => line && line.startsWith("{") && line.endsWith("}")) + .map(JSON.parse) + .map((finding) => { + if (!finding || !finding.AuditResultName) { + return null; + } + + if (finding.AuditResultName === "CapabilityNotDropped") { + return createDropCapabilityFinding(finding); + } + if ( + finding.AuditResultName === "ReadOnlyRootFilesystemFalse" || + finding.AuditResultName === "ReadOnlyRootFilesystemNil" + ) { + return createNonRootFsFinding(finding); + } + if (finding.AuditResultName === "PrivilegedTrue") { + return createPrivilegedContainerFinding(finding); + } + if ( + finding.AuditResultName === + "AutomountServiceAccountTokenTrueAndDefaultSA" + ) { + return createAutomountedServiceAccountTokenFinding(finding); + } + if ( + finding.AuditResultName === "RunAsNonRootCSCFalse" || + finding.AuditResultName === "RunAsNonRootPSCNilCSCNil" || + finding.AuditResultName === "RunAsNonRootPSCFalseCSCNil" + ) { + return createNonRootUserNotEnforcedFinding(finding); + } + + return null; + }) + .filter(Boolean); +} + +module.exports.parse = parse; diff --git a/scanners/kubeaudit/parser/parser.test.js b/scanners/kubeaudit/parser/parser.test.js new file mode 100644 index 0000000000..a6aadf6072 --- /dev/null +++ b/scanners/kubeaudit/parser/parser.test.js @@ -0,0 +1,18 @@ +const fs = require("fs"); +const util = require("util"); + +// eslint-disable-next-line security/detect-non-literal-fs-filename +const readFile = util.promisify(fs.readFile); + +const { parse } = require("./parser"); + +test("example parser parses empty json to zero findings", async () => { + const fileContent = await readFile( + __dirname + "/__testFiles__/juice-shop.jsonl", + { + encoding: "utf8", + } + ); + + expect(await parse(fileContent)).toMatchSnapshot(); +}); diff --git a/scanners/kubeaudit/scanner/Dockerfile b/scanners/kubeaudit/scanner/Dockerfile new file mode 100644 index 0000000000..87fcaf176d --- /dev/null +++ b/scanners/kubeaudit/scanner/Dockerfile @@ -0,0 +1,23 @@ +# FROM golang:1.15 as inliner +# WORKDIR /ui +# RUN go get -u github.com/Shopify/kubeaudit/... + +FROM golang:1.15.1 AS builder + +# no need to include cgo bindings +ENV CGO_ENABLED=0 GOOS=linux GOARCH=amd64 + +# this is where we build our app +WORKDIR /go/src/app/ + +RUN git clone https://github.com/Shopify/kubeaudit.git /go/src/app/ +RUN go mod download + +RUN go build -a -ldflags '-w -s -extldflags "-static"' -o /go/bin/kubeaudit ./cmd/ \ + && chmod +x /go/bin/kubeaudit + +FROM alpine:3.12 +COPY --from=builder /go/bin/kubeaudit /kubeaudit +COPY wrapper.sh /wrapper.sh +ENTRYPOINT ["/kubeaudit"] +CMD ["all"] \ No newline at end of file diff --git a/scanners/kubeaudit/scanner/wrapper.sh b/scanners/kubeaudit/scanner/wrapper.sh new file mode 100644 index 0000000000..919443fd55 --- /dev/null +++ b/scanners/kubeaudit/scanner/wrapper.sh @@ -0,0 +1 @@ +/kubeaudit $@ >/home/securecodebox/kubeaudit.jsonl \ No newline at end of file diff --git a/scanners/kubeaudit/templates/kubeaudit-parse-definition.yaml b/scanners/kubeaudit/templates/kubeaudit-parse-definition.yaml new file mode 100644 index 0000000000..4de4a9ad61 --- /dev/null +++ b/scanners/kubeaudit/templates/kubeaudit-parse-definition.yaml @@ -0,0 +1,7 @@ +apiVersion: "execution.securecodebox.io/v1" +kind: ParseDefinition +metadata: + name: "kubeaudit-jsonl" +spec: + handlesResultsType: kubeaudit-jsonl + image: "{{ .Values.parserImage.registry }}/{{ .Values.parserImage.repository }}:{{ .Values.parserImage.tag }}" diff --git a/scanners/kubeaudit/templates/kubeaudit-rbac.yaml b/scanners/kubeaudit/templates/kubeaudit-rbac.yaml new file mode 100644 index 0000000000..f2dbe0b6e6 --- /dev/null +++ b/scanners/kubeaudit/templates/kubeaudit-rbac.yaml @@ -0,0 +1,48 @@ +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: kubeaudit + namespace: {{ .Release.Namespace}} +--- +kind: Role +apiVersion: rbac.authorization.k8s.io/v1beta1 +metadata: + name: kubeaudit + namespace: {{ .Release.Namespace}} +rules: + - apiGroups: [""] + resources: + - pods + - podtemplates + - replicationcontrollers + - namespaces + verbs: ["get", "list"] + - apiGroups: ["apps"] + resources: + - daemonsets + - statefulsets + - deployments + verbs: ["get", "list"] + - apiGroups: ["batch"] + resources: + - cronjobs + verbs: ["get", "list"] + - apiGroups: ["networking"] + resources: + - networkpolicies + verbs: ["get", "list"] +--- +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1beta1 +metadata: + name: kubeaudit + namespace: {{ .Release.Namespace}} +subjects: + - kind: ServiceAccount + name: kubeaudit + namespace: {{ .Release.Namespace}} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: kubeaudit diff --git a/scanners/kubeaudit/templates/kubeaudit-scan-type.yaml b/scanners/kubeaudit/templates/kubeaudit-scan-type.yaml new file mode 100644 index 0000000000..526a6868ee --- /dev/null +++ b/scanners/kubeaudit/templates/kubeaudit-scan-type.yaml @@ -0,0 +1,29 @@ +apiVersion: "execution.securecodebox.io/v1" +kind: ScanType +metadata: + name: "kubeaudit" +spec: + extractResults: + type: kubeaudit-jsonl + location: "/home/securecodebox/kubeaudit.jsonl" + jobTemplate: + spec: + ttlSecondsAfterFinished: 10 + template: + spec: + restartPolicy: OnFailure + containers: + - name: kubeaudit + image: kubeaudit:latest + imagePullPolicy: Never + command: + - "sh" + - "/wrapper.sh" + - "all" + - "--exitcode" + - "0" + - "--format" + - "json" + resources: + {{- toYaml .Values.scannerJob.resources | nindent 16 }} + serviceAccountName: kubeaudit diff --git a/scanners/kubeaudit/values.yaml b/scanners/kubeaudit/values.yaml new file mode 100644 index 0000000000..6ab9f1cf66 --- /dev/null +++ b/scanners/kubeaudit/values.yaml @@ -0,0 +1,8 @@ +parserImage: + registry: docker.io + repository: j12934/parser-kubeaudit + # todo change to latest + tag: latest + +scannerJob: + resources: {} From d3a1ced40c0c81178081ba03928c29c509ca2948 Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach Date: Fri, 9 Oct 2020 10:44:51 +0200 Subject: [PATCH 02/38] Update kubeaudit snapshots to current kubeaudit version --- .../parser/__snapshots__/parser.test.js.snap | 2 +- .../parser/__testFiles__/juice-shop.jsonl | 47 +++++++++---------- 2 files changed, 24 insertions(+), 25 deletions(-) diff --git a/scanners/kubeaudit/parser/__snapshots__/parser.test.js.snap b/scanners/kubeaudit/parser/__snapshots__/parser.test.js.snap index 4ddc2d976d..81956096f6 100644 --- a/scanners/kubeaudit/parser/__snapshots__/parser.test.js.snap +++ b/scanners/kubeaudit/parser/__snapshots__/parser.test.js.snap @@ -5,7 +5,7 @@ Array [ Object { "attributes": Object {}, "category": "Automounted ServiceAccount Token", - "description": "Default service account with token mounted. automountServiceAccountToken should be set to 'false' or a non-default service account should be used.", + "description": "Default service account with token mounted. automountServiceAccountToken should be set to 'false' on either the ServiceAccount or on the PodSpec or a non-default service account should be used.", "location": null, "name": "Default ServiceAccount uses Automounted Service Account Token", "osi_layer": "NOT_APPLICABLE", diff --git a/scanners/kubeaudit/parser/__testFiles__/juice-shop.jsonl b/scanners/kubeaudit/parser/__testFiles__/juice-shop.jsonl index 4e9dfcf0c3..ef5cb75252 100644 --- a/scanners/kubeaudit/parser/__testFiles__/juice-shop.jsonl +++ b/scanners/kubeaudit/parser/__testFiles__/juice-shop.jsonl @@ -1,24 +1,23 @@ -time="2020-07-22T14:41:53Z" level=info msg="Running inside cluster, using the cluster config" -time="2020-07-22T14:41:53Z" level=error msg="namespaces \"juice-shop\" is forbidden: User \"system:serviceaccount:juice-shop:kube-audit-yolo\" cannot list resource \"namespaces\" in API group \"\" at the cluster scope" -{"AuditResultName":"AppArmorAnnotationMissing","Container":"juice-shop","MissingAnnotation":"container.apparmor.security.beta.kubernetes.io/juice-shop","level":"error","msg":"AppArmor annotation missing. The annotation 'container.apparmor.security.beta.kubernetes.io/juice-shop' should be added.","time":"2020-07-22T14:41:53Z"} -{"AuditResultName":"AutomountServiceAccountTokenTrueAndDefaultSA","level":"error","msg":"Default service account with token mounted. automountServiceAccountToken should be set to 'false' or a non-default service account should be used.","time":"2020-07-22T14:41:53Z"} -{"AuditResultName":"CapabilityNotDropped","Capability":"AUDIT_WRITE","Container":"juice-shop","level":"error","msg":"Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.","time":"2020-07-22T14:41:53Z"} -{"AuditResultName":"CapabilityNotDropped","Capability":"CHOWN","Container":"juice-shop","level":"error","msg":"Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.","time":"2020-07-22T14:41:53Z"} -{"AuditResultName":"CapabilityNotDropped","Capability":"DAC_OVERRIDE","Container":"juice-shop","level":"error","msg":"Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.","time":"2020-07-22T14:41:53Z"} -{"AuditResultName":"CapabilityNotDropped","Capability":"FOWNER","Container":"juice-shop","level":"error","msg":"Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.","time":"2020-07-22T14:41:53Z"} -{"AuditResultName":"CapabilityNotDropped","Capability":"FSETID","Container":"juice-shop","level":"error","msg":"Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.","time":"2020-07-22T14:41:53Z"} -{"AuditResultName":"CapabilityNotDropped","Capability":"KILL","Container":"juice-shop","level":"error","msg":"Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.","time":"2020-07-22T14:41:53Z"} -{"AuditResultName":"CapabilityNotDropped","Capability":"MKNOD","Container":"juice-shop","level":"error","msg":"Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.","time":"2020-07-22T14:41:53Z"} -{"AuditResultName":"CapabilityNotDropped","Capability":"NET_BIND_SERVICE","Container":"juice-shop","level":"error","msg":"Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.","time":"2020-07-22T14:41:53Z"} -{"AuditResultName":"CapabilityNotDropped","Capability":"NET_RAW","Container":"juice-shop","level":"error","msg":"Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.","time":"2020-07-22T14:41:53Z"} -{"AuditResultName":"CapabilityNotDropped","Capability":"SETFCAP","Container":"juice-shop","level":"error","msg":"Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.","time":"2020-07-22T14:41:53Z"} -{"AuditResultName":"CapabilityNotDropped","Capability":"SETGID","Container":"juice-shop","level":"error","msg":"Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.","time":"2020-07-22T14:41:53Z"} -{"AuditResultName":"CapabilityNotDropped","Capability":"SETPCAP","Container":"juice-shop","level":"error","msg":"Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.","time":"2020-07-22T14:41:53Z"} -{"AuditResultName":"CapabilityNotDropped","Capability":"SETUID","Container":"juice-shop","level":"error","msg":"Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.","time":"2020-07-22T14:41:53Z"} -{"AuditResultName":"CapabilityNotDropped","Capability":"SYS_CHROOT","Container":"juice-shop","level":"error","msg":"Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.","time":"2020-07-22T14:41:53Z"} -{"AuditResultName":"LimitsNotSet","Container":"juice-shop","level":"warning","msg":"Resource limits not set.","time":"2020-07-22T14:41:53Z"} -{"AuditResultName":"RunAsNonRootPSCNilCSCNil","Container":"juice-shop","level":"error","msg":"runAsNonRoot is not set in container SecurityContext nor the PodSecurityContext. It should be set to 'true' in at least one of the two.","time":"2020-07-22T14:41:53Z"} -{"AuditResultName":"AllowPrivilegeEscalationNil","Container":"juice-shop","level":"error","msg":"allowPrivilegeEscalation not set which allows privilege escalation. It should be set to 'false'.","time":"2020-07-22T14:41:53Z"} -{"AuditResultName":"PrivilegedNil","Container":"juice-shop","level":"warning","msg":"privileged is not set in container SecurityContext. Privileged defaults to 'false' but it should be explicitly set to 'false'.","time":"2020-07-22T14:41:53Z"} -{"AuditResultName":"ReadOnlyRootFilesystemNil","Container":"juice-shop","level":"error","msg":"readOnlyRootFilesystem is not set in container SecurityContext. It should be set to 'true'.","time":"2020-07-22T14:41:53Z"} -{"AuditResultName":"SeccompAnnotationMissing","MissingAnnotation":"seccomp.security.alpha.kubernetes.io/pod","level":"error","msg":"Seccomp annotation is missing. The annotation seccomp.security.alpha.kubernetes.io/pod: runtime/default should be added.","time":"2020-07-22T14:41:53Z"} \ No newline at end of file +{"AuditResultName":"AppArmorAnnotationMissing","Container":"juice-shop","MissingAnnotation":"container.apparmor.security.beta.kubernetes.io/juice-shop","ResourceApiVersion":"apps/v1","ResourceKind":"Deployment","ResourceName":"juice-shop","ResourceNamespace":"default","level":"error","msg":"AppArmor annotation missing. The annotation 'container.apparmor.security.beta.kubernetes.io/juice-shop' should be added.","time":"2020-10-09T08:32:57Z"} +{"AuditResultName":"AutomountServiceAccountTokenTrueAndDefaultSA","ResourceApiVersion":"apps/v1","ResourceKind":"Deployment","ResourceName":"juice-shop","ResourceNamespace":"default","level":"error","msg":"Default service account with token mounted. automountServiceAccountToken should be set to 'false' on either the ServiceAccount or on the PodSpec or a non-default service account should be used.","time":"2020-10-09T08:32:57Z"} +{"AuditResultName":"CapabilityNotDropped","Capability":"AUDIT_WRITE","Container":"juice-shop","ResourceApiVersion":"apps/v1","ResourceKind":"Deployment","ResourceName":"juice-shop","ResourceNamespace":"default","level":"error","msg":"Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.","time":"2020-10-09T08:32:57Z"} +{"AuditResultName":"CapabilityNotDropped","Capability":"CHOWN","Container":"juice-shop","ResourceApiVersion":"apps/v1","ResourceKind":"Deployment","ResourceName":"juice-shop","ResourceNamespace":"default","level":"error","msg":"Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.","time":"2020-10-09T08:32:57Z"} +{"AuditResultName":"CapabilityNotDropped","Capability":"DAC_OVERRIDE","Container":"juice-shop","ResourceApiVersion":"apps/v1","ResourceKind":"Deployment","ResourceName":"juice-shop","ResourceNamespace":"default","level":"error","msg":"Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.","time":"2020-10-09T08:32:57Z"} +{"AuditResultName":"CapabilityNotDropped","Capability":"FOWNER","Container":"juice-shop","ResourceApiVersion":"apps/v1","ResourceKind":"Deployment","ResourceName":"juice-shop","ResourceNamespace":"default","level":"error","msg":"Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.","time":"2020-10-09T08:32:57Z"} +{"AuditResultName":"CapabilityNotDropped","Capability":"FSETID","Container":"juice-shop","ResourceApiVersion":"apps/v1","ResourceKind":"Deployment","ResourceName":"juice-shop","ResourceNamespace":"default","level":"error","msg":"Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.","time":"2020-10-09T08:32:57Z"} +{"AuditResultName":"CapabilityNotDropped","Capability":"KILL","Container":"juice-shop","ResourceApiVersion":"apps/v1","ResourceKind":"Deployment","ResourceName":"juice-shop","ResourceNamespace":"default","level":"error","msg":"Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.","time":"2020-10-09T08:32:57Z"} +{"AuditResultName":"CapabilityNotDropped","Capability":"MKNOD","Container":"juice-shop","ResourceApiVersion":"apps/v1","ResourceKind":"Deployment","ResourceName":"juice-shop","ResourceNamespace":"default","level":"error","msg":"Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.","time":"2020-10-09T08:32:57Z"} +{"AuditResultName":"CapabilityNotDropped","Capability":"NET_BIND_SERVICE","Container":"juice-shop","ResourceApiVersion":"apps/v1","ResourceKind":"Deployment","ResourceName":"juice-shop","ResourceNamespace":"default","level":"error","msg":"Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.","time":"2020-10-09T08:32:57Z"} +{"AuditResultName":"CapabilityNotDropped","Capability":"NET_RAW","Container":"juice-shop","ResourceApiVersion":"apps/v1","ResourceKind":"Deployment","ResourceName":"juice-shop","ResourceNamespace":"default","level":"error","msg":"Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.","time":"2020-10-09T08:32:57Z"} +{"AuditResultName":"CapabilityNotDropped","Capability":"SETFCAP","Container":"juice-shop","ResourceApiVersion":"apps/v1","ResourceKind":"Deployment","ResourceName":"juice-shop","ResourceNamespace":"default","level":"error","msg":"Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.","time":"2020-10-09T08:32:57Z"} +{"AuditResultName":"CapabilityNotDropped","Capability":"SETGID","Container":"juice-shop","ResourceApiVersion":"apps/v1","ResourceKind":"Deployment","ResourceName":"juice-shop","ResourceNamespace":"default","level":"error","msg":"Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.","time":"2020-10-09T08:32:57Z"} +{"AuditResultName":"CapabilityNotDropped","Capability":"SETPCAP","Container":"juice-shop","ResourceApiVersion":"apps/v1","ResourceKind":"Deployment","ResourceName":"juice-shop","ResourceNamespace":"default","level":"error","msg":"Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.","time":"2020-10-09T08:32:57Z"} +{"AuditResultName":"CapabilityNotDropped","Capability":"SETUID","Container":"juice-shop","ResourceApiVersion":"apps/v1","ResourceKind":"Deployment","ResourceName":"juice-shop","ResourceNamespace":"default","level":"error","msg":"Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.","time":"2020-10-09T08:32:57Z"} +{"AuditResultName":"CapabilityNotDropped","Capability":"SYS_CHROOT","Container":"juice-shop","ResourceApiVersion":"apps/v1","ResourceKind":"Deployment","ResourceName":"juice-shop","ResourceNamespace":"default","level":"error","msg":"Capability not dropped. Ideally, the capability drop list should include the single capability 'ALL' which drops all capabilities.","time":"2020-10-09T08:32:57Z"} +{"AuditResultName":"LimitsNotSet","Container":"juice-shop","ResourceApiVersion":"apps/v1","ResourceKind":"Deployment","ResourceName":"juice-shop","ResourceNamespace":"default","level":"warning","msg":"Resource limits not set.","time":"2020-10-09T08:32:57Z"} +{"AuditResultName":"RunAsNonRootPSCNilCSCNil","Container":"juice-shop","ResourceApiVersion":"apps/v1","ResourceKind":"Deployment","ResourceName":"juice-shop","ResourceNamespace":"default","level":"error","msg":"runAsNonRoot is not set in container SecurityContext nor the PodSecurityContext. It should be set to 'true' in at least one of the two.","time":"2020-10-09T08:32:57Z"} +{"AuditResultName":"AllowPrivilegeEscalationNil","Container":"juice-shop","ResourceApiVersion":"apps/v1","ResourceKind":"Deployment","ResourceName":"juice-shop","ResourceNamespace":"default","level":"error","msg":"allowPrivilegeEscalation not set which allows privilege escalation. It should be set to 'false'.","time":"2020-10-09T08:32:57Z"} +{"AuditResultName":"PrivilegedNil","Container":"juice-shop","ResourceApiVersion":"apps/v1","ResourceKind":"Deployment","ResourceName":"juice-shop","ResourceNamespace":"default","level":"warning","msg":"privileged is not set in container SecurityContext. Privileged defaults to 'false' but it should be explicitly set to 'false'.","time":"2020-10-09T08:32:57Z"} +{"AuditResultName":"ReadOnlyRootFilesystemNil","Container":"juice-shop","ResourceApiVersion":"apps/v1","ResourceKind":"Deployment","ResourceName":"juice-shop","ResourceNamespace":"default","level":"error","msg":"readOnlyRootFilesystem is not set in container SecurityContext. It should be set to 'true'.","time":"2020-10-09T08:32:57Z"} +{"AuditResultName":"SeccompAnnotationMissing","MissingAnnotation":"seccomp.security.alpha.kubernetes.io/pod","ResourceApiVersion":"apps/v1","ResourceKind":"Deployment","ResourceName":"juice-shop","ResourceNamespace":"default","level":"error","msg":"Seccomp annotation is missing. The annotation seccomp.security.alpha.kubernetes.io/pod: runtime/default should be added.","time":"2020-10-09T08:32:57Z"} +{"AuditResultName":"MissingDefaultDenyIngressAndEgressNetworkPolicy","Namespace":"default","ResourceApiVersion":"v1","ResourceKind":"Namespace","ResourceName":"default","level":"error","msg":"Namespace is missing a default deny ingress and egress NetworkPolicy.","time":"2020-10-09T08:32:57Z"} From 50723e2556de69238b04f31e537c22982fb5be70 Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach Date: Fri, 9 Oct 2020 10:49:52 +0200 Subject: [PATCH 03/38] Build kubeaudit scanner image on ci --- .github/workflows/ci.yaml | 8 ++++++++ scanners/kubeaudit/Chart.yaml | 2 +- scanners/kubeaudit/templates/kubeaudit-scan-type.yaml | 3 +-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 7f176b44c1..fae464a5ba 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -327,6 +327,14 @@ jobs: path: ./scanners/kube-hunter/scanner/ # Note: not prefixed with a "v" as this matches the aquasec/kube-hunter tags tags: "0.3.0,latest" + - uses: docker/build-push-action@v1 + name: "Build & Push kubeaudit Scanner Image" + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + repository: securecodebox/scanner-kubeaudit + path: ./scanners/kubeaudit/scanner/ + tags: "v0.11.5,latest" - uses: docker/build-push-action@v1 name: "Build & Push test-scan Scanner Image" with: diff --git a/scanners/kubeaudit/Chart.yaml b/scanners/kubeaudit/Chart.yaml index e9a07c9c49..8c830eb62a 100644 --- a/scanners/kubeaudit/Chart.yaml +++ b/scanners/kubeaudit/Chart.yaml @@ -4,7 +4,7 @@ description: A Helm chart for the kubeaudit security scanner that integrates wit type: application version: latest -appVersion: 0.11.5 +appVersion: "v0.11.5" keywords: - security diff --git a/scanners/kubeaudit/templates/kubeaudit-scan-type.yaml b/scanners/kubeaudit/templates/kubeaudit-scan-type.yaml index 526a6868ee..44489a6aa8 100644 --- a/scanners/kubeaudit/templates/kubeaudit-scan-type.yaml +++ b/scanners/kubeaudit/templates/kubeaudit-scan-type.yaml @@ -14,8 +14,7 @@ spec: restartPolicy: OnFailure containers: - name: kubeaudit - image: kubeaudit:latest - imagePullPolicy: Never + image: "securecodebox/scanner-kubeaudit:{{ .Chart.AppVersion }}" command: - "sh" - "/wrapper.sh" From f55ffdd9a3f57d9f7b1717feac92a3e33f5fdc1a Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach Date: Fri, 9 Oct 2020 10:51:04 +0200 Subject: [PATCH 04/38] Build and use kubeaudit image on ci --- .../templates/kubeaudit-parse-definition.yaml | 2 +- scanners/kubeaudit/values.yaml | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/scanners/kubeaudit/templates/kubeaudit-parse-definition.yaml b/scanners/kubeaudit/templates/kubeaudit-parse-definition.yaml index 4de4a9ad61..808fd2d3c8 100644 --- a/scanners/kubeaudit/templates/kubeaudit-parse-definition.yaml +++ b/scanners/kubeaudit/templates/kubeaudit-parse-definition.yaml @@ -4,4 +4,4 @@ metadata: name: "kubeaudit-jsonl" spec: handlesResultsType: kubeaudit-jsonl - image: "{{ .Values.parserImage.registry }}/{{ .Values.parserImage.repository }}:{{ .Values.parserImage.tag }}" + image: "{{ .Values.parserImage.repository }}:{{ .Values.parserImage.tag | default .Chart.Version }}" diff --git a/scanners/kubeaudit/values.yaml b/scanners/kubeaudit/values.yaml index 6ab9f1cf66..aaf3bad98d 100644 --- a/scanners/kubeaudit/values.yaml +++ b/scanners/kubeaudit/values.yaml @@ -1,8 +1,10 @@ parserImage: - registry: docker.io - repository: j12934/parser-kubeaudit - # todo change to latest - tag: latest + # parserImage.tag - defaults to the charts version + # parserImage.repository -- Parser image repository + repository: docker.io/securecodebox/parser-kubeaudit + # parserImage.tag -- Parser image tag + # @default -- defaults to the charts version + tag: null scannerJob: resources: {} From c610a39103bd40d9856512a3b2e829c6e0b776fe Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach Date: Fri, 9 Oct 2020 10:53:34 +0200 Subject: [PATCH 05/38] Add securityContexts and other additional Configs --- .../templates/kubeaudit-scan-type.yaml | 11 ++++++ scanners/kubeaudit/values.yaml | 38 +++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/scanners/kubeaudit/templates/kubeaudit-scan-type.yaml b/scanners/kubeaudit/templates/kubeaudit-scan-type.yaml index 44489a6aa8..49d0122b2a 100644 --- a/scanners/kubeaudit/templates/kubeaudit-scan-type.yaml +++ b/scanners/kubeaudit/templates/kubeaudit-scan-type.yaml @@ -25,4 +25,15 @@ spec: - "json" resources: {{- toYaml .Values.scannerJob.resources | nindent 16 }} + securityContext: + {{- toYaml .Values.scannerJob.securityContext | nindent 16 }} + env: + {{- toYaml .Values.scannerJob.env | nindent 16 }} + volumeMounts: + {{- toYaml .Values.scannerJob.extraVolumeMounts | nindent 16 }} + {{- if .Values.scannerJob.extraContainers }} + {{- toYaml .Values.scannerJob.extraContainers | nindent 12 }} + {{- end }} + volumes: + {{- toYaml .Values.scannerJob.extraVolumeMounts | nindent 12 }} serviceAccountName: kubeaudit diff --git a/scanners/kubeaudit/values.yaml b/scanners/kubeaudit/values.yaml index aaf3bad98d..d011e03366 100644 --- a/scanners/kubeaudit/values.yaml +++ b/scanners/kubeaudit/values.yaml @@ -7,4 +7,42 @@ parserImage: tag: null scannerJob: + # scannerJob.ttlSecondsAfterFinished -- Defines how long the scanner job after finishing will be available (see: https://kubernetes.io/docs/concepts/workloads/controllers/ttlafterfinished/) + ttlSecondsAfterFinished: null + + # scannerJob.resources -- CPU/memory resource requests/limits (see: https://kubernetes.io/docs/tasks/configure-pod-container/assign-memory-resource/, https://kubernetes.io/docs/tasks/configure-pod-container/assign-cpu-resource/) resources: {} + # resources: + # requests: + # memory: "256Mi" + # cpu: "250m" + # limits: + # memory: "512Mi" + # cpu: "500m" + + # scannerJob.env -- Optional environment variables mapped into each scanJob (see: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) + env: [] + + # scannerJob.extraVolumes -- Optional Volumes mapped into each scanJob (see: https://kubernetes.io/docs/concepts/storage/volumes/) + extraVolumes: [] + + # scannerJob.extraVolumeMounts -- Optional VolumeMounts mapped into each scanJob (see: https://kubernetes.io/docs/concepts/storage/volumes/) + extraVolumeMounts: [] + + # scannerJob.extraContainers -- Optional additional Containers started with each scanJob (see: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/) + extraContainers: [] + + # scannerJob.securityContext -- Optional securityContext set on scanner container (see: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/) + securityContext: + # scannerJob.securityContext.runAsNonRoot -- Enforces that the scanner image is run as a non root user + runAsNonRoot: true + # scannerJob.securityContext.readOnlyRootFilesystem -- Prevents write access to the containers file system + readOnlyRootFilesystem: true + # scannerJob.securityContext.allowPrivilegeEscalation -- Ensure that users privileges cannot be escalated + allowPrivilegeEscalation: false + # scannerJob.securityContext.privileged -- Ensures that the scanner container is not run in privileged mode + privileged: false + capabilities: + drop: + # scannerJob.securityContext.capabilities.drop[0] -- This drops all linux privileges from the container. + - all From 95ec9deed31654035d1521f1c8373e965e0cb897 Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach Date: Fri, 9 Oct 2020 10:54:04 +0200 Subject: [PATCH 06/38] Update email address --- scanners/kubeaudit/Chart.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scanners/kubeaudit/Chart.yaml b/scanners/kubeaudit/Chart.yaml index 8c830eb62a..46538abc7a 100644 --- a/scanners/kubeaudit/Chart.yaml +++ b/scanners/kubeaudit/Chart.yaml @@ -17,4 +17,4 @@ sources: - https://github.com/secureCodeBox/secureCodeBox maintainers: - name: iteratec GmbH - email: security@iteratec.com + email: secureCodeBox@iteratec.com From ce8f05ab061ffbda0a0652c1c1f4685984feed08 Mon Sep 17 00:00:00 2001 From: J12934 Date: Fri, 9 Oct 2020 10:44:04 +0000 Subject: [PATCH 07/38] Updating Helm Docs --- scanners/kubeaudit/README.md | 46 ++++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/scanners/kubeaudit/README.md b/scanners/kubeaudit/README.md index 38177cbf2d..2f0b38dc76 100644 --- a/scanners/kubeaudit/README.md +++ b/scanners/kubeaudit/README.md @@ -1,20 +1,36 @@ ---- -title: "kubeaudit" -path: "scanners/kubeaudit" -category: "scanner" -type: "Kubernetes" -state: "roadmap" -appVersion: "0.9.0" -usecase: "Audit your Kubernetes clusters" ---- +# kubeaudit -kubeaudit helps you audit your Kubernetes clusters against common security controls. +![Version: latest](https://img.shields.io/badge/Version-latest-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: v0.11.5](https://img.shields.io/badge/AppVersion-v0.11.5-informational?style=flat-square) -To learn more about the kubeaudit scanner itself visit [kubeaudit GitHub]. +A Helm chart for the kubeaudit security scanner that integrates with the secureCodeBox. - +**Homepage:** -> 🔧 The secureCodeBox core team is working on an integration of kubeaudit. We will keep you informed. +## Maintainers -[kubeaudit GitHub]: https://github.com/Shopify/kubeaudit -[kubeaudit Documentation]: https://github.com/Shopify/kubeaudit#quick-start +| Name | Email | Url | +| ---- | ------ | --- | +| iteratec GmbH | secureCodeBox@iteratec.com | | + +## Source Code + +* + +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| parserImage.repository | string | `"docker.io/securecodebox/parser-kubeaudit"` | Parser image repository | +| parserImage.tag | string | defaults to the charts version | Parser image tag | +| scannerJob.env | list | `[]` | Optional environment variables mapped into each scanJob (see: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) | +| scannerJob.extraContainers | list | `[]` | Optional additional Containers started with each scanJob (see: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/) | +| scannerJob.extraVolumeMounts | list | `[]` | Optional VolumeMounts mapped into each scanJob (see: https://kubernetes.io/docs/concepts/storage/volumes/) | +| scannerJob.extraVolumes | list | `[]` | Optional Volumes mapped into each scanJob (see: https://kubernetes.io/docs/concepts/storage/volumes/) | +| scannerJob.resources | object | `{}` | CPU/memory resource requests/limits (see: https://kubernetes.io/docs/tasks/configure-pod-container/assign-memory-resource/, https://kubernetes.io/docs/tasks/configure-pod-container/assign-cpu-resource/) | +| scannerJob.securityContext | object | `{"allowPrivilegeEscalation":false,"capabilities":{"drop":["all"]},"privileged":false,"readOnlyRootFilesystem":true,"runAsNonRoot":true}` | Optional securityContext set on scanner container (see: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/) | +| scannerJob.securityContext.allowPrivilegeEscalation | bool | `false` | Ensure that users privileges cannot be escalated | +| scannerJob.securityContext.capabilities.drop[0] | string | `"all"` | This drops all linux privileges from the container. | +| scannerJob.securityContext.privileged | bool | `false` | Ensures that the scanner container is not run in privileged mode | +| scannerJob.securityContext.readOnlyRootFilesystem | bool | `true` | Prevents write access to the containers file system | +| scannerJob.securityContext.runAsNonRoot | bool | `true` | Enforces that the scanner image is run as a non root user | +| scannerJob.ttlSecondsAfterFinished | string | `nil` | Defines how long the scanner job after finishing will be available (see: https://kubernetes.io/docs/concepts/workloads/controllers/ttlafterfinished/) | From a6bf455a7b7af831a8d50b5f4d27e9a19e1644ee Mon Sep 17 00:00:00 2001 From: SebieF Date: Mon, 12 Oct 2020 10:51:39 +0200 Subject: [PATCH 08/38] Dockerfiles Updated --- scanners/kubeaudit/parser/Dockerfile | 7 +++++++ scanners/kubeaudit/scanner/Dockerfile | 4 +++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/scanners/kubeaudit/parser/Dockerfile b/scanners/kubeaudit/parser/Dockerfile index 5925068437..c53ee60cea 100644 --- a/scanners/kubeaudit/parser/Dockerfile +++ b/scanners/kubeaudit/parser/Dockerfile @@ -1,4 +1,11 @@ ARG baseImageTag +FROM node:12-alpine as build +RUN mkdir -p /home/app +WORKDIR /home/app +COPY package.json package-lock.json ./ +RUN npm ci --production + FROM securecodebox/parser-sdk-nodejs:${baseImageTag:-latest} WORKDIR /home/app/parser-wrapper/parser/ +COPY --from=build --chown=app:app /home/app/node_modules/ ./node_modules/ COPY --chown=app:app ./parser.js ./parser.js diff --git a/scanners/kubeaudit/scanner/Dockerfile b/scanners/kubeaudit/scanner/Dockerfile index 87fcaf176d..5eede33f16 100644 --- a/scanners/kubeaudit/scanner/Dockerfile +++ b/scanners/kubeaudit/scanner/Dockerfile @@ -19,5 +19,7 @@ RUN go build -a -ldflags '-w -s -extldflags "-static"' -o /go/bin/kubeaudit ./cm FROM alpine:3.12 COPY --from=builder /go/bin/kubeaudit /kubeaudit COPY wrapper.sh /wrapper.sh +RUN addgroup --system --gid 1001 kubeaudit && adduser kubeaudit --system --uid 1001 --ingroup kubeaudit +USER 1001 ENTRYPOINT ["/kubeaudit"] -CMD ["all"] \ No newline at end of file +CMD ["all"] From 10a418ae3d9db4325cd970f05a9f31fa3b89946e Mon Sep 17 00:00:00 2001 From: SebieF Date: Mon, 12 Oct 2020 10:51:59 +0200 Subject: [PATCH 09/38] Integrated Integration Test (wip) --- .github/workflows/ci.yaml | 9 ++++++++- tests/integration/scanner/kubeaudit.test.js | 20 ++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 tests/integration/scanner/kubeaudit.test.js diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index fae464a5ba..00b2dbf168 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -451,7 +451,14 @@ jobs: --set="parserImage.tag=sha-$(git rev-parse --short HEAD)" \ --set="image.tag=0.3.0" cd tests/integration/ - npx jest --ci --color kube-hunter + npx jest --ci --color kubeaudit + - name: "kubeaudit Integration Tests" + run: | + helm -n integration-tests install kubeaudit ./scanners/kubeaudit/ \ + --set="parserImage.tag=sha-$(git rev-parse --short HEAD)" \ + --set="image.tag=0.3.0" + cd tests/integration/ + npx jest --ci --color kube-hunter - name: "ssh-scan Integration Tests" run: | helm -n integration-tests install ssh-scan ./scanners/ssh_scan/ --set="parserImage.tag=sha-$(git rev-parse --short HEAD)" diff --git a/tests/integration/scanner/kubeaudit.test.js b/tests/integration/scanner/kubeaudit.test.js new file mode 100644 index 0000000000..70a078d308 --- /dev/null +++ b/tests/integration/scanner/kubeaudit.test.js @@ -0,0 +1,20 @@ +const { scan } = require("../helpers"); + +test( + "kubeaudit should find a fixed number of findings for the the juice-shop", + async () => { + const { categories, severities, count } = await scan( + "kubeaudit-jshop", + "kubeaudit", + ["-n", "juice-shop"], + 15 + ); + console.log(categories) + console.log(severities) + console.log(count) + // If we got here the scan succeded + // as the number of findings will depend on the cluster, we just check if it is defined at all + expect(true).toBe(true); + }, + 5 * 60 * 1000 +); From b2ae1e5cf5bf826475baf25592b2939341d4418d Mon Sep 17 00:00:00 2001 From: SebieF Date: Mon, 12 Oct 2020 11:40:59 +0200 Subject: [PATCH 10/38] Allow default Service Account --- .../controllers/execution/scans/scan_reconciler.go | 4 +++- scanners/kubeaudit/templates/kubeaudit-rbac.yaml | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/operator/controllers/execution/scans/scan_reconciler.go b/operator/controllers/execution/scans/scan_reconciler.go index 0117f1d496..67272fe248 100644 --- a/operator/controllers/execution/scans/scan_reconciler.go +++ b/operator/controllers/execution/scans/scan_reconciler.go @@ -176,7 +176,9 @@ func (r *ScanReconciler) constructJobForScan(scan *executionv1.Scan, scanType *e podAnnotations["sidecar.istio.io/inject"] = "false" job.Spec.Template.Annotations = podAnnotations - job.Spec.Template.Spec.ServiceAccountName = "lurcher" + if job.Spec.Template.Spec.ServiceAccountName == "" { + job.Spec.Template.Spec.ServiceAccountName = "lurcher" + } // merging volume definition from ScanType (if existing) with standard results volume if job.Spec.Template.Spec.Containers[0].VolumeMounts == nil || len(job.Spec.Template.Spec.Containers[0].VolumeMounts) == 0 { diff --git a/scanners/kubeaudit/templates/kubeaudit-rbac.yaml b/scanners/kubeaudit/templates/kubeaudit-rbac.yaml index f2dbe0b6e6..38fe43fc1b 100644 --- a/scanners/kubeaudit/templates/kubeaudit-rbac.yaml +++ b/scanners/kubeaudit/templates/kubeaudit-rbac.yaml @@ -46,3 +46,17 @@ roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: kubeaudit +--- +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1beta1 +metadata: + name: kubeaudit-lurcher + namespace: {{ .Release.Namespace}} +subjects: + - kind: ServiceAccount + name: kubeaudit + namespace: {{ .Release.Namespace}} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: lurcher From 03637cfc5d16dd691c893ab73b062406fc1fa860 Mon Sep 17 00:00:00 2001 From: SebieF Date: Mon, 12 Oct 2020 11:44:14 +0200 Subject: [PATCH 11/38] CI Fix --- .github/workflows/ci.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 00b2dbf168..8e9350df99 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -451,14 +451,14 @@ jobs: --set="parserImage.tag=sha-$(git rev-parse --short HEAD)" \ --set="image.tag=0.3.0" cd tests/integration/ - npx jest --ci --color kubeaudit + npx jest --ci --color kube-hunter - name: "kubeaudit Integration Tests" run: | helm -n integration-tests install kubeaudit ./scanners/kubeaudit/ \ --set="parserImage.tag=sha-$(git rev-parse --short HEAD)" \ - --set="image.tag=0.3.0" + --set="image.tag=0.11.5" cd tests/integration/ - npx jest --ci --color kube-hunter + npx jest --ci --color kubeaudit - name: "ssh-scan Integration Tests" run: | helm -n integration-tests install ssh-scan ./scanners/ssh_scan/ --set="parserImage.tag=sha-$(git rev-parse --short HEAD)" From 0f5d69a2b88a3ef93768f41f6d94f4e16d0d94be Mon Sep 17 00:00:00 2001 From: SebieF Date: Mon, 12 Oct 2020 13:38:24 +0200 Subject: [PATCH 12/38] Docu fixes --- tests/integration/helpers.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/integration/helpers.js b/tests/integration/helpers.js index 4c9e7663db..c446865cc4 100644 --- a/tests/integration/helpers.js +++ b/tests/integration/helpers.js @@ -102,7 +102,7 @@ async function disasterRecovery(scanName) { /** * - * @param {string} name name of the scan. Actual name will be sufixed with a random number to avoid conflicts + * @param {string} name name of the scan. Actual name will be suffixed with a random number to avoid conflicts * @param {string} scanType type of the scan. Must match the name of a ScanType CRD * @param {string[]} parameters cli argument to be passed to the scanner * @param {number} timeout in seconds @@ -114,7 +114,7 @@ async function scan(name, scanType, parameters = [], timeout = 180) { apiVersion: "execution.securecodebox.io/v1", kind: "Scan", metadata: { - // Use `generateName` instead of name to generate a random sufix and avoid name clashes + // Use `generateName` instead of name to generate a random suffix and avoid name clashes generateName: `${name}-`, }, spec: { @@ -163,6 +163,8 @@ async function scan(name, scanType, parameters = [], timeout = 180) { * @param {string} name name of the scan. Actual name will be sufixed with a random number to avoid conflicts * @param {string} scanType type of the scan. Must match the name of a ScanType CRD * @param {string[]} parameters cli argument to be passed to the scanner + * @param {string} nameCascade name of cascading scan + * @param {object} matchLabels set invasive and intensive of cascading scan * @param {number} timeout in seconds * @returns {scan.findings} returns findings { categories, severities, count } */ From 10dd3e70728880f61abeaa04d142cbcf3427756f Mon Sep 17 00:00:00 2001 From: SebieF Date: Mon, 12 Oct 2020 13:38:40 +0200 Subject: [PATCH 13/38] Integration test done --- tests/integration/scanner/kubeaudit.test.js | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/tests/integration/scanner/kubeaudit.test.js b/tests/integration/scanner/kubeaudit.test.js index 70a078d308..6096c419aa 100644 --- a/tests/integration/scanner/kubeaudit.test.js +++ b/tests/integration/scanner/kubeaudit.test.js @@ -1,19 +1,16 @@ const { scan } = require("../helpers"); test( - "kubeaudit should find a fixed number of findings for the the juice-shop", + "kubeaudit should run and check our integration-tests namespace", async () => { - const { categories, severities, count } = await scan( - "kubeaudit-jshop", + const { _ } = await scan( + "kubeaudit-test", "kubeaudit", - ["-n", "juice-shop"], - 15 + ["-n", "integration-tests"], + 20 ); - console.log(categories) - console.log(severities) - console.log(count) + // If we got here the scan succeded - // as the number of findings will depend on the cluster, we just check if it is defined at all expect(true).toBe(true); }, 5 * 60 * 1000 From 900a9e35fb28c76fadf94dbf30e1167ebd092f1f Mon Sep 17 00:00:00 2001 From: SebieF Date: Mon, 12 Oct 2020 13:49:32 +0200 Subject: [PATCH 14/38] Integration Test FIx --- tests/integration/scanner/kubeaudit.test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/integration/scanner/kubeaudit.test.js b/tests/integration/scanner/kubeaudit.test.js index 6096c419aa..cad0d07a62 100644 --- a/tests/integration/scanner/kubeaudit.test.js +++ b/tests/integration/scanner/kubeaudit.test.js @@ -3,7 +3,7 @@ const { scan } = require("../helpers"); test( "kubeaudit should run and check our integration-tests namespace", async () => { - const { _ } = await scan( + await scan( "kubeaudit-test", "kubeaudit", ["-n", "integration-tests"], @@ -11,6 +11,7 @@ test( ); // If we got here the scan succeded + expect() expect(true).toBe(true); }, 5 * 60 * 1000 From e68cebf378bc4f6919e07d51c97028703832ffa4 Mon Sep 17 00:00:00 2001 From: SebieF Date: Mon, 12 Oct 2020 13:52:57 +0200 Subject: [PATCH 15/38] Integration Test Fix 2 --- tests/integration/scanner/kubeaudit.test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/integration/scanner/kubeaudit.test.js b/tests/integration/scanner/kubeaudit.test.js index cad0d07a62..6cdc790ac5 100644 --- a/tests/integration/scanner/kubeaudit.test.js +++ b/tests/integration/scanner/kubeaudit.test.js @@ -11,7 +11,6 @@ test( ); // If we got here the scan succeded - expect() expect(true).toBe(true); }, 5 * 60 * 1000 From 0ff372cfc87f1f34df0c1a261577dc0228ff5913 Mon Sep 17 00:00:00 2001 From: SebieF Date: Mon, 12 Oct 2020 14:06:32 +0200 Subject: [PATCH 16/38] Increase timeout time --- tests/integration/scanner/kubeaudit.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/scanner/kubeaudit.test.js b/tests/integration/scanner/kubeaudit.test.js index 6cdc790ac5..6107558c01 100644 --- a/tests/integration/scanner/kubeaudit.test.js +++ b/tests/integration/scanner/kubeaudit.test.js @@ -7,7 +7,7 @@ test( "kubeaudit-test", "kubeaudit", ["-n", "integration-tests"], - 20 + 60 ); // If we got here the scan succeded From 396cc97449be63e49d5c914febf907a450185121 Mon Sep 17 00:00:00 2001 From: SebieF Date: Mon, 12 Oct 2020 16:26:56 +0200 Subject: [PATCH 17/38] New ClusterRole Service Account and fixed test --- .github/workflows/ci.yaml | 8 ++- .../kubeaudit/templates/kubeaudit-rbac.yaml | 53 +++++++++++++++++-- scanners/kubeaudit/values.yaml | 3 ++ tests/integration/scanner/kubeaudit.test.js | 24 ++++++--- 4 files changed, 75 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 8e9350df99..1f86729dba 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -454,11 +454,15 @@ jobs: npx jest --ci --color kube-hunter - name: "kubeaudit Integration Tests" run: | + kubectl create namespace kubeaudit-test + helm -n kubeaudit-test install juice-shop ./demo-apps/juice-shop/ --wait helm -n integration-tests install kubeaudit ./scanners/kubeaudit/ \ --set="parserImage.tag=sha-$(git rev-parse --short HEAD)" \ - --set="image.tag=0.11.5" + --set="image.tag=0.11.5" \ + --set="kubeauditScope=cluster" cd tests/integration/ - npx jest --ci --color kubeaudit + npx jest --ci --color kubeaudit + kubectl delete namespace kubeaudit-test - name: "ssh-scan Integration Tests" run: | helm -n integration-tests install ssh-scan ./scanners/ssh_scan/ --set="parserImage.tag=sha-$(git rev-parse --short HEAD)" diff --git a/scanners/kubeaudit/templates/kubeaudit-rbac.yaml b/scanners/kubeaudit/templates/kubeaudit-rbac.yaml index 38fe43fc1b..53d580a48e 100644 --- a/scanners/kubeaudit/templates/kubeaudit-rbac.yaml +++ b/scanners/kubeaudit/templates/kubeaudit-rbac.yaml @@ -5,6 +5,21 @@ metadata: name: kubeaudit namespace: {{ .Release.Namespace}} --- +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1beta1 +metadata: + name: kubeaudit-lurcher + namespace: {{ .Release.Namespace}} +subjects: + - kind: ServiceAccount + name: kubeaudit + namespace: {{ .Release.Namespace}} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: lurcher +--- +{{- if eq .Values.kubeauditScope "namespace" }} kind: Role apiVersion: rbac.authorization.k8s.io/v1beta1 metadata: @@ -46,17 +61,45 @@ roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: kubeaudit +{{- end }} +{{- if eq .Values.kubeauditScope "cluster" }} +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1beta1 +metadata: + name: kubeaudit +rules: + - apiGroups: [""] + resources: + - pods + - podtemplates + - replicationcontrollers + - namespaces + verbs: ["get", "list"] + - apiGroups: ["apps"] + resources: + - daemonsets + - statefulsets + - deployments + verbs: ["get", "list"] + - apiGroups: ["batch"] + resources: + - cronjobs + verbs: ["get", "list"] + - apiGroups: ["networking"] + resources: + - networkpolicies + verbs: ["get", "list"] --- -kind: RoleBinding +kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1beta1 metadata: - name: kubeaudit-lurcher - namespace: {{ .Release.Namespace}} + name: kubeaudit subjects: - kind: ServiceAccount name: kubeaudit namespace: {{ .Release.Namespace}} roleRef: apiGroup: rbac.authorization.k8s.io - kind: Role - name: lurcher + kind: ClusterRole + name: kubeaudit +{{- end }} \ No newline at end of file diff --git a/scanners/kubeaudit/values.yaml b/scanners/kubeaudit/values.yaml index d011e03366..f2a72981b8 100644 --- a/scanners/kubeaudit/values.yaml +++ b/scanners/kubeaudit/values.yaml @@ -46,3 +46,6 @@ scannerJob: drop: # scannerJob.securityContext.capabilities.drop[0] -- This drops all linux privileges from the container. - all + +# kubeauditScope -- Automatically sets up rbac roles for kubeaudit to access the ressources it scans. Can be either "cluster" (ClusterRole) or "namespace" (Role) +kubeauditScope: "namespace" \ No newline at end of file diff --git a/tests/integration/scanner/kubeaudit.test.js b/tests/integration/scanner/kubeaudit.test.js index 6107558c01..474866b741 100644 --- a/tests/integration/scanner/kubeaudit.test.js +++ b/tests/integration/scanner/kubeaudit.test.js @@ -1,17 +1,29 @@ const { scan } = require("../helpers"); test( - "kubeaudit should run and check our integration-tests namespace", + "kubeaudit should run and check the jshop in kubeaudit-test namespace", async () => { - await scan( + const { categories, severities } = await scan( "kubeaudit-test", "kubeaudit", - ["-n", "integration-tests"], + ["-n", "kubeaudit-test"], 60 ); - - // If we got here the scan succeded - expect(true).toBe(true); + + expect(categories).toMatchInlineSnapshot(` + Object { + "Automounted ServiceAccount Token": 1, + "Capability Not Dropped": 14, + "Non ReadOnly Root Filesystem": 1, + "Non Root User Not Enforced": 1, + } + `); + expect(severities).toMatchInlineSnapshot(` + Object { + "low": 16, + "medium": 1, + } + `); }, 5 * 60 * 1000 ); From 9a5b35ff85f29a556f71b7b502ffef3a3c571f6a Mon Sep 17 00:00:00 2001 From: SebieF Date: Mon, 12 Oct 2020 14:27:04 +0000 Subject: [PATCH 18/38] Updating Helm Docs --- scanners/kubeaudit/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/scanners/kubeaudit/README.md b/scanners/kubeaudit/README.md index 2f0b38dc76..504545a2f4 100644 --- a/scanners/kubeaudit/README.md +++ b/scanners/kubeaudit/README.md @@ -20,6 +20,7 @@ A Helm chart for the kubeaudit security scanner that integrates with the secureC | Key | Type | Default | Description | |-----|------|---------|-------------| +| kubeauditScope | string | `"namespace"` | Automatically sets up rbac roles for kubeaudit to access the ressources it scans. Can be either "cluster" (ClusterRole) or "namespace" (Role) | | parserImage.repository | string | `"docker.io/securecodebox/parser-kubeaudit"` | Parser image repository | | parserImage.tag | string | defaults to the charts version | Parser image tag | | scannerJob.env | list | `[]` | Optional environment variables mapped into each scanJob (see: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) | From b808c2d01d512dd3347625d32228ce85277f7f99 Mon Sep 17 00:00:00 2001 From: SebieF Date: Mon, 12 Oct 2020 16:52:32 +0200 Subject: [PATCH 19/38] Add kubeaudit parser to ci --- .github/workflows/ci.yaml | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 1f86729dba..80330b4305 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -121,6 +121,16 @@ jobs: tag_with_ref: true tag_with_sha: true build_args: baseImageTag=ci-local + - uses: docker/build-push-action@v1 + name: "Build & Push kubeaudit Parser Image" + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + repository: securecodebox/parser-kubeaudit + path: ./scanners/kubeaudit/parser/ + tag_with_ref: true + tag_with_sha: true + build_args: baseImageTag=ci-local - uses: docker/build-push-action@v1 name: "Build & Push kube-hunter Parser Image" with: @@ -454,15 +464,11 @@ jobs: npx jest --ci --color kube-hunter - name: "kubeaudit Integration Tests" run: | - kubectl create namespace kubeaudit-test - helm -n kubeaudit-test install juice-shop ./demo-apps/juice-shop/ --wait helm -n integration-tests install kubeaudit ./scanners/kubeaudit/ \ --set="parserImage.tag=sha-$(git rev-parse --short HEAD)" \ - --set="image.tag=0.11.5" \ - --set="kubeauditScope=cluster" + --set="image.tag=0.11.5" cd tests/integration/ - npx jest --ci --color kubeaudit - kubectl delete namespace kubeaudit-test + npx jest --ci --color kubeaudit - name: "ssh-scan Integration Tests" run: | helm -n integration-tests install ssh-scan ./scanners/ssh_scan/ --set="parserImage.tag=sha-$(git rev-parse --short HEAD)" From c533847aa7900e5c76cb8c3f6b3f8da6e22210bf Mon Sep 17 00:00:00 2001 From: SebieF Date: Fri, 16 Oct 2020 14:30:49 +0200 Subject: [PATCH 20/38] Test commit for ci tests (fixed typo) --- tests/integration/helpers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/helpers.js b/tests/integration/helpers.js index c446865cc4..575489c85c 100644 --- a/tests/integration/helpers.js +++ b/tests/integration/helpers.js @@ -175,7 +175,7 @@ async function cascadingScan(name, scanType, parameters = [], { nameCascade, mat apiVersion: "execution.securecodebox.io/v1", kind: "Scan", metadata: { - // Use `generateName` instead of name to generate a random sufix and avoid name clashes + // Use `generateName` instead of name to generate a random suffix and avoid name clashes generateName: `${name}-`, }, spec: { From fb38a1a33f40444a1765c3fc76d846498892cdbf Mon Sep 17 00:00:00 2001 From: SebieF Date: Fri, 16 Oct 2020 14:36:06 +0200 Subject: [PATCH 21/38] Changed ci yaml to newer version --- .github/workflows/ci.yaml | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 80330b4305..29430f9938 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -47,7 +47,7 @@ jobs: - name: "Run tests & publish code coverage" uses: paambaati/codeclimate-action@v2.6.0 env: - CC_TEST_REPORTER_ID: 545b7af20f13dc58a3284275828532a26d89a8e90c8f276fb54a23d78bae7a19 + CC_TEST_REPORTER_ID: a09c9e6ef697176ac6ec9f71063e2d18156aa1a904e2dd62ba2b68fab1d22ced with: coverageCommand: npm test -- --ci --colors --coverage operator: @@ -122,12 +122,22 @@ jobs: tag_with_sha: true build_args: baseImageTag=ci-local - uses: docker/build-push-action@v1 - name: "Build & Push kubeaudit Parser Image" + name: "Build & Push kubeaudit Parser Image" + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + repository: securecodebox/parser-kubeaudit + path: ./scanners/kubeaudit/parser/ + tag_with_ref: true + tag_with_sha: true + build_args: baseImageTag=ci-local + - uses: docker/build-push-action@v1 + name: "Build & Push Gitleaks Parser Image" with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - repository: securecodebox/parser-kubeaudit - path: ./scanners/kubeaudit/parser/ + repository: securecodebox/parser-gitleaks + path: ./scanners/gitleaks/parser/ tag_with_ref: true tag_with_sha: true build_args: baseImageTag=ci-local @@ -275,15 +285,6 @@ jobs: tag_with_ref: true tag_with_sha: true build_args: baseImageTag=ci-local - - uses: docker/build-push-action@v1 - name: "Build & Push ImperativeSubsequentScans Hook Image" - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - repository: securecodebox/hook-imperative-subsequent-scans - path: ./hooks/imperative-subsequent-scans/ - tag_with_ref: true - build_args: baseImageTag=ci-local - uses: docker/build-push-action@v1 name: "Build & Push DeclarativeSubsequentScans Hook Image" with: @@ -310,6 +311,14 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@master + - uses: docker/build-push-action@v1 + name: "Build & Push Gitleaks Scanner Image" + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + repository: securecodebox/scanner-gitleaks + path: ./scanners/gitleaks/scanner/ + tags: "v6.1.2,latest" - uses: docker/build-push-action@v1 name: "Build & Push Ncrack Scanner Image" with: @@ -468,7 +477,7 @@ jobs: --set="parserImage.tag=sha-$(git rev-parse --short HEAD)" \ --set="image.tag=0.11.5" cd tests/integration/ - npx jest --ci --color kubeaudit + npx jest --ci --color kubeaudit - name: "ssh-scan Integration Tests" run: | helm -n integration-tests install ssh-scan ./scanners/ssh_scan/ --set="parserImage.tag=sha-$(git rev-parse --short HEAD)" From 7c6245a8e9dfb3850bfccf3d657dc05cf11e91f5 Mon Sep 17 00:00:00 2001 From: SebieF Date: Fri, 16 Oct 2020 14:38:14 +0200 Subject: [PATCH 22/38] Fix yaml --- .github/workflows/ci.yaml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 29430f9938..2e3e1c72de 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -122,15 +122,15 @@ jobs: tag_with_sha: true build_args: baseImageTag=ci-local - uses: docker/build-push-action@v1 - name: "Build & Push kubeaudit Parser Image" - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - repository: securecodebox/parser-kubeaudit - path: ./scanners/kubeaudit/parser/ - tag_with_ref: true - tag_with_sha: true - build_args: baseImageTag=ci-local + name: "Build & Push kubeaudit Parser Image" + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + repository: securecodebox/parser-kubeaudit + path: ./scanners/kubeaudit/parser/ + tag_with_ref: true + tag_with_sha: true + build_args: baseImageTag=ci-local - uses: docker/build-push-action@v1 name: "Build & Push Gitleaks Parser Image" with: From 92cca969c14bec3d0a768669b76ab4626c97bb05 Mon Sep 17 00:00:00 2001 From: SebieF Date: Fri, 16 Oct 2020 14:45:44 +0200 Subject: [PATCH 23/38] Change parserImage Dockerfile --- scanners/kubeaudit/parser/Dockerfile | 7 ------- 1 file changed, 7 deletions(-) diff --git a/scanners/kubeaudit/parser/Dockerfile b/scanners/kubeaudit/parser/Dockerfile index c53ee60cea..5925068437 100644 --- a/scanners/kubeaudit/parser/Dockerfile +++ b/scanners/kubeaudit/parser/Dockerfile @@ -1,11 +1,4 @@ ARG baseImageTag -FROM node:12-alpine as build -RUN mkdir -p /home/app -WORKDIR /home/app -COPY package.json package-lock.json ./ -RUN npm ci --production - FROM securecodebox/parser-sdk-nodejs:${baseImageTag:-latest} WORKDIR /home/app/parser-wrapper/parser/ -COPY --from=build --chown=app:app /home/app/node_modules/ ./node_modules/ COPY --chown=app:app ./parser.js ./parser.js From 5adab93cd6d7ba0420a12f3c16bdf36818c1f24f Mon Sep 17 00:00:00 2001 From: SebieF Date: Fri, 16 Oct 2020 14:47:24 +0200 Subject: [PATCH 24/38] Add Githunter --- scanners/gitleaks/.helmignore | 5 + scanners/gitleaks/Chart.yaml | 23 + scanners/gitleaks/README.md | 76 +++ scanners/gitleaks/README.md.gotmpl | 64 +++ .../gitleaks/examples/multi-juicer/README.md | 1 + .../gitleaks/examples/multi-juicer/scan.yaml | 11 + .../examples/private-repository/README.md | 1 + .../examples/private-repository/scan.yaml | 15 + scanners/gitleaks/helm2.Chart.yaml | 22 + scanners/gitleaks/parser/Dockerfile | 4 + .../__testFiles__/test-empty-report.json | 1 + .../parser/__testFiles__/test-report.json | 107 ++++ scanners/gitleaks/parser/parser.js | 44 ++ scanners/gitleaks/parser/parser.test.js | 184 +++++++ scanners/gitleaks/scanner/Dockerfile | 3 + scanners/gitleaks/scanner/wrapper.sh | 5 + .../templates/gitleaks-parse-definition.yaml | 7 + .../templates/gitleaks-scan-type.yaml | 462 ++++++++++++++++++ scanners/gitleaks/values.yaml | 47 ++ 19 files changed, 1082 insertions(+) create mode 100644 scanners/gitleaks/.helmignore create mode 100644 scanners/gitleaks/Chart.yaml create mode 100644 scanners/gitleaks/README.md create mode 100644 scanners/gitleaks/README.md.gotmpl create mode 100644 scanners/gitleaks/examples/multi-juicer/README.md create mode 100644 scanners/gitleaks/examples/multi-juicer/scan.yaml create mode 100644 scanners/gitleaks/examples/private-repository/README.md create mode 100644 scanners/gitleaks/examples/private-repository/scan.yaml create mode 100644 scanners/gitleaks/helm2.Chart.yaml create mode 100644 scanners/gitleaks/parser/Dockerfile create mode 100644 scanners/gitleaks/parser/__testFiles__/test-empty-report.json create mode 100644 scanners/gitleaks/parser/__testFiles__/test-report.json create mode 100644 scanners/gitleaks/parser/parser.js create mode 100644 scanners/gitleaks/parser/parser.test.js create mode 100644 scanners/gitleaks/scanner/Dockerfile create mode 100644 scanners/gitleaks/scanner/wrapper.sh create mode 100644 scanners/gitleaks/templates/gitleaks-parse-definition.yaml create mode 100644 scanners/gitleaks/templates/gitleaks-scan-type.yaml create mode 100644 scanners/gitleaks/values.yaml diff --git a/scanners/gitleaks/.helmignore b/scanners/gitleaks/.helmignore new file mode 100644 index 0000000000..2b6e53d709 --- /dev/null +++ b/scanners/gitleaks/.helmignore @@ -0,0 +1,5 @@ +.DS_Store + +parser/ +scanner/ +examples/ \ No newline at end of file diff --git a/scanners/gitleaks/Chart.yaml b/scanners/gitleaks/Chart.yaml new file mode 100644 index 0000000000..d4f13067e4 --- /dev/null +++ b/scanners/gitleaks/Chart.yaml @@ -0,0 +1,23 @@ +apiVersion: v1 +name: gitleaks +description: A Helm chart for the gitleaks repository scanner that integrates with the secureCodeBox. + +type: application +# version - gets automatically set to the secureCodeBox release version when the helm charts gets published +version: latest +appVersion: v6.1.2 +kubeVersion: ">=v1.11.0-0" + +keywords: + - security + - gitleaks + - scanner + - secureCodeBox +home: https://docs.securecodebox.io/docs/scanners/gitleaks +icon: https://docs.securecodebox.io/img/integrationIcons/gitleakslogo.png +sources: + - https://github.com/secureCodeBox/secureCodeBox +maintainers: + - name: iteratec GmbH + email: secureCodeBox@iteratec.com + diff --git a/scanners/gitleaks/README.md b/scanners/gitleaks/README.md new file mode 100644 index 0000000000..6705dfc8c9 --- /dev/null +++ b/scanners/gitleaks/README.md @@ -0,0 +1,76 @@ +--- +title: "Gitleaks" +path: "scanners/gitleaks" +category: "scanner" +type: "Repository" +state: "in progress" +appVersion: "6.1.2" +usecase: "Find potential secrets in repositories" +--- + +![gitleaks logo](https://raw.githubusercontent.com/zricethezav/gifs/master/gitleakslogo.png) + +Gitleaks is a free and open source tool for finding secrets in git repositories. +These secrets could be passwords, API keys, tokens, private keys or suspicious file names or +file extensions like *id_rsa*, *.pem*, *htpasswd*. Furthermore gitleaks can scan your whole repository's history +with all commits up to the initial one. + +To learn more about gitleaks visit + +## Deployment +The gitleaks scanner can be deployed with helm: +```bash +helm upgrade --install gitleaks ./scanners/gitleaks/ +``` + +## Scanner configuration +For a complete overview of the configuration options checkout the +[Gitleaks documentation](https://github.com/zricethezav/gitleaks/wiki/Options). + +The only mandatory parameters are: +- `-r`: The link to the repository you want to scan. +- `--access-token`: Only for non-public repositories. +- `--username` and `--password`: Only for non-public repositories. +- `--config`: The ruleset you want to use. + +**Do not** override the option `--report-format` or `--report`. It is already configured for automatic findings parsing. + +#### Ruleset +At this point we provide three rulesets which you can pass to the `--config` oprtion: + +- `/home/config_all.toml`: Includes every rule. +- `/home/config_filenames_only.toml`: Gitleaks scans only file names and extensions. +- `/home/config_no_generics.toml`: No generic rules like searching for the word *password*. With this option you won't +find something like **password = Ej2ifDk2jfeo2** but it will reduce resulting false positives. + +#### Other useful options are: +- `--commit-since`: Scan commits more recent than a specific date. Ex: '2006-01-02' or '2006-01-02T15:04:05-0700' format. +- `--commit-until`: Scan commits older than a specific date. Ex: '2006-01-02' or '2006-01-02T15:04:05-0700' format. +- `--repo-config`: Load config from target repo. Config file must be ".gitleaks.toml" or "gitleaks.toml". + +#### Finding format +It is not an easy task to classify the severity of the scans because we can't tell for sure if the finding is e.g. a real +or a testing password. Another issue is that the rate of false positives for generic rules can be very high. Therefore, +we tried to classify the severity of the finding by looking at the accuracy of the rule which detected it. Rules for AWS +secrets or Artifactory tokens are very precise, so they get a high severity. Generic rules on the other hand get a low +severity because the often produce false positives. + +**Please keep in mind that findings with a low severity can be actually +very critical.** + +## Chart Configuration + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| image.repository | string | `"docker.io/securecodebox/scanner-gitleaks"` | Container Image to run the scan | +| image.tag | string | `nil` | defaults to the charts version | +| parserImage.repository | string | `"docker.io/securecodebox/parser-gitleaks"` | Parser image repository | +| parserImage.tag | string | defaults to the charts version | Parser image tag | +| scannerJob.env | list | `[]` | Optional environment variables mapped into each scanJob (see: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) | +| scannerJob.extraContainers | list | `[]` | Optional additional Containers started with each scanJob (see: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/) | +| scannerJob.extraVolumeMounts | list | `[{"mountPath":"/home/","name":"gitleaks-config"}]` | Optional VolumeMounts mapped into each scanJob (see: https://kubernetes.io/docs/concepts/storage/volumes/) | +| scannerJob.extraVolumes | list | `[{"configMap":{"name":"gitleaks-config"},"name":"gitleaks-config"}]` | Optional Volumes mapped into each scanJob (see: https://kubernetes.io/docs/concepts/storage/volumes/) | +| scannerJob.resources | object | `{}` | CPU/memory resource requests/limits (see: https://kubernetes.io/docs/tasks/configure-pod-container/assign-memory-resource/, https://kubernetes.io/docs/tasks/configure-pod-container/assign-cpu-resource/) | +| scannerJob.securityContext | object | `{}` | Optional securityContext set on scanner container (see: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/) | +| scannerJob.ttlSecondsAfterFinished | string | `nil` | Defines how long the scanner job after finishing will be available (see: https://kubernetes.io/docs/concepts/workloads/controllers/ttlafterfinished/) | + diff --git a/scanners/gitleaks/README.md.gotmpl b/scanners/gitleaks/README.md.gotmpl new file mode 100644 index 0000000000..0288012910 --- /dev/null +++ b/scanners/gitleaks/README.md.gotmpl @@ -0,0 +1,64 @@ +--- +title: "Gitleaks" +path: "scanners/gitleaks" +category: "scanner" +type: "Repository" +state: "in progress" +appVersion: "6.1.2" +usecase: "Find potential secrets in repositories" +--- + +![gitleaks logo](https://raw.githubusercontent.com/zricethezav/gifs/master/gitleakslogo.png) + +Gitleaks is a free and open source tool for finding secrets in git repositories. +These secrets could be passwords, API keys, tokens, private keys or suspicious file names or +file extensions like *id_rsa*, *.pem*, *htpasswd*. Furthermore gitleaks can scan your whole repository's history +with all commits up to the initial one. + +To learn more about gitleaks visit + +## Deployment +The gitleaks scanner can be deployed with helm: +```bash +helm upgrade --install gitleaks ./scanners/gitleaks/ +``` + +## Scanner configuration +For a complete overview of the configuration options checkout the +[Gitleaks documentation](https://github.com/zricethezav/gitleaks/wiki/Options). + +The only mandatory parameters are: +- `-r`: The link to the repository you want to scan. +- `--access-token`: Only for non-public repositories. +- `--username` and `--password`: Only for non-public repositories. +- `--config`: The ruleset you want to use. + +**Do not** override the option `--report-format` or `--report`. It is already configured for automatic findings parsing. + +#### Ruleset +At this point we provide three rulesets which you can pass to the `--config` oprtion: + +- `/home/config_all.toml`: Includes every rule. +- `/home/config_filenames_only.toml`: Gitleaks scans only file names and extensions. +- `/home/config_no_generics.toml`: No generic rules like searching for the word *password*. With this option you won't +find something like **password = Ej2ifDk2jfeo2** but it will reduce resulting false positives. + +#### Other useful options are: +- `--commit-since`: Scan commits more recent than a specific date. Ex: '2006-01-02' or '2006-01-02T15:04:05-0700' format. +- `--commit-until`: Scan commits older than a specific date. Ex: '2006-01-02' or '2006-01-02T15:04:05-0700' format. +- `--repo-config`: Load config from target repo. Config file must be ".gitleaks.toml" or "gitleaks.toml". + +#### Finding format +It is not an easy task to classify the severity of the scans because we can't tell for sure if the finding is e.g. a real +or a testing password. Another issue is that the rate of false positives for generic rules can be very high. Therefore, +we tried to classify the severity of the finding by looking at the accuracy of the rule which detected it. Rules for AWS +secrets or Artifactory tokens are very precise, so they get a high severity. Generic rules on the other hand get a low +severity because the often produce false positives. + +**Please keep in mind that findings with a low severity can be actually +very critical.** + +## Chart Configuration + +{{ template "chart.valuesTable" . }} + diff --git a/scanners/gitleaks/examples/multi-juicer/README.md b/scanners/gitleaks/examples/multi-juicer/README.md new file mode 100644 index 0000000000..631120c191 --- /dev/null +++ b/scanners/gitleaks/examples/multi-juicer/README.md @@ -0,0 +1 @@ +An Example for scanning all history of the multi juicer project on GitHub: diff --git a/scanners/gitleaks/examples/multi-juicer/scan.yaml b/scanners/gitleaks/examples/multi-juicer/scan.yaml new file mode 100644 index 0000000000..ca29c5b504 --- /dev/null +++ b/scanners/gitleaks/examples/multi-juicer/scan.yaml @@ -0,0 +1,11 @@ +apiVersion: "execution.securecodebox.io/v1" +kind: Scan +metadata: + name: "scan-multi-juicer-example" +spec: + scanType: "gitleaks" + parameters: + - "-r" + - "https://github.com/iteratec/multi-juicer" + - "--config" + - "/home/config_all.toml" diff --git a/scanners/gitleaks/examples/private-repository/README.md b/scanners/gitleaks/examples/private-repository/README.md new file mode 100644 index 0000000000..ee4653c46a --- /dev/null +++ b/scanners/gitleaks/examples/private-repository/README.md @@ -0,0 +1 @@ +Another example for how to scan a private GitLab repository: diff --git a/scanners/gitleaks/examples/private-repository/scan.yaml b/scanners/gitleaks/examples/private-repository/scan.yaml new file mode 100644 index 0000000000..349bcecee2 --- /dev/null +++ b/scanners/gitleaks/examples/private-repository/scan.yaml @@ -0,0 +1,15 @@ +apiVersion: "execution.securecodebox.io/v1" +kind: Scan +metadata: + name: "scan-private-repository-example" +spec: + scanType: "gitleaks" + parameters: + - "-r" + - "https://gitlab.yourcompany.com/group/project" + - "--access-token" + - "" + - "--config" + - "/home/config_filenames_only.toml" + - "--commit-since" + - "2020-04-20" diff --git a/scanners/gitleaks/helm2.Chart.yaml b/scanners/gitleaks/helm2.Chart.yaml new file mode 100644 index 0000000000..ebd580d344 --- /dev/null +++ b/scanners/gitleaks/helm2.Chart.yaml @@ -0,0 +1,22 @@ +apiVersion: v1 +name: gitleaks +description: A Helm chart for the gitleaks repository scanner that integrates with the secureCodeBox. + +type: application +# version - gets automatically set to the secureCodeBox release version when the helm charts gets published +version: latest +appVersion: v6.1.2 +kubeVersion: ">=v1.11.0-0" + +keywords: + - security + - gitleaks + - scanner + - secureCodeBox +home: https://docs.securecodebox.io/docs/scanners/gitleaks +icon: https://docs.securecodebox.io/img/integrationIcons/gitleakslogo.png +sources: + - https://github.com/secureCodeBox/secureCodeBox +maintainers: + - name: iteratec GmbH + email: secureCodeBox@iteratec.com diff --git a/scanners/gitleaks/parser/Dockerfile b/scanners/gitleaks/parser/Dockerfile new file mode 100644 index 0000000000..5925068437 --- /dev/null +++ b/scanners/gitleaks/parser/Dockerfile @@ -0,0 +1,4 @@ +ARG baseImageTag +FROM securecodebox/parser-sdk-nodejs:${baseImageTag:-latest} +WORKDIR /home/app/parser-wrapper/parser/ +COPY --chown=app:app ./parser.js ./parser.js diff --git a/scanners/gitleaks/parser/__testFiles__/test-empty-report.json b/scanners/gitleaks/parser/__testFiles__/test-empty-report.json new file mode 100644 index 0000000000..fe51488c70 --- /dev/null +++ b/scanners/gitleaks/parser/__testFiles__/test-empty-report.json @@ -0,0 +1 @@ +[] diff --git a/scanners/gitleaks/parser/__testFiles__/test-report.json b/scanners/gitleaks/parser/__testFiles__/test-report.json new file mode 100644 index 0000000000..4b2bde7eee --- /dev/null +++ b/scanners/gitleaks/parser/__testFiles__/test-report.json @@ -0,0 +1,107 @@ +[ + { + "line": " - aws --profile default configure set aws_access_key_id \"AKIAS2QBEJFO232FJDO\"", + "lineNumber": 67, + "offender": "AKIAS2QBEJFO232FJDO", + "commit": "2a42fc73f76e3fd9d015d0a98030037a8972e3d1", + "repo": "web-app", + "rule": "AWS Manager ID", + "commitMessage": "ci trials\n", + "author": "Max Mustermann", + "email": "max.mustermann@host.de", + "file": ".gitlab-ci.yml", + "date": "2019-12-11T12:45:48+01:00", + "tags": "key, AWS", + "operation": "addition" + }, + { + "line": " - aws --profile default configure set aws_secret_access_key \"IccA5EboL5foAY3uUyG+zh5OA3rWdpL4C1ePuUOv\"", + "lineNumber": 68, + "offender": "aws_secret_access_key \"IccA5EboL5foAY3uUyG+zh5OA3rWdpL4C1ePuUOv\"", + "commit": "2a42fc73f76e3fd9d015d0a98030037a8972e3d1", + "repo": "paul-web", + "rule": "AWS Secret Key", + "commitMessage": "ci trials\n", + "author": "Max Mustermann", + "email": "max.mustermann@host.de", + "file": ".gitlab-ci.yml", + "date": "2019-12-11T12:45:48+01:00", + "tags": "key, AWS", + "operation": "addition" + }, + { + "line":" password: ERzCT4pwBDxfCKRGmfrMa8KQ8sXf8GKy", + "lineNumber":33, + "offender":"password: ERzCT4pwBDxfCKRGmfrMa8KQ8sXf8GKy", + "commit":"eaf6864262dbbcbf19c972cd961121b340b9968f", + "repo":"multi-juicer", + "rule":"Generic credentials", + "commitMessage":"Add metrics to balancer\n", + "author":"Max Mustermann", + "email":"max.mustermann@host.de", + "file":"helm/multi-juicer/values.yaml", + "date":"2020-02-18T22:28:53+01:00", + "tags":"key, Generic", + "operation":"addition" + }, + { + "line":" \"password\": \"dRzCT4pwBDxfjfeRel23mMlKQ8sX\"", + "lineNumber":19, + "offender":"password\": \"dRzCT4pwBDxfjfeRel23mMlKQ8sX", + "commit":"eaf6864262dbbcbf19c972cd961121b340b9968f", + "repo":"multi-juicer", + "rule":"Generic credentials", + "commitMessage":"Add metrics to balancer\n", + "author":"Max Mustermann", + "email":"max.mustermann@host.de", + "file":"juice-balancer/config/config.json", + "date":"2020-02-18T22:28:53+01:00", + "tags":"key, Generic", + "operation":"addition" + }, + { + "line":"N/A", + "lineNumber":-1, + "offender":"Filename/path offender: .env", + "commit":"88cf8694d4202bb7361f6779588f566e8eae2ff2", + "repo":"secureCodeBox-v2", + "rule":"File names with potential keys and credentials", + "commitMessage":"minor change\n", + "author":"Max Mustermann", + "email":"max.mustermann@host.de", + "file":".env", + "date":"2019-01-16T19:18:54+01:00", + "tags":"key, FileName", + "operation":"addition" + }, + { + "line":" facebook_api_key: sj20gj2ß0kofepo2ṕf02", + "lineNumber":30, + "offender":"sj20gj2ß0kofepo2ṕf02", + "commit":"eaf6864262dbbcbf19c972cd961121b340b9968f", + "repo":"madeuprepo", + "rule":"Facebook Secret Key", + "commitMessage":"Adds secret\n", + "author":"Max Mustermann", + "email":"max.mustermann@host.de", + "file":".env", + "date":"2019-01-16T19:18:54+01:00", + "tags":"key, Facebook", + "operation":"addition" + }, + { + "line":" -----BEGIN PRIVATE KEY-----", + "lineNumber":1, + "offender":"-----BEGIN PRIVATE KEY-----", + "commit":"2a42fc73f76e3fd9d015d0a98030037a8972e3d1", + "repo":"madeuprepo", + "rule":"Asymmetric Private Key", + "commitMessage":"Adds secret\n", + "author":"Max Mustermann", + "email":"max.mustermann@host.de", + "file":"key.pem", + "date":"2019-01-16T19:18:54+01:00", + "tags":"key, PrivateKey", + "operation":"addition" + } +] diff --git a/scanners/gitleaks/parser/parser.js b/scanners/gitleaks/parser/parser.js new file mode 100644 index 0000000000..66763f603a --- /dev/null +++ b/scanners/gitleaks/parser/parser.js @@ -0,0 +1,44 @@ +const HIGH_TAGS = ['JWT', 'Artifactory', 'AWS', 'PrivateKey']; +const MEDIUM_TAGS = ['Hash', 'Facebook', 'Twitter', 'Github', 'LinkedIn', 'Slack', 'Google', 'Heroku', + 'Mailchimp', 'Mailgun', 'Paypal', 'Picatic', 'Teams', 'Jenkins', 'Stripe', 'Square', 'Twilio']; + +async function parse (fileContent) { + + return fileContent.map(finding => { + + let severity = 'LOW'; + + if (containsTag(finding.tags, HIGH_TAGS)) { + severity = 'HIGH' + } else if (containsTag(finding.tags, MEDIUM_TAGS)) { + severity = 'MEDIUM' + } + + return { + name: finding.rule, + description: 'The name of the rule which triggered the finding: ' + finding.rule, + osi_layer: 'APPLICATION', + severity: severity, + category: 'Potential Secret', + attributes: { + commit: finding.commit, + repo: finding.repo, + offender: finding.offender, + author: finding.author, + email: finding.email, + date: finding.date, + file: finding.file, + line_number: finding.lineNumber, + tags: finding.tags.split(',').map(tag => tag.trim()), + line: finding.line + } + } + }); +} + +function containsTag (tag, tags) { + let result = tags.filter(longTag => tag.includes(longTag)); + return result.length > 0; +} + +module.exports.parse = parse; diff --git a/scanners/gitleaks/parser/parser.test.js b/scanners/gitleaks/parser/parser.test.js new file mode 100644 index 0000000000..5e4f05001f --- /dev/null +++ b/scanners/gitleaks/parser/parser.test.js @@ -0,0 +1,184 @@ +const fs = require("fs"); +const util = require("util"); + +// eslint-disable-next-line security/detect-non-literal-fs-filename +const readFile = util.promisify(fs.readFile); + +const { parse } = require("./parser"); + +test("should properly parse empty gitleaks json file", async () => { + const jsonContent = await readFile( + __dirname + "/__testFiles__/test-empty-report.json", + { + encoding: "utf8" + } + ); + expect(await parse(JSON.parse(jsonContent))).toMatchObject([]); +}); + +test("should properly parse gitleaks json file", async () => { + const jsonContent = await readFile( + __dirname + "/__testFiles__/test-report.json", + { + encoding: "utf8" + } + ); + expect(await parse(JSON.parse(jsonContent))).toMatchInlineSnapshot(` + Array [ + Object { + "attributes": Object { + "author": "Max Mustermann", + "commit": "2a42fc73f76e3fd9d015d0a98030037a8972e3d1", + "date": "2019-12-11T12:45:48+01:00", + "email": "max.mustermann@host.de", + "file": ".gitlab-ci.yml", + "line": " - aws --profile default configure set aws_access_key_id \\"AKIAS2QBEJFO232FJDO\\"", + "line_number": 67, + "offender": "AKIAS2QBEJFO232FJDO", + "repo": "web-app", + "tags": Array [ + "key", + "AWS", + ], + }, + "category": "Potential Secret", + "description": "The name of the rule which triggered the finding: AWS Manager ID", + "name": "AWS Manager ID", + "osi_layer": "APPLICATION", + "severity": "HIGH", + }, + Object { + "attributes": Object { + "author": "Max Mustermann", + "commit": "2a42fc73f76e3fd9d015d0a98030037a8972e3d1", + "date": "2019-12-11T12:45:48+01:00", + "email": "max.mustermann@host.de", + "file": ".gitlab-ci.yml", + "line": " - aws --profile default configure set aws_secret_access_key \\"IccA5EboL5foAY3uUyG+zh5OA3rWdpL4C1ePuUOv\\"", + "line_number": 68, + "offender": "aws_secret_access_key \\"IccA5EboL5foAY3uUyG+zh5OA3rWdpL4C1ePuUOv\\"", + "repo": "paul-web", + "tags": Array [ + "key", + "AWS", + ], + }, + "category": "Potential Secret", + "description": "The name of the rule which triggered the finding: AWS Secret Key", + "name": "AWS Secret Key", + "osi_layer": "APPLICATION", + "severity": "HIGH", + }, + Object { + "attributes": Object { + "author": "Max Mustermann", + "commit": "eaf6864262dbbcbf19c972cd961121b340b9968f", + "date": "2020-02-18T22:28:53+01:00", + "email": "max.mustermann@host.de", + "file": "helm/multi-juicer/values.yaml", + "line": " password: ERzCT4pwBDxfCKRGmfrMa8KQ8sXf8GKy", + "line_number": 33, + "offender": "password: ERzCT4pwBDxfCKRGmfrMa8KQ8sXf8GKy", + "repo": "multi-juicer", + "tags": Array [ + "key", + "Generic", + ], + }, + "category": "Potential Secret", + "description": "The name of the rule which triggered the finding: Generic credentials", + "name": "Generic credentials", + "osi_layer": "APPLICATION", + "severity": "LOW", + }, + Object { + "attributes": Object { + "author": "Max Mustermann", + "commit": "eaf6864262dbbcbf19c972cd961121b340b9968f", + "date": "2020-02-18T22:28:53+01:00", + "email": "max.mustermann@host.de", + "file": "juice-balancer/config/config.json", + "line": " \\"password\\": \\"dRzCT4pwBDxfjfeRel23mMlKQ8sX\\"", + "line_number": 19, + "offender": "password\\": \\"dRzCT4pwBDxfjfeRel23mMlKQ8sX", + "repo": "multi-juicer", + "tags": Array [ + "key", + "Generic", + ], + }, + "category": "Potential Secret", + "description": "The name of the rule which triggered the finding: Generic credentials", + "name": "Generic credentials", + "osi_layer": "APPLICATION", + "severity": "LOW", + }, + Object { + "attributes": Object { + "author": "Max Mustermann", + "commit": "88cf8694d4202bb7361f6779588f566e8eae2ff2", + "date": "2019-01-16T19:18:54+01:00", + "email": "max.mustermann@host.de", + "file": ".env", + "line": "N/A", + "line_number": -1, + "offender": "Filename/path offender: .env", + "repo": "secureCodeBox-v2", + "tags": Array [ + "key", + "FileName", + ], + }, + "category": "Potential Secret", + "description": "The name of the rule which triggered the finding: File names with potential keys and credentials", + "name": "File names with potential keys and credentials", + "osi_layer": "APPLICATION", + "severity": "LOW", + }, + Object { + "attributes": Object { + "author": "Max Mustermann", + "commit": "eaf6864262dbbcbf19c972cd961121b340b9968f", + "date": "2019-01-16T19:18:54+01:00", + "email": "max.mustermann@host.de", + "file": ".env", + "line": " facebook_api_key: sj20gj2ß0kofepo2ṕf02", + "line_number": 30, + "offender": "sj20gj2ß0kofepo2ṕf02", + "repo": "madeuprepo", + "tags": Array [ + "key", + "Facebook", + ], + }, + "category": "Potential Secret", + "description": "The name of the rule which triggered the finding: Facebook Secret Key", + "name": "Facebook Secret Key", + "osi_layer": "APPLICATION", + "severity": "MEDIUM", + }, + Object { + "attributes": Object { + "author": "Max Mustermann", + "commit": "2a42fc73f76e3fd9d015d0a98030037a8972e3d1", + "date": "2019-01-16T19:18:54+01:00", + "email": "max.mustermann@host.de", + "file": "key.pem", + "line": " -----BEGIN PRIVATE KEY-----", + "line_number": 1, + "offender": "-----BEGIN PRIVATE KEY-----", + "repo": "madeuprepo", + "tags": Array [ + "key", + "PrivateKey", + ], + }, + "category": "Potential Secret", + "description": "The name of the rule which triggered the finding: Asymmetric Private Key", + "name": "Asymmetric Private Key", + "osi_layer": "APPLICATION", + "severity": "HIGH", + }, + ] + `); +}); diff --git a/scanners/gitleaks/scanner/Dockerfile b/scanners/gitleaks/scanner/Dockerfile new file mode 100644 index 0000000000..8366a86f9c --- /dev/null +++ b/scanners/gitleaks/scanner/Dockerfile @@ -0,0 +1,3 @@ +FROM zricethezav/gitleaks +COPY wrapper.sh /wrapper.sh +ENTRYPOINT [ "sh", "/wrapper.sh" ] diff --git a/scanners/gitleaks/scanner/wrapper.sh b/scanners/gitleaks/scanner/wrapper.sh new file mode 100644 index 0000000000..bb956a06d1 --- /dev/null +++ b/scanners/gitleaks/scanner/wrapper.sh @@ -0,0 +1,5 @@ +# Gitleaks Entrypoint Script to avoid problems gitleaks exiting with a non zero exit code +# This would cause the kubernetes job to fail no matter what +echo '[]' > /home/securecodebox/report.json # If no leaks found the file is not created +gitleaks $@ +exit 0 diff --git a/scanners/gitleaks/templates/gitleaks-parse-definition.yaml b/scanners/gitleaks/templates/gitleaks-parse-definition.yaml new file mode 100644 index 0000000000..4c2cea3d76 --- /dev/null +++ b/scanners/gitleaks/templates/gitleaks-parse-definition.yaml @@ -0,0 +1,7 @@ +apiVersion: "execution.securecodebox.io/v1" +kind: ParseDefinition +metadata: + name: "gitleaks-json" +spec: + handlesResultsType: gitleaks-json + image: "{{ .Values.parserImage.repository }}:{{ .Values.parserImage.tag | default .Chart.Version }}" diff --git a/scanners/gitleaks/templates/gitleaks-scan-type.yaml b/scanners/gitleaks/templates/gitleaks-scan-type.yaml new file mode 100644 index 0000000000..2ca1ef3c2c --- /dev/null +++ b/scanners/gitleaks/templates/gitleaks-scan-type.yaml @@ -0,0 +1,462 @@ +apiVersion: "execution.securecodebox.io/v1" +kind: ScanType +metadata: + name: "gitleaks" +spec: + extractResults: + type: gitleaks-json + location: "/home/securecodebox/report.json" + jobTemplate: + spec: + {{- if .Values.scannerJob.ttlSecondsAfterFinished }} + ttlSecondsAfterFinished: {{ .Values.scannerJob.ttlSecondsAfterFinished }} + {{- end }} + backoffLimit: 3 + template: + spec: + restartPolicy: OnFailure + containers: + - name: gitleaks + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + command: + - 'sh' + - '/wrapper.sh' + - "--verbose" + - "--report-format" + - "json" + - "--report" + - "/home/securecodebox/report.json" + resources: + {{- toYaml .Values.scannerJob.resources | nindent 16 }} + securityContext: + {{- toYaml .Values.scannerJob.securityContext | nindent 16 }} + env: + {{- toYaml .Values.scannerJob.env | nindent 16 }} + volumeMounts: + {{- toYaml .Values.scannerJob.extraVolumeMounts | nindent 16 }} + {{- if .Values.scannerJob.extraContainers }} + {{- toYaml .Values.scannerJob.extraContainers | nindent 12 }} + {{- end }} + volumes: + {{- toYaml .Values.scannerJob.extraVolumes | nindent 12 }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: gitleaks-config +data: + config_all.toml: |- + title = "gitleaks config" + [[rules]] + description = "JWT Token Format" + regex = '''eyJ[a-zA-Z0-9\-_]{17,}\.[a-zA-Z0-9\-_]{20,}\.[a-zA-Z0-9\-_]{20,}''' + tags = ["key", "JWT"] + + #TODO need a matcher for other typical hash-types + [[rules]] + description = "32 char hash (e.g. MD5 Checksum used for zah payment gateway, or zah-keys)" + regex = '''=[a-f0-9]{32}[^a-f0-9]''' + tags = ["key", "Hash", "Generic"] + + [[rules]] + description = "Format of Artifactory access keys" + regex = '''[^a-zA-Z0-9]AKC[a-zA-Z0-9]{70}[^a-zA-Z0-9]''' + tags = ["key", "Artifactory"] + + [[rules]] + description = "Generic credentials" + regex = '''(?i)(dbpasswd|api_key|apikey|secret|key|password|passwort|key|token|secret|guid|pw|auth)(.{0,20})?[^\S\r\n]?[:=][^\S\r\n]?["']?([0-9a-zA-Z-_\/+!{}\/=]{6,80})''' + tags = ["key", "Generic"] + [[rules.Entropies]] + Min = "3.8" + Max = "8.0" + Group = "3" + [rules.allowlist] + regexes = [ + + # *** generic whitelist *** + # excludes ${...} format + '''[:=]\s?\"?\'?\${.*?}''', + # excludes $... format + '''[:=]\s?\$[a-zA-z0-9_\-]+''', + # for parameter replacement, url, ... + '''(env.DOCKER_PASSWORT|credentials\[)''', + '''https://packages.instana.io/Instana.gpg''', + '''key=sonar\.(webhooks|forceAuthentication)''', + '''key=https:\/\/(openresty\.org|packages\.grafana)''', + '''(key=file:\/\/\/etc\/pki\/rpm-gpg|KEY: \"\$ARTIFACTORY_OPS)''', + '''(token|TOKEN)\s?=\s?(conn\.assume_role|\(\[a-zA-Z0-9)''', + '''(key|KEY)=(\/tmp\/helm\/\$VENDO_PROJECT|\$\(_get_key|\"?\/app(-security)?\/secret-service-volume\/tls\.key|\"\$EXTERNAL_CERTIFICATE)''', + '''(password|PASSWORD)\s?=\s?(getpass\.getpass|\$\(_get_key)''', + # Ignore JWT - they have an own rule with own whitelist + '''eyJ[a-zA-Z0-9\-_]{17,}\.[a-zA-Z0-9\-_]{20,}\.[a-zA-Z0-9\-_]{20,}''', + # Ignore AWS Manager ID rules - they have an own rule with own whitelist + '''(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}''', + # Ignore AWS Secret rules - they have an own rule with own whitelist + '''(?i)aws(.{0,20})?(?-i)['\"]?[0-9a-zA-Z\/+]{40}['\"]?''', + # Ignore Slack + '''xox[baprs]-([0-9a-zA-Z]{10,48})''', + # Ignore mailchimp + '''(?i)(.{0,20})?['"][0-9a-f]{32}-us[0-9]{1,2}['"]''' + ] + #files = [ + # '''\.java$''' + #] + + [[rules]] + description = "AWS Manager ID" + regex = '''(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}[\"\s]''' + tags = ["key", "AWS"] + + [[rules]] + description = "AWS cred file info" + regex = '''(?i)(aws_access_key_id|aws_secret_access_key)(.{0,20})?=.[0-9a-zA-Z\/+]{20,40}''' + tags = ["key", "AWS"] + + [[rules]] + description = "AWS Secret Key" + regex = '''(?i)aws(.{0,20})?[=:\s](?-i)['\"]?[0-9a-zA-Z\/+]{40}['\"]?''' + tags = ["key", "AWS"] + + [[rules]] + description = "AWS MWS key" + regex = '''amzn\.mws\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}''' + tags = ["key", "AWS", "MWS"] + + + [[rules]] + description = "Asymmetric Private Key" + regex = '''-----BEGIN ((EC|PGP|DSA|RSA|OPENSSH) )?PRIVATE KEY( BLOCK)?-----''' + tags = ["key", "PrivateKey"] + [rules.allowlist] + paths = ['''vagrant/\.vagrant\/machines\/default\/virtualbox'''] + description = "SSH key used to connect to local development machine" + + [[rules]] + description = "Facebook Secret Key" + regex = '''(?i)(facebook|fb)(.{0,20})?[=:\s](?-i)['\"][0-9a-f]{32}['\"]''' + tags = ["key", "Facebook"] + + [[rules]] + description = "Facebook Client ID" + regex = '''(?i)(facebook|fb)(.{0,20})?[=:\s]['\"][0-9]{13,17}['\"]''' + tags = ["key", "Facebook"] + + [[rules]] + description = "Twitter Secret Key" + regex = '''(?i)twitter(.{0,20})?[=:\s]['\"][0-9a-z]{35,44}['\"]''' + tags = ["key", "Twitter"] + + [[rules]] + description = "Twitter Client ID" + regex = '''(?i)twitter(.{0,20})?[=:\s]['\"][0-9a-z]{18,25}['\"]''' + tags = ["client", "Twitter"] + + [[rules]] + description = "Github" + regex = '''(?i)github(.{0,20})?[=:\s](?-i)['\"][0-9a-zA-Z]{35,40}['\"]''' + tags = ["key", "Github"] + + [[rules]] + description = "LinkedIn Client ID" + regex = '''(?i)linkedin(.{0,20})?[=:\s](?-i)['\"][0-9a-z]{12}['\"]''' + tags = ["client", "LinkedIn"] + + [[rules]] + description = "LinkedIn Secret Key" + regex = '''(?i)linkedin(.{0,20})?[=:\s]['\"][0-9a-z]{16}['\"]''' + tags = ["secret", "LinkedIn"] + + [[rules]] + description = "Slack" + regex = '''xox[baprs]-([0-9a-zA-Z]{10,48})?''' + tags = ["key", "Slack"] + + [[rules]] + description = "Google API key" + regex = '''AIza[0-9A-Za-z\\-_]{35}''' + tags = ["key", "Google"] + + + [[rules]] + description = "Heroku API key" + regex = '''(?i)heroku(.{0,20})?['"][0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}['"]''' + tags = ["key", "Heroku"] + + [[rules]] + description = "MailChimp API key" + regex = '''(?i)(mailchimp|mc)(.{0,20})?['"][0-9a-f]{32}-us[0-9]{1,2}['"]''' + tags = ["key", "Mailchimp"] + + [[rules]] + description = "Mailgun API key" + regex = '''(?i)(mailgun|mg)(.{0,20})?[=:\s]['"][0-9a-z]{32}['"]''' + tags = ["key", "Mailgun"] + + [[rules]] + description = "PayPal Braintree access token" + regex = '''access_token\$production\$[0-9a-z]{16}\$[0-9a-f]{32}''' + tags = ["key", "Paypal"] + + [[rules]] + description = "Picatic API key" + regex = '''sk_live_[0-9a-z]{32}''' + tags = ["key", "Picatic"] + + [[rules]] + description = "Slack Webhook" + regex = '''https://hooks.slack.com/services/T[a-zA-Z0-9_]{8}/B[a-zA-Z0-9_]{8}/[a-zA-Z0-9_]{24}''' + tags = ["key", "Slack"] + + #TODO Optimize + [[rules]] + description = "Teams Webhook" + regex = '''https://outlook.office.com/webhook/.{1,120}''' + tags = ["key", "Teams"] + + #TODO Optimize + [[rules]] + description = "Jenkins Webhook" + regex = '''https://.{6,100}/generic-webhook-trigger/invoke''' + tags = ["key", "Jenkins"] + + [[rules]] + description = "Stripe API key" + regex = '''(?i)stripe(.{0,20})?[=:\s]['\"][sk|rk]_live_[0-9a-zA-Z]{24}''' + tags = ["key", "Stripe"] + + [[rules]] + description = "Square access token" + regex = '''sq0atp-[0-9A-Za-z\-_]{22}''' + tags = ["key", "Square"] + + [[rules]] + description = "Square OAuth secret" + regex = '''sq0csp-[0-9A-Za-z\\-_]{43}''' + tags = ["key", "Square"] + + [[rules]] + description = "Twilio API key" + regex = '''(?i)twilio(.{0,20})?['\"][0-9a-f]{32}['\"]''' + tags = ["key", "Twilio"] + + [[rules]] + description = "File names with potential keys and credentials" + file = '''(?i)(id_rsa|id_dsa|id_ed25519|id_ecdsa|passwd|pgpass|pem|key|shadow + |npmrc_auth|s3cfg|dockercfg|wp-config\.php|htpasswd|env|git-credentials|tugboat|netrc|_netrc|ftpconfig + |remote-sync\.json|sftp\.json|sftp-config\.json|webservers\.xml|logins\.json|dbeaver-data-sources\.xml + |sshd_config|sh_history|history|bash_history|dhcpd\.conf|connections\.xml|pgpass|secret_token\.rb + |credentials\.xml|robomongo\.json|terraform\.tfvars)''' + tags = ["key", "FileName"] + + [[rules]] + description = "File extension with potential keys and credentials" + file = '''(?i)\.(pem|ppk|bashrc|pkcs12|p12|pfx|asc|ovpn|cscfg|rdp|mdf|sdf|sqlite|sqlite3|bek + |tpm|fve|jks|psafe3|keychain|pcap|gnucash|kwallet|tblk|s3cfg|kdbx|sqldumb|htpasswd|dockercfg)''' + tags = ["key", "FileExtension"] + + [allowlist] + description = "Whitelisted files" + files = [ + '''^.*gitleaks(config)?.*\.toml$''', + '''(.*?)(jpg|gif|doc|pdf|jepg|png|bin|yarn\.lock|svg)$''', + '''(go\.mod|go\.sum)$''', + '''(swagger-ui.*)(js|css|map)$''', + '''package-lock\.json''' + ] + paths = ["node_modules"] + + config_no_generics.toml: |- + title = "gitleaks config" + [[rules]] + description = "JWT Token Format" + regex = '''eyJ[a-zA-Z0-9\-_]{17,}\.[a-zA-Z0-9\-_]{20,}\.[a-zA-Z0-9\-_]{20,}''' + tags = ["key", "JWT"] + + [[rules]] + description = "Format of Artifactory access keys" + regex = '''[^a-zA-Z0-9]AKC[a-zA-Z0-9]{70}[^a-zA-Z0-9]''' + tags = ["key", "Artifactory"] + + [[rules]] + description = "AWS Manager ID" + regex = '''(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}[\"\s]''' + tags = ["key", "AWS"] + + [[rules]] + description = "AWS cred file info" + regex = '''(?i)(aws_access_key_id|aws_secret_access_key)(.{0,20})?=.[0-9a-zA-Z\/+]{20,40}''' + tags = ["key", "AWS"] + + [[rules]] + description = "AWS Secret Key" + regex = '''(?i)aws(.{0,20})?[=:\s](?-i)['\"]?[0-9a-zA-Z\/+]{40}['\"]?''' + tags = ["key", "AWS"] + + [[rules]] + description = "AWS MWS key" + regex = '''amzn\.mws\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}''' + tags = ["key", "AWS", "MWS"] + + + [[rules]] + description = "Asymmetric Private Key" + regex = '''-----BEGIN ((EC|PGP|DSA|RSA|OPENSSH) )?PRIVATE KEY( BLOCK)?-----''' + tags = ["key", "PrivateKey"] + [rules.allowlist] + paths = ['''vagrant/\.vagrant\/machines\/default\/virtualbox'''] + description = "SSH key used to connect to local development machine" + + [[rules]] + description = "Facebook Secret Key" + regex = '''(?i)(facebook|fb)(.{0,20})?[=:\s](?-i)['\"][0-9a-f]{32}['\"]''' + tags = ["key", "Facebook"] + + [[rules]] + description = "Facebook Client ID" + regex = '''(?i)(facebook|fb)(.{0,20})?[=:\s]['\"][0-9]{13,17}['\"]''' + tags = ["key", "Facebook"] + + [[rules]] + description = "Twitter Secret Key" + regex = '''(?i)twitter(.{0,20})?[=:\s]['\"][0-9a-z]{35,44}['\"]''' + tags = ["key", "Twitter"] + + [[rules]] + description = "Twitter Client ID" + regex = '''(?i)twitter(.{0,20})?[=:\s]['\"][0-9a-z]{18,25}['\"]''' + tags = ["client", "Twitter"] + + [[rules]] + description = "Github" + regex = '''(?i)github(.{0,20})?[=:\s](?-i)['\"][0-9a-zA-Z]{35,40}['\"]''' + tags = ["key", "Github"] + + [[rules]] + description = "LinkedIn Client ID" + regex = '''(?i)linkedin(.{0,20})?[=:\s](?-i)['\"][0-9a-z]{12}['\"]''' + tags = ["client", "LinkedIn"] + + [[rules]] + description = "LinkedIn Secret Key" + regex = '''(?i)linkedin(.{0,20})?[=:\s]['\"][0-9a-z]{16}['\"]''' + tags = ["secret", "LinkedIn"] + + [[rules]] + description = "Slack" + regex = '''xox[baprs]-([0-9a-zA-Z]{10,48})?''' + tags = ["key", "Slack"] + + [[rules]] + description = "Google API key" + regex = '''AIza[0-9A-Za-z\\-_]{35}''' + tags = ["key", "Google"] + + + [[rules]] + description = "Heroku API key" + regex = '''(?i)heroku(.{0,20})?['"][0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}['"]''' + tags = ["key", "Heroku"] + + [[rules]] + description = "MailChimp API key" + regex = '''(?i)(mailchimp|mc)(.{0,20})?['"][0-9a-f]{32}-us[0-9]{1,2}['"]''' + tags = ["key", "Mailchimp"] + + [[rules]] + description = "Mailgun API key" + regex = '''(?i)(mailgun|mg)(.{0,20})?[=:\s]['"][0-9a-z]{32}['"]''' + tags = ["key", "Mailgun"] + + [[rules]] + description = "PayPal Braintree access token" + regex = '''access_token\$production\$[0-9a-z]{16}\$[0-9a-f]{32}''' + tags = ["key", "Paypal"] + + [[rules]] + description = "Picatic API key" + regex = '''sk_live_[0-9a-z]{32}''' + tags = ["key", "Picatic"] + + [[rules]] + description = "Slack Webhook" + regex = '''https://hooks.slack.com/services/T[a-zA-Z0-9_]{8}/B[a-zA-Z0-9_]{8}/[a-zA-Z0-9_]{24}''' + tags = ["key", "Slack"] + + #TODO Optimize + [[rules]] + description = "Teams Webhook" + regex = '''https://outlook.office.com/webhook/.{1,120}''' + tags = ["key", "Teams"] + + #TODO Optimize + [[rules]] + description = "Jenkins Webhook" + regex = '''https://.{6,100}/generic-webhook-trigger/invoke''' + tags = ["key", "Jenkins"] + + [[rules]] + description = "Stripe API key" + regex = '''(?i)stripe(.{0,20})?[=:\s]['\"][sk|rk]_live_[0-9a-zA-Z]{24}''' + tags = ["key", "Stripe"] + + [[rules]] + description = "Square access token" + regex = '''sq0atp-[0-9A-Za-z\-_]{22}''' + tags = ["key", "Square"] + + [[rules]] + description = "Square OAuth secret" + regex = '''sq0csp-[0-9A-Za-z\\-_]{43}''' + tags = ["key", "Square"] + + [[rules]] + description = "Twilio API key" + regex = '''(?i)twilio(.{0,20})?['\"][0-9a-f]{32}['\"]''' + tags = ["key", "Twilio"] + + [allowlist] + description = "Whitelisted files" + files = [ + '''^.*gitleaks(config)?.*\.toml$''', + '''(.*?)(jpg|gif|doc|pdf|jepg|png|bin|yarn\.lock|svg)$''', + '''(go\.mod|go\.sum)$''', + '''(swagger-ui.*)(js|css|map)$''', + '''package-lock\.json''' + ] + paths = ["node_modules"] + + config_filenames_only.toml: |- + title = "gitleaks config" + + [[rules]] + description = "File names with potential keys and credentials" + file = '''(?i)(id_rsa|id_dsa|id_ed25519|id_ecdsa|passwd|pgpass|pem|key|shadow + |npmrc_auth|s3cfg|dockercfg|wp-config\.php|htpasswd|env|git-credentials|tugboat|netrc|_netrc|ftpconfig + |remote-sync\.json|sftp\.json|sftp-config\.json|webservers\.xml|logins\.json|dbeaver-data-sources\.xml + |sshd_config|sh_history|history|bash_history|dhcpd\.conf|connections\.xml|pgpass|secret_token\.rb + |credentials\.xml|robomongo\.json|terraform\.tfvars)''' + tags = ["key", "FileName"] + + [[rules]] + description = "File extension with potential keys and credentials" + file = '''(?i)\.(pem|ppk|bashrc|pkcs12|p12|pfx|asc|ovpn|cscfg|rdp|mdf|sdf|sqlite|sqlite3|bek + |tpm|fve|jks|psafe3|keychain|pcap|gnucash|kwallet|tblk|s3cfg|kdbx|sqldumb|htpasswd|dockercfg)''' + tags = ["key", "FileExtension"] + + + + [allowlist] + description = "Whitelisted files" + files = [ + '''^.*gitleaks(config)?.*\.toml$''', + '''(.*?)(jpg|gif|doc|pdf|jepg|png|bin|yarn\.lock|svg)$''', + '''(go\.mod|go\.sum)$''', + '''(swagger-ui.*)(js|css|map)$''', + '''package-lock\.json''' + ] + paths = ["node_modules"] + + + + diff --git a/scanners/gitleaks/values.yaml b/scanners/gitleaks/values.yaml new file mode 100644 index 0000000000..0f032a237f --- /dev/null +++ b/scanners/gitleaks/values.yaml @@ -0,0 +1,47 @@ +image: + # image.repository -- Container Image to run the scan + repository: docker.io/securecodebox/scanner-gitleaks + # image.tag -- defaults to the charts version + tag: null + +parserImage: + # parserImage.tag - defaults to the charts version + # parserImage.repository -- Parser image repository + repository: docker.io/securecodebox/parser-gitleaks + # parserImage.tag -- Parser image tag + # @default -- defaults to the charts version + tag: null + +scannerJob: + # scannerJob.ttlSecondsAfterFinished -- Defines how long the scanner job after finishing will be available (see: https://kubernetes.io/docs/concepts/workloads/controllers/ttlafterfinished/) + ttlSecondsAfterFinished: null + + # scannerJob.resources -- CPU/memory resource requests/limits (see: https://kubernetes.io/docs/tasks/configure-pod-container/assign-memory-resource/, https://kubernetes.io/docs/tasks/configure-pod-container/assign-cpu-resource/) + resources: {} + # resources: + # requests: + # memory: "256Mi" + # cpu: "250m" + # limits: + # memory: "512Mi" + # cpu: "500m" + + # scannerJob.env -- Optional environment variables mapped into each scanJob (see: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) + env: [] + + # scannerJob.extraVolumes -- Optional Volumes mapped into each scanJob (see: https://kubernetes.io/docs/concepts/storage/volumes/) + extraVolumes: + - name: "gitleaks-config" + configMap: + name: "gitleaks-config" + + # scannerJob.extraVolumeMounts -- Optional VolumeMounts mapped into each scanJob (see: https://kubernetes.io/docs/concepts/storage/volumes/) + extraVolumeMounts: + - name: "gitleaks-config" + mountPath: "/home/" + + # scannerJob.extraContainers -- Optional additional Containers started with each scanJob (see: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/) + extraContainers: [] + + # scannerJob.securityContext -- Optional securityContext set on scanner container (see: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/) + securityContext: {} From 97369b0a83fc98e6f590c64f0dcc37a0d8fd7160 Mon Sep 17 00:00:00 2001 From: SebieF Date: Fri, 16 Oct 2020 15:16:31 +0200 Subject: [PATCH 25/38] Added correct namespace to scan for test --- .github/workflows/ci.yaml | 2 ++ tests/integration/scanner/kubeaudit.test.js | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2e3e1c72de..3fd0175af3 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -473,6 +473,8 @@ jobs: npx jest --ci --color kube-hunter - name: "kubeaudit Integration Tests" run: | + kubectl create namespace kubeaudit-tests + helm -n kubeaudit-tests install juice-shop ./demo-apps/juice-shop/ --wait helm -n integration-tests install kubeaudit ./scanners/kubeaudit/ \ --set="parserImage.tag=sha-$(git rev-parse --short HEAD)" \ --set="image.tag=0.11.5" diff --git a/tests/integration/scanner/kubeaudit.test.js b/tests/integration/scanner/kubeaudit.test.js index 474866b741..887d001c0e 100644 --- a/tests/integration/scanner/kubeaudit.test.js +++ b/tests/integration/scanner/kubeaudit.test.js @@ -1,10 +1,10 @@ const { scan } = require("../helpers"); test( - "kubeaudit should run and check the jshop in kubeaudit-test namespace", + "kubeaudit should run and check the jshop in kubeaudit-tests namespace", async () => { const { categories, severities } = await scan( - "kubeaudit-test", + "kubeaudit-tests", "kubeaudit", ["-n", "kubeaudit-test"], 60 From 530e4c9646d716d18640c0ad64c253ed03615be5 Mon Sep 17 00:00:00 2001 From: SebieF Date: Fri, 16 Oct 2020 15:16:47 +0200 Subject: [PATCH 26/38] Added correct namespace to scan for test --- tests/integration/scanner/kubeaudit.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/scanner/kubeaudit.test.js b/tests/integration/scanner/kubeaudit.test.js index 887d001c0e..11f6826103 100644 --- a/tests/integration/scanner/kubeaudit.test.js +++ b/tests/integration/scanner/kubeaudit.test.js @@ -6,7 +6,7 @@ test( const { categories, severities } = await scan( "kubeaudit-tests", "kubeaudit", - ["-n", "kubeaudit-test"], + ["-n", "kubeaudit-tests"], 60 ); From c76b03d5caabdabf0197eab7ef281eeb0efcaaec Mon Sep 17 00:00:00 2001 From: SebieF Date: Fri, 16 Oct 2020 15:33:37 +0200 Subject: [PATCH 27/38] Increased test timeout, delete namespace after test --- .github/workflows/ci.yaml | 1 + tests/integration/scanner/kubeaudit.test.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 3fd0175af3..056ab32db2 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -480,6 +480,7 @@ jobs: --set="image.tag=0.11.5" cd tests/integration/ npx jest --ci --color kubeaudit + kubectl delete namespace kubeaudit-tests - name: "ssh-scan Integration Tests" run: | helm -n integration-tests install ssh-scan ./scanners/ssh_scan/ --set="parserImage.tag=sha-$(git rev-parse --short HEAD)" diff --git a/tests/integration/scanner/kubeaudit.test.js b/tests/integration/scanner/kubeaudit.test.js index 11f6826103..afaca745d4 100644 --- a/tests/integration/scanner/kubeaudit.test.js +++ b/tests/integration/scanner/kubeaudit.test.js @@ -7,7 +7,7 @@ test( "kubeaudit-tests", "kubeaudit", ["-n", "kubeaudit-tests"], - 60 + 90 ); expect(categories).toMatchInlineSnapshot(` From d992c01464811a2213125d14f42850331b77da69 Mon Sep 17 00:00:00 2001 From: SebieF Date: Mon, 19 Oct 2020 10:41:49 +0200 Subject: [PATCH 28/38] Fixed kubeaudit Scope in CI --- .github/workflows/ci.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 056ab32db2..1daf38c823 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -478,6 +478,7 @@ jobs: helm -n integration-tests install kubeaudit ./scanners/kubeaudit/ \ --set="parserImage.tag=sha-$(git rev-parse --short HEAD)" \ --set="image.tag=0.11.5" + --set="kubeauditScope=cluster" cd tests/integration/ npx jest --ci --color kubeaudit kubectl delete namespace kubeaudit-tests From 01962229767372bc35b40c7be6a6e9b9316f7fef Mon Sep 17 00:00:00 2001 From: SebieF Date: Mon, 19 Oct 2020 10:55:18 +0200 Subject: [PATCH 29/38] Fix typo --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 1daf38c823..1cf724e21e 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -477,7 +477,7 @@ jobs: helm -n kubeaudit-tests install juice-shop ./demo-apps/juice-shop/ --wait helm -n integration-tests install kubeaudit ./scanners/kubeaudit/ \ --set="parserImage.tag=sha-$(git rev-parse --short HEAD)" \ - --set="image.tag=0.11.5" + --set="image.tag=0.11.5" \ --set="kubeauditScope=cluster" cd tests/integration/ npx jest --ci --color kubeaudit From 66ef1ba8c2b711fa973f04a9aa04ece53445484d Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach Date: Mon, 19 Oct 2020 11:03:12 +0200 Subject: [PATCH 30/38] Create Missing NetworkPolicy findings --- .../parser/__snapshots__/parser.test.js.snap | 11 +++++++++++ scanners/kubeaudit/parser/parser.js | 19 +++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/scanners/kubeaudit/parser/__snapshots__/parser.test.js.snap b/scanners/kubeaudit/parser/__snapshots__/parser.test.js.snap index 81956096f6..ab3b0171b7 100644 --- a/scanners/kubeaudit/parser/__snapshots__/parser.test.js.snap +++ b/scanners/kubeaudit/parser/__snapshots__/parser.test.js.snap @@ -201,5 +201,16 @@ Array [ "osi_layer": "NOT_APPLICABLE", "severity": "LOW", }, + Object { + "attributes": Object { + "Namespace": "default", + }, + "category": "No Default Deny NetworkPolicy", + "description": "Namespace is missing a default deny ingress and egress NetworkPolicy.", + "location": "namespace://default", + "name": "Namespace \\"default\\" is missing a Default Deny NetworkPolicy", + "osi_layer": "NOT_APPLICABLE", + "severity": "MEDIUM", + }, ] `; diff --git a/scanners/kubeaudit/parser/parser.js b/scanners/kubeaudit/parser/parser.js index 6cd9363bcd..3288ef4c2b 100644 --- a/scanners/kubeaudit/parser/parser.js +++ b/scanners/kubeaudit/parser/parser.js @@ -67,6 +67,20 @@ function createNonRootUserNotEnforcedFinding({ msg, Container }) { }; } +function createMissingNetworkPolicyFinding({ msg, Namespace }) { + return { + name: `Namespace "${Namespace}" is missing a Default Deny NetworkPolicy`, + description: msg, + category: "No Default Deny NetworkPolicy", + location: `namespace://${Namespace}`, + osi_layer: "NOT_APPLICABLE", + severity: "MEDIUM", + attributes: { + Namespace: Namespace, + }, + }; +} + async function parse(fileContent) { return fileContent .split("\n") @@ -103,6 +117,11 @@ async function parse(fileContent) { ) { return createNonRootUserNotEnforcedFinding(finding); } + if ( + finding.AuditResultName === "MissingDefaultDenyIngressAndEgressNetworkPolicy" + ) { + return createMissingNetworkPolicyFinding(finding); + } return null; }) From 4d1c91b293466640054aa6ade1aaa355d30072b1 Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach Date: Mon, 19 Oct 2020 11:16:49 +0200 Subject: [PATCH 31/38] Update expected integration test findings --- tests/integration/scanner/kubeaudit.test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/integration/scanner/kubeaudit.test.js b/tests/integration/scanner/kubeaudit.test.js index afaca745d4..a84e860e7c 100644 --- a/tests/integration/scanner/kubeaudit.test.js +++ b/tests/integration/scanner/kubeaudit.test.js @@ -14,6 +14,7 @@ test( Object { "Automounted ServiceAccount Token": 1, "Capability Not Dropped": 14, + "No Default Deny NetworkPolicy": 1, "Non ReadOnly Root Filesystem": 1, "Non Root User Not Enforced": 1, } @@ -21,7 +22,7 @@ test( expect(severities).toMatchInlineSnapshot(` Object { "low": 16, - "medium": 1, + "medium": 2, } `); }, From b76d6034c68de0792efa398eaf278ea6f81e5c3a Mon Sep 17 00:00:00 2001 From: J12934 Date: Mon, 19 Oct 2020 09:20:46 +0000 Subject: [PATCH 32/38] Updating Helm Docs --- scanners/kubeaudit/README.md | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/scanners/kubeaudit/README.md b/scanners/kubeaudit/README.md index 3a9cb51a85..504545a2f4 100644 --- a/scanners/kubeaudit/README.md +++ b/scanners/kubeaudit/README.md @@ -1,11 +1,4 @@ ---- -title: "kubeaudit" -category: "scanner" -type: "Kubernetes" -state: "roadmap" -appVersion: "0.9.0" -usecase: "Audit your Kubernetes clusters" ---- +# kubeaudit ![Version: latest](https://img.shields.io/badge/Version-latest-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: v0.11.5](https://img.shields.io/badge/AppVersion-v0.11.5-informational?style=flat-square) From 7738ccba4089212f31647b49c056b827b32421e2 Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach Date: Mon, 19 Oct 2020 11:30:24 +0200 Subject: [PATCH 33/38] Add proper kubeaudit readme --- scanners/kubeaudit/README.md | 32 +++++++++++++++++++---------- scanners/kubeaudit/README.md.gotmpl | 31 ++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 11 deletions(-) create mode 100644 scanners/kubeaudit/README.md.gotmpl diff --git a/scanners/kubeaudit/README.md b/scanners/kubeaudit/README.md index 504545a2f4..ed91a7c5b0 100644 --- a/scanners/kubeaudit/README.md +++ b/scanners/kubeaudit/README.md @@ -1,22 +1,30 @@ -# kubeaudit +--- +title: "kubeaudit" +category: "scanner" +type: "Kubernetes" +state: "released" +appVersion: "0.15.1" +usecase: "Kubernetes Configuration Scanner" +--- -![Version: latest](https://img.shields.io/badge/Version-latest-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: v0.11.5](https://img.shields.io/badge/AppVersion-v0.11.5-informational?style=flat-square) +Kubeaudit finds security misconfigurations in you Kubernetes Resources and gives tips on how to resolve these. -A Helm chart for the kubeaudit security scanner that integrates with the secureCodeBox. +Kubeaudit comes with a large lists of "auditors" which test various aspects, like the SecurityContext of pods. +You can find the complete list of [auditors here](https://github.com/Shopify/kubeaudit/tree/master/docs/auditors). -**Homepage:** +To learn more about the kubeaudit itself visit [kubeaudit GitHub]. -## Maintainers + -| Name | Email | Url | -| ---- | ------ | --- | -| iteratec GmbH | secureCodeBox@iteratec.com | | +## Deployment -## Source Code +The kube-hunter ScanType can be deployed via helm: -* +```bash +helm upgrade --install kubeaudit secureCodeBox/kubeaudit +``` -## Values +## Chart Configuration | Key | Type | Default | Description | |-----|------|---------|-------------| @@ -35,3 +43,5 @@ A Helm chart for the kubeaudit security scanner that integrates with the secureC | scannerJob.securityContext.readOnlyRootFilesystem | bool | `true` | Prevents write access to the containers file system | | scannerJob.securityContext.runAsNonRoot | bool | `true` | Enforces that the scanner image is run as a non root user | | scannerJob.ttlSecondsAfterFinished | string | `nil` | Defines how long the scanner job after finishing will be available (see: https://kubernetes.io/docs/concepts/workloads/controllers/ttlafterfinished/) | + +[kubeaudit GitHub]: https://github.com/Shopify/kubeaudit/ diff --git a/scanners/kubeaudit/README.md.gotmpl b/scanners/kubeaudit/README.md.gotmpl new file mode 100644 index 0000000000..28e259bdfa --- /dev/null +++ b/scanners/kubeaudit/README.md.gotmpl @@ -0,0 +1,31 @@ +--- +title: "kubeaudit" +category: "scanner" +type: "Kubernetes" +state: "released" +appVersion: "0.15.1" +usecase: "Kubernetes Configuration Scanner" +--- + +Kubeaudit finds security misconfigurations in you Kubernetes Resources and gives tips on how to resolve these. + +Kubeaudit comes with a large lists of "auditors" which test various aspects, like the SecurityContext of pods. +You can find the complete list of [auditors here](https://github.com/Shopify/kubeaudit/tree/master/docs/auditors). + +To learn more about the kubeaudit itself visit [kubeaudit GitHub]. + + + +## Deployment + +The kube-hunter ScanType can be deployed via helm: + +```bash +helm upgrade --install kubeaudit secureCodeBox/kubeaudit +``` + +## Chart Configuration + +{{ template "chart.valuesTable" . }} + +[kubeaudit GitHub]: https://github.com/Shopify/kubeaudit/ From 2e05457f41f05c43ec3a116cb979852eca4b5e4e Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach Date: Mon, 19 Oct 2020 11:32:05 +0200 Subject: [PATCH 34/38] Add missing newlines at EOF --- scanners/kubeaudit/.helmignore | 2 +- scanners/kubeaudit/templates/kubeaudit-rbac.yaml | 2 +- scanners/kubeaudit/values.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/scanners/kubeaudit/.helmignore b/scanners/kubeaudit/.helmignore index 2b6e53d709..84e9115fbc 100644 --- a/scanners/kubeaudit/.helmignore +++ b/scanners/kubeaudit/.helmignore @@ -2,4 +2,4 @@ parser/ scanner/ -examples/ \ No newline at end of file +examples/ diff --git a/scanners/kubeaudit/templates/kubeaudit-rbac.yaml b/scanners/kubeaudit/templates/kubeaudit-rbac.yaml index 53d580a48e..69d7971e43 100644 --- a/scanners/kubeaudit/templates/kubeaudit-rbac.yaml +++ b/scanners/kubeaudit/templates/kubeaudit-rbac.yaml @@ -102,4 +102,4 @@ roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: kubeaudit -{{- end }} \ No newline at end of file +{{- end }} diff --git a/scanners/kubeaudit/values.yaml b/scanners/kubeaudit/values.yaml index f2a72981b8..9f185a3831 100644 --- a/scanners/kubeaudit/values.yaml +++ b/scanners/kubeaudit/values.yaml @@ -48,4 +48,4 @@ scannerJob: - all # kubeauditScope -- Automatically sets up rbac roles for kubeaudit to access the ressources it scans. Can be either "cluster" (ClusterRole) or "namespace" (Role) -kubeauditScope: "namespace" \ No newline at end of file +kubeauditScope: "namespace" From f4a517343d748d6e69eaf26188a51ed3002a6509 Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach Date: Mon, 19 Oct 2020 11:51:33 +0200 Subject: [PATCH 35/38] Remove commented out code --- scanners/kubeaudit/scanner/Dockerfile | 4 ---- 1 file changed, 4 deletions(-) diff --git a/scanners/kubeaudit/scanner/Dockerfile b/scanners/kubeaudit/scanner/Dockerfile index 5eede33f16..dc5d5c51b1 100644 --- a/scanners/kubeaudit/scanner/Dockerfile +++ b/scanners/kubeaudit/scanner/Dockerfile @@ -1,7 +1,3 @@ -# FROM golang:1.15 as inliner -# WORKDIR /ui -# RUN go get -u github.com/Shopify/kubeaudit/... - FROM golang:1.15.1 AS builder # no need to include cgo bindings From 150ab5a5477779dc89fee9872e42ef809b5d3c48 Mon Sep 17 00:00:00 2001 From: SebieF Date: Mon, 19 Oct 2020 12:20:05 +0200 Subject: [PATCH 36/38] Rename createNonRootFsFinding to createNonReadOnlyRootFsFinding --- scanners/kubeaudit/parser/parser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scanners/kubeaudit/parser/parser.js b/scanners/kubeaudit/parser/parser.js index 6cd9363bcd..198edfcd23 100644 --- a/scanners/kubeaudit/parser/parser.js +++ b/scanners/kubeaudit/parser/parser.js @@ -13,7 +13,7 @@ function createDropCapabilityFinding({ Capability, Container, msg }) { }; } -function createNonRootFsFinding({ Container, msg }) { +function createNonReadOnlyRootFsFinding({ Container, msg }) { return { name: `Container Uses a non ReadOnly Root Filesystem`, description: msg, From fd3a08095f247d3a0befd9ccfad3402038b4af15 Mon Sep 17 00:00:00 2001 From: SebieF Date: Mon, 19 Oct 2020 12:20:48 +0200 Subject: [PATCH 37/38] New line at EOF --- scanners/kubeaudit/scanner/wrapper.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scanners/kubeaudit/scanner/wrapper.sh b/scanners/kubeaudit/scanner/wrapper.sh index 919443fd55..67a782f987 100644 --- a/scanners/kubeaudit/scanner/wrapper.sh +++ b/scanners/kubeaudit/scanner/wrapper.sh @@ -1 +1 @@ -/kubeaudit $@ >/home/securecodebox/kubeaudit.jsonl \ No newline at end of file +/kubeaudit $@ >/home/securecodebox/kubeaudit.jsonl From 982bb7de9792b981d475a1fca8f6fd9959e547ee Mon Sep 17 00:00:00 2001 From: SebieF Date: Mon, 19 Oct 2020 12:27:05 +0200 Subject: [PATCH 38/38] Fixed function call --- scanners/kubeaudit/parser/parser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scanners/kubeaudit/parser/parser.js b/scanners/kubeaudit/parser/parser.js index 9a2b294215..a2af7251f3 100644 --- a/scanners/kubeaudit/parser/parser.js +++ b/scanners/kubeaudit/parser/parser.js @@ -99,7 +99,7 @@ async function parse(fileContent) { finding.AuditResultName === "ReadOnlyRootFilesystemFalse" || finding.AuditResultName === "ReadOnlyRootFilesystemNil" ) { - return createNonRootFsFinding(finding); + return createNonReadOnlyRootFsFinding(finding); } if (finding.AuditResultName === "PrivilegedTrue") { return createPrivilegedContainerFinding(finding);