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
diff --git a/hooks/persistence-dependencytrack/.helm-docs.gotmpl b/hooks/persistence-dependencytrack/.helm-docs.gotmpl
new file mode 100644
index 0000000000..3425e839e3
--- /dev/null
+++ b/hooks/persistence-dependencytrack/.helm-docs.gotmpl
@@ -0,0 +1,60 @@
+{{- /*
+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].
+
+To use the _secureCodeBox_ to generate SBOMs, you can use the [Trivy-SBOM scanner][trivy-sbom].
+{{- 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]: https://www.securecodebox.io/docs/scanners/trivy-sbom
+{{- end }}
diff --git a/hooks/persistence-dependencytrack/.helmignore b/hooks/persistence-dependencytrack/.helmignore
new file mode 100644
index 0000000000..32a22fbcf0
--- /dev/null
+++ b/hooks/persistence-dependencytrack/.helmignore
@@ -0,0 +1,41 @@
+# 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
+*.orig
+*~
+# Various IDEs
+.project
+.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
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..9ad2f636d6
--- /dev/null
+++ b/hooks/persistence-dependencytrack/README.md
@@ -0,0 +1,106 @@
+---
+title: "Dependency-Track"
+category: "hook"
+type: "persistenceProvider"
+state: "developing"
+usecase: "Publishes all CycloneDX SBOMs to Dependency-Track."
+---
+
+
+
+
+
+
+
+
+
+
+
+
+
+## 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].
+
+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:
+
+```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
+[](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]: https://www.securecodebox.io/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..54977895e2
--- /dev/null
+++ b/hooks/persistence-dependencytrack/hook/hook.js
@@ -0,0 +1,98 @@
+// SPDX-FileCopyrightText: the secureCodeBox authors
+//
+// SPDX-License-Identifier: Apache-2.0
+
+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.`);
+ 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)
+
+ // 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
+ 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));
+
+ const url = baseUrl.replace(/\/$/, "") + "/api/v1/bom"
+ console.log(`Uploading SBOM for name: ${name} version: ${version} to ${url}`);
+
+ // Send request to API endpoint
+ 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/
+ 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/hook.test.js b/hooks/persistence-dependencytrack/hook/hook.test.js
new file mode 100644
index 0000000000..036b0ef445
--- /dev/null
+++ b/hooks/persistence-dependencytrack/hook/hook.test.js
@@ -0,0 +1,180 @@
+// SPDX-FileCopyrightText: the secureCodeBox authors
+//
+// SPDX-License-Identifier: Apache-2.0
+
+const { handle } = require("./hook");
+const fetch = jest.fn(() => Promise.resolve({
+ ok: true,
+ 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));
+});
+
+// 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);
+});
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..b0a7fc4f49
--- /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": "jest --verbose --ci --colors --coverage --passWithNoTests"
+ },
+ "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 🙃
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,
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..7e031deb9f
--- /dev/null
+++ b/scanners/trivy-sbom/.helm-docs.gotmpl
@@ -0,0 +1,57 @@
+{{- /*
+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.
+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" -}}
+## 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" -}}
+[dependencytrack.org]: https://dependencytrack.org/
+[persistence-dependencytrack]: https://www.securecodebox.io/docs/hooks/dependency-track
+{{- 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..2b836e468b
--- /dev/null
+++ b/scanners/trivy-sbom/README.md
@@ -0,0 +1,122 @@
+---
+title: "Trivy SBOM"
+category: "scanner"
+type: "Container"
+state: "released"
+appVersion: "0.45.0"
+usecase: "Container Dependency Scanner"
+---
+
+
+
+
+
+
+
+
+
+
+
+
+
+## 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.
+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:
+
+```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
+[](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/
+[persistence-dependencytrack]: https://www.securecodebox.io/docs/hooks/dependency-track
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..f846a74448
--- /dev/null
+++ b/scanners/trivy-sbom/docs/README.ArtifactHub.md
@@ -0,0 +1,144 @@
+
+
+
+
+
+
+
+
+
+
+
+
+## What is OWASP secureCodeBox?
+
+
+
+
+
+_[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.
+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:
+
+```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
+[](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/
+[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
new file mode 100644
index 0000000000..f75b7e2989
--- /dev/null
+++ b/scanners/trivy-sbom/docs/README.DockerHub-Parser.md
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
+
+
+
+
+
+## What is OWASP secureCodeBox?
+
+
+
+
+
+_[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.
+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
+
+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
+[](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
+[dependencytrack.org]: https://dependencytrack.org/
+[persistence-dependencytrack]: https://www.securecodebox.io/docs/hooks/dependency-track
diff --git a/scanners/trivy-sbom/examples/image-juice-shop/README.md b/scanners/trivy-sbom/examples/image-juice-shop/README.md
new file mode 100644
index 0000000000..99a07b3806
--- /dev/null
+++ b/scanners/trivy-sbom/examples/image-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-sbom/examples/image-juice-shop/scan.yaml b/scanners/trivy-sbom/examples/image-juice-shop/scan.yaml
new file mode 100644
index 0000000000..662be10a7d
--- /dev/null
+++ b/scanners/trivy-sbom/examples/image-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-sbom-image-juice-shop"
+spec:
+ scanType: "trivy-sbom-image"
+ parameters:
+ - "bkimminich/juice-shop:v15.0.0"
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
+);
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-sbom/parser/Dockerfile b/scanners/trivy-sbom/parser/Dockerfile
new file mode 100644
index 0000000000..86543ec4f1
--- /dev/null
+++ b/scanners/trivy-sbom/parser/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-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/package-lock.json b/scanners/trivy-sbom/parser/package-lock.json
new file mode 100644
index 0000000000..adef34980c
--- /dev/null
+++ b/scanners/trivy-sbom/parser/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-sbom/parser/package.json b/scanners/trivy-sbom/parser/package.json
new file mode 100644
index 0000000000..bd85701561
--- /dev/null
+++ b/scanners/trivy-sbom/parser/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-sbom/parser/parser.js b/scanners/trivy-sbom/parser/parser.js
new file mode 100644
index 0000000000..58c535ea55
--- /dev/null
+++ b/scanners/trivy-sbom/parser/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-sbom/parser/parser.test.js b/scanners/trivy-sbom/parser/parser.test.js
new file mode 100644
index 0000000000..7fcf912f1a
--- /dev/null
+++ b/scanners/trivy-sbom/parser/parser.test.js
@@ -0,0 +1,86 @@
+// SPDX-FileCopyrightText: the secureCodeBox authors
+//
+// SPDX-License-Identifier: Apache-2.0
+
+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(() => {
+ 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",
+ },
+]
+`);
+});
+
+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",
+ },
+ ]
+ `);
+});
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