-
Notifications
You must be signed in to change notification settings - Fork 1.3k
CSHARP-5626 Add evergreen script to generate CycloneDX SBOM #1718
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
f5c13b3
44d1370
c5e87f9
63e7168
554317c
5d9665a
92d5331
bb5cbf3
92d5a4e
d130d93
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| name: Generate SBOM | ||
| # Generates sbom.json using cyclonedx-dotnet and opens a PR if it has changed. | ||
| # Triggered when NuGet dependency files change on main, or manually. | ||
| # Internal documentation: go/sbom-scope | ||
| on: | ||
| workflow_dispatch: {} | ||
| push: | ||
| branches: | ||
| - main | ||
| paths: | ||
| - "src/**/*.csproj" | ||
| - "src/Directory.Build.props" | ||
| - "MongoDB.Driver.sbom.slnf" | ||
| permissions: | ||
| contents: write | ||
| pull-requests: write | ||
| jobs: | ||
| sbom: | ||
| name: Generate SBOM and Create PR | ||
| runs-on: ubuntu-latest | ||
| concurrency: | ||
| group: sbom-${{ github.ref }} | ||
| cancel-in-progress: false | ||
| steps: | ||
| - name: Checkout repository | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| fetch-depth: 0 | ||
| - name: Set up .NET | ||
| uses: actions/setup-dotnet@v4 | ||
| with: | ||
| dotnet-version: "8.0" | ||
| - name: Stash existing SBOM | ||
| run: | | ||
| # Strip nondeterministic fields before diffing | ||
| jq 'del(.metadata.timestamp, .version)' sbom.json > ${{ runner.temp }}/sbom.existing.json | ||
| - name: Generate SBOM | ||
| run: bash evergreen/generate-sbom.sh | ||
| env: | ||
| GITHUB_USER: ${{ github.actor }} | ||
| GITHUB_APIKEY: ${{ secrets.GITHUB_TOKEN }} | ||
| - name: Download CycloneDX CLI | ||
| run: | | ||
| curl -fsSL -o ${{ runner.temp }}/cyclonedx \ | ||
| "https://github.com/CycloneDX/cyclonedx-cli/releases/download/v0.32.0/cyclonedx-linux-x64" | ||
| echo "454879e6a4a405c8a13bff49b8982adcb0596f3019b26b0811c66e4d7f0783e1 ${{ runner.temp }}/cyclonedx" | sha256sum --check | ||
| chmod +x ${{ runner.temp }}/cyclonedx | ||
| - name: Validate SBOM | ||
| run: ${{ runner.temp }}/cyclonedx validate --input-file sbom.json --fail-on-errors | ||
| - name: Check for SBOM changes | ||
| id: sbom_diff | ||
| run: | | ||
| jq 'del(.metadata.timestamp, .version)' sbom.json > ${{ runner.temp }}/sbom.generated.json | ||
| RESULT=$(diff --brief ${{ runner.temp }}/sbom.existing.json ${{ runner.temp }}/sbom.generated.json || true) | ||
| echo "result=$RESULT" | tee -a $GITHUB_OUTPUT | ||
| - name: Open Pull Request if SBOM has changed | ||
| if: steps.sbom_diff.outputs.result | ||
| uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 | ||
| with: | ||
| add-paths: sbom.json | ||
| branch: auto-update-sbom-${{ github.ref_name }} | ||
| commit-message: "chore: Update SBOM after dependency changes" | ||
| delete-branch: true | ||
| labels: sbom | ||
| title: "CSHARP-5626: Update SBOM [${{ github.ref_name }}]" | ||
| body: | | ||
| ## Automated SBOM Update | ||
|
|
||
| This PR was automatically generated because a NuGet dependency change was merged | ||
| into `${{ github.ref_name }}`. | ||
|
|
||
| `sbom.json` has been regenerated to reflect the current NuGet package dependency | ||
| graph across all four MongoDB.Driver packages. The SBOM serial number is stable; | ||
| the `.version` field has been incremented to reflect this update. | ||
|
|
||
| Once merged, an Evergreen CI task will upload the updated SBOM to the internal | ||
| tracking system. | ||
|
|
||
| ### Triggered by | ||
| - Commit: ${{ github.sha }} | ||
| - Workflow run: [${{ github.run_id }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| { | ||
| "solution": { | ||
| "path": "CSharpDriver.sln", | ||
| "projects": [ | ||
| "src/MongoDB.Bson/MongoDB.Bson.csproj", | ||
| "src/MongoDB.Driver/MongoDB.Driver.csproj", | ||
| "src/MongoDB.Driver.Authentication.AWS/MongoDB.Driver.Authentication.AWS.csproj", | ||
| "src/MongoDB.Driver.Encryption/MongoDB.Driver.Encryption.csproj" | ||
| ] | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,114 @@ | ||
| #!/usr/bin/env bash | ||
| # Environment Variables: | ||
| # PACKAGE_VERSION (optional) - Version to use for SBOM generation | ||
| # Defaults to output of get-version.sh | ||
| # GITHUB_USER (optional) - GitHub username for license resolution | ||
| # GITHUB_APIKEY (optional) - GitHub API token for license resolution | ||
| set -eo pipefail | ||
|
|
||
|
adelinowona marked this conversation as resolved.
|
||
| # Accommodate Git Bash or MSYS2 on Windows | ||
| export MSYS_NO_PATHCONV=1 | ||
|
|
||
| SERIAL_NUMBER="urn:uuid:a24dfa6a-26a8-44d8-94bd-a2488e01185b" | ||
| SBOM_SLNF="MongoDB.Driver.sbom.slnf" | ||
|
|
||
| echo -e "\n************************************************" | ||
|
|
||
| # Get package version if not set | ||
| if [[ -z "$PACKAGE_VERSION" ]]; then | ||
| PACKAGE_VERSION=$(bash ./evergreen/get-version.sh) | ||
| fi | ||
|
|
||
| echo "Package Version: ${PACKAGE_VERSION}" | ||
|
|
||
| echo "Restoring solution with version set to ${PACKAGE_VERSION}" | ||
| dotnet restore "${SBOM_SLNF}" /p:Version="${PACKAGE_VERSION}" /p:TreatWarningsAsErrors=false | ||
|
|
||
| # Install cyclonedx-dotnet | ||
| echo "Installing cyclonedx-dotnet" | ||
| dotnet tool install --global CycloneDX --version 6.2.0 --allow-downgrade | ||
|
|
||
| echo -e "\nGenerating SBOM" | ||
| echo "************************************************" | ||
|
|
||
| # Attempt GitHub license resolution only if GITHUB_USER and GITHUB_APIKEY are both non-empty | ||
| if [[ -n "${GITHUB_USER:-}" && -n "${GITHUB_APIKEY:-}" ]]; then | ||
| echo "GitHub license resolution enabled for user: ${GITHUB_USER}" | ||
| github_options=(--enable-github-licenses --github-username "${GITHUB_USER}" --github-token "${GITHUB_APIKEY}") | ||
| else | ||
| echo "GitHub license resolution disabled (GITHUB_USER or GITHUB_APIKEY not set)" | ||
| github_options=() | ||
| fi | ||
|
|
||
| # Packages with PrivateAssets="All" in Directory.Build.props that --exclude-dev does not | ||
| # cover reliably. Version specifiers are not required in cyclonedx-dotnet 6.2.0+. | ||
| EXCLUDE_FILTER="Microsoft.CodeAnalysis.FxCopAnalyzers,Microsoft.NETFramework.ReferenceAssemblies,Microsoft.SourceLink.GitHub" | ||
|
|
||
| dotnet-CycloneDX "${SBOM_SLNF}" \ | ||
| --disable-package-restore \ | ||
| --configuration Release \ | ||
| --set-type library \ | ||
| --set-nuget-purl \ | ||
| --exclude-dev \ | ||
| --set-name mongo-csharp-driver \ | ||
| --set-version "${PACKAGE_VERSION}" \ | ||
| --exclude-filter "${EXCLUDE_FILTER}" \ | ||
| --spec-version 1.5 \ | ||
| --filename sbom.cdx.json \ | ||
| "${github_options[@]+"${github_options[@]}"}" | ||
|
|
||
| echo -e "\n=================================" | ||
| echo "Resolving libmongocrypt version" | ||
| echo "=================================" | ||
|
|
||
| LIBMONGOCRYPT_VERSION=$(dotnet msbuild src/MongoDB.Driver.Encryption/MongoDB.Driver.Encryption.csproj \ | ||
| -getProperty:LibMongoCryptVersion 2>/dev/null) | ||
|
|
||
| if [[ -z "$LIBMONGOCRYPT_VERSION" ]]; then | ||
| echo "ERROR: Could not extract LibMongoCryptVersion from MongoDB.Driver.Encryption.csproj" >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| echo "libmongocrypt version: ${LIBMONGOCRYPT_VERSION}" | ||
|
|
||
| LIBMONGOCRYPT_PURL="pkg:github/mongodb/libmongocrypt@${LIBMONGOCRYPT_VERSION}" | ||
| ENCRYPTION_PURL="pkg:nuget/MongoDB.Driver.Encryption@${PACKAGE_VERSION}" | ||
|
|
||
| tmp=$(mktemp) | ||
| jq \ | ||
| --arg purl "$LIBMONGOCRYPT_PURL" \ | ||
| --arg ver "$LIBMONGOCRYPT_VERSION" \ | ||
| --arg encpurl "$ENCRYPTION_PURL" \ | ||
| '.components += [{ | ||
| "type": "library", | ||
| "bom-ref": $purl, | ||
| "name": "libmongocrypt", | ||
| "version": $ver, | ||
| "purl": $purl | ||
| }] | | ||
| .dependencies += [{ | ||
| "ref": $encpurl, | ||
| "dependsOn": [$purl] | ||
| }]' sbom.cdx.json > "$tmp" && mv "$tmp" sbom.cdx.json | ||
|
|
||
| echo -e "\n=================================" | ||
| echo "Updating sbom.json with version tracking" | ||
| echo "=================================" | ||
|
|
||
| CURRENT_VERSION=$(jq -r '.version // 0' sbom.json 2>/dev/null || echo 0) | ||
| NEW_CONTENT=$(jq -S 'del(.version, .metadata.timestamp)' sbom.cdx.json) | ||
| OLD_CONTENT=$(jq -S 'del(.version, .metadata.timestamp)' sbom.json 2>/dev/null || echo '{}') | ||
|
|
||
| if [ "$NEW_CONTENT" = "$OLD_CONTENT" ]; then | ||
| NEW_VERSION=$CURRENT_VERSION | ||
| echo "SBOM content unchanged, keeping version ${NEW_VERSION}" | ||
| else | ||
| NEW_VERSION=$((CURRENT_VERSION + 1)) | ||
| echo "SBOM content changed, incrementing version to ${NEW_VERSION}" | ||
| fi | ||
|
|
||
| jq --argjson v "$NEW_VERSION" --arg serial "$SERIAL_NUMBER" \ | ||
| '.version = $v | .serialNumber = $serial' sbom.cdx.json > sbom.json | ||
| rm sbom.cdx.json | ||
|
|
||
| echo "Generated sbom.json (version ${NEW_VERSION})" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| #!/usr/bin/env bash | ||
| set -eo pipefail | ||
|
|
||
| : "${branch_name:?}" | ||
| : "${AWS_ACCESS_KEY_ID:?}" | ||
| : "${AWS_SECRET_ACCESS_KEY:?}" | ||
| : "${AWS_SESSION_TOKEN:?}" | ||
|
|
||
| silkbomb="901841024863.dkr.ecr.us-east-1.amazonaws.com/release-infrastructure/silkbomb:2.0" | ||
| docker pull "${silkbomb}" | ||
|
|
||
| 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[@]}" | ||
|
jasonhills-mongodb marked this conversation as resolved.
Comment on lines
+12
to
+23
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This runs the same silkbomb The download path goes through |
||
|
|
||
| old_json=$(mktemp) | ||
| new_json=$(mktemp) | ||
| diff_txt=$(mktemp) | ||
| trap 'rm -f "$old_json" "$new_json" "$diff_txt"' EXIT | ||
|
|
||
| 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" | ||
|
Comment on lines
+30
to
+35
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 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? |
||
|
|
||
| if ! diff -sty --left-column -W 200 "$old_json" "$new_json" > "$diff_txt"; then | ||
| declare status | ||
| status='{"status":"failed", "type":"test", "should_continue":true, "desc":"detected significant changes in Augmented SBOM"}' | ||
| curl -sS -d "${status}" -H "Content-Type: application/json" -X POST http://localhost:2285/task_status || true | ||
| fi | ||
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.