From a0af51a9b80c2b8c93a4f2fd8589a569e8b30507 Mon Sep 17 00:00:00 2001 From: Jason Hills Date: Mon, 4 Aug 2025 16:22:24 -0400 Subject: [PATCH] GODRIVER-3599 Add task script to generate CycloneDX SBOM The GODRIVER SBOM (sbom.json) does not contain the direct and transitive dependencies defined in go.mod. Added code to generate a CycloneDX SBOM in order to better meet NITA Minimum Elements for Software Bill of Materials, OWASP Software Component Verification Standard (SCVS) Level 1, as well as include the necessary component identifiers for vulnerability discovery and VEX responses. Added a task, etc/script, and pre-commit hook for generating a CycloneDX SBOM using a pinned version of the cyclonedx-gomod tool. The SBOM includes the aggregate of modules required by packages in the mongo-go-driver library, excluding examples, tests and test packages. The task (generate-sbom) is added to the default tasks and will run only when go.mod is newer than sbom.cdx.json. The pre-commit hook (sbom-currency) ensures that if go.mod is staged for commit, that an updated sbom.json is also staged. Future TODO: Add libmongocrypt as an optional component once the libmongocrypt SBOM is updated with newer automation --- .evergreen/config.yml | 8 + .pre-commit-config.yaml | 7 + Taskfile.yml | 13 +- etc/generate-sbom.sh | 32 ++++ sbom.json | 387 ++++++++++++++++++++++++++++++++++++++-- 5 files changed, 436 insertions(+), 11 deletions(-) create mode 100755 etc/generate-sbom.sh diff --git a/.evergreen/config.yml b/.evergreen/config.yml index 2a7778d026..4a7ab2d10b 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -643,6 +643,14 @@ tasks: binary: bash args: [*task-runner, govulncheck] + - name: generate-sbom + tags: ["ssdlc"] + commands: + - command: subprocess.exec + params: + binary: bash + args: [*task-runner, generate-sbom] + - name: pull-request-helpers allowed_requesters: ["patch", "github_pr"] commands: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 27f3bd3147..62a1a625c7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -71,3 +71,10 @@ repos: language: system types: [go] entry: etc/check_license.sh + + - id: sbom-currency + name: sbom-currency + language: system + types: [json] + require_serial: true + entry: etc/generate-sbom.sh -c diff --git a/Taskfile.yml b/Taskfile.yml index a4c6f405bf..9f0074424d 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -11,7 +11,7 @@ tasks: ### Utility tasks. ### default: - deps: [build, check-license, check-fmt, check-modules, lint, test-short] + deps: [build, check-license, check-fmt, check-modules, lint, test-short, generate-sbom] add-license: bash etc/check_license.sh -a @@ -87,6 +87,17 @@ tasks: govulncheck: bash etc/govulncheck.sh + generate-sbom: + desc: Generate a CycloneDX SBOM + summary: | + Generate a CycloneDX SBOM with the cyclonedx-gomod 'mod' subcommand + The SBOM includes the aggregate of modules required by packages in the mongo-go-driver library, excluding examples, tests and test packages. + Task will run only when go.mod is newer than sbom.cdx.json. + method: timestamp + sources: [go.mod] + generates: [sbom.json] + cmd: bash etc/generate-sbom.sh + update-notices: bash etc/generate_notices.pl > THIRD-PARTY-NOTICES ### Local testing tasks. ### diff --git a/etc/generate-sbom.sh b/etc/generate-sbom.sh new file mode 100755 index 0000000000..ef13f378c2 --- /dev/null +++ b/etc/generate-sbom.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +set -e + +CHECK_CURRENCY="false" + +# Options are: +# -c : check currency of staged sbom.json versus go.mod. +while getopts "c" opt; do + case $opt in + c) + CHECK_CURRENCY="true" + ;; + *) + echo "usage: $0 [-c]" >&2 + echo " -c : (optional) check currency of staged sbom.json versus go.mod." >&2 + exit 1 + ;; + esac +done +#shift $((OPTIND - 1)) + +if ! $CHECK_CURRENCY; then + # The cyclonedx-gomod 'mod' subcommand is used to generate a CycloneDX SBOM with GOWORK=off to exclude example/test code. + # TODO: Add libmongocrypt as an optional component via a merge once the libmongocrypt SBOM is updated with newer automation + + ## The pipe to jq is a temporary workaround until this issue is resolved: https://github.com/CycloneDX/cyclonedx-gomod/issues/662. + ## When resolved, bump version and replace with commented line below. + # GOWORK=off go run github.com/CycloneDX/cyclonedx-gomod/cmd/cyclonedx-gomod@[UPDATED VERSION] mod -type library -licenses -assert-licenses -output-version 1.5 -json -output sbom.json . + GOWORK=off go run github.com/CycloneDX/cyclonedx-gomod/cmd/cyclonedx-gomod@v1.9.0 mod -type library -licenses -assert-licenses -output-version 1.5 -json . | jq '.metadata.component.purl |= split("?")[0]' | jq '.components[].purl |= split("?")[0]' > sbom.json +elif [[ $(git diff --name-only --cached go.mod) && ! $(git diff --name-only --cached sbom.json) ]]; then + echo "'go.mod' has changed. 'sbom.json' must be re-generated (run 'task generate-sbom' or 'etc/generate-sbom.sh') and staged." && exit 1 +fi diff --git a/sbom.json b/sbom.json index 3f3df0c7cc..149c64e914 100644 --- a/sbom.json +++ b/sbom.json @@ -1,11 +1,378 @@ { - "metadata": { - "timestamp": "2024-05-02T17:36:29.429171+00:00" - }, - "components": [], - "serialNumber": "urn:uuid:06a59521-ad52-420b-aee6-7d9ed15e1fd9", - "version": 1, - "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json", - "bomFormat": "CycloneDX", - "specVersion": "1.5" - } \ No newline at end of file + "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.5", + "serialNumber": "urn:uuid:03582aab-e289-4eb8-9a85-eebdc4b8bf8a", + "version": 1, + "metadata": { + "timestamp": "2025-08-04T16:09:43-04:00", + "tools": [ + { + "vendor": "CycloneDX", + "name": "cyclonedx-gomod", + "version": "v1.9.0", + "hashes": [ + { + "alg": "MD5", + "content": "56b744e437c1ba4bb7f443b5846a21d2" + }, + { + "alg": "SHA-1", + "content": "10ec58662be68ba3dfba7b2488d6585632f0a5c0" + }, + { + "alg": "SHA-256", + "content": "0102cf3082192b20c8e8c99c12ad54808eb0dbeb6627ea8ea5e2df117732f134" + }, + { + "alg": "SHA-384", + "content": "1e2c8756c2d4facfd910c938f68b5635626413a56b2aeefe0c6b8e79182ec0974242c3a38fcee85ccb3e227aa4adfbce" + }, + { + "alg": "SHA-512", + "content": "afffbed84e1bbc5320974be54df7bf58165ef40c4ba6d87f02494bdf2b6f19a1e3ccdc873dcd993c00eab29920ee94d8c6ee5f3fb09d4915b33e276260f8661a" + } + ], + "externalReferences": [ + { + "url": "https://github.com/CycloneDX/cyclonedx-gomod", + "type": "vcs" + }, + { + "url": "https://cyclonedx.org", + "type": "website" + } + ] + } + ], + "component": { + "bom-ref": "pkg:golang/go.mongodb.org/mongo-driver/v2@v2.2.3-0.20250605220432-a0f897bda6ce?type=module", + "type": "library", + "name": "go.mongodb.org/mongo-driver/v2", + "version": "v2.2.3-0.20250605220432-a0f897bda6ce", + "licenses": [ + { + "license": { + "id": "Apache-2.0" + } + } + ], + "purl": "pkg:golang/go.mongodb.org/mongo-driver/v2@v2.2.3-0.20250605220432-a0f897bda6ce" + } + }, + "components": [ + { + "bom-ref": "pkg:golang/github.com/davecgh/go-spew@v1.1.1?type=module", + "type": "library", + "name": "github.com/davecgh/go-spew", + "version": "v1.1.1", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "be3f63feed5baa7bc211f24ec1486d94e011aacdfeae41d8635de36164d4f7b7" + } + ], + "licenses": [ + { + "license": { + "id": "0BSD" + } + } + ], + "purl": "pkg:golang/github.com/davecgh/go-spew@v1.1.1", + "externalReferences": [ + { + "url": "https://github.com/davecgh/go-spew", + "type": "vcs" + } + ] + }, + { + "bom-ref": "pkg:golang/github.com/golang/snappy@v1.0.0?type=module", + "type": "library", + "name": "github.com/golang/snappy", + "version": "v1.0.0", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "3b2eb4ec65571eced1b5b820b4f067af64660c0ac8b0079f0f0beb756bd1846b" + } + ], + "licenses": [ + { + "license": { + "id": "BSD-3-Clause" + } + } + ], + "purl": "pkg:golang/github.com/golang/snappy@v1.0.0", + "externalReferences": [ + { + "url": "https://github.com/golang/snappy", + "type": "vcs" + } + ] + }, + { + "bom-ref": "pkg:golang/github.com/klauspost/compress@v1.16.7?type=module", + "type": "library", + "name": "github.com/klauspost/compress", + "version": "v1.16.7", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "da693730f18dccacb112b030f186a885887af7ea5ae2c210482d1f3c608547d2" + } + ], + "licenses": [ + { + "license": { + "id": "Apache-2.0" + } + } + ], + "purl": "pkg:golang/github.com/klauspost/compress@v1.16.7", + "externalReferences": [ + { + "url": "https://github.com/klauspost/compress", + "type": "vcs" + } + ] + }, + { + "bom-ref": "pkg:golang/github.com/xdg-go/pbkdf2@v1.0.0?type=module", + "type": "library", + "name": "github.com/xdg-go/pbkdf2", + "version": "v1.0.0", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "4aeec33eee3cc173300b76ececc08d1becf816173212ecf9765bdc85bab40747" + } + ], + "licenses": [ + { + "license": { + "id": "Apache-2.0" + } + } + ], + "purl": "pkg:golang/github.com/xdg-go/pbkdf2@v1.0.0", + "externalReferences": [ + { + "url": "https://github.com/xdg-go/pbkdf2", + "type": "vcs" + } + ] + }, + { + "bom-ref": "pkg:golang/github.com/xdg-go/scram@v1.1.2?type=module", + "type": "library", + "name": "github.com/xdg-go/scram", + "version": "v1.1.2", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "1475f92390788b884a455441085471ab589045e8fb58ede1841b897fe514c926" + } + ], + "licenses": [ + { + "license": { + "id": "Apache-2.0" + } + } + ], + "purl": "pkg:golang/github.com/xdg-go/scram@v1.1.2", + "externalReferences": [ + { + "url": "https://github.com/xdg-go/scram", + "type": "vcs" + } + ] + }, + { + "bom-ref": "pkg:golang/github.com/xdg-go/stringprep@v1.0.4?type=module", + "type": "library", + "name": "github.com/xdg-go/stringprep", + "version": "v1.0.4", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "5cb23f360dced40b73ab4a01b374d69bee5956092ad9aa9d96f3fd26da19e9cf" + } + ], + "licenses": [ + { + "license": { + "id": "Apache-2.0" + } + } + ], + "purl": "pkg:golang/github.com/xdg-go/stringprep@v1.0.4", + "externalReferences": [ + { + "url": "https://github.com/xdg-go/stringprep", + "type": "vcs" + } + ] + }, + { + "bom-ref": "pkg:golang/github.com/youmark/pkcs8@v0.0.0-20240726163527-a2c0da244d78?type=module", + "type": "library", + "name": "github.com/youmark/pkcs8", + "version": "v0.0.0-20240726163527-a2c0da244d78", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "8a5415d61cf38aef8b2ccdf3513274b6b473b5fc208ea2a705636d49191b9b03" + } + ], + "licenses": [ + { + "license": { + "id": "MIT" + } + } + ], + "purl": "pkg:golang/github.com/youmark/pkcs8@v0.0.0-20240726163527-a2c0da244d78", + "externalReferences": [ + { + "url": "https://github.com/youmark/pkcs8", + "type": "vcs" + } + ] + }, + { + "bom-ref": "pkg:golang/golang.org/x/crypto@v0.33.0?type=module", + "type": "library", + "name": "golang.org/x/crypto", + "version": "v0.33.0", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "20e04fb24922e8bcac8b4968f6a42f6f1890f85bec082fd858e79c087022c6eb" + } + ], + "licenses": [ + { + "license": { + "id": "BSD-Source-Code" + } + } + ], + "purl": "pkg:golang/golang.org/x/crypto@v0.33.0" + }, + { + "bom-ref": "pkg:golang/golang.org/x/sync@v0.11.0?type=module", + "type": "library", + "name": "golang.org/x/sync", + "version": "v0.11.0", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "186cfcf9740fe05bd34eb8d93f334a4cc16d4971fcd110331bee608453e02bdc" + } + ], + "licenses": [ + { + "license": { + "id": "BSD-Source-Code" + } + } + ], + "purl": "pkg:golang/golang.org/x/sync@v0.11.0" + }, + { + "bom-ref": "pkg:golang/golang.org/x/text@v0.22.0?type=module", + "type": "library", + "name": "golang.org/x/text", + "version": "v0.22.0", + "scope": "required", + "hashes": [ + { + "alg": "SHA-256", + "content": "6e87eaee6dff1c016f6c5e758f3dd0f702e0de392f48fba266efe90f55f082d3" + } + ], + "licenses": [ + { + "license": { + "id": "BSD-Source-Code" + } + } + ], + "purl": "pkg:golang/golang.org/x/text@v0.22.0" + } + ], + "dependencies": [ + { + "ref": "pkg:golang/go.mongodb.org/mongo-driver/v2@v2.2.3-0.20250605220432-a0f897bda6ce?type=module", + "dependsOn": [ + "pkg:golang/github.com/davecgh/go-spew@v1.1.1?type=module", + "pkg:golang/github.com/golang/snappy@v1.0.0?type=module", + "pkg:golang/github.com/klauspost/compress@v1.16.7?type=module", + "pkg:golang/github.com/xdg-go/scram@v1.1.2?type=module", + "pkg:golang/github.com/xdg-go/stringprep@v1.0.4?type=module", + "pkg:golang/github.com/youmark/pkcs8@v0.0.0-20240726163527-a2c0da244d78?type=module", + "pkg:golang/golang.org/x/crypto@v0.33.0?type=module", + "pkg:golang/golang.org/x/sync@v0.11.0?type=module" + ] + }, + { + "ref": "pkg:golang/github.com/davecgh/go-spew@v1.1.1?type=module" + }, + { + "ref": "pkg:golang/github.com/golang/snappy@v1.0.0?type=module" + }, + { + "ref": "pkg:golang/github.com/klauspost/compress@v1.16.7?type=module" + }, + { + "ref": "pkg:golang/github.com/xdg-go/pbkdf2@v1.0.0?type=module" + }, + { + "ref": "pkg:golang/github.com/xdg-go/scram@v1.1.2?type=module", + "dependsOn": [ + "pkg:golang/github.com/xdg-go/pbkdf2@v1.0.0?type=module", + "pkg:golang/github.com/xdg-go/stringprep@v1.0.4?type=module" + ] + }, + { + "ref": "pkg:golang/github.com/xdg-go/stringprep@v1.0.4?type=module", + "dependsOn": [ + "pkg:golang/golang.org/x/text@v0.22.0?type=module" + ] + }, + { + "ref": "pkg:golang/github.com/youmark/pkcs8@v0.0.0-20240726163527-a2c0da244d78?type=module", + "dependsOn": [ + "pkg:golang/golang.org/x/crypto@v0.33.0?type=module" + ] + }, + { + "ref": "pkg:golang/golang.org/x/crypto@v0.33.0?type=module", + "dependsOn": [ + "pkg:golang/golang.org/x/text@v0.22.0?type=module" + ] + }, + { + "ref": "pkg:golang/golang.org/x/sync@v0.11.0?type=module" + }, + { + "ref": "pkg:golang/golang.org/x/text@v0.22.0?type=module", + "dependsOn": [ + "pkg:golang/golang.org/x/sync@v0.11.0?type=module" + ] + } + ] +}