diff --git a/.github/workflows/github_release.sh b/.github/workflows/github_release.sh index 36b81204..31fc8b92 100755 --- a/.github/workflows/github_release.sh +++ b/.github/workflows/github_release.sh @@ -15,6 +15,7 @@ echo "Read project version '$project_version' from $pom_file" # Calculate checksum readonly artifact_path="$base_dir/product/target/openfasttrace-${project_version}.jar" +readonly sbom_path="$base_dir/product/target/openfasttrace-${project_version}.spdx3.json" echo "Calculate sha256sum for file '$artifact_path'" file_dir="$(dirname "$artifact_path")" readonly file_dir @@ -40,7 +41,9 @@ echo "Title : $title" echo "Changes file : $changes_file" echo "Artifact file: $artifact_path" echo "Checksum file: $checksum_file_path" +echo "SBOM file : $sbom_path" -release_url=$(gh release create --latest --title "$title" --notes "$notes" --target main "$tag" "$artifact_path" "$checksum_file_path") +# [impl->dsn~build.spdx-sbom-release-asset~1] +release_url=$(gh release create --latest --title "$title" --notes "$notes" --target main "$tag" "$artifact_path" "$checksum_file_path" "$sbom_path") readonly release_url echo "Release URL: $release_url" diff --git a/doc/changes/changes.md b/doc/changes/changes.md index a549f6b2..46a75171 100644 --- a/doc/changes/changes.md +++ b/doc/changes/changes.md @@ -1,5 +1,6 @@ # Changes +* [4.10.0](changes_4.10.0.md) * [4.9.0](changes_4.9.0.md) * [4.8.0](changes_4.8.0.md) * [4.7.0](changes_4.7.0.md) diff --git a/doc/changes/changes_4.10.0.md b/doc/changes/changes_4.10.0.md new file mode 100644 index 00000000..2c7ebf5a --- /dev/null +++ b/doc/changes/changes_4.10.0.md @@ -0,0 +1,11 @@ +# OpenFastTrace 4.10.0, released 2026-08-14 + +Code name: SPDX 3 SBOM + +## Summary + +OpenFastTrace release assets now include an SPDX 3 software bill of materials (SBOM) for the product. It lists the product's compile-time, runtime, and provided dependencies, including transitive dependencies. + +## New Features + +* #527: Add an SPDX 3 SBOM to the product build and GitHub release assets. diff --git a/doc/changesets/527-spdx-sbom.md b/doc/changesets/527-spdx-sbom.md new file mode 100644 index 00000000..2fe96176 --- /dev/null +++ b/doc/changesets/527-spdx-sbom.md @@ -0,0 +1,116 @@ +# GH-527 SPDX SBOM + +## Goal + +Produce one SPDX 3 JSON software bill of materials (SBOM) for the OpenFastTrace +product during the Maven build and attach it to every GitHub release. The SBOM +must represent the product at module granularity, include compile, runtime, and +provided dependencies, exclude test dependencies, and retain available +dependency-license information. + +## Scope + +In scope: + +* Define the build and release requirements for the product SPDX SBOM. +* Add and configure the SPDX Maven Plugin for the `product` module only. +* Generate `openfasttrace-.spdx3.json` as part of the regular Maven + build. +* Include compile, runtime, and provided dependencies. +* Exclude test-scope dependencies from the released-product SBOM. +* Ensure the release workflow uploads the SBOM with the product JAR and its + checksum. + +Out of scope: + +* File-level or snippet-level SPDX resolution. +* SBOMs for individual OpenFastTrace modules. +* Changing OpenFastTrace runtime behavior or its public CLI/API. + +## Design References + +* [System Requirements](../spec/system_requirements.md) +* [Design](../spec/design.md) +* [Quality Requirements](../spec/design/quality_requirements.md) +* [Maven build configuration](../../parent/pom.xml) +* [Product module](../../product/pom.xml) +* [Release workflow](../../.github/workflows/release.yml) +* [GitHub release script](../../.github/workflows/github_release.sh) +* [SPDX Maven Plugin documentation](https://spdx.github.io/spdx-maven-plugin/createSPDX-mojo.html) + +## Strategy + +Configure the selected SPDX Maven Plugin in `product/pom.xml`, rather than in +the shared parent, so the reactor creates exactly one SBOM for the distributable +product. Bind `createSPDX` to the normal Maven lifecycle, use SPDX 3 JSON-LD +output and the required product filename, and explicitly configure dependency +scope inclusion to make the acceptance criteria independent of plugin defaults. + +Extend the release script's explicit asset list with the generated SBOM. Keep +the SBOM beside the product JAR in `product/target` so the local build and the +release workflow consume the same artifact. + +The plugin's public documentation confirms that it produces SPDX documents from +Maven POM metadata, can emit SPDX 3 JSON-LD (`.spdx3.json`), and exposes the +required dependency-scope controls. Its documented default lifecycle binding is +`verify`; select and test the binding deliberately so both the current CI +`install` build and release build produce the file. + +## Task List + +- [x] Create and check out branch `feature/527_spdx_sbom`. + +### Requirements And Design + +- [x] Add `req~build.spdx-sbom~2` to `doc/spec/system_requirements.md`, covering + the product SBOM content, module granularity, required dependency scopes, + SPDX 3 JSON filename, and release availability; add scenarios for a + successful product build and a GitHub release asset. +- [x] Stop and ask the user for a review of the system requirements. +- [x] Add design items to `doc/spec/design.md` for product-module-only SPDX + generation and for publishing the generated SBOM with the release; map + each runtime design item to one scenario and require `impl` plus `itest` + coverage. +- [x] Record the selected generator version, output mode, lifecycle phase, + aggregation strategy, scope settings, and license metadata handling in + the design. +- [x] Stop and ask the user for a review of the design and approval to add the + external `org.spdx:spdx-maven-plugin`. + +### Implementation + +- [x] Add the approved SPDX Maven Plugin version and an execution to + `product/pom.xml`; configure `createSPDX`, SPDX 3 JSON-LD output, + `openfasttrace-${revision}.spdx3.json`, module granularity, and + compile/runtime/provided inclusion and test-scope exclusion. +- [x] Confirm the plugin generates only the product SBOM, contains the resolved + direct and transitive dependencies at module level, and preserves all + available declared license data; add narrowly scoped license overrides + only if the generated document demonstrates a required correction. +- [x] Add the SBOM as an explicit GitHub release asset in + `.github/workflows/github_release.sh`, alongside the JAR and `.sha256`. + +### Verification + +- [x] Add a Maven-build integration test that asserts exactly one product SBOM + exists with the versioned filename and verifies SPDX 3 JSON structure, + module-level dependency entries, the required scopes, and representative + available license fields. +- [x] Test the release script to prove it passes the SBOM path to + `gh release create`. +- [ ] Run `mvn -T 1C verify` and resolve all test, coverage, reproducibility, + static-analysis, and security-gate failures. +- [x] Run `./oft-self-trace.sh` and keep requirement, scenario, design, + implementation, and test coverage clean. + +### Update User Documentation + +- [x] Update `doc/developer_guide.md` to state where the SBOM is produced, how + to inspect it, and that release assets include it. Do not add end-user CLI + documentation because this introduces no runtime option. + +## Version And Changelog Update + +- [ ] During release preparation, update the version according to the release + policy and add the SPDX SBOM to `doc/changes/changes.md` and the matching + versioned changelog file. diff --git a/doc/developer_guide.md b/doc/developer_guide.md index ea69b416..9af0f26d 100644 --- a/doc/developer_guide.md +++ b/doc/developer_guide.md @@ -122,7 +122,7 @@ Build OFT: mvn -T 1C package -DskipTests ``` -This will build the executable JAR including all modules at `product/target/openfasttrace-$VERSION.jar`. +This will build the executable JAR including all modules at `product/target/openfasttrace-$VERSION.jar` and the SPDX 3 SBOM at `product/target/openfasttrace-$VERSION.spdx3.json`. #### Specify Java Version @@ -259,7 +259,8 @@ The build will use the last Git commit timestamp as timestamp for files in `.jar * Run command `gh workflow run release.yml --repo itsallcode/openfasttrace --ref main` * or go to [GitHub Actions](https://github.com/itsallcode/openfasttrace/actions/workflows/release.yml) and start the `release.yml` workflow on branch `main`. 2. Update the title and description of the newly created [GitHub release](https://github.com/itsallcode/openfasttrace/releases). -3. After some time the release will be available at [Maven Central](https://repo1.maven.org/maven2/org/itsallcode/openfasttrace/openfasttrace/). +3. Download the JAR, checksum, and SPDX 3 SBOM from the release assets. The SBOM file is named `openfasttrace-$VERSION.spdx3.json`. +4. After some time the release will be available at [Maven Central](https://repo1.maven.org/maven2/org/itsallcode/openfasttrace/openfasttrace/). ## Module Overview diff --git a/doc/spec/design.md b/doc/spec/design.md index b18a4e7f..ad4b4c61 100644 --- a/doc/spec/design.md +++ b/doc/spec/design.md @@ -844,6 +844,34 @@ Needs: impl, utest # Deployment View +# Cross-cutting Concerns + +## Build + +### Product SPDX SBOM Generation +`dsn~build.spdx-sbom-generation~2` + +The `product` module configures `org.spdx:spdx-maven-plugin` with the `createSPDX` goal. The execution runs during the Maven lifecycle and creates `product/target/openfasttrace-${revision}.spdx3.json`. It uses SPDX 3 JSON-LD, includes compile, runtime, and provided dependency scopes, and excludes test scope. License overwrites set the OpenFastTrace modules to `GPL-3.0-only`, because the plugin cannot resolve the inherited GPL URL unambiguously. + +Covers: + +* `req~build.spdx-sbom~2` +* `scn~build.spdx-sbom-generation~2` + +Needs: impl, itest + +### Product SPDX SBOM Release Asset +`dsn~build.spdx-sbom-release-asset~1` + +The GitHub release script passes `product/target/openfasttrace-${revision}.spdx3.json` to `gh release create` with the product JAR and its checksum. + +Covers: + +* `req~build.spdx-sbom~2` +* `scn~build.spdx-sbom-release-asset~1` + +Needs: impl, itest + # Concepts ## Data Structures diff --git a/doc/spec/system_requirements.md b/doc/spec/system_requirements.md index 35974a24..66894181 100644 --- a/doc/spec/system_requirements.md +++ b/doc/spec/system_requirements.md @@ -264,6 +264,39 @@ Needs: req ## Functional Requirements +### Product SPDX Software Bill Of Materials +`req~build.spdx-sbom~2` + +The Maven build creates one SPDX 3 JSON software bill of materials (SBOM) named `openfasttrace-.spdx3.json`. It lists dependencies at Maven module granularity, includes compile, runtime, and provided scopes, excludes test-scope dependencies, and retains available license metadata. GitHub releases include the SBOM as an asset. + +Needs: scn, dsn + +#### Generate Product SPDX SBOM +`scn~build.spdx-sbom-generation~2` + +**Given** a Maven build of an OpenFastTrace release version, +**when** it builds the product module, +**then** it creates a `openfasttrace-.spdx3.json` SPDX 3 SBOM for the product, including compile, runtime, and provided dependencies at module granularity, excluding test-scope dependencies, and retaining available dependency license information. + +Covers: + +* [req~build.spdx-sbom~2](#product-spdx-software-bill-of-materials) + +Needs: dsn + +#### Publish Product SPDX SBOM +`scn~build.spdx-sbom-release-asset~1` + +**Given** a successful OpenFastTrace release build with its product SBOM, +**when** the GitHub release is created, +**then** the release includes the generated `openfasttrace-.spdx3.json` as an asset alongside the product JAR and its checksum. + +Covers: + +* [req~build.spdx-sbom~2](#product-spdx-software-bill-of-materials) + +Needs: dsn + ### Anatomy of Specification Items #### Specification Item diff --git a/oft-self-trace.sh b/oft-self-trace.sh index a35633ef..90230eb6 100755 --- a/oft-self-trace.sh +++ b/oft-self-trace.sh @@ -30,6 +30,8 @@ if $oft_script trace \ "$base_dir/reporter/html/src" \ "$base_dir/reporter/aspec/src" \ "$base_dir/product/src/test/java" \ + "$base_dir/product/pom.xml" \ + "$base_dir/.github/workflows/github_release.sh" \ "$base_dir/api/src" \ "$base_dir/exporter/specobject/src" \ "$base_dir/exporter/common/src" \ diff --git a/parent/pom.xml b/parent/pom.xml index bb1d55ce..514beece 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -25,7 +25,7 @@ GNU General Public License v3.0 - https://www.gnu.org/licenses/gpl-3.0.html + https://spdx.org/licenses/GPL-3.0-or-later.html repo diff --git a/product/pom.xml b/product/pom.xml index 23d7f68f..895f522f 100644 --- a/product/pom.xml +++ b/product/pom.xml @@ -80,6 +80,142 @@ openfasttrace-${revision} + + + org.spdx + spdx-maven-plugin + 1.0.4 + + + create-product-spdx-sbom + package + + createSPDX + + + ${project.build.directory}/${project.build.finalName}.spdx3.json + JSON-LD + build + GPL-3.0-only + GPL-3.0-only + true + true + true + false + + + both + org.itsallcode.openfasttrace + openfasttrace + GPL-3.0-only + + + both + org.itsallcode.openfasttrace + openfasttrace-api + GPL-3.0-only + + + both + org.itsallcode.openfasttrace + openfasttrace-core + GPL-3.0-only + + + both + org.itsallcode.openfasttrace + openfasttrace-exporter-common + GPL-3.0-only + + + both + org.itsallcode.openfasttrace + openfasttrace-exporter-specobject + GPL-3.0-only + + + both + org.itsallcode.openfasttrace + openfasttrace-importer-gherkin + GPL-3.0-only + + + both + org.itsallcode.openfasttrace + openfasttrace-importer-lightweightmarkup + GPL-3.0-only + + + both + org.itsallcode.openfasttrace + openfasttrace-importer-markdown + GPL-3.0-only + + + both + org.itsallcode.openfasttrace + openfasttrace-importer-restructuredtext + GPL-3.0-only + + + both + org.itsallcode.openfasttrace + openfasttrace-importer-specobject + GPL-3.0-only + + + both + org.itsallcode.openfasttrace + openfasttrace-importer-tag + GPL-3.0-only + + + both + org.itsallcode.openfasttrace + openfasttrace-importer-tag-importer-common + GPL-3.0-only + + + both + org.itsallcode.openfasttrace + openfasttrace-importer-xmlparser + GPL-3.0-only + + + both + org.itsallcode.openfasttrace + openfasttrace-importer-zip + GPL-3.0-only + + + both + org.itsallcode.openfasttrace + openfasttrace-reporter-aspec + GPL-3.0-only + + + both + org.itsallcode.openfasttrace + openfasttrace-reporter-html + GPL-3.0-only + + + both + org.itsallcode.openfasttrace + openfasttrace-reporter-plaintext + GPL-3.0-only + + + both + org.itsallcode.openfasttrace + openfasttrace-testutil + GPL-3.0-only + + + + + + org.apache.maven.plugins maven-shade-plugin diff --git a/product/src/test/java/org/itsallcode/openfasttrace/ITestSelfTrace.java b/product/src/test/java/org/itsallcode/openfasttrace/ITestSelfTrace.java index bc29c24b..58695b0f 100644 --- a/product/src/test/java/org/itsallcode/openfasttrace/ITestSelfTrace.java +++ b/product/src/test/java/org/itsallcode/openfasttrace/ITestSelfTrace.java @@ -69,6 +69,8 @@ private ImportSettings buildOftSettings() final Path baseDir = getProjectBaseDir(); return ImportSettings.builder() // .addInputs(baseDir.resolve("doc/spec/")) // + .addInputs(baseDir.resolve("product/pom.xml")) // + .addInputs(baseDir.resolve(".github/workflows/github_release.sh")) // .addInputs(findInputDirectories(baseDir)) // .build(); } diff --git a/product/src/test/java/org/itsallcode/openfasttrace/SpdxSbomIT.java b/product/src/test/java/org/itsallcode/openfasttrace/SpdxSbomIT.java new file mode 100644 index 00000000..579a62e6 --- /dev/null +++ b/product/src/test/java/org/itsallcode/openfasttrace/SpdxSbomIT.java @@ -0,0 +1,51 @@ +package org.itsallcode.openfasttrace; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; +import static org.junit.jupiter.api.Assertions.assertAll; + +import java.io.IOException; +import java.nio.file.*; +import java.util.stream.Stream; + +import com.exasol.mavenprojectversiongetter.MavenProjectVersionGetter; +import org.junit.jupiter.api.Test; + +class SpdxSbomIT +{ + private static final Path PROJECT_ROOT = Path.of("..").toAbsolutePath(); + private static final String SPDX3_CONTEXT = "https://spdx.org/rdf/3.0.1/spdx-context.jsonld"; + private static final String GPL_3_ONLY = "http://spdx.org/licenses/GPL-3.0-only"; + private static final String VERSION = MavenProjectVersionGetter + .getProjectRevision(PROJECT_ROOT.resolve("parent/pom.xml")); + + @Test + // [itest->dsn~build.spdx-sbom-generation~2] + void testBuildCreatesSpdx3SbomWithDependencyAndLicenseData() throws IOException + { + final Path sbomFile = Path.of("target", "openfasttrace-" + VERSION + ".spdx3.json"); + assertThat(Files.exists(sbomFile), is(true)); + try (final Stream files = Files.list(sbomFile.getParent())) + { + assertThat(files.filter(path -> path.getFileName().toString().matches("openfasttrace-.*\\.spdx3\\.json")) + .count(), is(1L)); + } + final String sbom = Files.readString(sbomFile); + assertAll( + () -> assertThat(sbom, containsString("\"@context\" : \"" + SPDX3_CONTEXT + "\"")), + () -> assertThat(sbom, containsString("\"scope\" : \"runtime\"")), + () -> assertThat(sbom, not(containsString("\"scope\" : \"test\""))), + () -> assertThat(sbom, containsString("openfasttrace-api")), + () -> assertThat(sbom, not(containsString("Byte Buddy"))), + () -> assertThat(sbom, containsString(GPL_3_ONLY)), + () -> assertThat(sbom, containsString("http://spdx.org/licenses/"))); + } + + @Test + // [itest->dsn~build.spdx-sbom-release-asset~1] + void testReleaseScriptIncludesSpdxSbomAsset() throws IOException + { + final String releaseScript = Files.readString(PROJECT_ROOT.resolve(".github/workflows/github_release.sh")); + assertThat(releaseScript, containsString("\"$sbom_path\"")); + } +}