diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b009692..91ee65e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,3 +32,36 @@ jobs: with: name: unit-test-reports path: app/build/reports/ + + sbom: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: '17' + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Generate CycloneDX SBOM (foss release runtime deps) + run: ./gradlew cyclonedxBom --stacktrace + + - name: Upload SBOM + uses: actions/upload-artifact@v4 + with: + name: sbom + path: build/reports/cyclonedx/bom.json + + # Fails the build when the SBOM contains a package with a known + # vulnerability. Accepted findings can be suppressed in osv-scanner.toml. + - name: Scan SBOM with OSV-Scanner + uses: google/osv-scanner-action/osv-scanner-action@v2.5.1 + with: + scan-args: |- + scan + source + --lockfile=build/reports/cyclonedx/bom.json diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..a933ede --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,39 @@ +name: Release assets + +# Attaches a CycloneDX SBOM to every GitHub release once it is published. +# Releases are still created by hand (tag fdroid-release-vX.Y); this only +# adds the SBOM alongside the APK. +on: + release: + types: [published] + +permissions: + contents: write + +jobs: + sbom: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.release.tag_name }} + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: '17' + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Generate CycloneDX SBOM (foss release runtime deps) + run: ./gradlew cyclonedxBom --stacktrace + + - name: Attach SBOM to release + env: + GH_TOKEN: ${{ github.token }} + run: | + cp build/reports/cyclonedx/bom.json "lanshield-${{ github.event.release.tag_name }}.cdx.json" + gh release upload "${{ github.event.release.tag_name }}" \ + "lanshield-${{ github.event.release.tag_name }}.cdx.json" --clobber diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 7249a61..b06e0d4 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -168,6 +168,18 @@ ksp { arg("room.schemaLocation", "$projectDir/schemas") } +// CycloneDX SBOM of what actually ships in the FOSS release APK. +// Only the runtime classpath of that variant is scanned, so test libraries, +// annotation processors and debug-only tooling are left out. +tasks.cyclonedxDirectBom { + projectType = org.cyclonedx.model.Component.Type.APPLICATION + componentName = "LANShield" + componentVersion = android.defaultConfig.versionName + includeConfigs = listOf("fossReleaseRuntimeClasspath") + testConfigs = emptyList() + xmlOutput.convention(null as org.gradle.api.file.RegularFile?) +} + if (wantsPlayStoreBuild) { val firebaseConfig = layout.buildDirectory.file("firebase/firebase.gradle.kts").get().asFile firebaseConfig.parentFile.mkdirs() diff --git a/build.gradle.kts b/build.gradle.kts index 094a55e..c0789ec 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,7 +4,31 @@ plugins { alias(libs.plugins.compose.compiler) apply false id("com.google.devtools.ksp") version "2.3.6" apply false id("com.google.dagger.hilt.android") version "2.60.1" apply false + alias(libs.plugins.cyclonedx.bom) // id("com.google.gms.google-services") version "4.5.0" apply false // id("com.google.firebase.crashlytics") version "3.0.8" apply false -} \ No newline at end of file +} + +// Aggregate CycloneDX SBOM for the whole build: ./gradlew cyclonedxBom +// Output: build/reports/cyclonedx/bom.json +// The app module configures which configurations are scanned (see app/build.gradle.kts). +val appVersionName = provider { + project(":app").extensions + .getByType(com.android.build.api.dsl.ApplicationExtension::class.java) + .defaultConfig.versionName +} + +tasks.cyclonedxBom { + projectType = org.cyclonedx.model.Component.Type.APPLICATION + componentName = "LANShield" + componentVersion = appVersionName + xmlOutput.convention(null as org.gradle.api.file.RegularFile?) +} + +tasks.cyclonedxDirectBom { + projectType = org.cyclonedx.model.Component.Type.APPLICATION + componentName = "LANShield" + componentVersion = appVersionName + xmlOutput.convention(null as org.gradle.api.file.RegularFile?) +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0fb42ed..5c083f4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -34,6 +34,7 @@ uiautomator = "2.4.0" androidxTestRules = "1.7.0" testOrchestrator = "1.6.1" testServices = "1.6.0" +cyclonedx = "3.4.1" [libraries] accompanist-permissions = { module = "com.google.accompanist:accompanist-permissions", version.ref = "accompanistPermissions" } @@ -100,4 +101,5 @@ unit-test = [ [plugins] android-application = { id = "com.android.application", version.ref = "agp" } compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } +cyclonedx-bom = { id = "org.cyclonedx.bom", version.ref = "cyclonedx" }