From 2ad6589bf4726fe9e33fbc233c73441ce223069e Mon Sep 17 00:00:00 2001 From: Lukas Fischer Date: Tue, 8 Aug 2023 17:41:02 +0200 Subject: [PATCH 01/15] #1838 Use trivy to generate CycloneDX SBOMs Add a new ScanType to trivy, that allows generating CycloneDX SBOMs from container images. This is part of work to integrate an SBOM workflow into the secureCodeBox. At the moment the SBOMs are only generated and uploaded to storage, nothing else happens to them. Note that this is a different result type than the other trivy scans, this uses "sbom-cyclonedx", therefore the normal trivy parser will not run for these scans. Signed-off-by: Lukas Fischer --- .../examples/image-sbom-juice-shop/README.md | 7 +++ .../examples/image-sbom-juice-shop/scan.yaml | 12 +++++ scanners/trivy/templates/trivy-scan-type.yaml | 52 ++++++++++++++++++- 3 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 scanners/trivy/examples/image-sbom-juice-shop/README.md create mode 100644 scanners/trivy/examples/image-sbom-juice-shop/scan.yaml diff --git a/scanners/trivy/examples/image-sbom-juice-shop/README.md b/scanners/trivy/examples/image-sbom-juice-shop/README.md new file mode 100644 index 0000000000..99a07b3806 --- /dev/null +++ b/scanners/trivy/examples/image-sbom-juice-shop/README.md @@ -0,0 +1,7 @@ + + +This example shows how to generate a CycloneDX SBOM from a container image using the `trivy image` scanner with CycloneDX output with the secureCodeBox. diff --git a/scanners/trivy/examples/image-sbom-juice-shop/scan.yaml b/scanners/trivy/examples/image-sbom-juice-shop/scan.yaml new file mode 100644 index 0000000000..3d9a47947b --- /dev/null +++ b/scanners/trivy/examples/image-sbom-juice-shop/scan.yaml @@ -0,0 +1,12 @@ +# SPDX-FileCopyrightText: the secureCodeBox authors +# +# SPDX-License-Identifier: Apache-2.0 + +apiVersion: "execution.securecodebox.io/v1" +kind: Scan +metadata: + name: "trivy-image-sbom-juice-shop" +spec: + scanType: "trivy-image-sbom" + parameters: + - "bkimminich/juice-shop:v15.0.0" diff --git a/scanners/trivy/templates/trivy-scan-type.yaml b/scanners/trivy/templates/trivy-scan-type.yaml index 26ca7cb052..9701012dd2 100644 --- a/scanners/trivy/templates/trivy-scan-type.yaml +++ b/scanners/trivy/templates/trivy-scan-type.yaml @@ -297,4 +297,54 @@ spec: serviceAccountName: trivy-k8s volumes: {{- toYaml .Values.scanner.extraVolumes | nindent 12 }} - +--- +apiVersion: "execution.securecodebox.io/v1" +kind: ScanType +metadata: + name: "trivy-image-sbom{{ .Values.scanner.nameAppend | default ""}}" +spec: + extractResults: + type: sbom-cyclonedx + location: "/home/securecodebox/sbom-cyclonedx.json" + jobTemplate: + spec: + {{- if .Values.scanner.ttlSecondsAfterFinished }} + ttlSecondsAfterFinished: {{ .Values.scanner.ttlSecondsAfterFinished }} + {{- end }} + backoffLimit: {{ .Values.scanner.backoffLimit }} + {{- if .Values.scanner.activeDeadlineSeconds }} + activeDeadlineSeconds: {{ .Values.scanner.activeDeadlineSeconds }} + {{- end }} + template: + spec: + restartPolicy: OnFailure + affinity: + {{- toYaml .Values.scanner.affinity | nindent 12 }} + tolerations: + {{- toYaml .Values.scanner.tolerations | nindent 12 }} + containers: + - name: trivy + image: "{{ .Values.scanner.image.repository }}:{{ .Values.scanner.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.scanner.image.pullPolicy }} + command: + - "trivy" + - "image" + # Suppress progress bar, as it pollutes non interactive terminal logs + - "--no-progress" + - "--format" + - "cyclonedx" + - "--output" + - "/home/securecodebox/sbom-cyclonedx.json" + resources: + {{- toYaml .Values.scanner.resources | nindent 16 }} + securityContext: + {{- toYaml .Values.scanner.securityContext | nindent 16 }} + env: + {{- toYaml .Values.scanner.env | nindent 16 }} + volumeMounts: + {{- toYaml .Values.scanner.extraVolumeMounts | nindent 16 }} + {{- if .Values.scanner.extraContainers }} + {{- toYaml .Values.scanner.extraContainers | nindent 12 }} + {{- end }} + volumes: + {{- toYaml .Values.scanner.extraVolumes | nindent 12 }} From 643937fb0c05bcc06e5e53cdb0712e1d69218181 Mon Sep 17 00:00:00 2001 From: Lukas Fischer Date: Tue, 8 Aug 2023 17:54:25 +0200 Subject: [PATCH 02/15] #1838 Add a parser for CycloneDX SBOMs This is a bit tricky, because now trivy can generate two different kinds of scan results, normal trivy-json results and sbom-cyclonedx results. To keep these parsers separated, an additional CycloneDX parser is added. To not overcomplicate the structure, this parser is added in the trivy scanner directory. If in the future there are multiple scanners creating SBOMs, it can be moved somewhere else. Adding a second parser for a scanner requires some Makefile trickery. Usually the docker-build, docker-export, kind-import and deploy Makefile targets are used to build and deploy a scanner. Adapting the first three targets is easy, the additional parser just needs an extra build step wich can be added as another dependency. The deploy is more tricky because it is a single helm upgrade --install command, which sets the necessary values for docker repository and tag. This is achieved by redefining the Makefile target, but it is not the nicest solution, make warns about the target getting overridden. The parser is a generic cyclonedx parser (although with currently limited functionality), but currently lives in the trivy scanner directory because it is only used here and means the global structure does not have to get changed. Because it is not trivy-specific, it is not supposed to use "trivy" in its name/repository. This means, the common-... Makefile targets cannot always be used, and if they can, they have to be used in unintended ways. By default they get used by setting the module variable to the directory name of what is built and then add the name of the scanner in the end, for example the parser for trivy ends up being built as parser-trivy from the parser directory. Now to prevent the CycloneDX parser getting named parser-cyclonedx-trivy, the docker-build-cyclonedx-parser target cannot use the common-docker-build and the export/import targets have to be a bit creative with using the module and name variables. This might get reworked to make the Makefiles nicer again. Signed-off-by: Lukas Fischer --- scanners/trivy/Makefile | 40 +++++++++++++++++++ scanners/trivy/README.md | 9 +++++ scanners/trivy/docs/README.ArtifactHub.md | 9 +++++ scanners/trivy/parser-cyclonedx/Dockerfile | 9 +++++ .../trivy/parser-cyclonedx/package-lock.json | 13 ++++++ scanners/trivy/parser-cyclonedx/package.json | 9 +++++ scanners/trivy/parser-cyclonedx/parser.js | 25 ++++++++++++ .../templates/trivy-parse-definition.yaml | 25 ++++++++++++ scanners/trivy/values.yaml | 28 +++++++++++++ 9 files changed, 167 insertions(+) create mode 100644 scanners/trivy/parser-cyclonedx/Dockerfile create mode 100644 scanners/trivy/parser-cyclonedx/package-lock.json create mode 100644 scanners/trivy/parser-cyclonedx/package.json create mode 100644 scanners/trivy/parser-cyclonedx/parser.js diff --git a/scanners/trivy/Makefile b/scanners/trivy/Makefile index 2eb284ee54..ff3b62f995 100644 --- a/scanners/trivy/Makefile +++ b/scanners/trivy/Makefile @@ -8,4 +8,44 @@ include_guard = set scanner = trivy +# Add additional dependencies to the targets for the CycloneDX parser +docker-build: | docker-build-parser-sbom +docker-export: | docker-export-parser-sbom +kind-import: | kind-import-parser-sbom + include ../../scanners.mk + +# Cannot use common-docker-build because the Dockerfile is supposed to be in the $(module) directory and the resulting image is supposed to +# be named $(module)-$(name), but this image is in directory parser-cyclonedx and supposed to be named parser-cyclonedx +.PHONY: docker-build-parser-sbom +docker-build-parser-sbom: + @echo ".: โš™๏ธ Build 'cyclonedx' $(parser-prefix) with BASE_IMG_TAG: '$(BASE_IMG_TAG)'." + docker build \ + --build-arg=scannerVersion=$(shell yq -e .appVersion ./Chart.yaml) \ + --build-arg=baseImageTag=$(BASE_IMG_TAG) \ + --build-arg=namespace=$(IMG_NS) \ + -t $(IMG_NS)/$(parser-prefix)-cyclonedx:$(IMG_TAG) \ + -f ./$(parser-prefix)-cyclonedx/Dockerfile \ + ./$(parser-prefix)-cyclonedx + +# These targets creatively reuse the common-... make targets a bit by resetting the name, otherwise the repository of the image will end in -trivy +.PHONY: docker-export-parser-sbom +docker-export-parser-sbom: + @$(MAKE) -s common-docker-export module=$(parser-prefix) name=cyclonedx + +.PHONY: kind-import-parser-sbom +kind-import-parser-sbom: + @$(MAKE) -s common-kind-import module=$(parser-prefix) name=cyclonedx + +# Deploy needs to be defined again to set the image repository and tag for the cyclonedx parser as well +# This either overrides the original target or runs helm upgrade again, but it will work in either case +.PHONY: deploy-without-scanner +deploy-without-scanner: + @echo ".: ๐Ÿ’พ Deploying '$(name)' $(scanner-prefix) HelmChart with the docker tag '$(IMG_TAG)' into kind namespace 'integration-tests'." + helm -n integration-tests upgrade --install $(name) ./ --wait \ + --set="parser.image.repository=docker.io/$(IMG_NS)/$(parser-prefix)-$(name)" \ + --set="parser.image.tag=$(IMG_TAG)" \ + --set="cyclonedxParser.image.repository=docker.io/$(IMG_NS)/$(parser-prefix)-cyclonedx" \ + --set="cyclonedxParser.image.tag=$(IMG_TAG)" \ + --set="parser.env[0].name=CRASH_ON_FAILED_VALIDATION" \ + --set-string="parser.env[0].value=true" diff --git a/scanners/trivy/README.md b/scanners/trivy/README.md index 7002d29389..b1d9934af7 100644 --- a/scanners/trivy/README.md +++ b/scanners/trivy/README.md @@ -113,6 +113,15 @@ Kubernetes: `>=v1.11.0-0` |-----|------|---------|-------------| | cascadingRules.enabled | bool | `false` | Enables or disables the installation of the default cascading rules for this scanner | | createAutoDiscoveryScanType | bool | `false` | Creates a `trivy-image-autodiscovery` scanType with its own ServiceAccount for the SCB AutoDiscovery, enabled to scan images from both public & private registries. | +| cyclonedxParser.affinity | object | `{}` | Optional affinity settings that control how the parser job is scheduled (see: https://kubernetes.io/docs/tasks/configure-pod-container/assign-pods-nodes-using-node-affinity/) | +| cyclonedxParser.env | list | `[]` | Optional environment variables mapped into each parseJob (see: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) | +| cyclonedxParser.image.pullPolicy | string | `"IfNotPresent"` | Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images | +| cyclonedxParser.image.repository | string | `"docker.io/securecodebox/parser-cyclonedx"` | Parser image repository for CycloneDX SBOM parser | +| cyclonedxParser.image.tag | string | defaults to the charts version | Parser image tag | +| cyclonedxParser.resources | object | { requests: { cpu: "200m", memory: "100Mi" }, limits: { cpu: "400m", memory: "200Mi" } } | Optional resources lets you control resource limits and requests for the parser container. See https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ | +| cyclonedxParser.scopeLimiterAliases | object | `{}` | Optional finding aliases to be used in the scopeLimiter. | +| cyclonedxParser.tolerations | list | `[]` | Optional tolerations settings that control how the parser job is scheduled (see: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) | +| cyclonedxParser.ttlSecondsAfterFinished | string | `nil` | seconds after which the Kubernetes job for the parser will be deleted. Requires the Kubernetes TTLAfterFinished controller: https://kubernetes.io/docs/concepts/workloads/controllers/ttlafterfinished/ | | imagePullSecrets | list | `[]` | Define imagePullSecrets when a private registry is used (see: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/) | | kubeauditScope | string | `"cluster"` | Automatically sets up rbac roles for kubeaudit to access the resources it scans. Can be either "cluster" (ClusterRole) or "namespace" (Role) | | parser.affinity | object | `{}` | Optional affinity settings that control how the parser job is scheduled (see: https://kubernetes.io/docs/tasks/configure-pod-container/assign-pods-nodes-using-node-affinity/) | diff --git a/scanners/trivy/docs/README.ArtifactHub.md b/scanners/trivy/docs/README.ArtifactHub.md index c852079dbf..efde15c7fa 100644 --- a/scanners/trivy/docs/README.ArtifactHub.md +++ b/scanners/trivy/docs/README.ArtifactHub.md @@ -120,6 +120,15 @@ Kubernetes: `>=v1.11.0-0` |-----|------|---------|-------------| | cascadingRules.enabled | bool | `false` | Enables or disables the installation of the default cascading rules for this scanner | | createAutoDiscoveryScanType | bool | `false` | Creates a `trivy-image-autodiscovery` scanType with its own ServiceAccount for the SCB AutoDiscovery, enabled to scan images from both public & private registries. | +| cyclonedxParser.affinity | object | `{}` | Optional affinity settings that control how the parser job is scheduled (see: https://kubernetes.io/docs/tasks/configure-pod-container/assign-pods-nodes-using-node-affinity/) | +| cyclonedxParser.env | list | `[]` | Optional environment variables mapped into each parseJob (see: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) | +| cyclonedxParser.image.pullPolicy | string | `"IfNotPresent"` | Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images | +| cyclonedxParser.image.repository | string | `"docker.io/securecodebox/parser-cyclonedx"` | Parser image repository for CycloneDX SBOM parser | +| cyclonedxParser.image.tag | string | defaults to the charts version | Parser image tag | +| cyclonedxParser.resources | object | { requests: { cpu: "200m", memory: "100Mi" }, limits: { cpu: "400m", memory: "200Mi" } } | Optional resources lets you control resource limits and requests for the parser container. See https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ | +| cyclonedxParser.scopeLimiterAliases | object | `{}` | Optional finding aliases to be used in the scopeLimiter. | +| cyclonedxParser.tolerations | list | `[]` | Optional tolerations settings that control how the parser job is scheduled (see: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) | +| cyclonedxParser.ttlSecondsAfterFinished | string | `nil` | seconds after which the Kubernetes job for the parser will be deleted. Requires the Kubernetes TTLAfterFinished controller: https://kubernetes.io/docs/concepts/workloads/controllers/ttlafterfinished/ | | imagePullSecrets | list | `[]` | Define imagePullSecrets when a private registry is used (see: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/) | | kubeauditScope | string | `"cluster"` | Automatically sets up rbac roles for kubeaudit to access the resources it scans. Can be either "cluster" (ClusterRole) or "namespace" (Role) | | parser.affinity | object | `{}` | Optional affinity settings that control how the parser job is scheduled (see: https://kubernetes.io/docs/tasks/configure-pod-container/assign-pods-nodes-using-node-affinity/) | diff --git a/scanners/trivy/parser-cyclonedx/Dockerfile b/scanners/trivy/parser-cyclonedx/Dockerfile new file mode 100644 index 0000000000..86543ec4f1 --- /dev/null +++ b/scanners/trivy/parser-cyclonedx/Dockerfile @@ -0,0 +1,9 @@ +# SPDX-FileCopyrightText: the secureCodeBox authors +# +# SPDX-License-Identifier: Apache-2.0 + +ARG namespace +ARG baseImageTag +FROM ${namespace:-securecodebox}/parser-sdk-nodejs:${baseImageTag:-latest} +WORKDIR /home/app/parser-wrapper/parser/ +COPY --chown=app:app ./parser.js ./parser.js diff --git a/scanners/trivy/parser-cyclonedx/package-lock.json b/scanners/trivy/parser-cyclonedx/package-lock.json new file mode 100644 index 0000000000..adef34980c --- /dev/null +++ b/scanners/trivy/parser-cyclonedx/package-lock.json @@ -0,0 +1,13 @@ +{ + "name": "@securecodebox/parser-cyclonedx", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "@securecodebox/parser-cyclonedx", + "version": "1.0.0", + "license": "Apache-2.0" + } + } +} diff --git a/scanners/trivy/parser-cyclonedx/package.json b/scanners/trivy/parser-cyclonedx/package.json new file mode 100644 index 0000000000..bd85701561 --- /dev/null +++ b/scanners/trivy/parser-cyclonedx/package.json @@ -0,0 +1,9 @@ +{ + "name": "@securecodebox/parser-cyclonedx", + "version": "1.0.0", + "description": "Parses result files for the type: 'sbom-cyclonedx'.", + "main": "", + "scripts": {}, + "author": "the secureCodeBox authors", + "license": "Apache-2.0" +} diff --git a/scanners/trivy/parser-cyclonedx/parser.js b/scanners/trivy/parser-cyclonedx/parser.js new file mode 100644 index 0000000000..58c535ea55 --- /dev/null +++ b/scanners/trivy/parser-cyclonedx/parser.js @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: the secureCodeBox authors +// +// SPDX-License-Identifier: Apache-2.0 + +async function parse(_fileContent, scan) { + const imageId = scan.spec.parameters[0]; + const downloadLink = scan.status.rawResultDownloadLink; + + // TODO parse vulnerabilities section if it exists + + return [ + { + name: `SBOM for ${imageId}`, + description: `Generated an SBOM for: '${imageId}'`, + category: "SBOM", + osi_layer: "APPLICATION", + severity: "INFORMATIONAL", + attributes: { + downloadLink, + }, + }, + ]; +} + +module.exports.parse = parse; diff --git a/scanners/trivy/templates/trivy-parse-definition.yaml b/scanners/trivy/templates/trivy-parse-definition.yaml index ceea06b517..56c941569d 100644 --- a/scanners/trivy/templates/trivy-parse-definition.yaml +++ b/scanners/trivy/templates/trivy-parse-definition.yaml @@ -26,3 +26,28 @@ spec: resources: {{- toYaml . | nindent 4 }} {{- end }} +--- +apiVersion: "execution.securecodebox.io/v1" +kind: ParseDefinition +metadata: + name: "sbom-cyclonedx" +spec: + image: "{{ .Values.cyclonedxParser.image.repository }}:{{ .Values.cyclonedxParser.image.tag | default .Chart.Version }}" + imagePullPolicy: {{ .Values.cyclonedxParser.image.pullPolicy }} + ttlSecondsAfterFinished: {{ .Values.cyclonedxParser.ttlSecondsAfterFinished }} + env: + {{- toYaml .Values.cyclonedxParser.env | nindent 4 }} + scopeLimiterAliases: + {{- toYaml .Values.cyclonedxParser.scopeLimiterAliases | nindent 4 }} + affinity: + {{- toYaml .Values.cyclonedxParser.affinity | nindent 4 }} + tolerations: + {{- toYaml .Values.cyclonedxParser.tolerations | nindent 4 }} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.cyclonedxParser.resources }} + resources: + {{- toYaml . | nindent 4 }} + {{- end }} diff --git a/scanners/trivy/values.yaml b/scanners/trivy/values.yaml index 208929793e..bed5d7d551 100644 --- a/scanners/trivy/values.yaml +++ b/scanners/trivy/values.yaml @@ -32,6 +32,34 @@ parser: # @default -- { requests: { cpu: "200m", memory: "100Mi" }, limits: { cpu: "400m", memory: "200Mi" } } resources: {} +cyclonedxParser: + image: + # cyclonedxParser.image.repository -- Parser image repository for CycloneDX SBOM parser + repository: docker.io/securecodebox/parser-cyclonedx + # cyclonedxParser.image.tag -- Parser image tag + # @default -- defaults to the charts version + tag: null + # -- Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images + pullPolicy: IfNotPresent + + # cyclonedxParser.ttlSecondsAfterFinished -- seconds after which the Kubernetes job for the parser will be deleted. Requires the Kubernetes TTLAfterFinished controller: https://kubernetes.io/docs/concepts/workloads/controllers/ttlafterfinished/ + ttlSecondsAfterFinished: null + # cyclonedxParser.env -- Optional environment variables mapped into each parseJob (see: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) + env: [] + + # cyclonedxParser.scopeLimiterAliases -- Optional finding aliases to be used in the scopeLimiter. + scopeLimiterAliases: {} + + # cyclonedxParser.affinity -- Optional affinity settings that control how the parser job is scheduled (see: https://kubernetes.io/docs/tasks/configure-pod-container/assign-pods-nodes-using-node-affinity/) + affinity: {} + + # cyclonedxParser.tolerations -- Optional tolerations settings that control how the parser job is scheduled (see: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) + tolerations: [] + + # -- Optional resources lets you control resource limits and requests for the parser container. See https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + # @default -- { requests: { cpu: "200m", memory: "100Mi" }, limits: { cpu: "400m", memory: "200Mi" } } + resources: {} + scanner: image: # scanner.image.repository -- Container Image to run the scan From 1af7a181fb38b9898f7918828d9de1ca590cea30 Mon Sep 17 00:00:00 2001 From: Lukas Fischer Date: Wed, 9 Aug 2023 10:01:20 +0200 Subject: [PATCH 03/15] #1838 Add a Dependency-Track persistence hook Basic functionality of a persistence hook, that sends CycloneDX SBOMs to OWASP Dependency-Track. Since hooks run for all scans, the hook first checks the scanType and then the bomFormat property to see if the rawResult is actually a CycloneDX SBOM. If it is it gets uploaded to Dependency-Track. To assign an SBOM to a project, either the project UUID or the name and version are required. The hook tries to grab the name and version from the name property of the SBOM and automatically creates missing projects. This is not the nicest way to get name and version, but there is no better property in the SBOM to read it from. It works for container scans, but probably breaks for other kinds of SBOM scans. Signed-off-by: Lukas Fischer --- .../.helm-docs.gotmpl | 58 ++++++++++ hooks/persistence-dependencytrack/.helmignore | 23 ++++ hooks/persistence-dependencytrack/Chart.yaml | 29 +++++ hooks/persistence-dependencytrack/Makefile | 10 ++ hooks/persistence-dependencytrack/README.md | 104 ++++++++++++++++++ .../hook/Dockerfile | 15 +++ .../persistence-dependencytrack/hook/hook.js | 60 ++++++++++ .../hook/package-lock.json | 13 +++ .../hook/package-lock.json.license | 3 + .../hook/package.json | 30 +++++ .../hook/package.json.license | 3 + .../templates/NOTES.txt | 6 + .../templates/_helpers.tpl | 51 +++++++++ .../templates/persistence-provider.yaml | 37 +++++++ hooks/persistence-dependencytrack/values.yaml | 49 +++++++++ 15 files changed, 491 insertions(+) create mode 100644 hooks/persistence-dependencytrack/.helm-docs.gotmpl create mode 100644 hooks/persistence-dependencytrack/.helmignore create mode 100644 hooks/persistence-dependencytrack/Chart.yaml create mode 100644 hooks/persistence-dependencytrack/Makefile create mode 100644 hooks/persistence-dependencytrack/README.md create mode 100644 hooks/persistence-dependencytrack/hook/Dockerfile create mode 100644 hooks/persistence-dependencytrack/hook/hook.js create mode 100644 hooks/persistence-dependencytrack/hook/package-lock.json create mode 100644 hooks/persistence-dependencytrack/hook/package-lock.json.license create mode 100644 hooks/persistence-dependencytrack/hook/package.json create mode 100644 hooks/persistence-dependencytrack/hook/package.json.license create mode 100644 hooks/persistence-dependencytrack/templates/NOTES.txt create mode 100644 hooks/persistence-dependencytrack/templates/_helpers.tpl create mode 100644 hooks/persistence-dependencytrack/templates/persistence-provider.yaml create mode 100644 hooks/persistence-dependencytrack/values.yaml diff --git a/hooks/persistence-dependencytrack/.helm-docs.gotmpl b/hooks/persistence-dependencytrack/.helm-docs.gotmpl new file mode 100644 index 0000000000..186f24c358 --- /dev/null +++ b/hooks/persistence-dependencytrack/.helm-docs.gotmpl @@ -0,0 +1,58 @@ +{{- /* +SPDX-FileCopyrightText: the secureCodeBox authors + +SPDX-License-Identifier: Apache-2.0 +*/ -}} + +{{- define "extra.docsSection" -}} +--- +title: "Dependency-Track" +category: "hook" +type: "persistenceProvider" +state: "developing" +usecase: "Publishes all CycloneDX SBOMs to Dependency-Track." +--- +{{- end }} + +{{- define "extra.dockerDeploymentSection" -}} +## Supported Tags +- `latest` (represents the latest stable release build) +- tagged releases, e.g. `3.0.0`, `2.9.0`, `2.8.0`, `2.7.0` +{{- end }} + +{{- define "extra.chartAboutSection" -}} +## What is "Persistence Dependency-Track" Hook about? +The Dependency-Track persistenceProvider hook saves all generated CycloneDX SBOMs into the configured [OWASP Dependency-Track][dependencytrack.org] instance, other findings or SPDX SBOMs cannot be handled and are ignored. +This allows automatically cataloging infrastructure to gain an overview over the used components and dependencies. +To learn more about Dependency-Track visit [dependencytrack.org]. +{{- end }} + +{{- define "extra.scannerConfigurationSection" -}}{{- end }} + +{{- define "extra.chartConfigurationSection" -}} +## Additional Chart Configurations + +Installing the Dependency-Track persistenceProvider hook will add a _ReadOnly Hook_ to your namespace. + +You need to provide the API key to connect to Dependency-Track as a [Kubernetes secret][k8ssecret]. +Check the [Dependency-Track documentation][dt-api-docs], to learn how to configure an API key. + +```bash +kubectl create secret generic dependencytrack-credentials --from-literal="apikey=NoEs..." + +helm upgrade --install dt secureCodeBox/persistence-dependencytrack \ + --set="dependencytrack.url=https://dependency-track-backend.default.svc" +``` + +SBOMs are imported for a project in Dependency-Track. +To avoid configuring all of them by hand first and assigning projects to scans somehow, the hook automatically detects name and version from the scan and then creates Dependency-Track projects if they do not exist yet. +This requires either the `PORTFOLIO_MANAGEMENT` or `PROJECT_CREATION_UPLOAD` permission for the API key which gets used by the hook (or rather for the team the key is defined for). + +{{- end }} + +{{- define "extra.scannerLinksSection" -}} +[dependencytrack.org]: https://dependencytrack.org/ +[dt-api-docs]: https://docs.dependencytrack.org/integrations/rest-api/ +[k8ssecret]: https://kubernetes.io/docs/concepts/configuration/secret/ +[trivy-sbom]: /docs/scanners/trivy-sbom +{{- end }} diff --git a/hooks/persistence-dependencytrack/.helmignore b/hooks/persistence-dependencytrack/.helmignore new file mode 100644 index 0000000000..0e8a0eb36f --- /dev/null +++ b/hooks/persistence-dependencytrack/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/hooks/persistence-dependencytrack/Chart.yaml b/hooks/persistence-dependencytrack/Chart.yaml new file mode 100644 index 0000000000..9663800b6a --- /dev/null +++ b/hooks/persistence-dependencytrack/Chart.yaml @@ -0,0 +1,29 @@ +# SPDX-FileCopyrightText: the secureCodeBox authors +# +# SPDX-License-Identifier: Apache-2.0 + +apiVersion: v2 +name: persistence-dependencytrack +description: The dependencytrack persistence provider persists secureCodeBox CycloneDX SBOMs into Dependency-Track. + +type: application + +# version - gets automatically set to the secureCodeBox release version when the helm charts gets published +version: "4.0.0" + +appVersion: "4.8.2" +kubeVersion: ">=v1.11.0-0" + +keywords: + - security + - secureCodeBox + - hook + - dependencytrack + - owasp +sources: + - https://github.com/secureCodeBox/secureCodeBox +maintainers: + - name: iteratec GmbH + email: secureCodeBox@iteratec.com + +dependencies: [] diff --git a/hooks/persistence-dependencytrack/Makefile b/hooks/persistence-dependencytrack/Makefile new file mode 100644 index 0000000000..03cd8ff196 --- /dev/null +++ b/hooks/persistence-dependencytrack/Makefile @@ -0,0 +1,10 @@ +#!/usr/bin/make -f +# +# SPDX-FileCopyrightText: the secureCodeBox authors +# +# SPDX-License-Identifier: Apache-2.0 + +include_guard = set +hook = persistence-dependencytrack + +include ../../hooks.mk diff --git a/hooks/persistence-dependencytrack/README.md b/hooks/persistence-dependencytrack/README.md new file mode 100644 index 0000000000..dfc45f516f --- /dev/null +++ b/hooks/persistence-dependencytrack/README.md @@ -0,0 +1,104 @@ +--- +title: "Dependency-Track" +category: "hook" +type: "persistenceProvider" +state: "developing" +usecase: "Publishes all CycloneDX SBOMs to Dependency-Track." +--- + + + + +

+ License Apache-2.0 + GitHub release (latest SemVer) + OWASP Lab Project + Artifact HUB + GitHub Repo stars + Twitter Follower +

+ +## What is "Persistence Dependency-Track" Hook about? +The Dependency-Track persistenceProvider hook saves all generated CycloneDX SBOMs into the configured [OWASP Dependency-Track][dependencytrack.org] instance, other findings or SPDX SBOMs cannot be handled and are ignored. +This allows automatically cataloging infrastructure to gain an overview over the used components and dependencies. +To learn more about Dependency-Track visit [dependencytrack.org]. + +## Deployment +The persistence-dependencytrack chart can be deployed via helm: + +```bash +# Install HelmChart (use -n to configure another namespace) +helm upgrade --install persistence-dependencytrack secureCodeBox/persistence-dependencytrack +``` + +## Requirements + +Kubernetes: `>=v1.11.0-0` + +## Additional Chart Configurations + +Installing the Dependency-Track persistenceProvider hook will add a _ReadOnly Hook_ to your namespace. + +You need to provide the API key to connect to Dependency-Track as a [Kubernetes secret][k8ssecret]. +Check the [Dependency-Track documentation][dt-api-docs], to learn how to configure an API key. + +```bash +kubectl create secret generic dependencytrack-credentials --from-literal="apikey=NoEs..." + +helm upgrade --install dt secureCodeBox/persistence-dependencytrack \ + --set="dependencytrack.url=https://dependency-track-backend.default.svc" +``` + +SBOMs are imported for a project in Dependency-Track. +To avoid configuring all of them by hand first and assigning projects to scans somehow, the hook automatically detects name and version from the scan and then creates Dependency-Track projects if they do not exist yet. +This requires either the `PORTFOLIO_MANAGEMENT` or `PROJECT_CREATION_UPLOAD` permission for the API key which gets used by the hook (or rather for the team the key is defined for). + +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| dependencytrack.authentication | object | `{"apiKeyKey":"apikey","userSecret":"dependencytrack-credentials"}` | Authentication information. Dependency-Track expects an API key, which can be generated for a team (see: https://docs.dependencytrack.org/integrations/rest-api/). The hook automatically creates missing projects, for that either the PORTFOLIO_MANAGEMENT or PROJECT_CREATION_UPLOAD permission is required. | +| dependencytrack.authentication.apiKeyKey | string | `"apikey"` | Name of the apikey key in the `userSecret` secret. | +| dependencytrack.authentication.userSecret | string | `"dependencytrack-credentials"` | Link a pre-existing generic secret with `apikey` key / value pair | +| dependencytrack.url | string | `"http://dependency-track-backend.default.svc"` | Url to the Dependency-Track instance, make sure to use the backend url | +| hook.affinity | object | `{}` | Optional affinity settings that control how the hook job is scheduled (see: https://kubernetes.io/docs/tasks/configure-pod-container/assign-pods-nodes-using-node-affinity/) | +| hook.image.pullPolicy | string | `"IfNotPresent"` | Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images | +| hook.image.repository | string | `"docker.io/securecodebox/hook-persistence-dependencytrack"` | Hook image repository | +| hook.image.tag | string | defaults to the charts version | Container image tag | +| hook.labels | object | `{}` | Add Kubernetes Labels to the hook definition | +| hook.priority | int | `0` | Hook priority. Higher priority Hooks are guaranteed to execute before low priority Hooks. | +| hook.resources | object | { requests: { cpu: "200m", memory: "100Mi" }, limits: { cpu: "400m", memory: "200Mi" } } | Optional resources lets you control resource limits and requests for the hook container. See https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ | +| hook.tolerations | list | `[]` | Optional tolerations settings that control how the hook job is scheduled (see: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) | +| hook.ttlSecondsAfterFinished | string | `nil` | Seconds after which the kubernetes job for the hook will be deleted. Requires the Kubernetes TTLAfterFinished controller: https://kubernetes.io/docs/concepts/workloads/controllers/ttlafterfinished/ | +| imagePullSecrets | list | `[]` | Define imagePullSecrets when a private registry is used (see: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/) | + +## License +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) + +Code of secureCodeBox is licensed under the [Apache License 2.0][scb-license]. + +[scb-owasp]: https://www.owasp.org/index.php/OWASP_secureCodeBox +[scb-docs]: https://www.securecodebox.io/ +[scb-site]: https://www.securecodebox.io/ +[scb-github]: https://github.com/secureCodeBox/ +[scb-twitter]: https://twitter.com/secureCodeBox +[scb-slack]: https://join.slack.com/t/securecodebox/shared_invite/enQtNDU3MTUyOTM0NTMwLTBjOWRjNjVkNGEyMjQ0ZGMyNDdlYTQxYWQ4MzNiNGY3MDMxNThkZjJmMzY2NDRhMTk3ZWM3OWFkYmY1YzUxNTU +[scb-license]: https://github.com/secureCodeBox/secureCodeBox/blob/master/LICENSE +[dependencytrack.org]: https://dependencytrack.org/ +[dt-api-docs]: https://docs.dependencytrack.org/integrations/rest-api/ +[k8ssecret]: https://kubernetes.io/docs/concepts/configuration/secret/ +[trivy-sbom]: /docs/scanners/trivy-sbom diff --git a/hooks/persistence-dependencytrack/hook/Dockerfile b/hooks/persistence-dependencytrack/hook/Dockerfile new file mode 100644 index 0000000000..011abb7a6a --- /dev/null +++ b/hooks/persistence-dependencytrack/hook/Dockerfile @@ -0,0 +1,15 @@ +# SPDX-FileCopyrightText: the secureCodeBox authors +# +# SPDX-License-Identifier: Apache-2.0 + +ARG namespace +ARG baseImageTag +FROM node:18-alpine as build +RUN mkdir -p /home/app +WORKDIR /home/app +COPY package.json package-lock.json ./ +RUN npm ci --production + +FROM ${namespace:-securecodebox}/hook-sdk-nodejs:${baseImageTag:-latest} +WORKDIR /home/app/hook-wrapper/hook/ +COPY --chown=app:app ./hook.js ./hook.js diff --git a/hooks/persistence-dependencytrack/hook/hook.js b/hooks/persistence-dependencytrack/hook/hook.js new file mode 100644 index 0000000000..3f4dd4ca0a --- /dev/null +++ b/hooks/persistence-dependencytrack/hook/hook.js @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: the secureCodeBox authors +// +// SPDX-License-Identifier: Apache-2.0 + +const apiKey = process.env["DEPENDENCYTRACK_APIKEY"] +const baseUrl = process.env["DEPENDENCYTRACK_URL"]; +const url = baseUrl.replace(/\/$/, "") + "/api/v1/bom" + +async function handle({ getRawResults, scan }) { + if (scan.status.rawResultType !== "sbom-cyclonedx") { + // Not an SBOM scan, cannot be handled by Dependency-Track, ignore + console.log(`Scan ${scan.metadata.name} is not an SBOM scan, ignoring.`); + return; + } + + const result = await getRawResults(); + if (result.bomFormat !== "CycloneDX") { + // Not a CycloneDX SBOM, cannot be handled by Dependency-Track, ignore + console.log("Only CycloneDX SBOMs can be sent to DependencyTrack, ignoring."); + return; + } + + console.log(`Persisting SBOM for ${result.metadata.component.name} to Dependency-Track`); + + // Get the project name and version from the name attribute of the main component + // This might be a bit brittle, but there is not really a better way to get this information + // Neither Trivy's nor Syft's SBOM contains a useful version attribute (none or sha256) + const components = result.metadata.component.name.split(':'); + const name = components[0]; + const version = components.length > 1 ? components.pop() : "latest"; + + // The POST endpoint expects multipart/form-data + // Alternatively the PUT endpoint could be used, which requires base64-encoding the SBOM + const formData = new FormData(); + // Automatically create new projects for uploaded SBOMs, + // this requires either the PORTFOLIO_MANAGEMENT or PROJECT_CREATION_UPLOAD permission + formData.append("autoCreate", "true"); + formData.append("projectName", name); + formData.append("projectVersion", version); + formData.append("bom", JSON.stringify(result)); + + console.log(`Uploading SBOM for name: ${name} version: ${version} to ${url}`); + + // Send request to API endpoint + const response = await fetch(url, { + method: "POST", + cache: "no-cache", + headers: { + "X-API-Key": apiKey, + }, + body: formData, + }); + + // Response-token can be used to determine if any task is being performed on the BOM + // Endpoint: /api/v1/bom/ + const content = await response.json(); + console.log(`Successfully uploaded SBOM to Dependency-Track. Response-token to check the status: ${content.token}`); +} + +module.exports.handle = handle; diff --git a/hooks/persistence-dependencytrack/hook/package-lock.json b/hooks/persistence-dependencytrack/hook/package-lock.json new file mode 100644 index 0000000000..557a567405 --- /dev/null +++ b/hooks/persistence-dependencytrack/hook/package-lock.json @@ -0,0 +1,13 @@ +{ + "name": "@securecodebox/hook-persistence-dependencytrack", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "@securecodebox/hook-persistence-dependencytrack", + "version": "1.0.0", + "license": "Apache-2.0" + } + } +} diff --git a/hooks/persistence-dependencytrack/hook/package-lock.json.license b/hooks/persistence-dependencytrack/hook/package-lock.json.license new file mode 100644 index 0000000000..c95bc37185 --- /dev/null +++ b/hooks/persistence-dependencytrack/hook/package-lock.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: the secureCodeBox authors + +SPDX-License-Identifier: Apache-2.0 diff --git a/hooks/persistence-dependencytrack/hook/package.json b/hooks/persistence-dependencytrack/hook/package.json new file mode 100644 index 0000000000..135f3c6ef7 --- /dev/null +++ b/hooks/persistence-dependencytrack/hook/package.json @@ -0,0 +1,30 @@ +{ + "name": "@securecodebox/hook-persistence-dependencytrack", + "version": "1.0.0", + "description": "secureCodeBox hook to persist CycloneDX SBOMs to Dependency-Track.", + "homepage": "https://www.secureCodeBox.io", + "repository": { + "type": "git", + "url": "git+https://github.com/secureCodeBox/secureCodeBox.git" + }, + "main": "hook.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [ + "secureCodeBox", + "security", + "hook", + "dependencytrack", + "persistence" + ], + "author": { + "name": "iteratec GmbH", + "email": "securecodebox@iteratec.com", + "url": "https://www.iteratec.com" + }, + "bugs": { + "url": "https://github.com/secureCodeBox/secureCodeBox/issues" + }, + "license": "Apache-2.0" +} diff --git a/hooks/persistence-dependencytrack/hook/package.json.license b/hooks/persistence-dependencytrack/hook/package.json.license new file mode 100644 index 0000000000..c95bc37185 --- /dev/null +++ b/hooks/persistence-dependencytrack/hook/package.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: the secureCodeBox authors + +SPDX-License-Identifier: Apache-2.0 diff --git a/hooks/persistence-dependencytrack/templates/NOTES.txt b/hooks/persistence-dependencytrack/templates/NOTES.txt new file mode 100644 index 0000000000..6d7ce1ff94 --- /dev/null +++ b/hooks/persistence-dependencytrack/templates/NOTES.txt @@ -0,0 +1,6 @@ +{{- /* +SPDX-FileCopyrightText: the secureCodeBox authors + +SPDX-License-Identifier: Apache-2.0 +*/}} +Dependency-Track PersistenceProvider succesfully deployed ๐ŸŽ‰. diff --git a/hooks/persistence-dependencytrack/templates/_helpers.tpl b/hooks/persistence-dependencytrack/templates/_helpers.tpl new file mode 100644 index 0000000000..90ec55a6d5 --- /dev/null +++ b/hooks/persistence-dependencytrack/templates/_helpers.tpl @@ -0,0 +1,51 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "persistence-dependencytrack.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "persistence-dependencytrack.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "persistence-dependencytrack.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "persistence-dependencytrack.labels" -}} +helm.sh/chart: {{ include "persistence-dependencytrack.chart" . }} +{{ include "persistence-dependencytrack.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "persistence-dependencytrack.selectorLabels" -}} +app.kubernetes.io/name: {{ include "persistence-dependencytrack.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} diff --git a/hooks/persistence-dependencytrack/templates/persistence-provider.yaml b/hooks/persistence-dependencytrack/templates/persistence-provider.yaml new file mode 100644 index 0000000000..bbb3b23e16 --- /dev/null +++ b/hooks/persistence-dependencytrack/templates/persistence-provider.yaml @@ -0,0 +1,37 @@ +# SPDX-FileCopyrightText: the secureCodeBox authors +# +# SPDX-License-Identifier: Apache-2.0 + +apiVersion: "execution.securecodebox.io/v1" +kind: ScanCompletionHook +metadata: + name: {{ include "persistence-dependencytrack.fullname" . }} + labels: + {{- include "persistence-dependencytrack.labels" . | nindent 4 }} + type: Structured + {{- with .Values.hook.labels }} + {{ toYaml . }} + {{- end }} +spec: + priority: {{ .Values.hook.priority }} + type: ReadOnly + image: "{{ .Values.hook.image.repository }}:{{ .Values.hook.image.tag | default .Chart.Version }}" + ttlSecondsAfterFinished: {{ .Values.hook.ttlSecondsAfterFinished }} + env: + - name: DEPENDENCYTRACK_URL + value: {{ .Values.dependencytrack.url | quote }} + - name: DEPENDENCYTRACK_APIKEY + valueFrom: + secretKeyRef: + name: {{ .Values.dependencytrack.authentication.userSecret }} + key: {{ .Values.dependencytrack.authentication.apiKeyKey }} + affinity: {{- toYaml .Values.hook.affinity | nindent 4 }} + tolerations: {{- toYaml .Values.hook.tolerations | nindent 4 }} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.hook.resources }} + resources: + {{- toYaml . | nindent 4 }} + {{- end }} diff --git a/hooks/persistence-dependencytrack/values.yaml b/hooks/persistence-dependencytrack/values.yaml new file mode 100644 index 0000000000..5fca7a34e0 --- /dev/null +++ b/hooks/persistence-dependencytrack/values.yaml @@ -0,0 +1,49 @@ +# SPDX-FileCopyrightText: the secureCodeBox authors +# +# SPDX-License-Identifier: Apache-2.0 + +# Default values for persistence-dependencytrack. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +# -- Define imagePullSecrets when a private registry is used (see: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/) +imagePullSecrets: [] + +hook: + image: + # hook.image.repository -- Hook image repository + repository: docker.io/securecodebox/hook-persistence-dependencytrack + # -- Container image tag + # @default -- defaults to the charts version + tag: null + # -- Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images + pullPolicy: IfNotPresent + + # hook.labels -- Add Kubernetes Labels to the hook definition + labels: {} + + # -- Hook priority. Higher priority Hooks are guaranteed to execute before low priority Hooks. + priority: 0 + + # -- Seconds after which the kubernetes job for the hook will be deleted. Requires the Kubernetes TTLAfterFinished controller: https://kubernetes.io/docs/concepts/workloads/controllers/ttlafterfinished/ + ttlSecondsAfterFinished: null + + # hook.affinity -- Optional affinity settings that control how the hook job is scheduled (see: https://kubernetes.io/docs/tasks/configure-pod-container/assign-pods-nodes-using-node-affinity/) + affinity: {} + + # hook.tolerations -- Optional tolerations settings that control how the hook job is scheduled (see: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) + tolerations: [] + + # -- Optional resources lets you control resource limits and requests for the hook container. See https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + # @default -- { requests: { cpu: "200m", memory: "100Mi" }, limits: { cpu: "400m", memory: "200Mi" } } + resources: {} + +dependencytrack: + # -- Url to the Dependency-Track instance, make sure to use the backend url + url: "http://dependency-track-backend.default.svc" + # -- Authentication information. Dependency-Track expects an API key, which can be generated for a team (see: https://docs.dependencytrack.org/integrations/rest-api/). The hook automatically creates missing projects, for that either the PORTFOLIO_MANAGEMENT or PROJECT_CREATION_UPLOAD permission is required. + authentication: + # -- Link a pre-existing generic secret with `apikey` key / value pair + userSecret: dependencytrack-credentials + # -- Name of the apikey key in the `userSecret` secret. + apiKeyKey: apikey # what a name ๐Ÿ™ƒ From 812303edc0bcc8c9e2bf6fea609c9f8c314be90d Mon Sep 17 00:00:00 2001 From: Lukas Fischer Date: Fri, 1 Sep 2023 23:02:33 +0200 Subject: [PATCH 04/15] #1838 Clarify CycloneDX parser Makefile changes Expand the comments in the trivy Makefile to explain more in detail why the changes are necessary to build the the CycloneDX parser as well. Signed-off-by: Lukas Fischer --- scanners/trivy/Makefile | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/scanners/trivy/Makefile b/scanners/trivy/Makefile index ff3b62f995..eaeb137633 100644 --- a/scanners/trivy/Makefile +++ b/scanners/trivy/Makefile @@ -8,15 +8,22 @@ include_guard = set scanner = trivy -# Add additional dependencies to the targets for the CycloneDX parser +# Usually scanners are supposed to have exactly one parser, but Trivy can produce either vulnerability findings from +# normal scans or CycloneDX SBOMs. Using separate parsers for each of these formats is cleaner because technically the +# CycloneDX parser is not Trivy-specific. At the moment it is kept in the Trivy directory though so that the +# architecture does not have to get changed more than it needs to for an MVP. This Makefile is edited to allow building +# both parsers by using the targets as usual. + +# Add additional dependencies to the targets for the CycloneDX parser so that it gets built as well docker-build: | docker-build-parser-sbom docker-export: | docker-export-parser-sbom kind-import: | kind-import-parser-sbom include ../../scanners.mk -# Cannot use common-docker-build because the Dockerfile is supposed to be in the $(module) directory and the resulting image is supposed to -# be named $(module)-$(name), but this image is in directory parser-cyclonedx and supposed to be named parser-cyclonedx +# Cannot use common-docker-build to build the CycloneDX parser because the Dockerfile is supposed to be in the $(module) +# directory and the resulting image is supposed to be named $(module)-$(name), but this image is in directory +# parser-cyclonedx and also supposed to be named parser-cyclonedx, without any mention of trivy. .PHONY: docker-build-parser-sbom docker-build-parser-sbom: @echo ".: โš™๏ธ Build 'cyclonedx' $(parser-prefix) with BASE_IMG_TAG: '$(BASE_IMG_TAG)'." @@ -28,7 +35,8 @@ docker-build-parser-sbom: -f ./$(parser-prefix)-cyclonedx/Dockerfile \ ./$(parser-prefix)-cyclonedx -# These targets creatively reuse the common-... make targets a bit by resetting the name, otherwise the repository of the image will end in -trivy +# These targets creatively reuse the common-... make targets to export and import the CycloneDX parser image by +# resetting the name variable, otherwise the repository name of the image will end in -trivy .PHONY: docker-export-parser-sbom docker-export-parser-sbom: @$(MAKE) -s common-docker-export module=$(parser-prefix) name=cyclonedx @@ -37,8 +45,8 @@ docker-export-parser-sbom: kind-import-parser-sbom: @$(MAKE) -s common-kind-import module=$(parser-prefix) name=cyclonedx -# Deploy needs to be defined again to set the image repository and tag for the cyclonedx parser as well -# This either overrides the original target or runs helm upgrade again, but it will work in either case +# Deploy needs to be defined again to set the image repository and tag for the cyclonedx parser as well. This either +# overrides the original target or runs helm upgrade again, but it will work in either case .PHONY: deploy-without-scanner deploy-without-scanner: @echo ".: ๐Ÿ’พ Deploying '$(name)' $(scanner-prefix) HelmChart with the docker tag '$(IMG_TAG)' into kind namespace 'integration-tests'." From 55a4a7c5ed2d6979808520ea91a12a8e1b56022d Mon Sep 17 00:00:00 2001 From: Lukas Fischer Date: Mon, 4 Sep 2023 15:47:19 +0200 Subject: [PATCH 05/15] #1838 Add unit tests for dependencytrack hook Make sure the persistence-dependencytrack hook can be tested automatically by providing some tests for it. Some parts of the tests might be coupled a bit tightly to the actual implementation, if the endpoint is switched to the PUT endpoint the tests will have to be adapted. The tests inject the mocked dependencies through parameters, similar to how the tests for the generic webhook work. Since fetch() is the only dependency here, this works pretty well. Signed-off-by: Lukas Fischer --- .../persistence-dependencytrack/hook/hook.js | 13 ++- .../hook/hook.test.js | 110 ++++++++++++++++++ .../hook/package.json | 2 +- 3 files changed, 119 insertions(+), 6 deletions(-) create mode 100644 hooks/persistence-dependencytrack/hook/hook.test.js diff --git a/hooks/persistence-dependencytrack/hook/hook.js b/hooks/persistence-dependencytrack/hook/hook.js index 3f4dd4ca0a..b038df9e58 100644 --- a/hooks/persistence-dependencytrack/hook/hook.js +++ b/hooks/persistence-dependencytrack/hook/hook.js @@ -2,11 +2,13 @@ // // SPDX-License-Identifier: Apache-2.0 -const apiKey = process.env["DEPENDENCYTRACK_APIKEY"] -const baseUrl = process.env["DEPENDENCYTRACK_URL"]; -const url = baseUrl.replace(/\/$/, "") + "/api/v1/bom" - -async function handle({ getRawResults, scan }) { +async function handle({ + getRawResults, + scan, + apiKey = process.env["DEPENDENCYTRACK_APIKEY"], + baseUrl = process.env["DEPENDENCYTRACK_URL"], + fetch = global.fetch +}) { if (scan.status.rawResultType !== "sbom-cyclonedx") { // Not an SBOM scan, cannot be handled by Dependency-Track, ignore console.log(`Scan ${scan.metadata.name} is not an SBOM scan, ignoring.`); @@ -39,6 +41,7 @@ async function handle({ getRawResults, scan }) { formData.append("projectVersion", version); formData.append("bom", JSON.stringify(result)); + const url = baseUrl.replace(/\/$/, "") + "/api/v1/bom" console.log(`Uploading SBOM for name: ${name} version: ${version} to ${url}`); // Send request to API endpoint diff --git a/hooks/persistence-dependencytrack/hook/hook.test.js b/hooks/persistence-dependencytrack/hook/hook.test.js new file mode 100644 index 0000000000..dcbf3f3026 --- /dev/null +++ b/hooks/persistence-dependencytrack/hook/hook.test.js @@ -0,0 +1,110 @@ +// SPDX-FileCopyrightText: the secureCodeBox authors +// +// SPDX-License-Identifier: Apache-2.0 + +const { handle } = require("./hook"); +const fetch = jest.fn(() => Promise.resolve({ json: () => Promise.resolve({ token: "statustoken" }) })); + +beforeEach(() => { + jest.clearAllMocks(); +}); + +test("should not send a post request if not an SBOM scan", async () => { + const result = {}; + + const getRawResults = async () => result; + + const scan = { + metadata: { + uid: "25ea7ba4-48cf-45e4-ae5c-be1de83df9b8", + name: "demo-trivy", + }, + status: { + rawResultType: "trivy-json" + } + }; + + const apiKey = "verysecretgitleaksplsignore" + const baseUrl = "http://example.com/foo/bar"; + + await handle({ getRawResults, scan, apiKey, baseUrl, fetch }); + + expect(fetch).toBeCalledTimes(0); +}); + +test("should not send a post request if not a CycloneDX SBOM", async () => { + const result = { + spdxVersion: "SPDX-2.3", + dataLicense: "CC0-1.0", + SPDXID: "SPDXRef-DOCUMENT", + name: "bkimminich/juice-shop:v15.0.0", + documentNamespace: "https://anchore.com/syft/image/bkimminich/juice-shop-v15.0.0-f25938fd-9d66-4dc6-a4c6-b0390b4cf037", + creationInfo: { + licenseListVersion: "3.21", + creators: [ + "Organization: Anchore, Inc", + "Tool: syft-0.85.0", + ], + created: "2023-08-02T11:42:48Z", + } + }; + + const getRawResults = async () => result; + + // technically we're saying here that this scan is a CycloneDX scan even though we're then sending something looking like an SPDX SBOM + const scan = { + metadata: { + uid: "c79e135e-3624-47dc-92d1-2ae6e7355a44", + name: "demo-sbom", + }, + status: { + rawResultType: "sbom-cyclonedx" + } + }; + + const apiKey = "verysecretgitleaksplsignore" + const baseUrl = "http://example.com/foo/bar"; + + await handle({ getRawResults, scan, apiKey, baseUrl, fetch }); + + expect(fetch).toBeCalledTimes(0); +}); + +test("should send a post request to the url when fired", async () => { + const result = { + bomFormat: "CycloneDX", + metadata: { + component: { + name: "hello-world:latest" + } + } + }; + + const getRawResults = async () => result; + + const scan = { + metadata: { + uid: "69e71358-bb01-425b-9bde-e45653605490", + name: "demo-sbom", + }, + status: { + rawResultType: "sbom-cyclonedx" + } + }; + + const apiKey = "verysecretgitleaksplsignore" + const baseUrl = "http://example.com/foo/bar"; + const url = baseUrl + "/api/v1/bom" + + await handle({ getRawResults, scan, apiKey, baseUrl, fetch }); + + expect(fetch).toBeCalledTimes(1); + expect(fetch).toBeCalledWith(url, expect.objectContaining({ + method: "POST", + headers: { + "X-API-Key": apiKey, + }, + })); + + expect(fetch.mock.calls[0][1].body.get("bom")).toBe(JSON.stringify(result)); +}); diff --git a/hooks/persistence-dependencytrack/hook/package.json b/hooks/persistence-dependencytrack/hook/package.json index 135f3c6ef7..b0a7fc4f49 100644 --- a/hooks/persistence-dependencytrack/hook/package.json +++ b/hooks/persistence-dependencytrack/hook/package.json @@ -9,7 +9,7 @@ }, "main": "hook.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "jest --verbose --ci --colors --coverage --passWithNoTests" }, "keywords": [ "secureCodeBox", From 332a175acdfb99c2d344350878b128fa3cf8cbce Mon Sep 17 00:00:00 2001 From: Lukas Fischer Date: Mon, 4 Sep 2023 16:25:33 +0200 Subject: [PATCH 06/15] #1838 Add unit tests for parser-cyclonedx Add a small and very simple test for the CycloneDX parser, after all it doesn't do much. More annoying is again the Makefile structure, since the project is not set up for multiple parsers for a scanner, it also does not accommodate that for the tests. To circumvent this, the added target for the parser-cyclonedx tests needs to manually run the install-deps-js target with a different module name, so that all the dependencies get installed correctly. Signed-off-by: Lukas Fischer --- scanners/trivy/Makefile | 10 ++++ .../trivy/parser-cyclonedx/parser.test.js | 54 +++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 scanners/trivy/parser-cyclonedx/parser.test.js diff --git a/scanners/trivy/Makefile b/scanners/trivy/Makefile index eaeb137633..264c2fe168 100644 --- a/scanners/trivy/Makefile +++ b/scanners/trivy/Makefile @@ -18,6 +18,7 @@ scanner = trivy docker-build: | docker-build-parser-sbom docker-export: | docker-export-parser-sbom kind-import: | kind-import-parser-sbom +unit-tests: unit-tests-parser-sbom include ../../scanners.mk @@ -57,3 +58,12 @@ deploy-without-scanner: --set="cyclonedxParser.image.tag=$(IMG_TAG)" \ --set="parser.env[0].name=CRASH_ON_FAILED_VALIDATION" \ --set-string="parser.env[0].value=true" + +# The unit tests for the cyclonedx-parser cannot reuse the unit-test-js target, because it requires install-deps-js, +# which then tries to run npm ci in ../../${module}-sdk/nodejs and module is set to "parser-cyclonedx", but +# install-deps-js still needs to be executed, in case this target runs before the normal parser tests +.PHONY: unit-tests-parser-sbom +unit-tests-parser-sbom: + @$(MAKE) -s install-deps-js module=$(parser-prefix) + @echo ".: ๐Ÿงช Starting unit-tests for '$(name)' parser-cyclonedx." + npm run test:unit -- ${name}/parser-cyclonedx/ diff --git a/scanners/trivy/parser-cyclonedx/parser.test.js b/scanners/trivy/parser-cyclonedx/parser.test.js new file mode 100644 index 0000000000..9d01c02f65 --- /dev/null +++ b/scanners/trivy/parser-cyclonedx/parser.test.js @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: the secureCodeBox authors +// +// SPDX-License-Identifier: Apache-2.0 + +const { parse } = require("./parser"); +const { + validateParser, +} = require("@securecodebox/parser-sdk-nodejs/parser-utils"); + +let scan; + +beforeEach(() => { + scan = { + metadata: { + name: "my-cyclonedx-sbom-scan", + namespace: "default", + }, + spec: { + scanType: "trivy-image-sbom", + parameters: ["hello-world:latest"], + }, + status: { + rawResultDownloadLink: "https://s3.example.com/sbom-cyclonedx.json", + }, + }; +}); + +test("should create finding correctly", async () => { + const result = { + bomFormat: "CycloneDX", + metadata: { + component: { + name: "hello-world:latest" + } + } + }; + + const findings = await parse(JSON.stringify(result), scan); + await expect(validateParser(findings)).resolves.toBeUndefined(); + expect(findings).toMatchInlineSnapshot(` +[ + { + "attributes": { + "downloadLink": "https://s3.example.com/sbom-cyclonedx.json", + }, + "category": "SBOM", + "description": "Generated an SBOM for: 'hello-world:latest'", + "name": "SBOM for hello-world:latest", + "osi_layer": "APPLICATION", + "severity": "INFORMATIONAL", + }, +] +`); +}); From 3c8aab6b8779d06196ef27bf91cf3162f886fb39 Mon Sep 17 00:00:00 2001 From: Lukas Fischer Date: Fri, 8 Sep 2023 18:25:46 +0200 Subject: [PATCH 07/15] #1838 Move trivy-sbom to its own scanner Keeping SBOM creation as part of the original trivy scanner created a whole bunch of problems, all related to the fact that we decided that SBOMs should get their own parser instead of reusing the existing trivy parser. Since nothing in the project structure assumes that scanners can have more than one parser, making the Makefile targets work with it was pretty inconvenient. The release process, orchestrated by a GitHub Workflow, also cannot work with multiple parsers. This extracts all SBOM related functionality from the trivy scanner to a new trivy-sbom scanner, which then works again with the usual project structure. The parser is still a generic CycloneDX parser, although adding Syft and then reusing the parser needs resturcturing again (or copy-pasting of code). Signed-off-by: Lukas Fischer --- scanners/trivy-sbom/.gitignore | 5 + scanners/trivy-sbom/.helm-docs.gotmpl | 54 +++++++ scanners/trivy-sbom/.helmignore | 40 +++++ scanners/trivy-sbom/Chart.yaml | 31 ++++ scanners/trivy-sbom/Makefile | 11 ++ scanners/trivy-sbom/README.md | 120 +++++++++++++++ scanners/trivy-sbom/docs/.gitkeep | 0 .../trivy-sbom/docs/README.ArtifactHub.md | 142 ++++++++++++++++++ .../docs/README.DockerHub-Parser.md | 89 +++++++++++ .../examples/image-juice-shop}/README.md | 0 .../examples/image-juice-shop}/scan.yaml | 4 +- scanners/trivy-sbom/parser/.dockerignore | 5 + .../parser}/Dockerfile | 0 .../parser}/package-lock.json | 0 .../parser}/package.json | 0 .../parser}/parser.js | 0 .../parser}/parser.test.js | 0 .../trivy-sbom-parse-definition.yaml | 28 ++++ .../templates/trivy-sbom-scan-type.yaml | 58 +++++++ scanners/trivy-sbom/values.yaml | 110 ++++++++++++++ scanners/trivy/Makefile | 58 ------- scanners/trivy/README.md | 9 -- scanners/trivy/docs/README.ArtifactHub.md | 9 -- .../templates/trivy-parse-definition.yaml | 25 --- scanners/trivy/templates/trivy-scan-type.yaml | 52 +------ scanners/trivy/values.yaml | 28 ---- 26 files changed, 696 insertions(+), 182 deletions(-) create mode 100644 scanners/trivy-sbom/.gitignore create mode 100644 scanners/trivy-sbom/.helm-docs.gotmpl create mode 100644 scanners/trivy-sbom/.helmignore create mode 100644 scanners/trivy-sbom/Chart.yaml create mode 100644 scanners/trivy-sbom/Makefile create mode 100644 scanners/trivy-sbom/README.md create mode 100644 scanners/trivy-sbom/docs/.gitkeep create mode 100644 scanners/trivy-sbom/docs/README.ArtifactHub.md create mode 100644 scanners/trivy-sbom/docs/README.DockerHub-Parser.md rename scanners/{trivy/examples/image-sbom-juice-shop => trivy-sbom/examples/image-juice-shop}/README.md (100%) rename scanners/{trivy/examples/image-sbom-juice-shop => trivy-sbom/examples/image-juice-shop}/scan.yaml (75%) create mode 100644 scanners/trivy-sbom/parser/.dockerignore rename scanners/{trivy/parser-cyclonedx => trivy-sbom/parser}/Dockerfile (100%) rename scanners/{trivy/parser-cyclonedx => trivy-sbom/parser}/package-lock.json (100%) rename scanners/{trivy/parser-cyclonedx => trivy-sbom/parser}/package.json (100%) rename scanners/{trivy/parser-cyclonedx => trivy-sbom/parser}/parser.js (100%) rename scanners/{trivy/parser-cyclonedx => trivy-sbom/parser}/parser.test.js (100%) create mode 100644 scanners/trivy-sbom/templates/trivy-sbom-parse-definition.yaml create mode 100644 scanners/trivy-sbom/templates/trivy-sbom-scan-type.yaml create mode 100644 scanners/trivy-sbom/values.yaml diff --git a/scanners/trivy-sbom/.gitignore b/scanners/trivy-sbom/.gitignore new file mode 100644 index 0000000000..a5be59dc8d --- /dev/null +++ b/scanners/trivy-sbom/.gitignore @@ -0,0 +1,5 @@ +# SPDX-FileCopyrightText: the secureCodeBox authors +# +# SPDX-License-Identifier: Apache-2.0 + +*.tar diff --git a/scanners/trivy-sbom/.helm-docs.gotmpl b/scanners/trivy-sbom/.helm-docs.gotmpl new file mode 100644 index 0000000000..89e1b7ec34 --- /dev/null +++ b/scanners/trivy-sbom/.helm-docs.gotmpl @@ -0,0 +1,54 @@ +{{- /* +SPDX-FileCopyrightText: the secureCodeBox authors + +SPDX-License-Identifier: Apache-2.0 +*/ -}} + +{{- define "extra.docsSection" -}} +--- +title: "Trivy SBOM" +category: "scanner" +type: "Container" +state: "released" +appVersion: "{{ template "chart.appVersion" . }}" +usecase: "Container Dependency Scanner" +--- +{{- end }} + +{{- define "extra.dockerDeploymentSection" -}} +## Supported Tags +- `latest` (represents the latest stable release build) +- tagged releases, e.g. `{{ template "chart.appVersion" . }}` +{{- end }} + +{{- define "extra.chartAboutSection" -}} +## What is Trivy SBOM? + +`Trivy` (`tri` pronounced like **tri**gger, `vy` pronounced like en**vy**) is a simple and comprehensive vulnerability scanner for containers and other artifacts. +A software vulnerability is a glitch, flaw, or weakness present in the software or in an Operating System. +`Trivy` detects vulnerabilities of OS packages (Alpine, RHEL, CentOS, etc.) and application dependencies (Bundler, Composer, npm, yarn, etc.). +`Trivy` is easy to use. Just install the binary, and you're ready to scan. All you need to do for scanning is to specify a target such as an image name of the container. + +To learn more about the Trivy scanner itself visit [Trivy's GitHub Repository](https://github.com/aquasecurity/trivy). + +This chart uses Trivy's SBOM support to generate Software Bills of Material in CycloneDX format for container images. +{{- end }} + +{{- define "extra.scannerConfigurationSection" -}} +## Scanner Configuration + +The following SBOM generation configuration example is based on the [Trivy Documentation](https://aquasecurity.github.io/trivy/), please take a look at the original documentation for more configuration examples. + +Currently we support the following scanType, corresponding to the trivy scanning modes: +- scanType: "trivy-sbom-image" + - parameters: `[YOUR_IMAGE_NAME]` + +Simply specify an image name (and a tag) when you use the scanType `trivy-sbom-image`. +A complete example is listed below in our [example docs section](https://www.securecodebox.io/docs/scanners/trivy/#examples). +{{- end }} + +{{- define "extra.chartConfigurationSection" -}} +{{- end }} + +{{- define "extra.scannerLinksSection" -}} +{{- end }} diff --git a/scanners/trivy-sbom/.helmignore b/scanners/trivy-sbom/.helmignore new file mode 100644 index 0000000000..1b2144b9bb --- /dev/null +++ b/scanners/trivy-sbom/.helmignore @@ -0,0 +1,40 @@ +# SPDX-FileCopyrightText: the secureCodeBox authors +# +# SPDX-License-Identifier: Apache-2.0 +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ +# Node.js files +node_modules/* +package.json +package-lock.json +src/* +config/* +Dockerfile +.dockerignore +*.tar +parser/* +scanner/* +integration-tests/* +examples/* +docs/* +Makefile diff --git a/scanners/trivy-sbom/Chart.yaml b/scanners/trivy-sbom/Chart.yaml new file mode 100644 index 0000000000..36efeee6d4 --- /dev/null +++ b/scanners/trivy-sbom/Chart.yaml @@ -0,0 +1,31 @@ +# SPDX-FileCopyrightText: the secureCodeBox authors +# +# SPDX-License-Identifier: Apache-2.0 + +apiVersion: v2 +name: trivy-sbom +description: A Helm chart for the trivy-sbom security scanner that integrates with the secureCodeBox. + +type: application +# version - gets automatically set to the secureCodeBox release version when the helm charts gets published +version: v3.1.0-alpha1 +appVersion: "0.45.0" +kubeVersion: ">=v1.11.0-0" +annotations: + versionApi: https://api.github.com/repos/aquasecurity/trivy/releases/latest + supported-platforms: linux/amd64,linux/arm64,linux/ppc64le,linux/s390x +keywords: + - security + - trivy + - sbom + - cyclonedx + - image-scanning + - scanner + - secureCodeBox +home: https://www.securecodebox.io/docs/scanners/trivy-sbom +icon: https://www.securecodebox.io/img/integrationIcons/Trivy.svg +sources: + - https://github.com/secureCodeBox/secureCodeBox +maintainers: + - name: iteratec GmbH + email: secureCodeBox@iteratec.com diff --git a/scanners/trivy-sbom/Makefile b/scanners/trivy-sbom/Makefile new file mode 100644 index 0000000000..1ebc6a0b67 --- /dev/null +++ b/scanners/trivy-sbom/Makefile @@ -0,0 +1,11 @@ +#!/usr/bin/make -f +# +# SPDX-FileCopyrightText: the secureCodeBox authors +# +# SPDX-License-Identifier: Apache-2.0 +# + +include_guard = set +scanner = trivy-sbom + +include ../../scanners.mk diff --git a/scanners/trivy-sbom/README.md b/scanners/trivy-sbom/README.md new file mode 100644 index 0000000000..8497d15e38 --- /dev/null +++ b/scanners/trivy-sbom/README.md @@ -0,0 +1,120 @@ +--- +title: "Trivy SBOM" +category: "scanner" +type: "Container" +state: "released" +appVersion: "0.45.0" +usecase: "Container Dependency Scanner" +--- + + + + +

+ License Apache-2.0 + GitHub release (latest SemVer) + OWASP Lab Project + Artifact HUB + GitHub Repo stars + Twitter Follower +

+ +## What is Trivy SBOM? + +`Trivy` (`tri` pronounced like **tri**gger, `vy` pronounced like en**vy**) is a simple and comprehensive vulnerability scanner for containers and other artifacts. +A software vulnerability is a glitch, flaw, or weakness present in the software or in an Operating System. +`Trivy` detects vulnerabilities of OS packages (Alpine, RHEL, CentOS, etc.) and application dependencies (Bundler, Composer, npm, yarn, etc.). +`Trivy` is easy to use. Just install the binary, and you're ready to scan. All you need to do for scanning is to specify a target such as an image name of the container. + +To learn more about the Trivy scanner itself visit [Trivy's GitHub Repository](https://github.com/aquasecurity/trivy). + +This chart uses Trivy's SBOM support to generate Software Bills of Material in CycloneDX format for container images. + +## Deployment +The trivy-sbom chart can be deployed via helm: + +```bash +# Install HelmChart (use -n to configure another namespace) +helm upgrade --install trivy-sbom secureCodeBox/trivy-sbom +``` + +## Scanner Configuration + +The following SBOM generation configuration example is based on the [Trivy Documentation](https://aquasecurity.github.io/trivy/), please take a look at the original documentation for more configuration examples. + +Currently we support the following scanType, corresponding to the trivy scanning modes: +- scanType: "trivy-sbom-image" + - parameters: `[YOUR_IMAGE_NAME]` + +Simply specify an image name (and a tag) when you use the scanType `trivy-sbom-image`. +A complete example is listed below in our [example docs section](https://www.securecodebox.io/docs/scanners/trivy/#examples). + +## Requirements + +Kubernetes: `>=v1.11.0-0` + +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| cascadingRules.enabled | bool | `false` | Enables or disables the installation of the default cascading rules for this scanner | +| imagePullSecrets | list | `[]` | Define imagePullSecrets when a private registry is used (see: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/) | +| parser.affinity | object | `{}` | Optional affinity settings that control how the parser job is scheduled (see: https://kubernetes.io/docs/tasks/configure-pod-container/assign-pods-nodes-using-node-affinity/) | +| parser.env | list | `[]` | Optional environment variables mapped into each parseJob (see: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) | +| parser.image.pullPolicy | string | `"IfNotPresent"` | Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images | +| parser.image.repository | string | `"docker.io/securecodebox/parser-cyclonedx"` | Parser image repository | +| parser.image.tag | string | defaults to the charts version | Parser image tag | +| parser.resources | object | { requests: { cpu: "200m", memory: "100Mi" }, limits: { cpu: "400m", memory: "200Mi" } } | Optional resources lets you control resource limits and requests for the parser container. See https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ | +| parser.scopeLimiterAliases | object | `{}` | Optional finding aliases to be used in the scopeLimiter. | +| parser.tolerations | list | `[]` | Optional tolerations settings that control how the parser job is scheduled (see: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) | +| parser.ttlSecondsAfterFinished | string | `nil` | seconds after which the Kubernetes job for the parser will be deleted. Requires the Kubernetes TTLAfterFinished controller: https://kubernetes.io/docs/concepts/workloads/controllers/ttlafterfinished/ | +| scanner.activeDeadlineSeconds | string | `nil` | There are situations where you want to fail a scan Job after some amount of time. To do so, set activeDeadlineSeconds to define an active deadline (in seconds) when considering a scan Job as failed. (see: https://kubernetes.io/docs/concepts/workloads/controllers/job/#job-termination-and-cleanup) | +| scanner.affinity | object | `{}` | Optional affinity settings that control how the scanner job is scheduled (see: https://kubernetes.io/docs/tasks/configure-pod-container/assign-pods-nodes-using-node-affinity/) | +| scanner.backoffLimit | int | 3 | There are situations where you want to fail a scan Job after some amount of retries due to a logical error in configuration etc. To do so, set backoffLimit to specify the number of retries before considering a scan Job as failed. (see: https://kubernetes.io/docs/concepts/workloads/controllers/job/#pod-backoff-failure-policy) | +| scanner.env | list | `[]` | Optional environment variables mapped into each scanJob (see: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) | +| scanner.extraContainers | list | `[]` | Optional additional Containers started with each scanJob (see: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/) | +| scanner.extraVolumeMounts | list | `[]` | Optional VolumeMounts mapped into each scanJob (see: https://kubernetes.io/docs/concepts/storage/volumes/) | +| scanner.extraVolumes | list | `[]` | Optional Volumes mapped into each scanJob (see: https://kubernetes.io/docs/concepts/storage/volumes/) | +| scanner.image.pullPolicy | string | `"IfNotPresent"` | Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images | +| scanner.image.repository | string | `"docker.io/aquasec/trivy"` | Container Image to run the scan | +| scanner.image.tag | string | `nil` | defaults to the charts appVersion | +| scanner.nameAppend | string | `nil` | append a string to the default scantype name. | +| scanner.podSecurityContext | object | `{}` | Optional securityContext set on scanner pod (see: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/) | +| scanner.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/) | +| scanner.securityContext | object | `{"allowPrivilegeEscalation":false,"capabilities":{"drop":["all"]},"privileged":false,"readOnlyRootFilesystem":false,"runAsNonRoot":false}` | Optional securityContext set on scanner container (see: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/) | +| scanner.securityContext.allowPrivilegeEscalation | bool | `false` | Ensure that users privileges cannot be escalated | +| scanner.securityContext.capabilities.drop[0] | string | `"all"` | This drops all linux privileges from the container. | +| scanner.securityContext.privileged | bool | `false` | Ensures that the scanner container is not run in privileged mode | +| scanner.securityContext.readOnlyRootFilesystem | bool | `false` | Prevents write access to the containers file system | +| scanner.securityContext.runAsNonRoot | bool | `false` | Enforces that the scanner image is run as a non root user | +| scanner.suspend | bool | `false` | if set to true the scan job will be suspended after creation. You can then resume the job using `kubectl resume ` or using a job scheduler like kueue | +| scanner.tolerations | list | `[]` | Optional tolerations settings that control how the scanner job is scheduled (see: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) | +| scanner.ttlSecondsAfterFinished | string | `nil` | seconds after which the Kubernetes job for the scanner will be deleted. Requires the Kubernetes TTLAfterFinished controller: https://kubernetes.io/docs/concepts/workloads/controllers/ttlafterfinished/ | + +## License +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) + +Code of secureCodeBox is licensed under the [Apache License 2.0][scb-license]. + +[scb-owasp]: https://www.owasp.org/index.php/OWASP_secureCodeBox +[scb-docs]: https://www.securecodebox.io/ +[scb-site]: https://www.securecodebox.io/ +[scb-github]: https://github.com/secureCodeBox/ +[scb-twitter]: https://twitter.com/secureCodeBox +[scb-slack]: https://join.slack.com/t/securecodebox/shared_invite/enQtNDU3MTUyOTM0NTMwLTBjOWRjNjVkNGEyMjQ0ZGMyNDdlYTQxYWQ4MzNiNGY3MDMxNThkZjJmMzY2NDRhMTk3ZWM3OWFkYmY1YzUxNTU +[scb-license]: https://github.com/secureCodeBox/secureCodeBox/blob/master/LICENSE + diff --git a/scanners/trivy-sbom/docs/.gitkeep b/scanners/trivy-sbom/docs/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/scanners/trivy-sbom/docs/README.ArtifactHub.md b/scanners/trivy-sbom/docs/README.ArtifactHub.md new file mode 100644 index 0000000000..ac978c9aa3 --- /dev/null +++ b/scanners/trivy-sbom/docs/README.ArtifactHub.md @@ -0,0 +1,142 @@ + + + +

+ License Apache-2.0 + GitHub release (latest SemVer) + OWASP Lab Project + Artifact HUB + GitHub Repo stars + Twitter Follower +

+ +## What is OWASP secureCodeBox? + +

+ secureCodeBox Logo +

+ +_[OWASP secureCodeBox][scb-github]_ is an automated and scalable open source solution that can be used to integrate various *security vulnerability scanners* with a simple and lightweight interface. The _secureCodeBox_ mission is to support *DevSecOps* Teams to make it easy to automate security vulnerability testing in different scenarios. + +With the _secureCodeBox_ we provide a toolchain for continuous scanning of applications to find the low-hanging fruit issues early in the development process and free the resources of the penetration tester to concentrate on the major security issues. + +The secureCodeBox project is running on [Kubernetes](https://kubernetes.io/). To install it you need [Helm](https://helm.sh), a package manager for Kubernetes. It is also possible to start the different integrated security vulnerability scanners based on a docker infrastructure. + +### Quickstart with secureCodeBox on Kubernetes + +You can find resources to help you get started on our [documentation website](https://www.securecodebox.io) including instruction on how to [install the secureCodeBox project](https://www.securecodebox.io/docs/getting-started/installation) and guides to help you [run your first scans](https://www.securecodebox.io/docs/getting-started/first-scans) with it. + +## What is Trivy SBOM? + +`Trivy` (`tri` pronounced like **tri**gger, `vy` pronounced like en**vy**) is a simple and comprehensive vulnerability scanner for containers and other artifacts. +A software vulnerability is a glitch, flaw, or weakness present in the software or in an Operating System. +`Trivy` detects vulnerabilities of OS packages (Alpine, RHEL, CentOS, etc.) and application dependencies (Bundler, Composer, npm, yarn, etc.). +`Trivy` is easy to use. Just install the binary, and you're ready to scan. All you need to do for scanning is to specify a target such as an image name of the container. + +To learn more about the Trivy scanner itself visit [Trivy's GitHub Repository](https://github.com/aquasecurity/trivy). + +This chart uses Trivy's SBOM support to generate Software Bills of Material in CycloneDX format for container images. + +## Deployment +The trivy-sbom chart can be deployed via helm: + +```bash +# Install HelmChart (use -n to configure another namespace) +helm upgrade --install trivy-sbom secureCodeBox/trivy-sbom +``` + +## Scanner Configuration + +The following SBOM generation configuration example is based on the [Trivy Documentation](https://aquasecurity.github.io/trivy/), please take a look at the original documentation for more configuration examples. + +Currently we support the following scanType, corresponding to the trivy scanning modes: +- scanType: "trivy-sbom-image" + - parameters: `[YOUR_IMAGE_NAME]` + +Simply specify an image name (and a tag) when you use the scanType `trivy-sbom-image`. +A complete example is listed below in our [example docs section](https://www.securecodebox.io/docs/scanners/trivy/#examples). + +## Requirements + +Kubernetes: `>=v1.11.0-0` + +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| cascadingRules.enabled | bool | `false` | Enables or disables the installation of the default cascading rules for this scanner | +| imagePullSecrets | list | `[]` | Define imagePullSecrets when a private registry is used (see: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/) | +| parser.affinity | object | `{}` | Optional affinity settings that control how the parser job is scheduled (see: https://kubernetes.io/docs/tasks/configure-pod-container/assign-pods-nodes-using-node-affinity/) | +| parser.env | list | `[]` | Optional environment variables mapped into each parseJob (see: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) | +| parser.image.pullPolicy | string | `"IfNotPresent"` | Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images | +| parser.image.repository | string | `"docker.io/securecodebox/parser-cyclonedx"` | Parser image repository | +| parser.image.tag | string | defaults to the charts version | Parser image tag | +| parser.resources | object | { requests: { cpu: "200m", memory: "100Mi" }, limits: { cpu: "400m", memory: "200Mi" } } | Optional resources lets you control resource limits and requests for the parser container. See https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ | +| parser.scopeLimiterAliases | object | `{}` | Optional finding aliases to be used in the scopeLimiter. | +| parser.tolerations | list | `[]` | Optional tolerations settings that control how the parser job is scheduled (see: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) | +| parser.ttlSecondsAfterFinished | string | `nil` | seconds after which the Kubernetes job for the parser will be deleted. Requires the Kubernetes TTLAfterFinished controller: https://kubernetes.io/docs/concepts/workloads/controllers/ttlafterfinished/ | +| scanner.activeDeadlineSeconds | string | `nil` | There are situations where you want to fail a scan Job after some amount of time. To do so, set activeDeadlineSeconds to define an active deadline (in seconds) when considering a scan Job as failed. (see: https://kubernetes.io/docs/concepts/workloads/controllers/job/#job-termination-and-cleanup) | +| scanner.affinity | object | `{}` | Optional affinity settings that control how the scanner job is scheduled (see: https://kubernetes.io/docs/tasks/configure-pod-container/assign-pods-nodes-using-node-affinity/) | +| scanner.backoffLimit | int | 3 | There are situations where you want to fail a scan Job after some amount of retries due to a logical error in configuration etc. To do so, set backoffLimit to specify the number of retries before considering a scan Job as failed. (see: https://kubernetes.io/docs/concepts/workloads/controllers/job/#pod-backoff-failure-policy) | +| scanner.env | list | `[]` | Optional environment variables mapped into each scanJob (see: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) | +| scanner.extraContainers | list | `[]` | Optional additional Containers started with each scanJob (see: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/) | +| scanner.extraVolumeMounts | list | `[]` | Optional VolumeMounts mapped into each scanJob (see: https://kubernetes.io/docs/concepts/storage/volumes/) | +| scanner.extraVolumes | list | `[]` | Optional Volumes mapped into each scanJob (see: https://kubernetes.io/docs/concepts/storage/volumes/) | +| scanner.image.pullPolicy | string | `"IfNotPresent"` | Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images | +| scanner.image.repository | string | `"docker.io/aquasec/trivy"` | Container Image to run the scan | +| scanner.image.tag | string | `nil` | defaults to the charts appVersion | +| scanner.nameAppend | string | `nil` | append a string to the default scantype name. | +| scanner.podSecurityContext | object | `{}` | Optional securityContext set on scanner pod (see: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/) | +| scanner.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/) | +| scanner.securityContext | object | `{"allowPrivilegeEscalation":false,"capabilities":{"drop":["all"]},"privileged":false,"readOnlyRootFilesystem":false,"runAsNonRoot":false}` | Optional securityContext set on scanner container (see: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/) | +| scanner.securityContext.allowPrivilegeEscalation | bool | `false` | Ensure that users privileges cannot be escalated | +| scanner.securityContext.capabilities.drop[0] | string | `"all"` | This drops all linux privileges from the container. | +| scanner.securityContext.privileged | bool | `false` | Ensures that the scanner container is not run in privileged mode | +| scanner.securityContext.readOnlyRootFilesystem | bool | `false` | Prevents write access to the containers file system | +| scanner.securityContext.runAsNonRoot | bool | `false` | Enforces that the scanner image is run as a non root user | +| scanner.suspend | bool | `false` | if set to true the scan job will be suspended after creation. You can then resume the job using `kubectl resume ` or using a job scheduler like kueue | +| scanner.tolerations | list | `[]` | Optional tolerations settings that control how the scanner job is scheduled (see: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) | +| scanner.ttlSecondsAfterFinished | string | `nil` | seconds after which the Kubernetes job for the scanner will be deleted. Requires the Kubernetes TTLAfterFinished controller: https://kubernetes.io/docs/concepts/workloads/controllers/ttlafterfinished/ | + +## Contributing + +Contributions are welcome and extremely helpful ๐Ÿ™Œ +Please have a look at [Contributing](./CONTRIBUTING.md) + +## Community + +You are welcome, please join us on... ๐Ÿ‘‹ + +- [GitHub][scb-github] +- [Slack][scb-slack] +- [Twitter][scb-twitter] + +secureCodeBox is an official [OWASP][scb-owasp] project. + +## License +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) + +Code of secureCodeBox is licensed under the [Apache License 2.0][scb-license]. + +[scb-owasp]: https://www.owasp.org/index.php/OWASP_secureCodeBox +[scb-docs]: https://www.securecodebox.io/ +[scb-site]: https://www.securecodebox.io/ +[scb-github]: https://github.com/secureCodeBox/ +[scb-twitter]: https://twitter.com/secureCodeBox +[scb-slack]: https://join.slack.com/t/securecodebox/shared_invite/enQtNDU3MTUyOTM0NTMwLTBjOWRjNjVkNGEyMjQ0ZGMyNDdlYTQxYWQ4MzNiNGY3MDMxNThkZjJmMzY2NDRhMTk3ZWM3OWFkYmY1YzUxNTU +[scb-license]: https://github.com/secureCodeBox/secureCodeBox/blob/master/LICENSE + diff --git a/scanners/trivy-sbom/docs/README.DockerHub-Parser.md b/scanners/trivy-sbom/docs/README.DockerHub-Parser.md new file mode 100644 index 0000000000..208e21beea --- /dev/null +++ b/scanners/trivy-sbom/docs/README.DockerHub-Parser.md @@ -0,0 +1,89 @@ + + + +

+ License Apache-2.0 + GitHub release (latest SemVer) + OWASP Lab Project + Artifact HUB + GitHub Repo stars + Twitter Follower +

+ +## What is OWASP secureCodeBox? + +

+ secureCodeBox Logo +

+ +_[OWASP secureCodeBox][scb-github]_ is an automated and scalable open source solution that can be used to integrate various *security vulnerability scanners* with a simple and lightweight interface. The _secureCodeBox_ mission is to support *DevSecOps* Teams to make it easy to automate security vulnerability testing in different scenarios. + +With the _secureCodeBox_ we provide a toolchain for continuous scanning of applications to find the low-hanging fruit issues early in the development process and free the resources of the penetration tester to concentrate on the major security issues. + +The secureCodeBox project is running on [Kubernetes](https://kubernetes.io/). To install it you need [Helm](https://helm.sh), a package manager for Kubernetes. It is also possible to start the different integrated security vulnerability scanners based on a docker infrastructure. + +### Quickstart with secureCodeBox on Kubernetes + +You can find resources to help you get started on our [documentation website](https://www.securecodebox.io) including instruction on how to [install the secureCodeBox project](https://www.securecodebox.io/docs/getting-started/installation) and guides to help you [run your first scans](https://www.securecodebox.io/docs/getting-started/first-scans) with it. + +## Supported Tags +- `latest` (represents the latest stable release build) +- tagged releases, e.g. `0.45.0` + +## How to use this image +This `parser` image is intended to work in combination with the corresponding security scanner docker image to parse the `findings` results. For more information details please take a look at the documentation page: https://www.securecodebox.io/docs/scanners/trivy-sbom. + +```bash +docker pull securecodebox/parser-trivy-sbom +``` + +## What is Trivy SBOM? + +`Trivy` (`tri` pronounced like **tri**gger, `vy` pronounced like en**vy**) is a simple and comprehensive vulnerability scanner for containers and other artifacts. +A software vulnerability is a glitch, flaw, or weakness present in the software or in an Operating System. +`Trivy` detects vulnerabilities of OS packages (Alpine, RHEL, CentOS, etc.) and application dependencies (Bundler, Composer, npm, yarn, etc.). +`Trivy` is easy to use. Just install the binary, and you're ready to scan. All you need to do for scanning is to specify a target such as an image name of the container. + +To learn more about the Trivy scanner itself visit [Trivy's GitHub Repository](https://github.com/aquasecurity/trivy). + +This chart uses Trivy's SBOM support to generate Software Bills of Material in CycloneDX format for container images. + +## Community + +You are welcome, please join us on... ๐Ÿ‘‹ + +- [GitHub][scb-github] +- [Slack][scb-slack] +- [Twitter][scb-twitter] + +secureCodeBox is an official [OWASP][scb-owasp] project. + +## License +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) + +As with all Docker images, these likely also contain other software which may be under other licenses (such as Bash, etc from the base distribution, along with any direct or indirect dependencies of the primary software being contained). + +As for any pre-built image usage, it is the image user's responsibility to ensure that any use of this image complies with any relevant licenses for all software contained within. + +[scb-owasp]: https://www.owasp.org/index.php/OWASP_secureCodeBox +[scb-docs]: https://www.securecodebox.io/ +[scb-site]: https://www.securecodebox.io/ +[scb-github]: https://github.com/secureCodeBox/ +[scb-twitter]: https://twitter.com/secureCodeBox +[scb-slack]: https://join.slack.com/t/securecodebox/shared_invite/enQtNDU3MTUyOTM0NTMwLTBjOWRjNjVkNGEyMjQ0ZGMyNDdlYTQxYWQ4MzNiNGY3MDMxNThkZjJmMzY2NDRhMTk3ZWM3OWFkYmY1YzUxNTU +[scb-license]: https://github.com/secureCodeBox/secureCodeBox/blob/master/LICENSE + diff --git a/scanners/trivy/examples/image-sbom-juice-shop/README.md b/scanners/trivy-sbom/examples/image-juice-shop/README.md similarity index 100% rename from scanners/trivy/examples/image-sbom-juice-shop/README.md rename to scanners/trivy-sbom/examples/image-juice-shop/README.md diff --git a/scanners/trivy/examples/image-sbom-juice-shop/scan.yaml b/scanners/trivy-sbom/examples/image-juice-shop/scan.yaml similarity index 75% rename from scanners/trivy/examples/image-sbom-juice-shop/scan.yaml rename to scanners/trivy-sbom/examples/image-juice-shop/scan.yaml index 3d9a47947b..662be10a7d 100644 --- a/scanners/trivy/examples/image-sbom-juice-shop/scan.yaml +++ b/scanners/trivy-sbom/examples/image-juice-shop/scan.yaml @@ -5,8 +5,8 @@ apiVersion: "execution.securecodebox.io/v1" kind: Scan metadata: - name: "trivy-image-sbom-juice-shop" + name: "trivy-sbom-image-juice-shop" spec: - scanType: "trivy-image-sbom" + scanType: "trivy-sbom-image" parameters: - "bkimminich/juice-shop:v15.0.0" diff --git a/scanners/trivy-sbom/parser/.dockerignore b/scanners/trivy-sbom/parser/.dockerignore new file mode 100644 index 0000000000..2d2da7ae86 --- /dev/null +++ b/scanners/trivy-sbom/parser/.dockerignore @@ -0,0 +1,5 @@ +# SPDX-FileCopyrightText: the secureCodeBox authors +# +# SPDX-License-Identifier: Apache-2.0 + +node_modules/ diff --git a/scanners/trivy/parser-cyclonedx/Dockerfile b/scanners/trivy-sbom/parser/Dockerfile similarity index 100% rename from scanners/trivy/parser-cyclonedx/Dockerfile rename to scanners/trivy-sbom/parser/Dockerfile diff --git a/scanners/trivy/parser-cyclonedx/package-lock.json b/scanners/trivy-sbom/parser/package-lock.json similarity index 100% rename from scanners/trivy/parser-cyclonedx/package-lock.json rename to scanners/trivy-sbom/parser/package-lock.json diff --git a/scanners/trivy/parser-cyclonedx/package.json b/scanners/trivy-sbom/parser/package.json similarity index 100% rename from scanners/trivy/parser-cyclonedx/package.json rename to scanners/trivy-sbom/parser/package.json diff --git a/scanners/trivy/parser-cyclonedx/parser.js b/scanners/trivy-sbom/parser/parser.js similarity index 100% rename from scanners/trivy/parser-cyclonedx/parser.js rename to scanners/trivy-sbom/parser/parser.js diff --git a/scanners/trivy/parser-cyclonedx/parser.test.js b/scanners/trivy-sbom/parser/parser.test.js similarity index 100% rename from scanners/trivy/parser-cyclonedx/parser.test.js rename to scanners/trivy-sbom/parser/parser.test.js diff --git a/scanners/trivy-sbom/templates/trivy-sbom-parse-definition.yaml b/scanners/trivy-sbom/templates/trivy-sbom-parse-definition.yaml new file mode 100644 index 0000000000..95212f5936 --- /dev/null +++ b/scanners/trivy-sbom/templates/trivy-sbom-parse-definition.yaml @@ -0,0 +1,28 @@ +# SPDX-FileCopyrightText: the secureCodeBox authors +# +# SPDX-License-Identifier: Apache-2.0 + +apiVersion: "execution.securecodebox.io/v1" +kind: ParseDefinition +metadata: + name: "sbom-cyclonedx" +spec: + image: "{{ .Values.parser.image.repository }}:{{ .Values.parser.image.tag | default .Chart.Version }}" + imagePullPolicy: {{ .Values.parser.image.pullPolicy }} + ttlSecondsAfterFinished: {{ .Values.parser.ttlSecondsAfterFinished }} + env: + {{- toYaml .Values.parser.env | nindent 4 }} + scopeLimiterAliases: + {{- toYaml .Values.parser.scopeLimiterAliases | nindent 4 }} + affinity: + {{- toYaml .Values.parser.affinity | nindent 4 }} + tolerations: + {{- toYaml .Values.parser.tolerations | nindent 4 }} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.parser.resources }} + resources: + {{- toYaml . | nindent 4 }} + {{- end }} diff --git a/scanners/trivy-sbom/templates/trivy-sbom-scan-type.yaml b/scanners/trivy-sbom/templates/trivy-sbom-scan-type.yaml new file mode 100644 index 0000000000..0b68cf8b20 --- /dev/null +++ b/scanners/trivy-sbom/templates/trivy-sbom-scan-type.yaml @@ -0,0 +1,58 @@ +# SPDX-FileCopyrightText: the secureCodeBox authors +# +# SPDX-License-Identifier: Apache-2.0 + +apiVersion: "execution.securecodebox.io/v1" +kind: ScanType +metadata: + name: "trivy-sbom-image{{ .Values.scanner.nameAppend | default ""}}" +spec: + extractResults: + type: sbom-cyclonedx + location: "/home/securecodebox/sbom-cyclonedx.json" + jobTemplate: + spec: + suspend: {{ .Values.scanner.suspend | default false }} + {{- if .Values.scanner.ttlSecondsAfterFinished }} + ttlSecondsAfterFinished: {{ .Values.scanner.ttlSecondsAfterFinished }} + {{- end }} + backoffLimit: {{ .Values.scanner.backoffLimit }} + {{- if .Values.scanner.activeDeadlineSeconds }} + activeDeadlineSeconds: {{ .Values.scanner.activeDeadlineSeconds }} + {{- end }} + + template: + spec: + restartPolicy: OnFailure + affinity: + {{- toYaml .Values.scanner.affinity | nindent 12 }} + tolerations: + {{- toYaml .Values.scanner.tolerations | nindent 12 }} + securityContext: + {{- toYaml .Values.scanner.podSecurityContext | nindent 12 }} + containers: + - name: trivy-sbom + image: "{{ .Values.scanner.image.repository }}:{{ .Values.scanner.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.scanner.image.pullPolicy }} + command: + - "trivy" + - "image" + # Suppress progress bar, as it pollutes non interactive terminal logs + - "--no-progress" + - "--format" + - "cyclonedx" + - "--output" + - "/home/securecodebox/sbom-cyclonedx.json" + resources: + {{- toYaml .Values.scanner.resources | nindent 16 }} + securityContext: + {{- toYaml .Values.scanner.securityContext | nindent 16 }} + env: + {{- toYaml .Values.scanner.env | nindent 16 }} + volumeMounts: + {{- toYaml .Values.scanner.extraVolumeMounts | nindent 16 }} + {{- if .Values.scanner.extraContainers }} + {{- toYaml .Values.scanner.extraContainers | nindent 12 }} + {{- end }} + volumes: + {{- toYaml .Values.scanner.extraVolumes | nindent 12 }} diff --git a/scanners/trivy-sbom/values.yaml b/scanners/trivy-sbom/values.yaml new file mode 100644 index 0000000000..7675ca169c --- /dev/null +++ b/scanners/trivy-sbom/values.yaml @@ -0,0 +1,110 @@ +# SPDX-FileCopyrightText: the secureCodeBox authors +# +# SPDX-License-Identifier: Apache-2.0 + +# -- Define imagePullSecrets when a private registry is used (see: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/) +imagePullSecrets: [] + +parser: + image: + # parser.image.repository -- Parser image repository + repository: docker.io/securecodebox/parser-cyclonedx + # parser.image.tag -- Parser image tag + # @default -- defaults to the charts version + tag: null + # -- Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images + pullPolicy: IfNotPresent + + # parser.ttlSecondsAfterFinished -- seconds after which the Kubernetes job for the parser will be deleted. Requires the Kubernetes TTLAfterFinished controller: https://kubernetes.io/docs/concepts/workloads/controllers/ttlafterfinished/ + ttlSecondsAfterFinished: null + + # parser.env -- Optional environment variables mapped into each parseJob (see: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) + env: [] + + # parser.scopeLimiterAliases -- Optional finding aliases to be used in the scopeLimiter. + scopeLimiterAliases: {} + + # parser.affinity -- Optional affinity settings that control how the parser job is scheduled (see: https://kubernetes.io/docs/tasks/configure-pod-container/assign-pods-nodes-using-node-affinity/) + affinity: {} + + # parser.tolerations -- Optional tolerations settings that control how the parser job is scheduled (see: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) + tolerations: [] + + # -- Optional resources lets you control resource limits and requests for the parser container. See https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + # @default -- { requests: { cpu: "200m", memory: "100Mi" }, limits: { cpu: "400m", memory: "200Mi" } } + resources: {} + +scanner: + image: + # scanner.image.repository -- Container Image to run the scan + repository: docker.io/aquasec/trivy + # scanner.image.tag -- defaults to the charts appVersion + tag: null + # -- Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images + pullPolicy: IfNotPresent + + # scanner.nameAppend -- append a string to the default scantype name. + nameAppend: null + + # -- seconds after which the Kubernetes job for the scanner will be deleted. Requires the Kubernetes TTLAfterFinished controller: https://kubernetes.io/docs/concepts/workloads/controllers/ttlafterfinished/ + ttlSecondsAfterFinished: null + # -- There are situations where you want to fail a scan Job after some amount of time. To do so, set activeDeadlineSeconds to define an active deadline (in seconds) when considering a scan Job as failed. (see: https://kubernetes.io/docs/concepts/workloads/controllers/job/#job-termination-and-cleanup) + activeDeadlineSeconds: null + # -- There are situations where you want to fail a scan Job after some amount of retries due to a logical error in configuration etc. To do so, set backoffLimit to specify the number of retries before considering a scan Job as failed. (see: https://kubernetes.io/docs/concepts/workloads/controllers/job/#pod-backoff-failure-policy) + # @default -- 3 + backoffLimit: 3 + + # scanner.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" + + # scanner.env -- Optional environment variables mapped into each scanJob (see: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) + env: [] + + # scanner.extraVolumes -- Optional Volumes mapped into each scanJob (see: https://kubernetes.io/docs/concepts/storage/volumes/) + extraVolumes: [] + + # scanner.extraVolumeMounts -- Optional VolumeMounts mapped into each scanJob (see: https://kubernetes.io/docs/concepts/storage/volumes/) + extraVolumeMounts: [] + + # scanner.extraContainers -- Optional additional Containers started with each scanJob (see: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/) + extraContainers: [] + + # scanner.podSecurityContext -- Optional securityContext set on scanner pod (see: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/) + podSecurityContext: + {} + # fsGroup: 2000 + + # scanner.securityContext -- Optional securityContext set on scanner container (see: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/) + securityContext: + # scanner.securityContext.runAsNonRoot -- Enforces that the scanner image is run as a non root user + runAsNonRoot: false + # scanner.securityContext.readOnlyRootFilesystem -- Prevents write access to the containers file system + readOnlyRootFilesystem: false + # scanner.securityContext.allowPrivilegeEscalation -- Ensure that users privileges cannot be escalated + allowPrivilegeEscalation: false + # scanner.securityContext.privileged -- Ensures that the scanner container is not run in privileged mode + privileged: false + capabilities: + drop: + # scanner.securityContext.capabilities.drop[0] -- This drops all linux privileges from the container. + - all + + # scanner.affinity -- Optional affinity settings that control how the scanner job is scheduled (see: https://kubernetes.io/docs/tasks/configure-pod-container/assign-pods-nodes-using-node-affinity/) + affinity: {} + + # scanner.tolerations -- Optional tolerations settings that control how the scanner job is scheduled (see: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) + tolerations: [] + + # -- if set to true the scan job will be suspended after creation. You can then resume the job using `kubectl resume ` or using a job scheduler like kueue + suspend: false + +cascadingRules: + # cascadingRules.enabled -- Enables or disables the installation of the default cascading rules for this scanner + enabled: false diff --git a/scanners/trivy/Makefile b/scanners/trivy/Makefile index 264c2fe168..2eb284ee54 100644 --- a/scanners/trivy/Makefile +++ b/scanners/trivy/Makefile @@ -8,62 +8,4 @@ include_guard = set scanner = trivy -# Usually scanners are supposed to have exactly one parser, but Trivy can produce either vulnerability findings from -# normal scans or CycloneDX SBOMs. Using separate parsers for each of these formats is cleaner because technically the -# CycloneDX parser is not Trivy-specific. At the moment it is kept in the Trivy directory though so that the -# architecture does not have to get changed more than it needs to for an MVP. This Makefile is edited to allow building -# both parsers by using the targets as usual. - -# Add additional dependencies to the targets for the CycloneDX parser so that it gets built as well -docker-build: | docker-build-parser-sbom -docker-export: | docker-export-parser-sbom -kind-import: | kind-import-parser-sbom -unit-tests: unit-tests-parser-sbom - include ../../scanners.mk - -# Cannot use common-docker-build to build the CycloneDX parser because the Dockerfile is supposed to be in the $(module) -# directory and the resulting image is supposed to be named $(module)-$(name), but this image is in directory -# parser-cyclonedx and also supposed to be named parser-cyclonedx, without any mention of trivy. -.PHONY: docker-build-parser-sbom -docker-build-parser-sbom: - @echo ".: โš™๏ธ Build 'cyclonedx' $(parser-prefix) with BASE_IMG_TAG: '$(BASE_IMG_TAG)'." - docker build \ - --build-arg=scannerVersion=$(shell yq -e .appVersion ./Chart.yaml) \ - --build-arg=baseImageTag=$(BASE_IMG_TAG) \ - --build-arg=namespace=$(IMG_NS) \ - -t $(IMG_NS)/$(parser-prefix)-cyclonedx:$(IMG_TAG) \ - -f ./$(parser-prefix)-cyclonedx/Dockerfile \ - ./$(parser-prefix)-cyclonedx - -# These targets creatively reuse the common-... make targets to export and import the CycloneDX parser image by -# resetting the name variable, otherwise the repository name of the image will end in -trivy -.PHONY: docker-export-parser-sbom -docker-export-parser-sbom: - @$(MAKE) -s common-docker-export module=$(parser-prefix) name=cyclonedx - -.PHONY: kind-import-parser-sbom -kind-import-parser-sbom: - @$(MAKE) -s common-kind-import module=$(parser-prefix) name=cyclonedx - -# Deploy needs to be defined again to set the image repository and tag for the cyclonedx parser as well. This either -# overrides the original target or runs helm upgrade again, but it will work in either case -.PHONY: deploy-without-scanner -deploy-without-scanner: - @echo ".: ๐Ÿ’พ Deploying '$(name)' $(scanner-prefix) HelmChart with the docker tag '$(IMG_TAG)' into kind namespace 'integration-tests'." - helm -n integration-tests upgrade --install $(name) ./ --wait \ - --set="parser.image.repository=docker.io/$(IMG_NS)/$(parser-prefix)-$(name)" \ - --set="parser.image.tag=$(IMG_TAG)" \ - --set="cyclonedxParser.image.repository=docker.io/$(IMG_NS)/$(parser-prefix)-cyclonedx" \ - --set="cyclonedxParser.image.tag=$(IMG_TAG)" \ - --set="parser.env[0].name=CRASH_ON_FAILED_VALIDATION" \ - --set-string="parser.env[0].value=true" - -# The unit tests for the cyclonedx-parser cannot reuse the unit-test-js target, because it requires install-deps-js, -# which then tries to run npm ci in ../../${module}-sdk/nodejs and module is set to "parser-cyclonedx", but -# install-deps-js still needs to be executed, in case this target runs before the normal parser tests -.PHONY: unit-tests-parser-sbom -unit-tests-parser-sbom: - @$(MAKE) -s install-deps-js module=$(parser-prefix) - @echo ".: ๐Ÿงช Starting unit-tests for '$(name)' parser-cyclonedx." - npm run test:unit -- ${name}/parser-cyclonedx/ diff --git a/scanners/trivy/README.md b/scanners/trivy/README.md index b1d9934af7..7002d29389 100644 --- a/scanners/trivy/README.md +++ b/scanners/trivy/README.md @@ -113,15 +113,6 @@ Kubernetes: `>=v1.11.0-0` |-----|------|---------|-------------| | cascadingRules.enabled | bool | `false` | Enables or disables the installation of the default cascading rules for this scanner | | createAutoDiscoveryScanType | bool | `false` | Creates a `trivy-image-autodiscovery` scanType with its own ServiceAccount for the SCB AutoDiscovery, enabled to scan images from both public & private registries. | -| cyclonedxParser.affinity | object | `{}` | Optional affinity settings that control how the parser job is scheduled (see: https://kubernetes.io/docs/tasks/configure-pod-container/assign-pods-nodes-using-node-affinity/) | -| cyclonedxParser.env | list | `[]` | Optional environment variables mapped into each parseJob (see: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) | -| cyclonedxParser.image.pullPolicy | string | `"IfNotPresent"` | Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images | -| cyclonedxParser.image.repository | string | `"docker.io/securecodebox/parser-cyclonedx"` | Parser image repository for CycloneDX SBOM parser | -| cyclonedxParser.image.tag | string | defaults to the charts version | Parser image tag | -| cyclonedxParser.resources | object | { requests: { cpu: "200m", memory: "100Mi" }, limits: { cpu: "400m", memory: "200Mi" } } | Optional resources lets you control resource limits and requests for the parser container. See https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ | -| cyclonedxParser.scopeLimiterAliases | object | `{}` | Optional finding aliases to be used in the scopeLimiter. | -| cyclonedxParser.tolerations | list | `[]` | Optional tolerations settings that control how the parser job is scheduled (see: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) | -| cyclonedxParser.ttlSecondsAfterFinished | string | `nil` | seconds after which the Kubernetes job for the parser will be deleted. Requires the Kubernetes TTLAfterFinished controller: https://kubernetes.io/docs/concepts/workloads/controllers/ttlafterfinished/ | | imagePullSecrets | list | `[]` | Define imagePullSecrets when a private registry is used (see: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/) | | kubeauditScope | string | `"cluster"` | Automatically sets up rbac roles for kubeaudit to access the resources it scans. Can be either "cluster" (ClusterRole) or "namespace" (Role) | | parser.affinity | object | `{}` | Optional affinity settings that control how the parser job is scheduled (see: https://kubernetes.io/docs/tasks/configure-pod-container/assign-pods-nodes-using-node-affinity/) | diff --git a/scanners/trivy/docs/README.ArtifactHub.md b/scanners/trivy/docs/README.ArtifactHub.md index efde15c7fa..c852079dbf 100644 --- a/scanners/trivy/docs/README.ArtifactHub.md +++ b/scanners/trivy/docs/README.ArtifactHub.md @@ -120,15 +120,6 @@ Kubernetes: `>=v1.11.0-0` |-----|------|---------|-------------| | cascadingRules.enabled | bool | `false` | Enables or disables the installation of the default cascading rules for this scanner | | createAutoDiscoveryScanType | bool | `false` | Creates a `trivy-image-autodiscovery` scanType with its own ServiceAccount for the SCB AutoDiscovery, enabled to scan images from both public & private registries. | -| cyclonedxParser.affinity | object | `{}` | Optional affinity settings that control how the parser job is scheduled (see: https://kubernetes.io/docs/tasks/configure-pod-container/assign-pods-nodes-using-node-affinity/) | -| cyclonedxParser.env | list | `[]` | Optional environment variables mapped into each parseJob (see: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) | -| cyclonedxParser.image.pullPolicy | string | `"IfNotPresent"` | Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images | -| cyclonedxParser.image.repository | string | `"docker.io/securecodebox/parser-cyclonedx"` | Parser image repository for CycloneDX SBOM parser | -| cyclonedxParser.image.tag | string | defaults to the charts version | Parser image tag | -| cyclonedxParser.resources | object | { requests: { cpu: "200m", memory: "100Mi" }, limits: { cpu: "400m", memory: "200Mi" } } | Optional resources lets you control resource limits and requests for the parser container. See https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ | -| cyclonedxParser.scopeLimiterAliases | object | `{}` | Optional finding aliases to be used in the scopeLimiter. | -| cyclonedxParser.tolerations | list | `[]` | Optional tolerations settings that control how the parser job is scheduled (see: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) | -| cyclonedxParser.ttlSecondsAfterFinished | string | `nil` | seconds after which the Kubernetes job for the parser will be deleted. Requires the Kubernetes TTLAfterFinished controller: https://kubernetes.io/docs/concepts/workloads/controllers/ttlafterfinished/ | | imagePullSecrets | list | `[]` | Define imagePullSecrets when a private registry is used (see: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/) | | kubeauditScope | string | `"cluster"` | Automatically sets up rbac roles for kubeaudit to access the resources it scans. Can be either "cluster" (ClusterRole) or "namespace" (Role) | | parser.affinity | object | `{}` | Optional affinity settings that control how the parser job is scheduled (see: https://kubernetes.io/docs/tasks/configure-pod-container/assign-pods-nodes-using-node-affinity/) | diff --git a/scanners/trivy/templates/trivy-parse-definition.yaml b/scanners/trivy/templates/trivy-parse-definition.yaml index 56c941569d..ceea06b517 100644 --- a/scanners/trivy/templates/trivy-parse-definition.yaml +++ b/scanners/trivy/templates/trivy-parse-definition.yaml @@ -26,28 +26,3 @@ spec: resources: {{- toYaml . | nindent 4 }} {{- end }} ---- -apiVersion: "execution.securecodebox.io/v1" -kind: ParseDefinition -metadata: - name: "sbom-cyclonedx" -spec: - image: "{{ .Values.cyclonedxParser.image.repository }}:{{ .Values.cyclonedxParser.image.tag | default .Chart.Version }}" - imagePullPolicy: {{ .Values.cyclonedxParser.image.pullPolicy }} - ttlSecondsAfterFinished: {{ .Values.cyclonedxParser.ttlSecondsAfterFinished }} - env: - {{- toYaml .Values.cyclonedxParser.env | nindent 4 }} - scopeLimiterAliases: - {{- toYaml .Values.cyclonedxParser.scopeLimiterAliases | nindent 4 }} - affinity: - {{- toYaml .Values.cyclonedxParser.affinity | nindent 4 }} - tolerations: - {{- toYaml .Values.cyclonedxParser.tolerations | nindent 4 }} - {{- with .Values.imagePullSecrets }} - imagePullSecrets: - {{- toYaml . | nindent 4 }} - {{- end }} - {{- with .Values.cyclonedxParser.resources }} - resources: - {{- toYaml . | nindent 4 }} - {{- end }} diff --git a/scanners/trivy/templates/trivy-scan-type.yaml b/scanners/trivy/templates/trivy-scan-type.yaml index 9701012dd2..26ca7cb052 100644 --- a/scanners/trivy/templates/trivy-scan-type.yaml +++ b/scanners/trivy/templates/trivy-scan-type.yaml @@ -297,54 +297,4 @@ spec: serviceAccountName: trivy-k8s volumes: {{- toYaml .Values.scanner.extraVolumes | nindent 12 }} ---- -apiVersion: "execution.securecodebox.io/v1" -kind: ScanType -metadata: - name: "trivy-image-sbom{{ .Values.scanner.nameAppend | default ""}}" -spec: - extractResults: - type: sbom-cyclonedx - location: "/home/securecodebox/sbom-cyclonedx.json" - jobTemplate: - spec: - {{- if .Values.scanner.ttlSecondsAfterFinished }} - ttlSecondsAfterFinished: {{ .Values.scanner.ttlSecondsAfterFinished }} - {{- end }} - backoffLimit: {{ .Values.scanner.backoffLimit }} - {{- if .Values.scanner.activeDeadlineSeconds }} - activeDeadlineSeconds: {{ .Values.scanner.activeDeadlineSeconds }} - {{- end }} - template: - spec: - restartPolicy: OnFailure - affinity: - {{- toYaml .Values.scanner.affinity | nindent 12 }} - tolerations: - {{- toYaml .Values.scanner.tolerations | nindent 12 }} - containers: - - name: trivy - image: "{{ .Values.scanner.image.repository }}:{{ .Values.scanner.image.tag | default .Chart.AppVersion }}" - imagePullPolicy: {{ .Values.scanner.image.pullPolicy }} - command: - - "trivy" - - "image" - # Suppress progress bar, as it pollutes non interactive terminal logs - - "--no-progress" - - "--format" - - "cyclonedx" - - "--output" - - "/home/securecodebox/sbom-cyclonedx.json" - resources: - {{- toYaml .Values.scanner.resources | nindent 16 }} - securityContext: - {{- toYaml .Values.scanner.securityContext | nindent 16 }} - env: - {{- toYaml .Values.scanner.env | nindent 16 }} - volumeMounts: - {{- toYaml .Values.scanner.extraVolumeMounts | nindent 16 }} - {{- if .Values.scanner.extraContainers }} - {{- toYaml .Values.scanner.extraContainers | nindent 12 }} - {{- end }} - volumes: - {{- toYaml .Values.scanner.extraVolumes | nindent 12 }} + diff --git a/scanners/trivy/values.yaml b/scanners/trivy/values.yaml index bed5d7d551..208929793e 100644 --- a/scanners/trivy/values.yaml +++ b/scanners/trivy/values.yaml @@ -32,34 +32,6 @@ parser: # @default -- { requests: { cpu: "200m", memory: "100Mi" }, limits: { cpu: "400m", memory: "200Mi" } } resources: {} -cyclonedxParser: - image: - # cyclonedxParser.image.repository -- Parser image repository for CycloneDX SBOM parser - repository: docker.io/securecodebox/parser-cyclonedx - # cyclonedxParser.image.tag -- Parser image tag - # @default -- defaults to the charts version - tag: null - # -- Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images - pullPolicy: IfNotPresent - - # cyclonedxParser.ttlSecondsAfterFinished -- seconds after which the Kubernetes job for the parser will be deleted. Requires the Kubernetes TTLAfterFinished controller: https://kubernetes.io/docs/concepts/workloads/controllers/ttlafterfinished/ - ttlSecondsAfterFinished: null - # cyclonedxParser.env -- Optional environment variables mapped into each parseJob (see: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) - env: [] - - # cyclonedxParser.scopeLimiterAliases -- Optional finding aliases to be used in the scopeLimiter. - scopeLimiterAliases: {} - - # cyclonedxParser.affinity -- Optional affinity settings that control how the parser job is scheduled (see: https://kubernetes.io/docs/tasks/configure-pod-container/assign-pods-nodes-using-node-affinity/) - affinity: {} - - # cyclonedxParser.tolerations -- Optional tolerations settings that control how the parser job is scheduled (see: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) - tolerations: [] - - # -- Optional resources lets you control resource limits and requests for the parser container. See https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ - # @default -- { requests: { cpu: "200m", memory: "100Mi" }, limits: { cpu: "400m", memory: "200Mi" } } - resources: {} - scanner: image: # scanner.image.repository -- Container Image to run the scan From fc8b41376ee54424551eaeca7a150cb74217fc2f Mon Sep 17 00:00:00 2001 From: Lukas Fischer Date: Mon, 11 Sep 2023 10:54:24 +0200 Subject: [PATCH 08/15] #1838 Add CycloneDX test file to sbom parser tests MAke sure the parser properly reads an actual trivy-generated CycloneDX SBOM file. Signed-off-by: Lukas Fischer --- .../__testFiles__/hello-world-cyclonedx.json | 53 +++++++++++++++++++ scanners/trivy-sbom/parser/parser.test.js | 34 +++++++++++- 2 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 scanners/trivy-sbom/parser/__testFiles__/hello-world-cyclonedx.json diff --git a/scanners/trivy-sbom/parser/__testFiles__/hello-world-cyclonedx.json b/scanners/trivy-sbom/parser/__testFiles__/hello-world-cyclonedx.json new file mode 100644 index 0000000000..2f9290476d --- /dev/null +++ b/scanners/trivy-sbom/parser/__testFiles__/hello-world-cyclonedx.json @@ -0,0 +1,53 @@ +{ + "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.5", + "serialNumber": "urn:uuid:cb1e21e0-f51c-4a22-bf3c-ca9b8f3f7e0c", + "version": 1, + "metadata": { + "timestamp": "2023-09-11T08:50:04+00:00", + "tools": [ + { + "vendor": "aquasecurity", + "name": "trivy", + "version": "0.45.0" + } + ], + "component": { + "bom-ref": "pkg:oci/hello-world@sha256%3Adcba6daec718f547568c562956fa47e1b03673dd010fe6ee58ca806767031d1c?arch=amd64\u0026repository_url=index.docker.io%2Flibrary%2Fhello-world", + "type": "container", + "name": "hello-world:latest", + "purl": "pkg:oci/hello-world@sha256%3Adcba6daec718f547568c562956fa47e1b03673dd010fe6ee58ca806767031d1c?arch=amd64\u0026repository_url=index.docker.io%2Flibrary%2Fhello-world", + "properties": [ + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:01bb4fce3eb1b56b05adf99504dafd31907a5aadac736e36b27595c8b92f07f1" + }, + { + "name": "aquasecurity:trivy:ImageID", + "value": "sha256:9c7a54a9a43cca047013b82af109fe963fde787f63f9e016fdc3384500c2823d" + }, + { + "name": "aquasecurity:trivy:RepoDigest", + "value": "hello-world@sha256:dcba6daec718f547568c562956fa47e1b03673dd010fe6ee58ca806767031d1c" + }, + { + "name": "aquasecurity:trivy:RepoTag", + "value": "hello-world:latest" + }, + { + "name": "aquasecurity:trivy:SchemaVersion", + "value": "2" + } + ] + } + }, + "components": [], + "dependencies": [ + { + "ref": "pkg:oci/hello-world@sha256%3Adcba6daec718f547568c562956fa47e1b03673dd010fe6ee58ca806767031d1c?arch=amd64\u0026repository_url=index.docker.io%2Flibrary%2Fhello-world", + "dependsOn": [] + } + ], + "vulnerabilities": [] +} diff --git a/scanners/trivy-sbom/parser/parser.test.js b/scanners/trivy-sbom/parser/parser.test.js index 9d01c02f65..7fcf912f1a 100644 --- a/scanners/trivy-sbom/parser/parser.test.js +++ b/scanners/trivy-sbom/parser/parser.test.js @@ -2,11 +2,18 @@ // // SPDX-License-Identifier: Apache-2.0 -const { parse } = require("./parser"); +const fs = require("fs"); +const util = require("util"); + const { validateParser, } = require("@securecodebox/parser-sdk-nodejs/parser-utils"); +// eslint-disable-next-line security/detect-non-literal-fs-filename +const readFile = util.promisify(fs.readFile); + +const { parse } = require("./parser"); + let scan; beforeEach(() => { @@ -52,3 +59,28 @@ test("should create finding correctly", async () => { ] `); }); + +test("should properly parse cyclonedx json sbom file", async () => { + const fileContent = JSON.parse( + await readFile(__dirname + "/__testFiles__/hello-world-cyclonedx.json", { + encoding: "utf8", + }) + ); + const findings = await parse(fileContent, scan); + // validate findings + await expect(validateParser(findings)).resolves.toBeUndefined(); + expect(findings).toMatchInlineSnapshot(` + [ + { + "attributes": { + "downloadLink": "https://s3.example.com/sbom-cyclonedx.json", + }, + "category": "SBOM", + "description": "Generated an SBOM for: 'hello-world:latest'", + "name": "SBOM for hello-world:latest", + "osi_layer": "APPLICATION", + "severity": "INFORMATIONAL", + }, + ] + `); +}); From c9f34dfad2f4fe45b3e792d39217af2eedb5e659 Mon Sep 17 00:00:00 2001 From: Lukas Fischer Date: Mon, 11 Sep 2023 11:30:08 +0200 Subject: [PATCH 09/15] #1838 Add integration tests for trivy-sbom scanner Signed-off-by: Lukas Fischer --- .../integration-tests/trivy-sbom.test.js | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 scanners/trivy-sbom/integration-tests/trivy-sbom.test.js diff --git a/scanners/trivy-sbom/integration-tests/trivy-sbom.test.js b/scanners/trivy-sbom/integration-tests/trivy-sbom.test.js new file mode 100644 index 0000000000..2d79c876e2 --- /dev/null +++ b/scanners/trivy-sbom/integration-tests/trivy-sbom.test.js @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: the secureCodeBox authors +// +// SPDX-License-Identifier: Apache-2.0 + +const {scan} = require("../../helpers"); + +jest.retryTimes(3); + +test( + "trivy-sbom image scan for juiceshop should create sbom", + async () => { + const { categories, severities, count } = await scan( + "trivy-juice-test", + "trivy-sbom-image", + ["bkimminich/juice-shop:v15.0.0"], + 90 + ); + + expect(count).toEqual(1); + expect(categories["SBOM"]).toEqual(1); + expect(severities["informational"]).toEqual(1); + }, + 3 * 60 * 1000 +); + +test( + "Invalid argument should be marked as errored", + async () => { + await expect( + scan( + "trivy-invalidArg", + "trivy-sbom-image", + ["--invalidArg", "not/a-valid-image:v0.0.0"], + 90 + ) + ).rejects.toThrow("HTTP request failed"); + }, + 3 * 60 * 1000 +); From 282a6718dfa54ab514ac3b6c81f42ce4f58896df Mon Sep 17 00:00:00 2001 From: Lukas Fischer Date: Mon, 11 Sep 2023 13:13:22 +0200 Subject: [PATCH 10/15] #1838 Add SBOM components to GitHub workflows Releases and CI only partially use makefiles. To make sure the trivy-sbom scanner and the persistence-dependencytrack hook are included in all builds, they are added to the corresponding matrix lists here. Signed-off-by: Lukas Fischer --- .github/workflows/ci.yaml | 2 ++ .github/workflows/release-build.yaml | 2 ++ .github/workflows/scb-bot.yaml | 1 + 3 files changed, 5 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b6dfacfa10..20fc43a852 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -350,6 +350,7 @@ jobs: - ssh-scan - sslyze - trivy + - trivy-sbom - typo3scan - whatweb - wpscan @@ -703,6 +704,7 @@ jobs: - generic-webhook - persistence-azure-monitor # - persistence-elastic # Fails on the CI due to insufficient cpu as mentioned in issue #1165 + - persistence-dependencytrack - update-field-hook - finding-post-processing - notification diff --git a/.github/workflows/release-build.yaml b/.github/workflows/release-build.yaml index 3786e556e5..97600af6a7 100644 --- a/.github/workflows/release-build.yaml +++ b/.github/workflows/release-build.yaml @@ -223,6 +223,7 @@ jobs: - notification - persistence-elastic - persistence-defectdojo + - persistence-dependencytrack - persistence-azure-monitor - update-field-hook steps: @@ -340,6 +341,7 @@ jobs: - sslyze - test-scan - trivy + - trivy-sbom - typo3scan - whatweb - wpscan diff --git a/.github/workflows/scb-bot.yaml b/.github/workflows/scb-bot.yaml index 6eb1ca020b..089521752e 100644 --- a/.github/workflows/scb-bot.yaml +++ b/.github/workflows/scb-bot.yaml @@ -38,6 +38,7 @@ jobs: - ssh-scan - sslyze - trivy + - trivy-sbom - typo3scan - whatweb - wpscan From 7a1453d8aa3c10dfa00fee566a2350ddf0d13ec9 Mon Sep 17 00:00:00 2001 From: Lukas Fischer Date: Mon, 11 Sep 2023 15:44:04 +0200 Subject: [PATCH 11/15] #1838 Exclude more files from helm chart During CI the persistence-dependencytrack hook fails because the helm chart (or some file in there) is larger than allowed. To fix this, copy over ignored files and patterns from elastic's .helmignore. Signed-off-by: Lukas Fischer --- hooks/persistence-dependencytrack/.helmignore | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/hooks/persistence-dependencytrack/.helmignore b/hooks/persistence-dependencytrack/.helmignore index 0e8a0eb36f..32a22fbcf0 100644 --- a/hooks/persistence-dependencytrack/.helmignore +++ b/hooks/persistence-dependencytrack/.helmignore @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: the secureCodeBox authors +# +# SPDX-License-Identifier: Apache-2.0 # Patterns to ignore when building packages. # This supports shell glob matching, relative path matching, and # negation (prefixed with !). Only one pattern per line. @@ -21,3 +24,18 @@ .idea/ *.tmproj .vscode/ +# Node.js files +node_modules/* +package.json +package-lock.json +src/* +config/* +Dockerfile +.dockerignore +docs/* +*.tar +hook/* +integration-tests/* +examples/* +coverage/* +Makefile From 337bdce7fecb159324a636c3dd02e027f87ec28e Mon Sep 17 00:00:00 2001 From: Lukas Fischer Date: Fri, 15 Sep 2023 11:26:37 +0200 Subject: [PATCH 12/15] #1838 Cross-reference SBOM workflow components Make sure that people can more easily combine the Trivy SBOM scanner with the Dependency-Track hook by linking eachother from their documentation. Use absolute links to make sure that external links from GitHub, DockerHub and ArtifactHub also work. See #1963 Signed-off-by: Lukas Fischer --- hooks/persistence-dependencytrack/.helm-docs.gotmpl | 4 +++- hooks/persistence-dependencytrack/README.md | 4 +++- scanners/trivy-sbom/.helm-docs.gotmpl | 3 +++ scanners/trivy-sbom/README.md | 4 +++- scanners/trivy-sbom/docs/README.ArtifactHub.md | 4 +++- scanners/trivy-sbom/docs/README.DockerHub-Parser.md | 4 +++- 6 files changed, 18 insertions(+), 5 deletions(-) diff --git a/hooks/persistence-dependencytrack/.helm-docs.gotmpl b/hooks/persistence-dependencytrack/.helm-docs.gotmpl index 186f24c358..3425e839e3 100644 --- a/hooks/persistence-dependencytrack/.helm-docs.gotmpl +++ b/hooks/persistence-dependencytrack/.helm-docs.gotmpl @@ -25,6 +25,8 @@ usecase: "Publishes all CycloneDX SBOMs to Dependency-Track." The Dependency-Track persistenceProvider hook saves all generated CycloneDX SBOMs into the configured [OWASP Dependency-Track][dependencytrack.org] instance, other findings or SPDX SBOMs cannot be handled and are ignored. This allows automatically cataloging infrastructure to gain an overview over the used components and dependencies. To learn more about Dependency-Track visit [dependencytrack.org]. + +To use the _secureCodeBox_ to generate SBOMs, you can use the [Trivy-SBOM scanner][trivy-sbom]. {{- end }} {{- define "extra.scannerConfigurationSection" -}}{{- end }} @@ -54,5 +56,5 @@ This requires either the `PORTFOLIO_MANAGEMENT` or `PROJECT_CREATION_UPLOAD` per [dependencytrack.org]: https://dependencytrack.org/ [dt-api-docs]: https://docs.dependencytrack.org/integrations/rest-api/ [k8ssecret]: https://kubernetes.io/docs/concepts/configuration/secret/ -[trivy-sbom]: /docs/scanners/trivy-sbom +[trivy-sbom]: https://www.securecodebox.io/docs/scanners/trivy-sbom {{- end }} diff --git a/hooks/persistence-dependencytrack/README.md b/hooks/persistence-dependencytrack/README.md index dfc45f516f..9ad2f636d6 100644 --- a/hooks/persistence-dependencytrack/README.md +++ b/hooks/persistence-dependencytrack/README.md @@ -37,6 +37,8 @@ The Dependency-Track persistenceProvider hook saves all generated CycloneDX SBOM This allows automatically cataloging infrastructure to gain an overview over the used components and dependencies. To learn more about Dependency-Track visit [dependencytrack.org]. +To use the _secureCodeBox_ to generate SBOMs, you can use the [Trivy-SBOM scanner][trivy-sbom]. + ## Deployment The persistence-dependencytrack chart can be deployed via helm: @@ -101,4 +103,4 @@ Code of secureCodeBox is licensed under the [Apache License 2.0][scb-license]. [dependencytrack.org]: https://dependencytrack.org/ [dt-api-docs]: https://docs.dependencytrack.org/integrations/rest-api/ [k8ssecret]: https://kubernetes.io/docs/concepts/configuration/secret/ -[trivy-sbom]: /docs/scanners/trivy-sbom +[trivy-sbom]: https://www.securecodebox.io/docs/scanners/trivy-sbom diff --git a/scanners/trivy-sbom/.helm-docs.gotmpl b/scanners/trivy-sbom/.helm-docs.gotmpl index 89e1b7ec34..7e031deb9f 100644 --- a/scanners/trivy-sbom/.helm-docs.gotmpl +++ b/scanners/trivy-sbom/.helm-docs.gotmpl @@ -32,6 +32,7 @@ A software vulnerability is a glitch, flaw, or weakness present in the software To learn more about the Trivy scanner itself visit [Trivy's GitHub Repository](https://github.com/aquasecurity/trivy). This chart uses Trivy's SBOM support to generate Software Bills of Material in CycloneDX format for container images. +You can use the [Dependency-Track hook][persistence-dependencytrack] to send the generated SBOMs to an instance of [Dependency-Track][dependencytrack.org] to manage them there. {{- end }} {{- define "extra.scannerConfigurationSection" -}} @@ -51,4 +52,6 @@ A complete example is listed below in our [example docs section](https://www.sec {{- end }} {{- define "extra.scannerLinksSection" -}} +[dependencytrack.org]: https://dependencytrack.org/ +[persistence-dependencytrack]: https://www.securecodebox.io/docs/hooks/dependency-track {{- end }} diff --git a/scanners/trivy-sbom/README.md b/scanners/trivy-sbom/README.md index 8497d15e38..2b836e468b 100644 --- a/scanners/trivy-sbom/README.md +++ b/scanners/trivy-sbom/README.md @@ -43,6 +43,7 @@ A software vulnerability is a glitch, flaw, or weakness present in the software To learn more about the Trivy scanner itself visit [Trivy's GitHub Repository](https://github.com/aquasecurity/trivy). This chart uses Trivy's SBOM support to generate Software Bills of Material in CycloneDX format for container images. +You can use the [Dependency-Track hook][persistence-dependencytrack] to send the generated SBOMs to an instance of [Dependency-Track][dependencytrack.org] to manage them there. ## Deployment The trivy-sbom chart can be deployed via helm: @@ -117,4 +118,5 @@ Code of secureCodeBox is licensed under the [Apache License 2.0][scb-license]. [scb-twitter]: https://twitter.com/secureCodeBox [scb-slack]: https://join.slack.com/t/securecodebox/shared_invite/enQtNDU3MTUyOTM0NTMwLTBjOWRjNjVkNGEyMjQ0ZGMyNDdlYTQxYWQ4MzNiNGY3MDMxNThkZjJmMzY2NDRhMTk3ZWM3OWFkYmY1YzUxNTU [scb-license]: https://github.com/secureCodeBox/secureCodeBox/blob/master/LICENSE - +[dependencytrack.org]: https://dependencytrack.org/ +[persistence-dependencytrack]: https://www.securecodebox.io/docs/hooks/dependency-track diff --git a/scanners/trivy-sbom/docs/README.ArtifactHub.md b/scanners/trivy-sbom/docs/README.ArtifactHub.md index ac978c9aa3..f846a74448 100644 --- a/scanners/trivy-sbom/docs/README.ArtifactHub.md +++ b/scanners/trivy-sbom/docs/README.ArtifactHub.md @@ -50,6 +50,7 @@ A software vulnerability is a glitch, flaw, or weakness present in the software To learn more about the Trivy scanner itself visit [Trivy's GitHub Repository](https://github.com/aquasecurity/trivy). This chart uses Trivy's SBOM support to generate Software Bills of Material in CycloneDX format for container images. +You can use the [Dependency-Track hook][persistence-dependencytrack] to send the generated SBOMs to an instance of [Dependency-Track][dependencytrack.org] to manage them there. ## Deployment The trivy-sbom chart can be deployed via helm: @@ -139,4 +140,5 @@ Code of secureCodeBox is licensed under the [Apache License 2.0][scb-license]. [scb-twitter]: https://twitter.com/secureCodeBox [scb-slack]: https://join.slack.com/t/securecodebox/shared_invite/enQtNDU3MTUyOTM0NTMwLTBjOWRjNjVkNGEyMjQ0ZGMyNDdlYTQxYWQ4MzNiNGY3MDMxNThkZjJmMzY2NDRhMTk3ZWM3OWFkYmY1YzUxNTU [scb-license]: https://github.com/secureCodeBox/secureCodeBox/blob/master/LICENSE - +[dependencytrack.org]: https://dependencytrack.org/ +[persistence-dependencytrack]: https://www.securecodebox.io/docs/hooks/dependency-track diff --git a/scanners/trivy-sbom/docs/README.DockerHub-Parser.md b/scanners/trivy-sbom/docs/README.DockerHub-Parser.md index 208e21beea..f75b7e2989 100644 --- a/scanners/trivy-sbom/docs/README.DockerHub-Parser.md +++ b/scanners/trivy-sbom/docs/README.DockerHub-Parser.md @@ -61,6 +61,7 @@ A software vulnerability is a glitch, flaw, or weakness present in the software To learn more about the Trivy scanner itself visit [Trivy's GitHub Repository](https://github.com/aquasecurity/trivy). This chart uses Trivy's SBOM support to generate Software Bills of Material in CycloneDX format for container images. +You can use the [Dependency-Track hook][persistence-dependencytrack] to send the generated SBOMs to an instance of [Dependency-Track][dependencytrack.org] to manage them there. ## Community @@ -86,4 +87,5 @@ As for any pre-built image usage, it is the image user's responsibility to ensur [scb-twitter]: https://twitter.com/secureCodeBox [scb-slack]: https://join.slack.com/t/securecodebox/shared_invite/enQtNDU3MTUyOTM0NTMwLTBjOWRjNjVkNGEyMjQ0ZGMyNDdlYTQxYWQ4MzNiNGY3MDMxNThkZjJmMzY2NDRhMTk3ZWM3OWFkYmY1YzUxNTU [scb-license]: https://github.com/secureCodeBox/secureCodeBox/blob/master/LICENSE - +[dependencytrack.org]: https://dependencytrack.org/ +[persistence-dependencytrack]: https://www.securecodebox.io/docs/hooks/dependency-track From dccdeac1f61f443665119048c8e2c56a49c56ed9 Mon Sep 17 00:00:00 2001 From: Lukas Fischer Date: Fri, 15 Sep 2023 16:55:50 +0200 Subject: [PATCH 13/15] #1838 Check Dependency-Track response for errors Instead of just crashing hard, check the response the Dependency-Track API returns for network errors and error status codes to print nicer error messages. Signed-off-by: Lukas Fischer --- .../persistence-dependencytrack/hook/hook.js | 34 ++++++++++++++----- .../hook/hook.test.js | 5 ++- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/hooks/persistence-dependencytrack/hook/hook.js b/hooks/persistence-dependencytrack/hook/hook.js index b038df9e58..230f1d5335 100644 --- a/hooks/persistence-dependencytrack/hook/hook.js +++ b/hooks/persistence-dependencytrack/hook/hook.js @@ -45,14 +45,32 @@ async function handle({ console.log(`Uploading SBOM for name: ${name} version: ${version} to ${url}`); // Send request to API endpoint - const response = await fetch(url, { - method: "POST", - cache: "no-cache", - headers: { - "X-API-Key": apiKey, - }, - body: formData, - }); + let response; + try { + response = await fetch(url, { + method: "POST", + cache: "no-cache", + headers: { + "X-API-Key": apiKey, + }, + body: formData, + }); + } catch (error) { + console.error("Error sending request to Dependency-Track"); + throw error + } + + if (!response.ok) { + switch (response.status) { + case 401: + console.error(`Request failed with status ${response.status}, please check your API key`) + break; + case 403: + console.error(`Request failed with status ${response.status}, make sure you gave the team/API key either the PORTFOLIO_MANAGEMENT or PROJECT_CREATION_UPLOAD permission`) + break; + } + throw new Error(`Request to Dependency-Track was unsuccessful, status ${response.status}`) + } // Response-token can be used to determine if any task is being performed on the BOM // Endpoint: /api/v1/bom/ diff --git a/hooks/persistence-dependencytrack/hook/hook.test.js b/hooks/persistence-dependencytrack/hook/hook.test.js index dcbf3f3026..6cfb015643 100644 --- a/hooks/persistence-dependencytrack/hook/hook.test.js +++ b/hooks/persistence-dependencytrack/hook/hook.test.js @@ -3,7 +3,10 @@ // SPDX-License-Identifier: Apache-2.0 const { handle } = require("./hook"); -const fetch = jest.fn(() => Promise.resolve({ json: () => Promise.resolve({ token: "statustoken" }) })); +const fetch = jest.fn(() => Promise.resolve({ + ok: true, + json: () => Promise.resolve({ token: "statustoken" }) +})); beforeEach(() => { jest.clearAllMocks(); From 67c3f4ffbbd2a9cd9694b1b3aace0b771a0fd1eb Mon Sep 17 00:00:00 2001 From: Lukas Fischer Date: Tue, 12 Sep 2023 17:47:41 +0200 Subject: [PATCH 14/15] #1838 Fix image name and version recognition The previous way of detecting the name and version to supply to Dependency-Track was very brittle, it already failed for image references including a hash, resulting in names like hello-world@sha256, because it would only split on ':' and then select the first and last component. This version uses a regex to as accurately as possible match the individual components of a docker image reference. The regex comes from the [official implementation] on GitHub, but is actually taken from a [pull request], which adds named capture groups and fixes an issue with domain recognition being too eager. Yes the regex looks pretty wild, yes there are tests. I don't think it makes sense to build the regex from the individual components like the docker library does it. Unfortunately this does not solve the problem of actually getting the reference from somewhere, for images it works with getting it from the name of the main component, but this part stays brittle. Annotations to the scans might be a possible solution for that. [official implementation]: https://github.com/distribution/reference [pull request]: https://github.com/distribution/distribution/pull/3803 Signed-off-by: Lukas Fischer --- .../persistence-dependencytrack/hook/hook.js | 23 ++++++- .../hook/hook.test.js | 67 +++++++++++++++++++ 2 files changed, 87 insertions(+), 3 deletions(-) diff --git a/hooks/persistence-dependencytrack/hook/hook.js b/hooks/persistence-dependencytrack/hook/hook.js index 230f1d5335..54977895e2 100644 --- a/hooks/persistence-dependencytrack/hook/hook.js +++ b/hooks/persistence-dependencytrack/hook/hook.js @@ -27,9 +27,26 @@ async function handle({ // Get the project name and version from the name attribute of the main component // This might be a bit brittle, but there is not really a better way to get this information // Neither Trivy's nor Syft's SBOM contains a useful version attribute (none or sha256) - const components = result.metadata.component.name.split(':'); - const name = components[0]; - const version = components.length > 1 ? components.pop() : "latest"; + + // Get the components of a docker image reference, the regex is a direct JavaScript adaption of + // the official Go-implementation available at https://github.com/distribution/reference/blob/main/regexp.go + // but taken from pull request https://github.com/distribution/distribution/pull/3803 which + // introduces the named groups and fixes the issue that in "bkimminich/juice-shop" the regex + // detects "bkimminich" as part of the domain/host. + const imageRegex = new RegExp([ + '^(?(?:(?(?:localhost|(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])', + '(?:\\.(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]))+|', + '(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])', + '(?:\\.(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]))*', + '(?::[0-9]+)|\\[(?:[a-fA-F0-9:]+)\\](?::[0-9]+)?)(?::[0-9]+)?)\\/)?', + '(?[a-z0-9]+(?:(?:[._]|__|[-]+)[a-z0-9]+)*', + '(?:\\/[a-z0-9]+(?:(?:[._]|__|[-]+)[a-z0-9]+)*)*))', + '(?::(?[\\w][\\w.-]{0,127}))?', + '(?:@(?[A-Za-z][A-Za-z0-9]*(?:[-_+.][A-Za-z][A-Za-z0-9]*)*[:][0-9A-Fa-f]{32,}))?$', + ].join('')); + const groups = imageRegex.exec(result.metadata.component.name).groups + const name = groups.name + const version = groups.tag || groups.digest || "latest" // The POST endpoint expects multipart/form-data // Alternatively the PUT endpoint could be used, which requires base64-encoding the SBOM diff --git a/hooks/persistence-dependencytrack/hook/hook.test.js b/hooks/persistence-dependencytrack/hook/hook.test.js index 6cfb015643..036b0ef445 100644 --- a/hooks/persistence-dependencytrack/hook/hook.test.js +++ b/hooks/persistence-dependencytrack/hook/hook.test.js @@ -111,3 +111,70 @@ test("should send a post request to the url when fired", async () => { expect(fetch.mock.calls[0][1].body.get("bom")).toBe(JSON.stringify(result)); }); + +// Make sure that the crazy regex to parse the reference parts actually works +test.each([ + { + reference: "bkimminich/juice-shop:v15.0.0", + name: "bkimminich/juice-shop", + version: "v15.0.0" + }, + { + reference: "ubuntu@sha256:b492494d8e0113c4ad3fe4528a4b5ff89faa5331f7d52c5c138196f69ce176a6", + name: "ubuntu", + version: "sha256:b492494d8e0113c4ad3fe4528a4b5ff89faa5331f7d52c5c138196f69ce176a6" + }, + { + reference: "hello-world", + name: "hello-world", + version: "latest" + }, + { + reference: "gcr.io/distroless/cc-debian12:debug-nonroot", + name: "gcr.io/distroless/cc-debian12", + version: "debug-nonroot" + }, + { + reference: "myawesomedockerhub.example.org:8080/notthetag", + name: "myawesomedockerhub.example.org:8080/notthetag", + version: "latest" + }, +])("should detect image reference components accurately", async ({ reference, name, version }) => { + const result = { + bomFormat: "CycloneDX", + metadata: { + component: { + name: reference + } + } + }; + + const getRawResults = async () => result; + + const scan = { + metadata: { + uid: "a30122a6-7f1a-4e37-ae81-2c25ed7fb8f5", + name: "demo-sbom", + }, + status: { + rawResultType: "sbom-cyclonedx" + } + }; + + const apiKey = "verysecretgitleaksplsignore" + const baseUrl = "http://example.com/foo/bar"; + const url = baseUrl + "/api/v1/bom" + + await handle({ getRawResults, scan, apiKey, baseUrl, fetch }); + + expect(fetch).toBeCalledTimes(1); + expect(fetch).toBeCalledWith(url, expect.objectContaining({ + method: "POST", + headers: { + "X-API-Key": apiKey, + }, + })); + + expect(fetch.mock.calls[0][1].body.get("projectName")).toBe(name); + expect(fetch.mock.calls[0][1].body.get("projectVersion")).toBe(version); +}); From 06f68747535a74ae1d55d3aaa5e92dd4a2e4586c Mon Sep 17 00:00:00 2001 From: Lukas Fischer Date: Tue, 19 Sep 2023 16:55:28 +0200 Subject: [PATCH 15/15] #1838 Add trivy-sbom-image to telemetry service Make sure the telemetry service recognizes trivy-sbom-image as an official ScanType and sends correct data. Signed-off-by: Lukas Fischer --- operator/internal/telemetry/telemetry.go | 1 + 1 file changed, 1 insertion(+) diff --git a/operator/internal/telemetry/telemetry.go b/operator/internal/telemetry/telemetry.go index 9ed124af93..46f532ea73 100644 --- a/operator/internal/telemetry/telemetry.go +++ b/operator/internal/telemetry/telemetry.go @@ -44,6 +44,7 @@ var officialScanTypes map[string]bool = map[string]bool{ "trivy-image": true, "trivy-filesystem": true, "trivy-repo": true, + "trivy-sbom-image": true, "typo3scan": true, "whatweb": true, "wpscan": true,