CSHARP-5626 Add evergreen script to generate CycloneDX SBOM - #1718
CSHARP-5626 Add evergreen script to generate CycloneDX SBOM#1718jasonhills-mongodb wants to merge 14 commits into
Conversation
Added a bash script to generate a 'build' lifecycle CycloneDX SBOM using the cyclonedx-dotnet tool. The script installed a fixed version of cyclonedx-dotnet, runs a versioned dotnet restore, and generates an SBOM for each of the 4 MongoDB.Driver Nuget packages. To ensure accurate results, two queries are run against each of the .csproj files to ensure all development packages are excluded and that any local <ProjectReference> items are marked in the SBOM as Nuget packages. Once all 4 SBOMs are created, they are merged into a single heirarchical SBOM file. The file is saved as sbom.cdx.json (as opposed to the current sbom.json) which is the preferred file extention for CycloneDX files. There is not yet any code to commit the new SBOM to the repo. This is to allow for evaluation of the new SBOM first without intefering with the current workflow to upload the current static sbom.json file to Kondukto. There is also a line added to download-augmented-sbom.sh to copy the augmented SBOM to ./vex.cdx.json (also not yet committed to repo) after it has been uploaded to the release artifacts bucket, as this is how we should be storing the augmented SBOM for public consumption. Once the SBOM generation process has been approved, I will add commit code for both of the cdx.json files, remove th sbom.json file and update download-augmented-sbom.sh to use the new sbom.cdx.json file.
Undoing unintended changes to evergreen.yml
|
To generate the SBOM, run |
… solution filter Replaces the per-package loop + cyclonedx-cli Docker merge approach with a single cyclonedx-dotnet 6.2.0 invocation on a new MongoDB.Driver.sbom.slnf solution filter. Key changes: - MongoDB.Driver.sbom.slnf: solution filter scoping generation to the 4 production packages, replacing the packages.sh loop and ProjectReference PURL-patching workarounds - generate-sbom.sh: upgraded to cyclonedx-dotnet 6.2.0; drops Docker dependency; adds stable serialNumber, monotonic version tracking, and libmongocrypt injection (version resolved from LibMongoCryptCommit via GitHub tags API, with SHA fallback) - .github/workflows/sbom.yml: new GitHub Actions workflow that regenerates sbom.json and opens an automated PR when NuGet dependency files change on main - evergreen/evergreen.yml: new upload-sbom function and task, new sbom build variant triggered by changes to sbom.json - evergreen/upload-sbom.sh: new script that uploads via silkbomb to ECR - evergreen/download-augmented-sbom.sh: fix path bug (cp used /pwd/ outside Docker context)
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds automation for generating, versioning, validating, and uploading a CycloneDX SBOM for the driver via GitHub Actions (regeneration PRs) and Evergreen (silkbomb augmentation/upload).
Changes:
- Add
generate-sbom.shto producesbom.json, enrich it with libmongocrypt dependency info, and manage SBOM versioning. - Add
upload-sbom.shand wire Evergreen tasks/buildvariant to run silkbomb augmentation and report status. - Add GitHub workflow and a
.slnffilter to generate/validate SBOMs and open automated PRs when it changes.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| evergreen/upload-sbom.sh | New script to run silkbomb augment and diff against previous augmented SBOM before reporting task status. |
| evergreen/generate-sbom.sh | New SBOM generation/versioning script using CycloneDX + GitHub tag lookup to model libmongocrypt. |
| evergreen/evergreen.yml | Adds Evergreen functions/tasks/buildvariant for uploading SBOM via silkbomb. |
| evergreen/download-augmented-sbom.sh | Copies augmented SBOM to vex.cdx.json after augment step. |
| MongoDB.Driver.sbom.slnf | Adds a solution filter for SBOM generation scope. |
| .github/workflows/sbom.yml | New GitHub Action to regenerate/validate SBOM and open a PR when sbom.json changes. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Use -n instead of -v for GITHUB_USER/GITHUB_APIKEY checks so that set-but-empty variables do not enable license resolution or auth headers - Guard against empty MONGOCRYPT_COMMIT with an explicit error exit - Validate GitHub API response is an array before processing to handle rate-limit or auth error objects gracefully - Write libmongocrypt jq output to a temp file and mv into place rather than capturing into a shell variable
- Add --user "$(id -u):$(id -g)" to docker run so silkbomb output files are owned by the invoking user rather than root - Use mktemp for old/new/diff temp files with a trap for cleanup, avoiding working directory pollution and concurrent-run interference - Add http:// scheme to localhost:2285 task status URL so curl can connect
…neration_using_cyclonedx-dotnet
Upstream switched from commit-based S3 downloads (LibMongoCryptCommit + LibMongoCryptVersion as branch prefix) to GitHub Releases downloads with LibMongoCryptVersion as a proper semver (1.18.2). The version is now available directly, so the GitHub tags API pagination loop is no longer needed. Also update sbom.json to reflect the current dependency graph.
Replaces the sed text-parsing approach with dotnet msbuild -getProperty, which uses the MSBuild evaluation engine and correctly handles computed or conditionally-set property values.
| generate-sbom: | ||
| - command: shell.exec | ||
| params: | ||
| working_dir: "mongo-csharp-driver" | ||
| env: | ||
| GITHUB_USER: ${github_user} | ||
| GITHUB_APIKEY: ${github_apikey} | ||
| PACKAGE_VERSION: ${PACKAGE_VERSION} | ||
| script: | | ||
| ${PREPARE_SHELL} | ||
| ./evergreen/generate-sbom.sh |
There was a problem hiding this comment.
The generate-sbom function is defined here but I can't find anything that invokes it — no task uses func: generate-sbom, and the sbom variant only runs upload-sbom. Since SBOM generation now lives entirely in the .github/workflows/sbom.yml GitHub Actions workflow, is this Evergreen function leftover from the earlier design? If so, let's delete it; if it's intended for manual/future use, a comment saying so would help.
There was a problem hiding this comment.
Removed the dead generate-sbom function (SBOM generation lives entirely in .github/workflows/sbom.yml)
| if [ -f ./augmented.sbom.json ]; then | ||
| jq -S 'del(.metadata.timestamp)' ./augmented.sbom.json > "$old_json" | ||
| else | ||
| echo '{}' > "$old_json" | ||
| fi | ||
| jq -S 'del(.metadata.timestamp)' ./augmented.sbom.json.new > "$new_json" |
There was a problem hiding this comment.
The "significant changes" check diffs augmented.sbom.json.new against augmented.sbom.json, but nothing in the upload-sbom task ever creates or downloads augmented.sbom.json (the script only writes the .new file, and the task runs func: upload-sbom alone). So old_json always falls into the echo '{}' branch and the diff reports significant changes on every run, making the guard effectively constant-true. Was the intent to download the previously-augmented SBOM as the baseline first (analogous to download-augmented-sbom.sh), or to persist .new back as the baseline after upload?
There was a problem hiding this comment.
The augment command no longer used/needed. Instead using upload without a diff.
| silkbomb_augment_flags=( | ||
| --repo mongodb/mongo-csharp-driver | ||
| --branch "${branch_name}" | ||
| --sbom-in /pwd/sbom.json | ||
| --sbom-out /pwd/augmented.sbom.json.new | ||
| --no-update-sbom-version | ||
| ) | ||
|
|
||
| docker run --rm -v "$(pwd):/pwd" \ | ||
| --user "$(id -u):$(id -g)" \ | ||
| --env 'AWS_ACCESS_KEY_ID' --env 'AWS_SECRET_ACCESS_KEY' --env 'AWS_SESSION_TOKEN' \ | ||
| "${silkbomb}" augment "${silkbomb_augment_flags[@]}" |
There was a problem hiding this comment.
This runs the same silkbomb augment command as our existing download-augmented-sbom.sh, but the upload-sbom function assumes the silkbomb role and forwards only AWS creds + branch_name + DOCKER_CONFIG — it never runs fetch-kondukto-token.sh and never passes --env-file kondukto_credentials.env / KONDUKTO_TOKEN.
The download path goes through kondukto_role_arn → fetch-kondukto-token.sh → --env-file for the identical augment call, which suggests augment needs the Kondukto token to reach Kondukto. Does the silkbomb IAM role somehow cover Kondukto auth (e.g. silkbomb self-fetches the token from Secrets Manager), or is the fetch-kondukto-token.sh + --env-file step missing here?
There was a problem hiding this comment.
Good callout. When running in Evergreen, silkbomb only needs AWS credentials. It retrieves the keys for Kondukto and Dependency Track automatically.
…neration_using_cyclonedx-dotnet # Conflicts: # evergreen/evergreen.yml # purls.txt # sbom.json
…generate sbom.json cyclonedx-dotnet 6.2.0's --exclude-filter runs its orphan-removal pass before project-reference packages are marked as direct references, so for any .sln/.slnx/.slnf input (and any single-project input without --recursive) it wipes the entire component list regardless of the filter value. Replaced with a jq post-processing step that removes the same build-tool packages (plus their now-orphaned children, per CycloneDX/cyclonedx-dotnet#445) without relying on the broken flag. Also fixed the permission bit on generate-sbom.sh (100644 -> 100755, matching its sibling scripts) and regenerated sbom.json with the fixed script, which also picks up AWSSDK.Core/SecurityToken 3.7.100.14 -> 4.0.100.2 and libmongocrypt 1.18.2 -> 1.20.0 from the upstream merge.
…rgreen function Replicates the pattern from mongo-go-driver#2154's upload-sbom (internal/cmd/upload-sbom/main.go): use silkbomb's "upload" subcommand instead of "augment". "upload" publishes directly to Dependency-Track and Kondukto in one call using only the Silkbomb IAM role's credentials, so it needs no separately fetched Kondukto token (unlike "augment", which is what download-augmented-sbom.sh still uses for its own, unrelated S3-promotion purpose). This also removes the local before/after diff of augmented.sbom.json[.new] that was meant to flag "significant changes" in the augmented output: the baseline file it compared against was never created or downloaded by the upload-sbom task, so the guard always took the "no baseline" branch and reported every run as significant -- a no-op check. silkbomb upload does not produce a local artifact to diff in the first place. Also deletes the generate-sbom Evergreen function: nothing invokes it (the sbom build variant only runs upload-sbom; SBOM generation happens entirely in .github/workflows/sbom.yml), matching mongo-go-driver's equivalent config, which has no Evergreen generation task either.
The SSDLC compliance report (ssdlc_compliance_report.md) and the augmented-SBOM artifact it was published alongside (augmented-sbom.json) are a vestige of a retired SSDLC program: stale, unread attestations that this ticket tracks removing across many repos. Per the ticket, nothing replaces this -- SBOM generation and upload to Kondukto (evergreen/generate-sbom.sh, evergreen/upload-sbom.sh) continue unchanged, since that is the artifact with real consumers and current data. Removed: - generate-ssdlc-reports task/matrix and the two Evergreen functions it alone used (download-and-promote-augmented-sbom-to-s3-bucket, generate-ssdlc-report) - evergreen/generate-ssdlc-report.sh, evergreen/template_ssdlc_compliance_report.md - evergreen/download-augmented-sbom.sh, evergreen/fetch-kondukto-token.sh (kondukto_role_arn had no other use) The removed task also ran func: trace-artifacts (Papertrail provenance tracing of the built .nupkg/.snupkg files) -- an unrelated, still-valid supply-chain control that just happened to live in the same task. Moved it into push-packages-nuget, right before the packages are actually pushed to NuGet.org, so it keeps running.
Overview
This PR automates
sbom.jsongeneration and upload using a two-stage pipeline:GitHub Actions (
sbom.yml): Triggered when NuGet dependency files change onmain. Regeneratessbom.jsonand opens an automated PR if the content has changed. This stage runs in GitHub Actions because it needs GitHub API access for license resolution.Evergreen (
upload-sbomtask/variant): Triggered whensbom.jsonchanges. Publishes the SBOM via silkbomb directly to Dependency-Track and Kondukto. This stage runs in Evergreen because it needs access to internal ECR and IAM roles.Changes
New files
MongoDB.Driver.sbom.slnf— Solution filter scoping SBOM generation to the 4 production packages (MongoDB.Bson,MongoDB.Driver,MongoDB.Driver.Authentication.AWS,MongoDB.Driver.Encryption). Replaces the per-package loop andpackages.sh..github/workflows/sbom.yml— GitHub Actions workflow that regeneratessbom.json, validates it with the CycloneDX CLI (SHA256-pinned binary), and opens an automated PR viapeter-evans/create-pull-requestwhen content changes.evergreen/upload-sbom.sh— Pulls silkbomb from ECR and runsupload, publishingsbom.jsondirectly to Dependency-Track and Kondukto in one call.Modified files
evergreen/generate-sbom.sh— Complete rewrite:cyclonedx-dotnet5.3.1 → 6.2.0, which adds solution filter (.slnf) support, eliminating the need for Docker and the per-package loop + merge approachdotnet-CycloneDXinvocation on the.slnfinstead of 4 separate invocations + Docker merge--spec-version 1.5to prevent cyclonedx-dotnet 6.2.0 from defaulting to 1.7/p:TreatWarningsAsErrors=falseon restore to suppress NU1902/NU1903 vulnerability warnings fromMongoDB.rulesetserialNumber(hardcoded UUID, same across all regenerations).versionfield — only increments when SBOM content changes (compared withoutmetadata.timestampand.version)libmongocryptinjected as a component after generation: version extracted fromLibMongoCryptVersioninMongoDB.Driver.Encryption.csprojusingdotnet msbuild -getProperty:LibMongoCryptVersion. Added toMongoDB.Driver.Encryption'sdependsOnin thedependenciesgraph.Microsoft.CodeAnalysis.FxCopAnalyzers,Microsoft.NETFramework.ReferenceAssemblies,Microsoft.SourceLink.GitHub, and their orphaned children) is done via ajqpost-processing step rather thandotnet-CycloneDX's--exclude-filter— see "cyclonedx-dotnet bug" below.evergreen/evergreen.yml:upload-sbomfunction, task (tags: ["ssdlc"]), andsbombuild variant withpaths: [sbom.json]triggergenerate-sbomfunction (nothing invoked it — SBOM generation lives entirely in.github/workflows/sbom.yml)generate-ssdlc-reportstask/matrix and its two Evergreen functions — see "RNDSEC-4060" belowevergreen/generate-sbom.sh: was committed as100644, sibling scripts are100755Deleted files
purls.txt— No longer referenced or needed.evergreen/download-augmented-sbom.sh,evergreen/fetch-kondukto-token.sh,evergreen/generate-ssdlc-report.sh,evergreen/template_ssdlc_compliance_report.md— see "RNDSEC-4060" below.cyclonedx-dotnet's
--exclude-filteris broken for our input shapeWhile regenerating
sbom.jsonfrom scratch, found thatdotnet-CycloneDX's--exclude-filter— originally used here to drop the 3 build-tool packages above — silently discards every component, not just the filtered ones, for any.sln/.slnf/.slnxinput (or a single project without--recursive). Root cause: in cyclonedx-dotnet 6.2.0, the filter's orphan-removal pass runs before packages are flagged as direct references, so its reachability search starts from an empty set and intersects the whole graph down to nothing. No released version avoids this for a solution-filter input like ours. Filed upstream: https://github.com/CycloneDX/cyclonedx-dotnet/issues (search "exclude-filter" if the link needs updating once filed).Fix applied here: dropped
--exclude-filterfrom thedotnet-CycloneDXinvocation, replaced with ajqstep that removes the same packages (plus children orphaned by a related bug, cyclonedx-dotnet#445) directly fromsbom.cdx.json, using a small reachability check so only things exclusively reachable through an excluded package are ever removed — genuine direct dependencies are untouched. Verified withcyclonedx validate --fail-on-errors(same check CI runs) and by diffing the dependency graph before/after for dangling references.sbom.jsonhas been regenerated with the fixed script, and also reflects dependency movement from themainmerge:AWSSDK.Core/AWSSDK.SecurityToken3.7.100.14 → 4.0.100.2,libmongocrypt1.18.2 → 1.20.0, and new transitive packages (System.Text.Encodings.Web,System.Text.Json,System.ValueTuple).augment→upload, replicating mongo-go-driver#2154evergreen/upload-sbom.shoriginally ran silkbomb'saugmentsubcommand, which only enriches an SBOM with Kondukto scan data locally — it doesn't publish anywhere by itself. That left two problems (both flagged in review):upload-sbomtask never fetched a Kondukto token or passed--env-file, unlikedownload-augmented-sbom.sh's identicalaugmentcall, which does — real gap, not just a missing comment.augmented.sbom.json.newagainst a baseline (augmented.sbom.json) that this task never created or downloaded, so it always took the "no baseline" branch and reported every run as significant — a no-op check.mongo-go-driver's equivalent (
internal/cmd/upload-sbom/main.go, from mongodb/mongo-go-driver#2154) uses silkbomb'suploadsubcommand instead, passing only AWS creds for the assumed Silkbomb IAM role — no Kondukto token needed, becauseuploadpublishes directly to Dependency-Track and Kondukto in one call. Replicated that pattern here: switched toupload, and removed the diff-guard entirely, sinceuploaddoesn't produce a local artifact to diff against in the first place (go-driver doesn't do any local diffing either).RNDSEC-4060: Remove deprecated SSDLC compliance report automation
Per RNDSEC-4060: the SSDLC compliance report (
ssdlc_compliance_report.md) and the augmented-SBOM artifact it was published alongside (augmented-sbom.json, via Kondukto'saugment) are a vestige of a retired SSDLC program — stale, unread attestations, tracked for removal across many repos. Per the ticket, nothing replaces this; SBOM generation and upload to Kondukto (this PR'sgenerate-sbom.sh/upload-sbom.sh) continue unchanged, since that's the artifact with real consumers and current data.Removed: the
generate-ssdlc-reportstask/matrix (git_tag_only, ran on every release tag) and the two Evergreen functions it alone used (download-and-promote-augmented-sbom-to-s3-bucket,generate-ssdlc-report), plus their four backing scripts/templates.That task also ran
func: trace-artifacts(Papertrail provenance tracing of the built.nupkg/.snupkgfiles) — an unrelated, still-valid supply-chain control that just happened to live in the same task. Moved it intopush-packages-nuget, right before packages are pushed to NuGet.org, so it keeps running.Addressing previous review feedback
startswith("MongoDB.")+join(",")stamping wrong PURLs): Entire approach replaced. The.slnfcauses the 4 MongoDB packages to be treated as in-solution projects, so they never appear incomponents[]and no PURL patching is needed./pwd/path indownload-augmented-sbom.sh: moot — file removed (RNDSEC-4060, above).generate-sbom.sh.enabled for user: X/disabled).upload-sbom.shuses thesilkbomb:2.0tag (consistent with other Evergreen-based silkbomb usage in the driver ecosystem).--exclude-devnot coveringPrivateAssets="All"packages:--exclude-devdoes correctly drop the top-level packages (confirmed 2 excluded as dev dependencies in local runs) — what it doesn't clean up is their transitive-only orphaned children (cyclonedx-dotnet#445), now handled by thejqstep above.generate-sbomEvergreen function: confirmed unused (nothing callsfunc: generate-sbom) and deleted, matching mongo-go-driver#2154's config, which has no Evergreen generation task either.upload-sbom.sh: resolved by switching toupload(above), which needs no separate Kondukto token.uploaddoesn't produce a local artifact to diff against.SBOM content comparison
The new output vs. the previous per-package + Docker merge approach:
.slnf)specVersionserialNumberdependencies[]graphmainmerge; excludes 6 build-tool-only packages)MongoDB.Bson/MongoDB.Driveras componentslibmongocryptMongoDB.Driver.Encryption.version, stableserialNumberTesting
dotnet-CycloneDXrun locally against the.slnfconfirmed: 34 NuGet components,specVersion: 1.5, all 6 excluded build-tool packages absent, all packages have NuGet PURLs, no danglingdependencies[].dependsOnreferences.cyclonedx validate --input-file sbom.json --fail-on-errors(same binary/version CI uses) passes.dotnet msbuild -getProperty:LibMongoCryptVersionextracts1.20.0directly — no GitHub API lookup needed.--exclude-filterbug in isolation against a single leaf project (no project references at all) and against the full.slnf, with and without--recursive, to confirm no CLI flag combination avoids it for this project's input type.evergreen/evergreen.ymlandevergreen/upload-sbom.shchanges validated withpython3 -c "import yaml; yaml.safe_load(...)"andbash -n; confirmed viagrepthat no task still references the removed functions/scripts/roles.