diff --git a/.evergreen/config.yml b/.evergreen/config.yml index 490093811..942bae514 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -56,6 +56,7 @@ github_checks_aliases: # Include files that contain various tasks, task groups, and build variant definitions include: - filename: .evergreen/config/functions.yml + - filename: .evergreen/config/sbom.yml - filename: .evergreen/config/build-task-groups.yml - filename: .evergreen/config/build-variants.yml diff --git a/.evergreen/config/functions.yml b/.evergreen/config/functions.yml index 8a7608859..821f19ca9 100644 --- a/.evergreen/config/functions.yml +++ b/.evergreen/config/functions.yml @@ -1,4 +1,40 @@ functions: + "upload-sbom": + - command: ec2.assume_role + display_name: Assume ECR readonly IAM role + params: + role_arn: arn:aws:iam::901841024863:role/ecr-role-evergreen-ro + - command: subprocess.exec + type: setup + display_name: Log in to ECR + params: + working_dir: src + binary: bash + include_expansions_in_env: + - AWS_ACCESS_KEY_ID + - AWS_SECRET_ACCESS_KEY + - AWS_SESSION_TOKEN + args: + - -c + - aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin 901841024863.dkr.ecr.us-east-1.amazonaws.com + - command: ec2.assume_role + display_name: Assume Silkbomb IAM role + params: + role_arn: arn:aws:iam::901841024863:role/silkbomb + - command: subprocess.exec + type: test + display_name: Upload SBOM via Silkbomb + params: + working_dir: src + binary: bash + include_expansions_in_env: + - branch_name + - AWS_ACCESS_KEY_ID + - AWS_SECRET_ACCESS_KEY + - AWS_SESSION_TOKEN + args: + - .evergreen/upload-sbom.sh + "fetch source": # Executes git clone and applies the submitted patch, if any - command: git.get_project diff --git a/.evergreen/config/sbom.yml b/.evergreen/config/sbom.yml new file mode 100644 index 000000000..434c375ae --- /dev/null +++ b/.evergreen/config/sbom.yml @@ -0,0 +1,19 @@ +tasks: + - name: upload-sbom + tags: ["ssdlc"] + allowed_requesters: ["commit"] + exec_timeout_secs: 600 + commands: + - func: "upload-sbom" + +buildvariants: + - name: sbom + display_name: "SBOM" + allowed_requesters: ["commit"] + stepback: false + paths: + - sbom.json + run_on: + - ubuntu2004-small + tasks: + - name: upload-sbom diff --git a/.evergreen/generate-sbom.sh b/.evergreen/generate-sbom.sh new file mode 100755 index 000000000..7432627bb --- /dev/null +++ b/.evergreen/generate-sbom.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +# Environment Variables: +# None required — all inputs are derived from the repository. +set -eo pipefail + +SERIAL_NUMBER="urn:uuid:dc42a43b-4ace-4c42-9a6e-0b9e28fdd100" + +echo "Installing CycloneDX PHP Composer plugin" +composer config allow-plugins.cyclonedx/cyclonedx-php-composer true +composer require --dev cyclonedx/cyclonedx-php-composer:6.2.0 --no-update + +echo "Updating dependencies" +# --ignore-platform-reqs: SBOM generation doesn't need to run the code, so extension +# availability and exact PHP patch versions don't matter here. +# --no-scripts: skip git submodule updates and other scripts that require a full dev setup. +composer update --ignore-platform-reqs --no-scripts + +echo "Generating SBOM" +composer CycloneDX:make-sbom \ + --spec-version=1.5 \ + --output-format=JSON \ + --output-file=sbom.cdx.json \ + --omit dev \ + --no-validate + +echo "Updating sbom.json with version tracking" + +CURRENT_VERSION=$(jq -r '.version // 0' sbom.json 2>/dev/null || echo 0) + +tmp_new=$(mktemp) +tmp_old=$(mktemp) +trap 'rm -f "$tmp_new" "$tmp_old"' EXIT +jq -S 'del(.version, .metadata.timestamp)' sbom.cdx.json > "$tmp_new" +jq -S 'del(.version, .metadata.timestamp)' sbom.json 2>/dev/null > "$tmp_old" || echo '{}' > "$tmp_old" + +if diff -q "$tmp_new" "$tmp_old" > /dev/null; 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})" diff --git a/.evergreen/upload-sbom.sh b/.evergreen/upload-sbom.sh new file mode 100755 index 000000000..4c4def51f --- /dev/null +++ b/.evergreen/upload-sbom.sh @@ -0,0 +1,46 @@ +#!/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-php-library + --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[@]}" + +[[ -f ./augmented.sbom.json.new ]] || { + echo "failed to generate augmented SBOM" >&2 + exit 1 +} + +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" + +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 diff --git a/.github/workflows/sbom.yml b/.github/workflows/sbom.yml new file mode 100644 index 000000000..35c6d751b --- /dev/null +++ b/.github/workflows/sbom.yml @@ -0,0 +1,84 @@ +name: Generate SBOM +# Generates sbom.json using cyclonedx-php-composer and opens a PR if it has changed. +# Triggered when Composer dependency files change on v2.x, or manually. +# Internal documentation: go/sbom-scope +on: + workflow_dispatch: {} + push: + branches: + - v2.x + paths: + - "composer.json" + +permissions: + contents: write + pull-requests: write + +jobs: + sbom: + name: Generate SBOM and Create PR + runs-on: ubuntu-24.04 + concurrency: + group: sbom-${{ github.ref }} + cancel-in-progress: false + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "8.3" + tools: composer + + - name: Stash existing SBOM + run: | + jq -S 'del(.metadata.timestamp, .version)' sbom.json > ${{ runner.temp }}/sbom.existing.json + + - name: Generate SBOM + run: bash .evergreen/generate-sbom.sh + + - 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 -S '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: "Automation: Update SBOM [${{ github.ref_name }}]" + body: | + ## Automated SBOM Update + + This PR was automatically generated because a Composer dependency change was merged + into `${{ github.ref_name }}`. + + `sbom.json` has been regenerated to reflect the current Composer package dependency + graph for production dependencies. 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 }}) diff --git a/sbom.json b/sbom.json index 4dce69b3b..81c7577fc 100644 --- a/sbom.json +++ b/sbom.json @@ -1,85 +1,246 @@ { - "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json", - "bomFormat": "CycloneDX", - "specVersion": "1.5", - "serialNumber": "urn:uuid:dc42a43b-4ace-4c42-9a6e-0b9e28fdd100", - "version": 1, - "metadata": { - "timestamp": "2024-05-08T09:51:01Z", - "tools": [ - { - "name": "composer", - "version": "2.7.6" - }, - { - "vendor": "cyclonedx", - "name": "cyclonedx-php-composer", - "version": "v5.2.0", - "externalReferences": [ - { - "type": "distribution", - "url": "https://api.github.com/repos/CycloneDX/cyclonedx-php-composer/zipball/f3a3cdc1a9e34bf1d5748e4279a24569cbf31fed", - "comment": "dist reference: f3a3cdc1a9e34bf1d5748e4279a24569cbf31fed" - }, - { - "type": "vcs", - "url": "https://github.com/CycloneDX/cyclonedx-php-composer.git", - "comment": "source reference: f3a3cdc1a9e34bf1d5748e4279a24569cbf31fed" - }, - { - "type": "website", - "url": "https://github.com/CycloneDX/cyclonedx-php-composer/#readme", - "comment": "as detected from Composer manifest 'homepage'" - }, - { - "type": "issue-tracker", - "url": "https://github.com/CycloneDX/cyclonedx-php-composer/issues", - "comment": "as detected from Composer manifest 'support.issues'" - }, - { - "type": "vcs", - "url": "https://github.com/CycloneDX/cyclonedx-php-composer/", - "comment": "as detected from Composer manifest 'support.source'" - } - ] - }, - { - "vendor": "cyclonedx", - "name": "cyclonedx-library", - "version": "v3.3.1", - "externalReferences": [ - { - "type": "distribution", - "url": "https://api.github.com/repos/CycloneDX/cyclonedx-php-library/zipball/cad0f92b36c85f36b3d3c11ff96002af5f20cd10", - "comment": "dist reference: cad0f92b36c85f36b3d3c11ff96002af5f20cd10" - }, - { - "type": "vcs", - "url": "https://github.com/CycloneDX/cyclonedx-php-library.git", - "comment": "source reference: cad0f92b36c85f36b3d3c11ff96002af5f20cd10" - }, - { - "type": "website", - "url": "https://github.com/CycloneDX/cyclonedx-php-library/#readme", - "comment": "as detected from Composer manifest 'homepage'" - }, - { - "type": "documentation", - "url": "https://cyclonedx-php-library.readthedocs.io", - "comment": "as detected from Composer manifest 'support.docs'" - }, - { - "type": "issue-tracker", - "url": "https://github.com/CycloneDX/cyclonedx-php-library/issues", - "comment": "as detected from Composer manifest 'support.issues'" - }, - { - "type": "vcs", - "url": "https://github.com/CycloneDX/cyclonedx-php-library/", - "comment": "as detected from Composer manifest 'support.source'" - } - ] - } + "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.5", + "serialNumber": "urn:uuid:dc42a43b-4ace-4c42-9a6e-0b9e28fdd100", + "version": 5, + "metadata": { + "timestamp": "2026-06-10T17:07:33Z", + "tools": [ + { + "name": "composer", + "version": "2.7.1" + }, + { + "vendor": "cyclonedx", + "name": "cyclonedx-php-composer", + "version": "v6.2.0", + "externalReferences": [ + { + "type": "distribution", + "url": "https://api.github.com/repos/CycloneDX/cyclonedx-php-composer/zipball/934440a5ef7c3c3cdb58c3c3d389d412630ccbf6", + "comment": "dist reference: 934440a5ef7c3c3cdb58c3c3d389d412630ccbf6" + }, + { + "type": "vcs", + "url": "https://github.com/CycloneDX/cyclonedx-php-composer.git", + "comment": "source reference: 934440a5ef7c3c3cdb58c3c3d389d412630ccbf6" + }, + { + "type": "website", + "url": "https://github.com/CycloneDX/cyclonedx-php-composer/#readme", + "comment": "as detected from Composer manifest 'homepage'" + }, + { + "type": "issue-tracker", + "url": "https://github.com/CycloneDX/cyclonedx-php-composer/issues", + "comment": "as detected from Composer manifest 'support.issues'" + }, + { + "type": "vcs", + "url": "https://github.com/CycloneDX/cyclonedx-php-composer/", + "comment": "as detected from Composer manifest 'support.source'" + } ] + }, + { + "vendor": "cyclonedx", + "name": "cyclonedx-library", + "version": "v4.1.0", + "externalReferences": [ + { + "type": "distribution", + "url": "https://api.github.com/repos/CycloneDX/cyclonedx-php-library/zipball/6a4f249cecf3b4211ad6bd8cee30fca89e0f81e5", + "comment": "dist reference: 6a4f249cecf3b4211ad6bd8cee30fca89e0f81e5" + }, + { + "type": "vcs", + "url": "https://github.com/CycloneDX/cyclonedx-php-library.git", + "comment": "source reference: 6a4f249cecf3b4211ad6bd8cee30fca89e0f81e5" + }, + { + "type": "website", + "url": "https://github.com/CycloneDX/cyclonedx-php-library/#readme", + "comment": "as detected from Composer manifest 'homepage'" + }, + { + "type": "documentation", + "url": "https://cyclonedx-php-library.readthedocs.io", + "comment": "as detected from Composer manifest 'support.docs'" + }, + { + "type": "issue-tracker", + "url": "https://github.com/CycloneDX/cyclonedx-php-library/issues", + "comment": "as detected from Composer manifest 'support.issues'" + }, + { + "type": "vcs", + "url": "https://github.com/CycloneDX/cyclonedx-php-library/", + "comment": "as detected from Composer manifest 'support.source'" + } + ] + } + ], + "component": { + "bom-ref": "mongodb/mongodb-dev-v2.x", + "type": "application", + "name": "mongodb", + "version": "dev-v2.x", + "group": "mongodb", + "description": "MongoDB driver library", + "author": "Andreas Braun, Jeremy Mikola, Jérôme Tamarelle", + "licenses": [ + { + "license": { + "id": "Apache-2.0" + } + } + ], + "purl": "pkg:composer/mongodb/mongodb@dev-v2.x", + "externalReferences": [ + { + "type": "website", + "url": "https://jira.mongodb.org/browse/PHPLIB", + "comment": "as detected from Composer manifest 'homepage'" + } + ], + "properties": [ + { + "name": "cdx:composer:package:distReference", + "value": "556579d82a087870b05898da0052e849d7596c81" + }, + { + "name": "cdx:composer:package:sourceReference", + "value": "556579d82a087870b05898da0052e849d7596c81" + }, + { + "name": "cdx:composer:package:type", + "value": "library" + } + ] + } + }, + "components": [ + { + "bom-ref": "psr/log-3.0.2.0", + "type": "library", + "name": "log", + "version": "3.0.2", + "group": "psr", + "description": "Common interface for logging libraries", + "author": "PHP-FIG", + "licenses": [ + { + "license": { + "id": "MIT" + } + } + ], + "purl": "pkg:composer/psr/log@3.0.2", + "externalReferences": [ + { + "type": "distribution", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "comment": "dist reference: f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + { + "type": "vcs", + "url": "https://github.com/php-fig/log.git", + "comment": "source reference: f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + { + "type": "website", + "url": "https://github.com/php-fig/log", + "comment": "as detected from Composer manifest 'homepage'" + }, + { + "type": "vcs", + "url": "https://github.com/php-fig/log/tree/3.0.2", + "comment": "as detected from Composer manifest 'support.source'" + } + ], + "properties": [ + { + "name": "cdx:composer:package:distReference", + "value": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + { + "name": "cdx:composer:package:sourceReference", + "value": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + { + "name": "cdx:composer:package:type", + "value": "library" + } + ] + }, + { + "bom-ref": "symfony/polyfill-php85-1.38.1.0", + "type": "library", + "name": "polyfill-php85", + "version": "v1.38.1", + "group": "symfony", + "description": "Symfony polyfill backporting some PHP 8.5+ features to lower PHP versions", + "author": "Nicolas Grekas, Symfony Community", + "licenses": [ + { + "license": { + "id": "MIT" + } + } + ], + "purl": "pkg:composer/symfony/polyfill-php85@v1.38.1", + "externalReferences": [ + { + "type": "distribution", + "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/ba2ba04f3352cfa2dcbbcb90aee13ed967f505b1", + "comment": "dist reference: ba2ba04f3352cfa2dcbbcb90aee13ed967f505b1" + }, + { + "type": "vcs", + "url": "https://github.com/symfony/polyfill-php85.git", + "comment": "source reference: ba2ba04f3352cfa2dcbbcb90aee13ed967f505b1" + }, + { + "type": "website", + "url": "https://symfony.com", + "comment": "as detected from Composer manifest 'homepage'" + }, + { + "type": "vcs", + "url": "https://github.com/symfony/polyfill-php85/tree/v1.38.1", + "comment": "as detected from Composer manifest 'support.source'" + } + ], + "properties": [ + { + "name": "cdx:composer:package:distReference", + "value": "ba2ba04f3352cfa2dcbbcb90aee13ed967f505b1" + }, + { + "name": "cdx:composer:package:sourceReference", + "value": "ba2ba04f3352cfa2dcbbcb90aee13ed967f505b1" + }, + { + "name": "cdx:composer:package:type", + "value": "library" + } + ] + } + ], + "dependencies": [ + { + "ref": "psr/log-3.0.2.0" + }, + { + "ref": "symfony/polyfill-php85-1.38.1.0" + }, + { + "ref": "mongodb/mongodb-dev-PHPLIB-SBOM/sbom_generation_using_cyclonedx-php-composer", + "dependsOn": [ + "psr/log-3.0.2.0", + "symfony/polyfill-php85-1.38.1.0" + ] } + ] }