Skip to content

Commit

Permalink
Merge pull request #370 from gini/IPC-68-generate-SBOM
Browse files Browse the repository at this point in the history
Ipc 68 generate SBOM
  • Loading branch information
a-szotyori authored Nov 27, 2023
2 parents b5c2706 + e2b1034 commit 1ccde04
Show file tree
Hide file tree
Showing 13 changed files with 269 additions and 10 deletions.
65 changes: 65 additions & 0 deletions .github/workflows/generate-sboms.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: Generate SBOMs for all projects

# TODO: remove push trigger after merging to main
# TODO: add trigger to run after we have published a release
# TODO: use GitHub's dependency submission API to submit the SBOMs to GitHub's dependency graph:
# https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/using-the-dependency-submission-api
on:
push:
workflow_dispatch:

jobs:
generate-sbom:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v3

- name: setup java
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '17'
cache: 'gradle'

- name: generate a cyclonedx sbom for each project
run: >
./gradlew clean
core-api-library:library:cyclonedxBom
health-api-library:library:cyclonedxBom
bank-api-library:library:cyclonedxBom
health-sdk:sdk:cyclonedxBom
capture-sdk:sdk:cyclonedxBom
capture-sdk:default-network:cyclonedxBom
bank-sdk:sdk:cyclonedxBom
-PcreateSBOM=true
- name: validate cyclonedx sboms
shell: bash
run: |
curl -Lo cyclonedx https://github.com/CycloneDX/cyclonedx-cli/releases/download/v0.25.0/cyclonedx-linux-x64
chmod +x cyclonedx
./cyclonedx validate --input-file core-api-library/library/build/reports/gini-internal-core-api-lib-sbom.json
./cyclonedx validate --input-file health-api-library/library/build/reports/gini-health-api-lib-sbom.json
./cyclonedx validate --input-file bank-api-library/library/build/reports/gini-bank-api-lib-sbom.json
./cyclonedx validate --input-file health-sdk/sdk/build/reports/gini-health-sdk-sbom.json
./cyclonedx validate --input-file capture-sdk/sdk/build/reports/gini-capture-sdk-sbom.json
./cyclonedx validate --input-file capture-sdk/default-network/build/reports/gini-capture-sdk-default-network-sbom.json
./cyclonedx validate --input-file bank-sdk/sdk/build/reports/gini-bank-sdk-sbom.json
- name: zip the cyclonedx sboms
run: >
zip -rj sbom-jsons.zip
core-api-library/library/build/reports/gini-internal-core-api-lib-sbom.json
health-api-library/library/build/reports/gini-health-api-lib-sbom.json
bank-api-library/library/build/reports/gini-bank-api-lib-sbom.json
health-sdk/sdk/build/reports/gini-health-sdk-sbom.json
capture-sdk/sdk/build/reports/gini-capture-sdk-sbom.json
capture-sdk/default-network/build/reports/gini-capture-sdk-default-network-sbom.json
bank-sdk/sdk/build/reports/gini-bank-sdk-sbom.json
- name: archive the cyclonedx sboms
uses: actions/upload-artifact@v3
with:
name: CycloneDX SBOM JSONs
path: sbom-jsons.zip
9 changes: 8 additions & 1 deletion bank-api-library/library/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import net.gini.gradle.*
import net.gini.gradle.extensions.apiProjectDependencyForSBOM
import org.jetbrains.dokka.gradle.DokkaCollectorTask

plugins {
Expand Down Expand Up @@ -64,7 +65,12 @@ tasks.withType(type = org.jetbrains.kotlin.gradle.internal.KaptGenerateStubsTask
}

dependencies {
api(project(":core-api-library:library"))
val coreApiLibrary = project(":core-api-library:library")
if (properties["createSBOM"] == "true") {
apiProjectDependencyForSBOM(coreApiLibrary)
} else {
api(coreApiLibrary)
}

implementation(libs.androidx.core.ktx)
implementation(libs.kotlinx.coroutines.core)
Expand Down Expand Up @@ -96,6 +102,7 @@ apply<PublishToMavenPlugin>()
apply<DokkaPlugin>()
apply<CodeAnalysisPlugin>()
apply<PropertiesPlugin>()
apply<SBOMPlugin>()

tasks.getByName<DokkaCollectorTask>("dokkaHtmlSiblingCollector") {
this.moduleName.set("Gini Bank API Library for Android")
Expand Down
17 changes: 14 additions & 3 deletions bank-sdk/sdk/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import net.gini.gradle.*
import net.gini.gradle.extensions.apiProjectDependencyForSBOM
import org.jetbrains.dokka.gradle.DokkaCollectorTask

plugins {
Expand Down Expand Up @@ -90,9 +91,18 @@ tasks.withType(type = org.jetbrains.kotlin.gradle.internal.KaptGenerateStubsTask

dependencies {

api(project(":bank-api-library:library"))
api(project(":capture-sdk:sdk"))
api(project(":capture-sdk:default-network"))
val bankApiLibrary = project(":bank-api-library:library")
val captureSdk = project(":capture-sdk:sdk")
val captureSdkDefaultNetwork = project(":capture-sdk:default-network")
if (properties["createSBOM"] == "true") {
apiProjectDependencyForSBOM(bankApiLibrary)
apiProjectDependencyForSBOM(captureSdk)
apiProjectDependencyForSBOM(captureSdkDefaultNetwork)
} else {
api(bankApiLibrary)
api(captureSdk)
api(captureSdkDefaultNetwork)
}

implementation(libs.androidx.core.ktx)
implementation(libs.androidx.activity.ktx)
Expand Down Expand Up @@ -122,6 +132,7 @@ dependencies {
apply<PublishToMavenPlugin>()
apply<DokkaPlugin>()
apply<CodeAnalysisPlugin>()
apply<SBOMPlugin>()

tasks.getByName<DokkaCollectorTask>("dokkaHtmlSiblingCollector") {
this.moduleName.set("Gini Bank SDK for Android")
Expand Down
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ buildscript {
classpath(libs.jacocoAndroid)
classpath(libs.benManesVersions.gradle)
classpath(libs.hilt.plugin)
classpath(libs.cyclonedx.gradle)

// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle.kts files
Expand Down
3 changes: 3 additions & 0 deletions buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,7 @@ dependencies {
implementation(libs.ktlint.gradle)
implementation(libs.benManesVersions.gradle)
implementation(libs.java.poet)
implementation(libs.cyclonedx.gradle)
implementation(libs.cyclonedx.core.java)
implementation(libs.jsonJava)
}
113 changes: 113 additions & 0 deletions buildSrc/src/main/kotlin/net/gini/gradle/SBOMPlugin.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package net.gini.gradle

import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.getByName
import org.json.JSONArray
import org.json.JSONObject

/**
* Uses the CycloneDX Gradle plugin to generate an SBOM (Software Bill of Materials) for the project.
*
* Applies the necessary configuration and edits the generated SBOM to use the artifactId instead of the project name and "aar" type instead of "jar".
*/
class SBOMPlugin : Plugin<Project> {

override fun apply(target: Project) {
if (target.properties["createSBOM"] != "true") {
return
}

target.plugins.apply("org.cyclonedx.bom")

target.group = target.properties["groupId"] as String

target.tasks.getByName<org.cyclonedx.gradle.CycloneDxTask>("cyclonedxBom") {
setSchemaVersion("1.4")

setIncludeConfigs(listOf(
"releaseRuntimeClasspath",
"releaseCompileClasspath"
))

val outputName = "${project.properties["artifactId"]}-sbom"
setOutputName(outputName)

setOrganizationalEntity {
it.name = "Gini GmbH"
it.urls = listOf("https://gini.net")
}

doLast {
val artifactId = project.properties["artifactId"] as String
val projectPURL = "pkg:maven/${project.group}/$artifactId@${project.version}?type=aar"
val generatedPURL = "pkg:maven/${project.group}/${project.name}@${project.version}?type=jar"

val bomFile = project.file("build/reports/$outputName.json")
val bom = bomFile.readText()

val bomJson = JSONObject(bom)
val metadataJson = bomJson["metadata"] as JSONObject

fixComponentName(bomJson, artifactId)

fixPURLAndRefs(
bomJson = bomJson,
incorrectPURL = generatedPURL,
correctPURL = projectPURL)

renameManufactureToSupplier(metadataJson)

addAuthors(metadataJson)

bomFile.writeText(bomJson.toString(4))
}
}
}

private fun fixComponentName(bomJson: JSONObject, artifactId: String,) {
val metadataJson = bomJson["metadata"] as JSONObject
val componentJson = metadataJson["component"] as JSONObject
componentJson.put("name", artifactId)
}

private fun fixPURLAndRefs(
bomJson: JSONObject,
incorrectPURL: String,
correctPURL: String,
) {
val metadataJson = bomJson["metadata"] as JSONObject
val componentJson = metadataJson["component"] as JSONObject
componentJson.put("purl", correctPURL)
componentJson.put("bom-ref", correctPURL)

val dependenciesJson = bomJson["dependencies"] as JSONArray
dependenciesJson.forEach {
val dependencyJson = it as JSONObject
if (dependencyJson["ref"].toString() == incorrectPURL) {
dependencyJson.put("ref", correctPURL)
}
}
}

private fun renameManufactureToSupplier(metadataJson: JSONObject) {
val manufactureJson = metadataJson["manufacture"] as JSONObject
metadataJson.remove("manufacture")
metadataJson.put("supplier", manufactureJson)
}

private fun addAuthors(metadataJson: JSONObject) {
metadataJson.put(
"authors", JSONArray(
listOf(
JSONObject(
mapOf(
"name" to "Gini GmbH"
)
)
)
)
)
}

}
29 changes: 29 additions & 0 deletions buildSrc/src/main/kotlin/net/gini/gradle/extensions/Project.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package net.gini.gradle.extensions

import org.gradle.api.Project
import org.gradle.api.artifacts.ProjectDependency
import org.gradle.api.artifacts.VersionCatalog
import org.gradle.api.artifacts.VersionCatalogsExtension
import org.gradle.api.artifacts.dsl.DependencyHandler
import org.gradle.kotlin.dsl.DependencyHandlerScope
import org.gradle.kotlin.dsl.getByType
import org.gradle.kotlin.dsl.project

/**
* Created by Alpár Szotyori on 04.10.21.
Expand All @@ -29,3 +33,28 @@ internal fun Project.forEachAndroidProject(runAfterEvaluate: Boolean = false, ac

internal val Project.isAndroidProject: Boolean
get() = plugins.hasPlugin("com.android.library") || plugins.hasPlugin("com.android.application")

/**
* Adds a project dependency to the 'api' configuration via it's maven coordinates: group, artifactId and version.
*
* This is required for generating a CycloneDX SBOM.
*/
fun DependencyHandler.apiProjectDependencyForSBOM(project: ProjectDependency) {
projectDependencyForSBOM("api", project)
}

/**
* Adds a project dependency to the 'implementation' configuration via it's maven coordinates: group, artifactId and version.
*
* This is required for generating a CycloneDX SBOM.
*/
fun DependencyHandler.implementationProjectDependencyForSBOM(project: ProjectDependency) {
projectDependencyForSBOM("implementation", project)
}

private fun DependencyHandler.projectDependencyForSBOM(configurationName: String, project: ProjectDependency) {
val groupId = project.dependencyProject.properties["groupId"]
val artifactId = project.dependencyProject.properties["artifactId"]
val version = project.dependencyProject.version
add(configurationName, "$groupId:$artifactId:$version")
}
17 changes: 15 additions & 2 deletions capture-sdk/default-network/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import net.gini.gradle.*
import net.gini.gradle.extensions.apiProjectDependencyForSBOM
import net.gini.gradle.extensions.implementationProjectDependencyForSBOM
import org.jetbrains.dokka.gradle.DokkaCollectorTask

plugins {
Expand Down Expand Up @@ -65,9 +67,19 @@ tasks.withType(type = org.jetbrains.kotlin.gradle.internal.KaptGenerateStubsTask
dependencies {
api(libs.slf4j.api)

api(project(":bank-api-library:library"))
val bankApiLibrary = project(":bank-api-library:library")
if (properties["createSBOM"] == "true") {
apiProjectDependencyForSBOM(bankApiLibrary)
} else {
api(bankApiLibrary)
}

implementation(project(":capture-sdk:sdk"))
val captureSdk = project(":capture-sdk:sdk")
if (properties["createSBOM"] == "true") {
implementationProjectDependencyForSBOM(captureSdk)
} else {
implementation(captureSdk)
}

implementation(libs.androidx.annotation)
implementation(libs.kotlinx.coroutines.core)
Expand Down Expand Up @@ -96,6 +108,7 @@ dependencies {
apply<PublishToMavenPlugin>()
apply<CodeAnalysisPlugin>()
apply<DokkaPlugin>()
apply<SBOMPlugin>()

tasks.getByName<DokkaCollectorTask>("dokkaHtmlSiblingCollector") {
this.moduleName.set("Gini Capture SDK - Default Network Library for Android")
Expand Down
1 change: 1 addition & 0 deletions capture-sdk/sdk/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ dependencies {
apply<PublishToMavenPlugin>()
apply<DokkaPlugin>()
apply<CodeAnalysisPlugin>()
apply<SBOMPlugin>()

tasks.getByName<DokkaCollectorTask>("dokkaHtmlSiblingCollector") {
this.moduleName.set("Gini Capture SDK for Android")
Expand Down
1 change: 1 addition & 0 deletions core-api-library/library/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,4 @@ dependencies {
apply<PublishToMavenPlugin>()
apply<DokkaPlugin>()
apply<CodeAnalysisPlugin>()
apply<SBOMPlugin>()
4 changes: 3 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,6 @@ hilt-library = { module = "com.google.dagger:hilt-android", version.ref = "hilt"
hilt-compiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hilt" }
hilt-plugin = { module = "com.google.dagger:hilt-android-gradle-plugin", version.ref = "hilt" }
java-poet = "com.squareup:javapoet:1.13.0"

cyclonedx-gradle = "org.cyclonedx:cyclonedx-gradle-plugin:1.8.1"
cyclonedx-core-java = "org.cyclonedx:cyclonedx-core-java:8.0.3"
jsonJava = "org.json:json:20231013"
9 changes: 8 additions & 1 deletion health-api-library/library/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import net.gini.gradle.*
import net.gini.gradle.extensions.apiProjectDependencyForSBOM
import org.jetbrains.dokka.gradle.DokkaCollectorTask

plugins {
Expand Down Expand Up @@ -74,7 +75,12 @@ tasks.withType(type = org.jetbrains.kotlin.gradle.internal.KaptGenerateStubsTask
}

dependencies {
api(project(":core-api-library:library"))
val coreApiLibrary = project(":core-api-library:library")
if (properties["createSBOM"] == "true") {
apiProjectDependencyForSBOM(coreApiLibrary)
} else {
api(coreApiLibrary)
}

implementation(libs.androidx.core.ktx)
implementation(libs.kotlinx.coroutines.core)
Expand Down Expand Up @@ -105,6 +111,7 @@ apply<PublishToMavenPlugin>()
apply<DokkaPlugin>()
apply<CodeAnalysisPlugin>()
apply<PropertiesPlugin>()
apply<SBOMPlugin>()

tasks.getByName<DokkaCollectorTask>("dokkaHtmlSiblingCollector") {
this.moduleName.set("Gini Health API Library for Android")
Expand Down
Loading

0 comments on commit 1ccde04

Please sign in to comment.