Skip to content

Commit

Permalink
#136 - refactor saveFormattedTrivyReport
Browse files Browse the repository at this point in the history
  • Loading branch information
meiserloh committed Dec 20, 2024
1 parent 2bf5665 commit 60eb8f1
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 28 deletions.
55 changes: 28 additions & 27 deletions src/com/cloudogu/ces/cesbuildlib/Trivy.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class Trivy implements Serializable {
private String trivyImage
private String trivyDirectory = "trivy"

Trivy(script, String trivyVersion = "0.57.1", String trivyImage = "aquasec/trivy", Docker docker = new Docker(script)) {
Trivy(script, String trivyVersion = DEFAULT_TRIVY_VERSION, String trivyImage = DEFAULT_TRIVY_IMAGE, Docker docker = new Docker(script)) {
this.script = script
this.trivyVersion = trivyVersion
this.trivyImage = trivyImage
Expand Down Expand Up @@ -94,39 +94,37 @@ class Trivy implements Serializable {
) {
String image = script.sh(script: "jq .Image ${doguDir}/dogu.json", returnStdout: true).trim()
String version = script.sh(script: "jq .Version ${doguDir}/dogu.json", returnStdout: true).trim()
return scanImage(image+":"+version, severityLevel, strategy, additionalFlags, trivyReportFile)
return scanImage(image + ":" + version, severityLevel, strategy, additionalFlags, trivyReportFile)
}

/**
* Save the Trivy scan results as a file with a specific format
*
* @param format The format of the output file (@see TrivyScanFormat)
* @param format The format of the output file {@link TrivyScanFormat}.
* You may enter supported formats (sarif, cyclonedx, spdx, spdx-json, github, cosign-vuln, table or json)
* or your own template ("template --template @FILENAME").
* If you want to convert to a format that requires a list of packages, such as SBOM, you need to add
* the `--list-all-pkgs` flag to the {@link Trivy#scanImage} call, when outputting in JSON
* (See <a href="https://trivy.dev/latest/docs/configuration/reporting/?ref=anaisurl.com#converting">trivy docs</a>).
* @param severity Severities of security issues to be added (taken from UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL)
* @param formattedTrivyReportFilename The file name your report files should get, without file extension. E.g. "ubuntu24report"
* @param formattedTrivyReportFilename The file name your report files should get, with file extension. E.g. "ubuntu24report.html"
* @param trivyReportFile The "trivyReportFile" parameter you used in the "scanImage" function, if it was set
*/
void saveFormattedTrivyReport(String format = TrivyScanFormat.HTML, String severity = "UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL", String formattedTrivyReportFilename = "formattedTrivyReport.txt", String trivyReportFile = "trivy/trivyReport.json") {
void saveFormattedTrivyReport(String format = TrivyScanFormat.HTML,
String severity = "UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL",
String formattedTrivyReportFilename = null,
String trivyReportFile = "trivy/trivyReport.json") {

// set default report filename depending on the chosen format
if (formattedTrivyReportFilename == null) {
formattedTrivyReportFilename = "formattedTrivyReport" + getFileExtension(format)
}

String formatString
String defaultSeverityLevels = "UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL"
String defaultFilename = "formattedTrivyReport.txt"
switch (format) {
// TrivyScanFormat.JSON and TrivyScanFormat.TABLE are handled by the default case, too
case TrivyScanFormat.HTML:
formatString = "template --template \"@/contrib/html.tpl\""
if (formattedTrivyReportFilename == defaultFilename) {
formattedTrivyReportFilename == "formattedTrivyReport.html"
}
break
case TrivyScanFormat.JSON:
formatString = "json"
if (formattedTrivyReportFilename == defaultFilename) {
formattedTrivyReportFilename == "formattedTrivyReport.json"
}
break
case TrivyScanFormat.TABLE:
formatString = "table"
if (formattedTrivyReportFilename == defaultFilename) {
formattedTrivyReportFilename == "formattedTrivyReport.table"
}
break
default:
// You may enter supported formats (sarif, cyclonedx, spdx, spdx-json, github, cosign-vuln, table or json)
Expand All @@ -135,7 +133,7 @@ class Trivy implements Serializable {
// Check if "format" is a custom template from a file
boolean isTemplateFormat = format ==~ /^template --template @\S+$/
// Check if "format" is one of the trivyFormats or a template
if (trivyFormats.any { format.contains(it) } || isTemplateFormat) {
if (trivyFormats.any { (format == it) } || isTemplateFormat) {
formatString = format
break
} else {
Expand All @@ -144,15 +142,18 @@ class Trivy implements Serializable {
}
}
// Validate severity input parameter to prevent injection of additional parameters
if (severity != defaultSeverityLevels) {
if (!severity.split(',').every { it.trim() in ["UNKNOWN", "LOW", "MEDIUM", "HIGH", "CRITICAL"] }) {
script.error("The severity levels provided ($severity) do not match the applicable levels (UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL).")
}
if (!severity.split(',').every { it.trim() in ["UNKNOWN", "LOW", "MEDIUM", "HIGH", "CRITICAL"] }) {
script.error("The severity levels provided ($severity) do not match the applicable levels (UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL).")
}

docker.image("${trivyImage}:${trivyVersion}")
.inside("-v ${script.env.WORKSPACE}/.trivy/.cache:/root/.cache/") {
script.sh(script: "trivy convert --format ${formatString} --severity ${severity} --output ${trivyDirectory}/${formattedTrivyReportFilename} ${trivyReportFile}")
}
script.archiveArtifacts artifacts: "${trivyDirectory}/${formattedTrivyReportFilename}.*", allowEmptyArchive: true
}

private static String getFileExtension(String format) {
return TrivyScanFormat.isStandardScanFormat(format) ? "." + format : ".txt"
}
}
4 changes: 4 additions & 0 deletions src/com/cloudogu/ces/cesbuildlib/TrivyScanFormat.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,8 @@ class TrivyScanFormat {
* Output as table.
*/
static String TABLE = "table"

static boolean isStandardScanFormat(String format) {
return format == HTML || format == JSON || format == TABLE
}
}
39 changes: 38 additions & 1 deletion test/com/cloudogu/ces/cesbuildlib/TrivyTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class TrivyTest extends GroovyTestCase {
// emulate trivy call with local trivy installation and check that it has the same behavior
Files.createDirectories(trivyDir)
Process process = trivyExec.exec(Trivy.DEFAULT_TRIVY_VERSION, trivyArguments, workDir)
if(process.waitFor(2, TimeUnit.MINUTES)) {
if (process.waitFor(2, TimeUnit.MINUTES)) {
assertEquals(expectedStatusCode, process.exitValue())
} else {
process.destroyForcibly()
Expand Down Expand Up @@ -120,4 +120,41 @@ class TrivyTest extends GroovyTestCase {
}
assertTrue(gotException)
}

void testSaveFormattedTrivyReport() {
ScriptMock scriptMock = mockSaveFormattedTrivyReport(
"template --template \"@/contrib/html.tpl\"",
"UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL",
"trivy/formattedTrivyReport.html")

println(scriptMock.archivedArtifacts)
assertFalse(true)
}

ScriptMock mockSaveFormattedTrivyReport(String expectedFormat, String expectedSeverity, String expectedOutput) {
String trivyArguments = "convert --format ${expectedFormat} --severity ${expectedSeverity} --output ${expectedOutput} trivy/trivyReport.json"
String expectedTrivyCommand = "trivy $trivyArguments"

def scriptMock = new ScriptMock()
scriptMock.env.WORKSPACE = "/test"
Docker dockerMock = mock(Docker.class)
Docker.Image imageMock = mock(Docker.Image.class)
when(dockerMock.image(trivyImage)).thenReturn(imageMock)
when(imageMock.inside(matches("-v /test/.trivy/.cache:/root/.cache/"), any())).thenAnswer(new Answer<Integer>() {
@Override
Integer answer(InvocationOnMock invocation) throws Throwable {
// mock "sh trivy" so that it returns the expected status code and check trivy arguments
Closure closure = invocation.getArgument(1)
scriptMock.expectedShRetValueForScript.put(expectedTrivyCommand, 0)
closure.call()
assertEquals(expectedTrivyCommand, scriptMock.getActualShMapArgs().getLast())
println(scriptMock.getActualShMapArgs().getLast())
return 0
}
})
Trivy trivy = new Trivy(scriptMock, Trivy.DEFAULT_TRIVY_VERSION, Trivy.DEFAULT_TRIVY_IMAGE, dockerMock)
trivy.saveFormattedTrivyReport()

return scriptMock
}
}

0 comments on commit 60eb8f1

Please sign in to comment.