Skip to content

SOLR-17328: Add CycloneDX SBOMs to Solr binary distributions - #4690

Open
ppkarwasz wants to merge 1 commit into
apache:mainfrom
vex-generation-toolset:feat/sboms_main
Open

SOLR-17328: Add CycloneDX SBOMs to Solr binary distributions#4690
ppkarwasz wants to merge 1 commit into
apache:mainfrom
vex-generation-toolset:feat/sboms_main

Conversation

@ppkarwasz

Copy link
Copy Markdown
Member

https://issues.apache.org/jira/browse/SOLR-17328

Generates precise SBOM for the Solr binary distributions (solr-<version>.tgz and solr-<version>-slim.tgz) that include all the information
available today to the build tool:

  1. Maven artifacts included in the distribution by :solr:packaging,
  2. Vendored JavaScript libraries in solr/webapp/web/libs,
  3. NPM libraries bundled by :solr:webapp:js-client, inserted as nested assemblies of solr-js-client.
  4. Maven artifacts compiled into :solr:ui, inserted as nested assemblies of solr-ui.

The components for 1 and 4 are generated by the CycloneDX Gradle Plugin, while components from 3 are generated by @cyclonedx/cyclonedx-npm.
Vendored libraries, of course, are handled as a static list that maintainers need to keep up to date.

This is the second attempt after #3929.

Known unknowns

This PR does not attempt to manually detect the components embedded in JARs through shading or binary library inclusion.
An incomplete list of artifacts with such hidden dependencies is:

com.fasterxml.woodstox:woodstox-core:7.2.1
com.ibm.icu:icu4j:78.3
io.netty:netty-common:4.2.15.Final
io.netty:netty-tcnative-boringssl-static:2.0.79.Final
io.netty:netty-transport-native-epoll:4.2.15.Final
io.opentelemetry:opentelemetry-sdk-trace:1.56.0
org.apache.curator:curator-client:5.9.0

(The Hadoop artifacts and org.tallison.xmp:xmpcore-shaded from earlier iterations of this list no longer ship in the distribution.)

SBOM location

Each archive contains its SBOM as bom.json in the root of the distribution, next to LICENSE.txt and NOTICE.txt (see this comment on the previous PR for the full discussion):

  • The SBOM travels with the artifact, so it cannot become detached or lost. This is loosely inspired by PEP 770, which standardizes the same idea for Python wheels.
  • bom.json is one of the recognized CycloneDX filename patterns, so tools can discover it without configuration.
  • This is a Build SBOM in CISA's classification: it records the dependency information available to Gradle at build time. An Analyzed SBOM produced from the finished archive by tools such as syft or cdxgen remains a useful, complementary verification step.

There are other publication models: a solr-<version>.tgz.cdx.json sidecar next to the .asc and .sha512 files on downloads.apache.org, or embedding the SBOM in an in-toto attestation bundle together with SLSA provenance.

SBOM postprocessing

The raw output of the CycloneDX Gradle Plugin and @cyclonedx/cyclonedx-npm is postprocessed this way:

  1. Metadata: the build lifecycle phase is recorded, and the post-processing and cyclonedx-npm are listed in the tools next to the plugin.
  2. Main component: the binary release is identified by a draft sid purl (purl-spec issue #516), pkg:sid/apache.org/solr/solr@<version>?edition=full|slim, and by the Solr CPE used by the NVD.
  3. Removed components: Maven BOM/platform dependencies and the internal :platform project are stripped, they are not part of the distribution.
  4. Solr components: valid Maven purls (the CycloneDX Gradle Plugin generates incorrect Maven coordinates), description and Apache-2.0 license.
  5. Vendored JavaScript libraries: the third-party files checked into solr/webapp/web/libs are added.
  6. JavaScript client bundle: the npm packages bundled by browserify into server/solr-webapp/webapp/libs/solr/index.js are nested as subassemblies of a first-party solr-js-client component.
  7. New UI bundle: the Maven artifacts compiled into server/solr-webapp/webapp/ui are nested as subassemblies of a first-party solr-ui component, from a child SBOM generated in :solr:ui over wasmJsRuntimeClasspath, together with the @js-joda/core npm package bundled by the Kotlin toolchain.
  8. Archive locations: the assembled distribution directories are scanned, and files are matched to components by SHA-256 hash, recorded as evidence.occurrences. This also proves that each vendored JavaScript library ships unmodified.
  9. Hashes: only the SHA-256 hash of each component is kept; the plugin emits eight algorithms per artifact, which only adds bulk. The npm integrity hashes on externalReferences (SHA-512 of the registry tarballs) are preserved.

This change was developed with significant use of an AI coding assistant (Claude), with human review of every step; commits carry an Assisted-By trailer.

Checklist

Please review the following and check all that apply:

  • I have reviewed the guidelines for How to Contribute and my code conforms to the standards described there to the best of my ability.
  • I have created a Jira issue and added the issue ID to my pull request title.
  • I have given Solr maintainers access to contribute to my PR branch. (optional but recommended, not available for branches on forks living under an organisation)
  • I have developed this patch against the main branch.
  • I have run ./gradlew precommit; the full ./gradlew check test suite was not run for this build-only change.
  • I have verified the change by generating both SBOMs and validating them (graph integrity at every nesting level, occurrence coverage, SHA-256 hashes, opt-out builds, up-to-date re-runs); there is no unit-testable production code.
  • I have added documentation for the Reference Guide (the shipped bom.json could be documented in a follow-up)
  • I have added a changelog entry for my change

Each binary distribution (full and slim) now ships a CycloneDX 1.6
bom.json describing its actual contents. Two resolvable configurations
in :solr:packaging (bomFull, bomSlim) mirror the distribution assembly
and are rendered by the CycloneDX Gradle plugin, then a post-processing
step adjusts the result:

* The metadata declares the "build" lifecycle phase (a CISA Build SBOM)
  and lists the post-processing and cyclonedx-npm next to the plugin in
  the tools.
* The main component identifies the binary release with a draft "sid"
  purl (purl-spec issue apache#516, pkg:sid/apache.org/solr/solr@<version>
  with an edition qualifier) and the Solr CPE used by the NVD.
* Maven BOM/platform dependencies and the internal :platform project,
  which are not part of the distribution, are stripped.
* Solr project components get valid Maven purls (artifactId instead of
  the Gradle project name, with the ASF snapshots repository for
  snapshot builds), a description and the Apache-2.0 license.
* The UI artifacts inside the webapp are covered: the npm packages
  bundled by browserify into the OpenAPI JS client and the Maven
  artifacts compiled into the wasmJs UI are nested as subassemblies of
  first-party solr-js-client and solr-ui components, from child SBOMs
  generated in :solr:webapp:js-client (official cyclonedx-npm tool,
  runtime dependencies only) and :solr:ui (plugin task over
  wasmJsRuntimeClasspath). The vendored JavaScript libraries of the
  AngularJS admin UI are listed from a curated list.
* The location of every JAR and JavaScript file in the distribution is
  recorded as evidence.occurrences by matching SHA-256 hashes against
  the assembled directories, which also proves the vendored libraries
  ship unmodified.
* Only the SHA-256 hash of each component is kept; the plugin emits
  eight algorithms per artifact, which only adds bulk.

The BOM configurations resolve strictly with the JVM runtime attributes
(the packaging project applies jvm-ecosystem for the attribute schema),
so BOM-managed and variant-aware dependencies resolve like a runtime
classpath instead of silently disappearing. bomFull resolves
consistently with bomSlim, matching the distribution layout where
server libraries win over module-pulled versions. The child SBOMs
degrade gracefully when the UI projects are disabled with
-PdisableJsClient / -PdisableUiModule.

Assisted-By: Claude Fable 5 <noreply@anthropic.com>

@epugh epugh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels like there is a lot of boilerplate-ish comments. Do we need all of them? Maybe the more verbose pattern is something we want to adopt?

Comment thread gradle/libs.versions.toml
cuvs-java = "26.06.0"
cuvs-lucene = "25.12.0"
cyclonedx = "3.0.2"
# @keep npm tool generating the SBOM of the OpenAPI JS client, installed by :solr:webapp:js-client

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does @keep mean? Do we need this comment? the -npm suffix seems clear.

id 'jvm-ecosystem'
}

final APACHE_SNAPSHOTS_QUALIFIER = '&repository_url=https:%2F%2Frepository.apache.org%2Fcontent%2Fgroups%2Fsnapshots%2F'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this block of code seems like a lot, should it go in it's own .gradle file? There is a lot of docs about how this works in here, and I guess I don't totally know if it's needed. It would seem like a sbom.gradle might be a better home?

// 5. Vendored JavaScript libraries
// Entries without a version marker in the file get no version and no purl
def vendoredJsLibs = [
[file: 'angular.min.js', name: 'angular', version: '1.8.0', license: 'MIT'],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i don't love the hard coding here... CAn we consult other files like the licenses etc? Yes, most of these won't ever be updated at this point.. but what happens if they are bumped?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants