diff --git a/.github/workflows/sbom.yml b/.github/workflows/sbom.yml new file mode 100644 index 00000000000..e580ca8ace9 --- /dev/null +++ b/.github/workflows/sbom.yml @@ -0,0 +1,113 @@ +name: Generate SBOM + +# This workflow uses CycloneDX Gradle plugin and publishes an sbom.json artifact. +# It runs on manual trigger or when Gradle build files change on main branch, +# and creates a PR with the updated SBOM. +# Internal documentation: go/sbom-scope + +on: + workflow_dispatch: {} + push: + branches: ['main'] + paths: + - '**/build.gradle.kts' + - '**/build.gradle' + - 'gradle/libs.versions.toml' + - 'gradle.properties' + - 'settings.gradle.kts' + +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: + persist-credentials: false + + - name: Set up Java + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '21' + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Generate SBOM + run: ./gradlew cyclonedxBom + + - name: Add licenses to MongoDB components + run: | + jq ' + .components |= map( + if (.group == "org.mongodb" or .group == "org.mongodb.scala") and (.licenses == null or .licenses == []) + then . + { + "licenses": [ + { + "license": { + "name": "Apache-2.0" + } + } + ] + } + else . + end + ) + ' sbom.json > sbom-with-licenses.json + mv sbom-with-licenses.json sbom.json + + - name: Download CycloneDX CLI + run: | + curl -L -s -o /tmp/cyclonedx "https://github.com/CycloneDX/cyclonedx-cli/releases/download/v0.29.1/cyclonedx-linux-x64" + chmod +x /tmp/cyclonedx + + - name: Validate SBOM + run: /tmp/cyclonedx validate --input-file sbom.json --fail-on-errors + + - name: Upload SBOM artifact + uses: actions/upload-artifact@v4 + with: + name: sbom + path: sbom.json + if-no-files-found: error + + - name: Create Pull Request + uses: peter-evans/create-pull-request@b4733b9419fd47bbfa1807b15627e17cd70b5b22 + with: + token: ${{ secrets.GITHUB_TOKEN }} + commit-message: 'chore: Update SBOM after dependency changes' + branch: auto-update-sbom-${{ github.run_id }} + delete-branch: true + title: 'chore: Update SBOM' + body: | + ## Automated SBOM Update + + This PR was automatically generated because dependency manifest files changed. + + ### Changes + - Updated `sbom.json` to reflect current dependencies + + ### Verification + The SBOM was generated using CycloneDX Gradle plugin v2.3.1 with CycloneDX specification v1.5. + + ### Triggered by + - Commit: ${{ github.sha }} + - Workflow run: ${{ github.run_id }} + + --- + _This PR was created automatically by the [SBOM workflow](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})_ + labels: | + sbom + automated + dependencies + diff --git a/build.gradle.kts b/build.gradle.kts index 3112e2c59b9..6dfbd7889bd 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -14,11 +14,13 @@ * limitations under the License. */ import java.time.Duration +import org.cyclonedx.model.* plugins { id("eclipse") id("idea") alias(libs.plugins.nexus.publish) + id("org.cyclonedx.bom") version "2.3.1" } val nexusUsername: Provider = providers.gradleProperty("nexusUsername") @@ -47,3 +49,42 @@ nexusPublishing { delayBetween.set(Duration.ofSeconds(10)) } } + +tasks.cyclonedxBom { + setGroup("org.mongodb") + + // includeConfigs is the list of configuration names to include when generating the BOM (leave empty to include every configuration), regex is supported + setIncludeConfigs(listOf("runtimeClasspath","baseline")) + // skipConfigs is a list of configuration names to exclude when generating the BOM, regex is supported + //setSkipConfigs(listOf("(?i)(.*(compile|test|checkstyle|codenarc|spotbugs|detekt|analysis|zinc|dokka|commonizer|implementation|annotation).*)")) + // skipProjects is a list of project names to exclude when generating the BOM + setSkipProjects(listOf(rootProject.name, "bom")) + // Specified the type of project being built. Defaults to 'library' + setProjectType("library") + // Specified the version of the CycloneDX specification to use. Defaults to '1.6' + setSchemaVersion("1.5") + // Boms destination directory. Defaults to 'build/reports' + setDestination(project.file("./")) + // The file name for the generated BOMs (before the file format suffix). Defaults to 'bom' + setOutputName("sbom") + // The file format generated, can be xml, json or all for generating both. Defaults to 'all' + setOutputFormat("json") + + // declaration of the Object from OrganizationalContact + var organizationalContact1 = OrganizationalContact() + + // setting the Name[String], Email[String] and Phone[String] of the Object + organizationalContact1.setName("MongoDB, Inc.") + + // passing data to the plugin + setOrganizationalEntity { oe -> + oe.name = "MongoDB, Inc." + oe.urls = listOf("www.mongodb.com") + oe.addContact(organizationalContact1) + } + + // Configure VCS external reference with proper HTTPS URL + setVCSGit { vcs -> + vcs.url = "https://github.com/mongodb/mongo-java-driver.git" + } +}