Skip to content

Commit

Permalink
Merge pull request #4 from Liftric/feat/risk_score_check
Browse files Browse the repository at this point in the history
Add Risk Score & Generate Sbom
  • Loading branch information
nvima committed Jul 25, 2023
2 parents 6e595c1 + 9f10bf9 commit 8ab0c7c
Show file tree
Hide file tree
Showing 20 changed files with 214 additions and 13,832 deletions.
24 changes: 20 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
# Gradle Dependency Track Companion w Plugin

This Gradle plugin is designed to ease the process of working with [Dependency Track](https://dependencytrack.org/), a Continuous SBOM Analysis Platform. With this plugin, you can automate the upload process of SBOM files, generate Vex files for component or vulnerability suppression, and more.
This plugin internally applies the [CycloneDX Gradle plugin](https://github.com/CycloneDX/cyclonedx-gradle-plugin), so you don't need to manually include it in your project.

## Features

The plugin offers several tasks:

- `runDepTrackWorkflow`: Runs `generateSbom`, `uploadSbom`, `generateVex` and `uploadVex` tasks for CI/CD.
- `generateSbom`: Generates the SBOM (Runs "cyclonedxBom" from [cyclonedx-gradle-plugin](https://github.com/CycloneDX/cyclonedx-gradle-plugin) under the hood)
- `uploadSbom`: Uploads SBOM file.
- `generateVex`: Generates VEX file.
- `uploadVex`: Uploads VEX file.
- `getOutdatedDependencies`: Gets outdated dependencies.
- `getSuppressedVuln`: Gets suppressed vulnerabilities.
- `runDepTrackWorkflow`: Runs `uploadSbom`, `generateVex` and `uploadVex` tasks for CI/CD.
- `uploadSbom`: Uploads SBOM file.
- `uploadVex`: Uploads VEX file.

### Task Configuration

Expand All @@ -38,10 +40,18 @@ Each task requires certain inputs which are to be specified in your `build.gradl
- `outputFile`: *Optional* (Default "build/reports/vex.json")
- `uploadVex`: [Dependency Track VEX Upload API Reference](https://yoursky.blue/documentation-api/dependencytrack.html#tag/vex/operation/uploadVex)

#### riskScore

- `url`: Dependency Track API URL
- `apiKey`: Dependency Track API KEY
- `riskScore`: *Optional* - [Dependency Track Project Lookup API Reference](https://yoursky.blue/documentation-api/dependencytrack.html#tag/project/operation/getProjectByNameAndVersion)
- `timeout`: *Optional* - If specified, the task will wait for the risk score to be calculated. Default: 0 seconds
- `maxRiskScore`: *Optional* - If specified, the task will fail if the risk score is higher than the specified value.

#### runDepTrackWorkflow

- This task requires configuration for `uploadSbom`, `generateVex`, and `uploadVex`.
- Runs `uploadSbom`, `generateVex` and `uploadVex` tasks for CI/CD.
- Runs `uploadSbom`, `generateVex`, `uploadVex` and `riskScore` tasks for CI/CD.

#### getOutdatedDependencies

Expand Down Expand Up @@ -86,6 +96,12 @@ dependencyTrackCompanion {
projectName.set(name)
projectVersion.set(version)
}
riskScore{
projectName.set(name)
projectVersion.set(version)
timeout.set(20.seconds)
maxRiskScore.set(7.0)
}
vexComponent {
purl.set("pkg:maven/org.eclipse.jetty/jetty-http@9.4.49.v20220914?type=jar")
vulnerability {
Expand Down
3 changes: 2 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -79,14 +79,14 @@ gradlePlugin {
}
}
}

pluginBundle {
website = "https://github.com/Liftric/dependency-track-companion-plugin"
vcsUrl = "https://github.com/Liftric/dependency-track-companion-plugin"
description = "Common tasks for Dependency Track interaction, like SBOM upload or VEX Generation"
tags = listOf("dependency", "track", "sbom", "vex", "upload", "generate")
}


dependencies {
implementation(platform(libs.kotlinBom))
implementation(libs.kotlinStdlibJdk8)
Expand All @@ -98,6 +98,7 @@ dependencies {
implementation(libs.ktorClientContentNegotiation)
implementation(libs.ktorSerializationKotlinxJson)
implementation(libs.kotlinReflect)
implementation(libs.cyclonedxGradlePlugin)

testImplementation(libs.junitJupiter)

Expand Down
2 changes: 2 additions & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pluginManagement {
version("kotlin", "1.8.21")
version("ktor", "2.3.0")
version("cyclonedx-core-java", "7.3.2")
version("cyclonedx-gradle-plugin", "1.7.4")
version("junit-bom", "5.9.3")

plugin("versioning", "net.nemerosa.versioning").version("3.0.0")
Expand All @@ -17,6 +18,7 @@ pluginManagement {

library("kotlinStdlibJdk8", "org.jetbrains.kotlin", "kotlin-stdlib-jdk8").versionRef("kotlin")
library("cyclonedxCoreJava", "org.cyclonedx", "cyclonedx-core-java").versionRef("cyclonedx-core-java")
library("cyclonedxGradlePlugin", "org.cyclonedx", "cyclonedx-gradle-plugin").versionRef("cyclonedx-gradle-plugin")
library("kotlinBom", "org.jetbrains.kotlin", "kotlin-bom").versionRef("kotlin")
library("ktorClientCio", "io.ktor", "ktor-client-cio").versionRef("ktor")
library("ktorClientCore", "io.ktor", "ktor-client-core").versionRef("ktor")
Expand Down
34 changes: 6 additions & 28 deletions src/integrationTest/kotlin/com/liftric/dtcp/GenerateVexTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,31 +13,9 @@ import java.nio.file.Paths
import java.nio.file.StandardCopyOption
import org.cyclonedx.parsers.JsonParser

class GenerateVexTest {

class GenerateVexTest: IntegrationTestBase() {
@Test
fun testGenerateVexTask() {

val vexComponent = VexComponent(
purl = "pkg:maven/org.eclipse.jetty/jetty-http@9.4.49.v20220914?type=jar",
vulnerability = VexVulnerability(
id = "CVE-2023-26048",
source = "NVD",
analysis = "Vulnerability.Analysis.State.FALSE_POSITIVE",
analysisValue = "FALSE_POSITIVE",
detail = null,
)
)

val vexVulnerability = VexVulnerability(
id = "CVE-2020-8908",
source = "NVD",
analysis = "Vulnerability.Analysis.State.RESOLVED",
analysisValue = "RESOLVED",
detail = "This is resolved",
)


val projectDir = File("build/generateVexTest")

projectDir.mkdirs()
Expand All @@ -48,9 +26,14 @@ import com.liftric.dtcp.extensions.*
import org.cyclonedx.model.vulnerability.Vulnerability
plugins {
kotlin("jvm") version "1.8.21"
id("com.liftric.dependency-track-companion-plugin")
}
repositories {
mavenCentral()
}
group = "com.liftric.test"
version = "1.0.0"
Expand All @@ -73,11 +56,6 @@ dependencyTrackCompanion {
"""
)

val sourceJsonFile = Paths.get("test/data/bom.json")
val targetJsonFile = projectDir.toPath().resolve("build/reports/bom.json")
Files.createDirectories(targetJsonFile.parent)
Files.copy(sourceJsonFile, targetJsonFile, StandardCopyOption.REPLACE_EXISTING)

val result = GradleRunner
.create()
.withProjectDir(projectDir)
Expand Down
33 changes: 33 additions & 0 deletions src/integrationTest/kotlin/com/liftric/dtcp/IntegrationTestBase.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.liftric.dtcp

import com.liftric.dtcp.model.VexComponent
import com.liftric.dtcp.model.VexVulnerability

/**
* This base class provides common resources for integration tests.
* By using this base class, resources don't need to be recreated for each individual test,
* thus preventing duplication of code and unnecessary instantiation of resources in each test.
*/

abstract class IntegrationTestBase {
val dependencyTrackApiEndpoint = "http://localhost:8081"

val vexComponent = VexComponent(
purl = "pkg:maven/org.jetbrains.kotlin/kotlin-stdlib-jdk8@1.8.21?type=jar",
vulnerability = VexVulnerability(
id = "CVE-2023-26048",
source = "NVD",
analysis = "Vulnerability.Analysis.State.FALSE_POSITIVE",
analysisValue = "FALSE_POSITIVE",
detail = null,
)
)

val vexVulnerability = VexVulnerability(
id = "CVE-2020-8908",
source = "NVD",
analysis = "Vulnerability.Analysis.State.RESOLVED",
analysisValue = "RESOLVED",
detail = "This is resolved",
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@ import kotlinx.coroutines.runBlocking
* For more information about why this is necessary, refer to the ApiServiceIgnoreError class comments.
*/

internal const val dependencyTrackApiEndpoint = "http://localhost:8081"

class RunDepTrackWorkflowTest {
class RunDepTrackWorkflowTest: IntegrationTestBase() {
@Test
fun testRunDepTrackWorkflowTest() {
val projectName = "dtTest"
Expand All @@ -42,26 +40,6 @@ class RunDepTrackWorkflowTest {
).createProject(projectName)
}

val vexComponent = VexComponent(
purl = "pkg:maven/org.eclipse.jetty/jetty-http@9.4.49.v20220914?type=jar",
vulnerability = VexVulnerability(
id = "CVE-2023-26048",
source = "NVD",
analysis = "Vulnerability.Analysis.State.FALSE_POSITIVE",
analysisValue = "FALSE_POSITIVE",
detail = null,
)
)

val vexVulnerability = VexVulnerability(
id = "CVE-2020-8908",
source = "NVD",
analysis = "Vulnerability.Analysis.State.RESOLVED",
analysisValue = "RESOLVED",
detail = "This is resolved",
)


val projectDir = File("build/runDepTrackWorkflowTest")

projectDir.mkdirs()
Expand All @@ -72,9 +50,14 @@ import com.liftric.dtcp.extensions.*
import org.cyclonedx.model.vulnerability.Vulnerability
plugins {
kotlin("jvm") version "1.8.21"
id("com.liftric.dependency-track-companion-plugin")
}
repositories {
mavenCentral()
}
group = "com.liftric.$projectName"
version = "$version"
Expand Down Expand Up @@ -115,11 +98,6 @@ dependencyTrackCompanion {
"""
)

val sourceJsonFile = Paths.get("test/data/bom.json")
val targetJsonFile = projectDir.toPath().resolve("build/reports/bom.json")
Files.createDirectories(targetJsonFile.parent)
Files.copy(sourceJsonFile, targetJsonFile, StandardCopyOption.REPLACE_EXISTING)

val result = GradleRunner
.create()
.withProjectDir(projectDir)
Expand Down
21 changes: 20 additions & 1 deletion src/main/kotlin/com/liftric/dtcp/DepTrackCompanionPlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ internal const val taskGroup = "Dependency Track Companion Plugin"

class DepTrackCompanionPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.pluginManager.apply("org.cyclonedx.bom")
val extension =
project.extensions.create(extensionName, DepTrackCompanionExtension::class.java, project)

Expand All @@ -20,13 +21,20 @@ class DepTrackCompanionPlugin : Plugin<Project> {
project.layout.buildDirectory.file("reports/vex.json")
)

val generateSbom = project.tasks.register("generateSbom") { task ->
task.group = taskGroup
task.description = "Generate SBOM file"
task.dependsOn("cyclonedxBom")
}

val uploadSbom = project.tasks.register("uploadSbom", UploadSBOMTask::class.java) { task ->
task.group = taskGroup
task.description = "Uploads SBOM file"
task.url.set(extension.url)
task.apiKey.set(extension.apiKey)
task.inputFile.set(extension.inputFile)
task.uploadSBOM.set(extension.uploadSBOMData)
task.dependsOn(generateSbom)
}

val generateVex = project.tasks.register("generateVex", GenerateVexTask::class.java) { task ->
Expand All @@ -37,6 +45,7 @@ class DepTrackCompanionPlugin : Plugin<Project> {
task.vexComponent.set(extension.vexComponentList)
task.vexVulnerability.set(extension.vexVulnerabilityList)
task.mustRunAfter(uploadSbom)
task.dependsOn(generateSbom)
}

val uploadVex = project.tasks.register("uploadVex", UploadVexTask::class.java) { task ->
Expand All @@ -47,13 +56,23 @@ class DepTrackCompanionPlugin : Plugin<Project> {
task.url.set(extension.url)
task.uploadVex.set(extension.uploadVexData)
task.mustRunAfter(generateVex)
task.dependsOn(generateVex)
}

val riskScore = project.tasks.register("riskScore", RiskScoreTask::class.java) { task ->
task.group = taskGroup
task.description = "Get Risk Score"
task.apiKey.set(extension.apiKey)
task.url.set(extension.url)
task.riskScore.set(extension.riskScoreData)
task.mustRunAfter(uploadVex)
}

project.tasks.register("runDepTrackWorkflow") { task ->
task.group = taskGroup
task.description =
"Runs uploadSbom, generateVex and uploadVex for CI/CD"
task.dependsOn(uploadSbom, generateVex, uploadVex)
task.dependsOn(generateSbom, uploadSbom, generateVex, uploadVex, riskScore)
}

project.tasks.register("getOutdatedDependencies", GetOutdatedDependenciesTask::class.java) { task ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ abstract class DepTrackCompanionExtension(val project: Project) {
abstract val uploadSBOMData: Property<UploadSBOMBuilder>

abstract val uploadVexData: Property<UploadVexBuilder>
abstract val riskScoreData: Property<RiskScoreBuilder>
abstract val getOutdatedDependenciesData: Property<GetOutdatedDependenciesBuilder>
abstract val getSuppressedVulnData: Property<GetSuppressedVulnBuilder>

Expand All @@ -26,7 +27,6 @@ fun DepTrackCompanionExtension.vexComponent(action: VexComponentBuilder.() -> Un
vexComponentList.add(VexComponentBuilder(project).apply(action))
}


fun DepTrackCompanionExtension.vexVulnerability(action: VexVulnerabilityBuilder.() -> Unit) {
vexVulnerabilityList.add(VexVulnerabilityBuilder(project).apply(action))
}
Expand All @@ -35,11 +35,14 @@ fun DepTrackCompanionExtension.uploadSBOM(action: UploadSBOMBuilder.() -> Unit)
uploadSBOMData.set(UploadSBOMBuilder(project).apply(action))
}


fun DepTrackCompanionExtension.uploadVex(action: UploadVexBuilder.() -> Unit) {
uploadVexData.set(UploadVexBuilder(project).apply(action))
}

fun DepTrackCompanionExtension.riskScore(action: RiskScoreBuilder.() -> Unit) {
riskScoreData.set(RiskScoreBuilder(project).apply(action))
}

fun DepTrackCompanionExtension.getOutdatedDependencies(action: GetOutdatedDependenciesBuilder.() -> Unit) {
getOutdatedDependenciesData.set(GetOutdatedDependenciesBuilder(project).apply(action))
}
Expand Down
45 changes: 45 additions & 0 deletions src/main/kotlin/com/liftric/dtcp/extensions/RiskScore.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.liftric.dtcp.extensions

import org.gradle.api.Project
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.Optional
import kotlin.time.Duration
import kotlin.time.ExperimentalTime

data class RiskScore @OptIn(ExperimentalTime::class) constructor(
val projectName: String,
val projectVersion: String,
val maxRiskScore: Double?,
val timeout: Duration?,
)

@Suppress("MemberVisibilityCanBePrivate")
@ConfigDsl
class RiskScoreBuilder(@get:Internal val proj: Project) {
@get:Input
@get:Optional
val projectName: Property<String> = proj.objects.property(String::class.java)

@get:Input
@get:Optional
val projectVersion: Property<String> = proj.objects.property(String::class.java)

@get:Input
@get:Optional
val maxRiskScore: Property<Double> = proj.objects.property(Double::class.java)

@OptIn(ExperimentalTime::class)
@get:Input
@get:Optional
val timeout: Property<Duration> = proj.objects.property(Duration::class.java)

@OptIn(ExperimentalTime::class)
fun build(): RiskScore = RiskScore(
projectName = this.projectName.get(),
projectVersion = this.projectVersion.get(),
maxRiskScore = this.maxRiskScore.orNull,
timeout = this.timeout.orNull,
)
}
Loading

0 comments on commit 8ab0c7c

Please sign in to comment.