Skip to content

Commit 67ac20b

Browse files
committed
#1260 Improves/fixes Trivy Stage by being able to use severity thresholds while keeping report if scan failed
1 parent 3c66695 commit 67ac20b

File tree

6 files changed

+147
-127
lines changed

6 files changed

+147
-127
lines changed

docs/modules/jenkins-shared-library/partials/odsComponentStageScanWithTrivy.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,6 @@ _String_
8080

8181
| *scanners* +
8282
_String_
83-
|Comma-separated list of what security issues to detect. Defaults to `vuln,config,secret,license`.
83+
|Comma-separated list of what security issues to detect. Defaults to `vuln,misconfig,secret,license`.
8484

8585
|===

src/org/ods/component/ScanWithTrivyOptions.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class ScanWithTrivyOptions extends Options {
1414
String format
1515

1616
/**
17-
* Comma-separated list of what security issues to detect. Defaults to `vuln,config,secret,license`. */
17+
* Comma-separated list of what security issues to detect. Defaults to `vuln,misconfig,secret,license`. */
1818
String scanners
1919

2020
/**

src/org/ods/component/ScanWithTrivyStage.groovy

Lines changed: 38 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ class ScanWithTrivyStage extends Stage {
2727
if (!config.resourceName) {
2828
config.resourceName = context.componentId
2929
}
30-
// check format
3130
if (!config.format) {
3231
config.format = 'cyclonedx'
3332
}
@@ -57,59 +56,31 @@ class ScanWithTrivyStage extends Stage {
5756
}
5857

5958
protected run() {
60-
String errorMessages = ''
61-
int returnCode = scanViaCli(options.scanners, options.pkgType, options.format,
62-
options.additionalFlags, options.reportFile, options.nexusDataBaseRepository,
63-
openShift.getApplicationDomain())
64-
if ([TrivyService.TRIVY_SUCCESS].contains(returnCode)) {
65-
try {
66-
URI reportUriNexus = archiveReportInNexus(options.reportFile, options.nexusReportRepository)
67-
createBitbucketCodeInsightReport(options.nexusReportRepository ? reportUriNexus.toString() : null,
68-
returnCode, errorMessages)
69-
archiveReportInJenkins(!context.triggeredByOrchestrationPipeline, options.reportFile)
70-
} catch (err) {
71-
logger.warn("Error archiving the Trivy reports due to: ${err}")
72-
}
73-
} else {
74-
errorMessages += "<li>Error executing Trivy CLI</li>"
75-
createBitbucketCodeInsightReport(errorMessages)
76-
}
77-
return
78-
}
79-
80-
@SuppressWarnings('ParameterCount')
81-
private int scanViaCli(String scanners, String pkgType, String format,
82-
List<String> additionalFlags, String reportFile, String nexusDataBaseRepository, String openshiftAppDomain) {
83-
logger.startClocked(options.resourceName)
84-
String flags = ""
85-
additionalFlags.each { flag ->
86-
flags += " " + flag
87-
}
88-
int returnCode = trivy.scanViaCli(scanners, pkgType, format, flags, reportFile,
89-
nexusDataBaseRepository, openshiftAppDomain)
90-
switch (returnCode) {
91-
case TrivyService.TRIVY_SUCCESS:
92-
logger.info "Finished scan via Trivy CLI successfully!"
93-
break
94-
case TrivyService.TRIVY_OPERATIONAL_ERROR:
95-
logger.info "An error occurred in processing the scan request " +
96-
"(e.g. invalid command line options, operational error)."
97-
break
98-
default:
99-
logger.info "An unknown return code was returned: ${returnCode}"
59+
String flags = options.additionalFlags.collect { " $it" }.join("")
60+
int returnCode = trivy.scan(
61+
options.resourceName,
62+
options.scanners,
63+
options.pkgType,
64+
options.format,
65+
flags,
66+
options.reportFile,
67+
options.nexusDataBaseRepository,
68+
openShift.getApplicationDomain()
69+
)
70+
try {
71+
URI reportUriNexus = archiveReportInNexus()
72+
createBitbucketCodeInsightReport(reportUriNexus?.toString(), returnCode)
73+
archiveReportInJenkins(!context.triggeredByOrchestrationPipeline, options.reportFile)
74+
} catch (Exception err) {
75+
logger.warn("Error archiving the Trivy reports due to: ${err}")
76+
createBitbucketCodeInsightErrorReport("<li>Error archiving Trivy report: ${err.message}</li>")
10077
}
101-
logger.infoClocked(options.resourceName, "Trivy scan (via CLI)")
102-
return returnCode
10378
}
10479

105-
@SuppressWarnings('ParameterCount')
106-
private createBitbucketCodeInsightReport(String nexusUrlReport, int returnCode, String messages) {
107-
String title = "Trivy Security"
108-
String details = "Please visit the following link to review the Trivy Security scan report:"
109-
String result = returnCode == 0 ? "PASS" : "FAIL"
80+
private void createBitbucketCodeInsightReport(String nexusUrlReport, int returnCode) {
11081
def data = [
11182
key: BITBUCKET_TRIVY_REPORT_KEY,
112-
title: title,
83+
title: "Trivy Security",
11384
link: nexusUrlReport,
11485
otherLinks: [
11586
[
@@ -118,65 +89,44 @@ class ScanWithTrivyStage extends Stage {
11889
link: nexusUrlReport
11990
]
12091
],
121-
details: details,
122-
result: result,
92+
details: "Please visit the following link to review the Trivy Security scan report:",
93+
result: returnCode == TrivyService.TRIVY_SUCCESS ? "PASS" : "FAIL",
12394
]
124-
125-
if (messages) {
126-
data.put("messages", [
127-
[ title: "Messages", value: prepareMessageToBitbucket(messages), ]
128-
])
129-
}
130-
13195
bitbucket.createCodeInsightReport(data, context.repoName, context.gitCommit)
13296
}
13397

134-
private createBitbucketCodeInsightReport(String messages) {
135-
String title = "Trivy Security"
136-
String details = "There was some problems with Trivy:"
137-
138-
String result = "FAIL"
139-
98+
private void createBitbucketCodeInsightErrorReport(String errorMessages) {
14099
def data = [
141100
key: BITBUCKET_TRIVY_REPORT_KEY,
142-
title: title,
101+
title: "Trivy Security",
143102
messages: [
144103
[
145104
title: "Messages",
146-
value: prepareMessageToBitbucket(messages)
105+
value: prepareMessageToBitbucket(errorMessages)
147106
]
148107
],
149-
details: details,
150-
result: result,
108+
details: "There was some problems with Trivy:",
109+
result: "FAIL",
151110
]
152-
153111
bitbucket.createCodeInsightReport(data, context.repoName, context.gitCommit)
154112
}
155113

156-
private String prepareMessageToBitbucket(String message = "") {
114+
private static String prepareMessageToBitbucket(String message = "") {
157115
return message?.replaceAll("<li>", "")?.replaceAll("</li>", ". ")
158116
}
159117

160-
@SuppressWarnings('ReturnNullFromCatchBlock')
161-
private URI archiveReportInNexus(String reportFile, nexusReportRepository) {
162-
try {
163-
URI report = nexus.storeArtifact(
164-
"${nexusReportRepository}",
165-
"${context.projectId}/${this.options.resourceName}/" +
166-
"${new Date().format('yyyy-MM-dd')}-${context.buildNumber}/trivy",
167-
"${reportFile}",
168-
(steps.readFile(file: reportFile) as String).bytes, "json")
169-
170-
logger.info "Report stored in: ${report}"
171-
172-
return report
173-
} catch (err) {
174-
logger.warn("Error archiving the Trivy reports in Nexus due to: ${err}")
175-
return null
176-
}
118+
private URI archiveReportInNexus() {
119+
URI report = nexus.storeArtifact(
120+
options.nexusReportRepository,
121+
"${context.projectId}/${options.resourceName}/" +
122+
"${new Date().format('yyyy-MM-dd')}-${context.buildNumber}/trivy",
123+
options.reportFile,
124+
(steps.readFile(file: options.reportFile) as String).bytes, "json")
125+
logger.info "Report stored in: ${report}"
126+
return report
177127
}
178128

179-
private archiveReportInJenkins(boolean archive, String reportFile) {
129+
private void archiveReportInJenkins(boolean archive, String reportFile) {
180130
String targetReport = "SCSR-${context.projectId}-${context.componentId}-${reportFile}"
181131
steps.sh(
182132
label: 'Create artifacts dir',
@@ -191,7 +141,6 @@ class ScanWithTrivyStage extends Stage {
191141
}
192142
String trivyScanStashPath = "scsr-report-${context.componentId}-${context.buildNumber}"
193143
context.addArtifactURI('trivyScanStashPath', trivyScanStashPath)
194-
195144
steps.stash(
196145
name: "${trivyScanStashPath}",
197146
includes: 'artifacts/SCSR*',

src/org/ods/services/TrivyService.groovy

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import org.ods.util.IPipelineSteps
88
class TrivyService {
99

1010
static final int TRIVY_SUCCESS = 0
11-
static final int TRIVY_OPERATIONAL_ERROR = 1
11+
static final int TRIVY_FAIL = 1
1212

1313
private final IPipelineSteps steps
1414
private final ILogger logger
@@ -19,11 +19,11 @@ class TrivyService {
1919
}
2020

2121
@SuppressWarnings('ParameterCount')
22-
int scanViaCli(String scanners, String pkgType, String format, String flags,
23-
String reportFile, String nexusRepository, String openshiftDomain ) {
22+
int scan(String resourceName, String scanners, String pkgType, String format, String flags,
23+
String reportFile, String nexusRepository, String openshiftDomain) {
24+
logger.startClocked(resourceName)
2425
logger.info "Starting to scan via Trivy CLI..."
25-
int status = TRIVY_SUCCESS
26-
status = steps.sh(
26+
int returnCode = steps.sh(
2727
label: 'Scan via Trivy CLI',
2828
returnStatus: true,
2929
script: """
@@ -53,8 +53,22 @@ class TrivyService {
5353
set -e
5454
"""
5555
)
56-
57-
return status
56+
switch (returnCode) {
57+
case TRIVY_SUCCESS:
58+
logger.info "Finished scan via Trivy CLI successfully!"
59+
break
60+
case TRIVY_FAIL:
61+
logger.info(
62+
"An error occurred while processing the Trivy scan request " +
63+
"(e.g. invalid command line options, operational error, or " +
64+
"severity threshold exceeded when using the --exit-code flag)."
65+
)
66+
break
67+
default:
68+
logger.info "An unknown return code was returned: ${returnCode}"
69+
}
70+
logger.infoClocked(resourceName, "Trivy scan (via CLI)")
71+
return returnCode
5872
}
5973

6074
}

test/groovy/org/ods/component/ScanWithTrivyStageSpec.groovy

Lines changed: 42 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import org.ods.services.TrivyService
44
import org.ods.services.BitbucketService
55
import org.ods.services.NexusService
66
import org.ods.services.OpenShiftService
7-
import org.ods.services.ServiceRegistry
87
import org.ods.util.Logger
98
import vars.test_helper.PipelineSpockTestBase
109
import util.PipelineSteps
@@ -32,6 +31,7 @@ class ScanWithTrivyStageSpec extends PipelineSpockTestBase {
3231
logger))
3332
def nexus = Spy(new NexusService ("http://nexus", script, "foo-cd-cd-user-with-password"))
3433
def openShift = Spy(new OpenShiftService (script, logger))
34+
openShift.getApplicationDomain() >> "openshift-domain.com"
3535
def stage = new ScanWithTrivyStage(
3636
script,
3737
context,
@@ -105,7 +105,7 @@ class ScanWithTrivyStageSpec extends PipelineSpockTestBase {
105105
def stage = createStage()
106106

107107
when:
108-
stage.archiveReportInNexus("trivy-sbom.json", "leva-documentation")
108+
stage.archiveReportInNexus()
109109

110110
then:
111111
1 * stage.script.readFile([file: "trivy-sbom.json"]) >> "Cool report"
@@ -133,7 +133,7 @@ class ScanWithTrivyStageSpec extends PipelineSpockTestBase {
133133
]
134134

135135
when:
136-
stage.createBitbucketCodeInsightReport("http://nexus", 0, null)
136+
stage.createBitbucketCodeInsightReport("http://nexus", 0)
137137

138138
then:
139139
1 * stage.bitbucket.createCodeInsightReport(data, stage.context.repoName, stage.context.gitCommit)
@@ -158,13 +158,13 @@ class ScanWithTrivyStageSpec extends PipelineSpockTestBase {
158158
]
159159

160160
when:
161-
stage.createBitbucketCodeInsightReport("http://nexus", 1, null)
161+
stage.createBitbucketCodeInsightReport("http://nexus", 1)
162162

163163
then:
164164
1 * stage.bitbucket.createCodeInsightReport(data, stage.context.repoName, stage.context.gitCommit)
165165
}
166166

167-
def "create Bitbucket Insight report - Messages"() {
167+
def "create Bitbucket Insight report - Error Messages"() {
168168
given:
169169
def stage = createStage()
170170
def data = [
@@ -181,7 +181,7 @@ class ScanWithTrivyStageSpec extends PipelineSpockTestBase {
181181
]
182182

183183
when:
184-
stage.createBitbucketCodeInsightReport('Message')
184+
stage.createBitbucketCodeInsightErrorReport('Message')
185185

186186
then:
187187
1 * stage.bitbucket.createCodeInsightReport(data, stage.context.repoName, stage.context.gitCommit)
@@ -192,12 +192,18 @@ class ScanWithTrivyStageSpec extends PipelineSpockTestBase {
192192
def stage = createStage()
193193

194194
when:
195-
def result = stage.scanViaCli("vuln,config,secret,license", "os,library", "cyclonedx",
196-
[], "trivy-sbom.json", "docker-group-ods", "openshift-domain.com")
195+
def result = stage.trivy.scan("component1", "vuln,misconfig,secret,license", "os,library",
196+
"cyclonedx", "", "trivy-sbom.json", "docker-group-ods", "openshift-domain.com")
197197

198198
then:
199-
1 * stage.trivy.scanViaCli("vuln,config,secret,license", "os,library",
200-
"cyclonedx", "", "trivy-sbom.json", "docker-group-ods", "openshift-domain.com") >> TrivyService.TRIVY_SUCCESS
199+
1 * stage.script.sh(_) >> {
200+
assert it.label == ['Scan via Trivy CLI']
201+
return TrivyService.TRIVY_SUCCESS
202+
}
203+
1 * stage.script.sh(_) >> {
204+
assert it.label == ['Read SBOM with Trivy CLI']
205+
return 0
206+
}
201207
1 * stage.logger.info("Finished scan via Trivy CLI successfully!")
202208
TrivyService.TRIVY_SUCCESS == result
203209
}
@@ -207,28 +213,43 @@ class ScanWithTrivyStageSpec extends PipelineSpockTestBase {
207213
def stage = createStage()
208214

209215
when:
210-
def result = stage.scanViaCli("vuln,config,secret,license", "os,library", "cyclonedx",
211-
[], "trivy-sbom.json", "docker-group-ods", "openshift-domain.com")
216+
def result = stage.trivy.scan("component1", "vuln,misconfig,secret,license", "os,library",
217+
"cyclonedx", "", "trivy-sbom.json", "docker-group-ods", "openshift-domain.com")
212218

213219
then:
214-
1 * stage.trivy.scanViaCli("vuln,config,secret,license", "os,library",
215-
"cyclonedx", "", "trivy-sbom.json", "docker-group-ods", "openshift-domain.com") >> TrivyService.TRIVY_OPERATIONAL_ERROR
216-
1 * stage.logger.info("An error occurred in processing the scan request " +
217-
"(e.g. invalid command line options, operational error).")
218-
TrivyService.TRIVY_OPERATIONAL_ERROR == result
220+
1 * stage.script.sh(_) >> {
221+
assert it.label == ['Scan via Trivy CLI']
222+
return TrivyService.TRIVY_FAIL
223+
}
224+
1 * stage.script.sh(_) >> {
225+
assert it.label == ['Read SBOM with Trivy CLI']
226+
return 0
227+
}
228+
1 * stage.logger.info(
229+
"An error occurred while processing the Trivy scan request " +
230+
"(e.g. invalid command line options, operational error, or " +
231+
"severity threshold exceeded when using the --exit-code flag)."
232+
)
233+
TrivyService.TRIVY_FAIL == result
219234
}
220235

221236
def "scan with CLI - Error"() {
222237
given:
223238
def stage = createStage()
224239

225240
when:
226-
def result = stage.scanViaCli("vuln,config,secret,license", "os,library", "cyclonedx",
227-
[], "trivy-sbom.json", "docker-group-ods", "openshift-domain.com")
241+
def result = stage.trivy.scan("component1", "vuln,misconfig,secret,license", "os,library",
242+
"cyclonedx", "", "trivy-sbom.json", "docker-group-ods", "openshift-domain.com")
228243

229244
then:
230-
1 * stage.trivy.scanViaCli("vuln,config,secret,license", "os,library",
231-
"cyclonedx", "", "trivy-sbom.json", "docker-group-ods", "openshift-domain.com") >> 127
245+
1 * stage.script.sh(_) >> {
246+
assert it.label == ['Scan via Trivy CLI']
247+
return 127
248+
}
249+
1 * stage.script.sh(_) >> {
250+
assert it.label == ['Read SBOM with Trivy CLI']
251+
return 0
252+
}
232253
1 * stage.logger.info("An unknown return code was returned: 127")
233254
127 == result
234255
}

0 commit comments

Comments
 (0)