From 01369d7caf3b69a5585b0175b8d63d558b7bba99 Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Tue, 18 Nov 2025 13:28:15 +0100 Subject: [PATCH] SOLR-17328: Add CycloneDX SBOMs to Solr binary distributions This change introduces two new tasks in the `:solr:packaging` module that generate accurate CycloneDX SBOMs for both the **full** and **slim** Solr binary distributions. Each SBOM is produced at build time using resolvable configurations that reflect the actual runtime dependencies included in the distribution. The resulting files are packaged as `bom.json` into the root of their respective `.tar.gz` archives. This ensures downstream users, security scanners, and compliance tooling have an authoritative SBOM directly embedded in each published artifact. Solves [SOLR-17328](https://issues.apache.org/jira/browse/SOLR-17328) --- build.gradle | 9 +++-- gradle/libs.versions.toml | 2 + solr/packaging/build.gradle | 73 ++++++++++++++++++++++++++++++++++--- solr/server/build.gradle | 35 ++++++++++++++++-- 4 files changed, 107 insertions(+), 12 deletions(-) diff --git a/build.gradle b/build.gradle index 50a618d6936..8a702517c0b 100644 --- a/build.gradle +++ b/build.gradle @@ -33,6 +33,7 @@ plugins { alias(libs.plugins.diffplug.spotless) apply false alias(libs.plugins.nodegradle.node) apply false alias(libs.plugins.openapi.generator) apply false + alias(libs.plugins.cyclonedx) apply false alias(libs.plugins.logchange) } @@ -54,10 +55,10 @@ version = { // On a release explicitly set release version in one go: // -Dversion.release=x.y.z - + // Jenkins can just set just a suffix, overriding SNAPSHOT, e.g. using build id: // -Dversion.suffix=jenkins123 - + String versionSuffix = propertyOrDefault('version.suffix', 'SNAPSHOT') String v = propertyOrDefault('version.release', "${baseVersion}-${versionSuffix}") if (v.endsWith("-")) { @@ -110,8 +111,8 @@ ext { logger.info("External tool '${name}' resolved to: ${resolved}") return resolved } - - luceneBaseVersionProvider = project.provider { + + luceneBaseVersionProvider = project.provider { def luceneVersion = libs.versions.apache.lucene.get() def m = (luceneVersion =~ /^\d+\.\d+\.\d+\b/) if (!m) { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index eaa9f3f3c49..9007bd30868 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -76,6 +76,7 @@ compose = "1.8.2" cutterslade-analyze = "1.10.0" cuvs-lucene = "25.10.0" cybozulabs-langdetect = "1.1-20120112" +cyclonedx = "3.0.2" decompose = "3.3.0" diffplug-spotless = "7.2.1" # @keep Use for dockerfile JRE version @@ -212,6 +213,7 @@ xerial-snappy = "1.1.10.8" benmanes-versions = { id = "com.github.ben-manes.versions", version.ref = "benmanes-versions" } compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } cutterslade-analyze = { id = "ca.cutterslade.analyze", version.ref = "cutterslade-analyze" } +cyclonedx = { id = "org.cyclonedx.bom", version.ref = "cyclonedx" } diffplug-spotless = { id = "com.diffplug.spotless", version.ref = "diffplug-spotless" } jetbrains-compose = { id = "org.jetbrains.compose", version.ref = "compose" } kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } diff --git a/solr/packaging/build.gradle b/solr/packaging/build.gradle index 33fd9197bb3..398ed22931e 100644 --- a/solr/packaging/build.gradle +++ b/solr/packaging/build.gradle @@ -15,19 +15,49 @@ * limitations under the License. */ -import org.apache.tools.ant.util.TeeOutputStream - // This project puts together a "distribution", assembling dependencies from // various other projects. plugins { id 'base' id 'distribution' + id 'com.github.node-gradle.node' version '7.1.0' +} + +import org.apache.tools.ant.util.TeeOutputStream +import com.github.gradle.node.npm.task.NpmTask +import org.cyclonedx.gradle.CyclonedxDirectTask + +tasks.register('cyclonedxFull', CyclonedxDirectTask) { + group = 'Bill of Materials' + description = 'Generates CycloneDX BOM for the full Solr distribution' + + includeConfigs = ['bomFull'] + + jsonOutput = cyclonedxDir.get().file("bom-full.json").asFile +} + +tasks.register('cyclonedxSlim', CyclonedxDirectTask) { + group = 'Bill of Materials' + description = 'Generates CycloneDX BOM for the full Solr distribution' + + includeConfigs = ['bomSlim'] + + jsonOutput = cyclonedxDir.get().file("bom-slim.json").asFile +} + +tasks.register('cyclonedx') { + group = 'Bill of Materials' + description = 'Generates CycloneDX BOMs for Solr distributions' + + dependsOn 'cyclonedxFull' + dependsOn 'cyclonedxSlim' } description = 'Solr distribution packaging' ext { + cyclonedxDir = layout.buildDirectory.dir("cyclonedx") distDir = file("$buildDir/solr-${version}") slimDistDir = file("$buildDir/solr-${version}-slim") devDir = file("$buildDir/dev") @@ -45,17 +75,34 @@ configurations { solrSlimTgz solrFullTgzSignature solrSlimTgzSignature + // For the CycloneDX BOM generation + bomSlim { + canBeResolved = true + canBeConsumed = false + } + bomFull { + canBeResolved = true + canBeConsumed = false + extendsFrom bomSlim + } } dependencies { project(":solr:modules").childProjects.values().stream().map(project -> project.path).each { module -> modules project(path: module, configuration: "packaging") + bomFull project(path: module, configuration: 'runtimeLibs') } crossDcManager project(path: ":solr:cross-dc-manager", configuration: "packaging") + bomFull project(path: ':solr:cross-dc-manager', configuration: 'runtimeLibs') example project(path: ":solr:example", configuration: "packaging") server project(path: ":solr:server", configuration: "packaging") + bomSlim project(path: ':solr:server', configuration: 'startJar') + bomSlim project(path: ':solr:server', configuration: 'runtimeLibs') + bomSlim project(path: ':solr:server', configuration: 'libExt') + bomSlim project(path: ':solr:server', configuration: 'solrCore') + bomSlim project(path: ':solr:server', configuration: 'webapp') // Copy files from documentation output docs project(path: ':solr:documentation', configuration: 'minimalSite') @@ -118,6 +165,12 @@ distributions { } }) + // Include CycloneDX BOM + from(cyclonedxDir) { + include 'bom-slim.json' + rename 'bom-slim.json', 'bom.json' + } + // Manually correct posix permissions (matters when packaging on Windows). filesMatching([ "**/*.sh", @@ -126,7 +179,6 @@ distributions { ]) { copy -> copy.setMode(0755) } - } } full { @@ -139,6 +191,13 @@ distributions { into "modules" }) + // Include CycloneDX BOM + from(cyclonedxDir) { + include 'bom-full.json' + rename 'bom-full.json', 'bom.json' + duplicatesStrategy = DuplicatesStrategy.INCLUDE + } + from(configurations.crossDcManager, { into "cross-dc-manager" filesMatching([ @@ -152,16 +211,18 @@ distributions { } installFullDist { + dependsOn 'cyclonedx' into distDir } installSlimDist { + dependsOn 'cyclonedx' into slimDistDir } assembleDist { - dependsOn tasks.assembleFullDist - dependsOn tasks.assembleSlimDist + dependsOn('assembleFullDist') + dependsOn('assembleSlimDist') } installDist { @@ -194,10 +255,12 @@ task dev { } fullDistTar { + dependsOn 'cyclonedx' compression = Compression.GZIP } slimDistTar { + dependsOn 'cyclonedx' compression = Compression.GZIP } diff --git a/solr/server/build.gradle b/solr/server/build.gradle index 132f92ec0f8..eccd6496ecd 100644 --- a/solr/server/build.gradle +++ b/solr/server/build.gradle @@ -25,14 +25,43 @@ javadoc.enabled(false) compileJava.enabled(false) configurations { - libExt + // === Custom configurations used to assemble the Solr server binary distribution === + + // 1. Jetty Bootstrap JAR + // Output Path: server/start.jar + // Description: Contains the Jetty bootstrap JAR responsible for launching the Solr server. + startJar + + // 2. Server Libraries + // Output Path: server/lib/ + // Description: Contains core libraries required by the Solr server at runtime (mostly Jetty-related JARs). serverLib + + // 3. Extended Server Libraries + // Output Path: server/lib/ext/ + // Description: Includes optional runtime libraries such as logging (SLF4J, Log4j) and metrics (Dropwizard, etc.). + libExt + + // 4. Solr Core JAR + // Output Path: server/solr-webapp/webapp/WEB-INF/lib/ + // Description: Contains the solr-core JAR, which includes the core functionality and indexing logic of Solr. solrCore + + // 5. Solr Web Application Libraries + // Output Path: server/solr-webapp/webapp/WEB-INF/lib/ + // Description: Contains the remaining Solr modules, packaged in exploded WAR format for deployment via Jetty. + webapp + + // === Runtime Configuration === + + // Combines core runtime dependencies for launching the Solr server, + // aggregating required libraries from serverLib, libExt, and solrCore. runtimeClasspath { extendsFrom serverLib, libExt, solrCore } - startJar - webapp + + // Internal configuration used by packaging tasks (e.g., creating distributions). + // This configuration only includes the `packagingDir` folder generated during assembly packaging }