spring-projects / spring-boot

Spring Boot
https://spring.io/projects/spring-boot
Apache License 2.0
74.83k stars 40.61k forks source link

No SBOM generated in a multi projects Gradle build #42601

Closed fmunch closed 2 hours ago

fmunch commented 3 hours ago

In a multi projects Gradle build the CycloneDX SBOM is not generated and not added to the boot JAR.

Sample project: https://github.com/fmunch/spring-boot-gradle-cyclonedx (our actual project is a bit more complicated, I tried to keep the bare minimum)

gradle bootJar does not generate a boot JAR containing the SBOM. When looking at the task tree for bootJar the cyclonedxBom task is not present:

> Task :tiTree
:server:bootJar                         (org.springframework.boot.gradle.tasks.bundling.BootJar)
+--- :server:classes                    (org.gradle.api.DefaultTask)
|    +--- :server:compileJava           (org.gradle.api.tasks.compile.JavaCompile)
|    `--- :server:processResources      (org.gradle.language.jvm.tasks.ProcessResources)
+--- :server:compileJava                (org.gradle.api.tasks.compile.JavaCompile)
`--- :server:resolveMainClassName       (org.springframework.boot.gradle.plugin.ResolveMainClassName)
     +--- :server:classes               (org.gradle.api.DefaultTask)
     |    +--- :server:compileJava      (org.gradle.api.tasks.compile.JavaCompile)
     |    `--- :server:processResources (org.gradle.language.jvm.tasks.ProcessResources)
     `--- :server:compileJava           (org.gradle.api.tasks.compile.JavaCompile)

> Task :server:tiTree
:server:bootJar                         (org.springframework.boot.gradle.tasks.bundling.BootJar)
+--- :server:classes                    (org.gradle.api.DefaultTask)
|    +--- :server:compileJava           (org.gradle.api.tasks.compile.JavaCompile)
|    `--- :server:processResources      (org.gradle.language.jvm.tasks.ProcessResources)
+--- :server:compileJava                (org.gradle.api.tasks.compile.JavaCompile)
`--- :server:resolveMainClassName       (org.springframework.boot.gradle.plugin.ResolveMainClassName)
     +--- :server:classes               (org.gradle.api.DefaultTask)
     |    +--- :server:compileJava      (org.gradle.api.tasks.compile.JavaCompile)
     |    `--- :server:processResources (org.gradle.language.jvm.tasks.ProcessResources)
     `--- :server:compileJava           (org.gradle.api.tasks.compile.JavaCompile)

When debugging org.springframework.boot.gradle.plugin.SpringBootPlugin it seems that CycloneDxPlugin is not in the classpath, preventing CycloneDxPluginActionfrom configuring CycloneDxTask.

Even if we add the following, cyclonedxBom appears in the task tree and generates server/build/reports/bom.* but CycloneDxPlugin is still missing from the classpath and the SBOM is missing in the JAR and manifest.

tasks.named('bootJar') {
    dependsOn cyclonedxBom
}
wilkinsona commented 2 hours ago

Thanks for the minimal reproducer.

The problem's due to the structure of your project and the effect that has on the class loaders that Gradle creates. With Spring Boot's plugin being a dependency of buildSrc and the CycloneDX plugin being a plugin dependency of a project, they're loaded by different class loaders. This means that the Spring Boot plugin cannot load the CycloneDX plugin's classes which prevents it from being able to react to the plugin being applied. Unfortunately, there's nothing that Spring Boot can do about this as it's Gradle that controls the class loaders and their hierarchy.

You can fix the problem by adding a dependency on the CycloneDX plugin to buildSrc. The plugin still has to be applied in the server project but you can remove the version and Gradle will automatically use the version from buildSrc:

diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle
index 652faac..d5fd2b8 100644
--- a/buildSrc/build.gradle
+++ b/buildSrc/build.gradle
@@ -5,6 +5,7 @@ plugins {

 repositories {
     mavenCentral()
+    gradlePluginPortal()
 }

 dependencies {
@@ -14,4 +15,5 @@ dependencies {

     implementation 'org.springframework.boot:spring-boot-gradle-plugin:3.3.4'
     implementation 'io.spring.gradle:dependency-management-plugin:1.1.6'
+    implementation 'org.cyclonedx:cyclonedx-gradle-plugin:1.10.0'
 }
diff --git a/server/build.gradle b/server/build.gradle
index 3990375..486d825 100644
--- a/server/build.gradle
+++ b/server/build.gradle
@@ -1,6 +1,6 @@
 plugins {
     id 'custom.spring'
-    id 'org.cyclonedx.bom' version '1.10.0'
+    id 'org.cyclonedx.bom'
 }

With these changes in place, the cyclonedxBom task will be run when executing bootJar:

$ ./gradlew bootJar --console=plain 
> Task :buildSrc:extractPluginRequests UP-TO-DATE
> Task :buildSrc:generatePluginAdapters UP-TO-DATE
> Task :buildSrc:compileJava UP-TO-DATE
> Task :buildSrc:compileGroovy NO-SOURCE
> Task :buildSrc:compileGroovyPlugins UP-TO-DATE
> Task :buildSrc:pluginDescriptors UP-TO-DATE
> Task :buildSrc:processResources UP-TO-DATE
> Task :buildSrc:classes UP-TO-DATE
> Task :buildSrc:jar UP-TO-DATE
> Task :server:compileJava
> Task :server:cyclonedxBom
> Task :server:processResources
> Task :server:classes
> Task :server:resolveMainClassName
> Task :server:bootJar

BUILD SUCCESSFUL in 2s
12 actionable tasks: 5 executed, 7 up-to-date

And the bom's included in the jar:

$ unzip -l server/build/libs/server-0.0.1-SNAPSHOT.jar | grep cdx 
    61768  10-11-2024 14:53   META-INF/sbom/application.cdx.json