Skip to content

Commit 2d5e672

Browse files
authored
feat: add fail on severity threshold (#183)
1 parent 65bd009 commit 2d5e672

File tree

6 files changed

+131
-14
lines changed

6 files changed

+131
-14
lines changed

docs/trivyv2.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,15 @@ For more information about creating the connected service, see [Configuring Aqua
3535

3636
### Scan Options
3737

38-
| Input | Type | Defaults | Description |
39-
| ------------------ | -------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------- |
40-
| `type` | pickList | | The type of scan to perform. Options: `filesystem`, `image`, `repository`. |
41-
| `target` | string | | The specified target will be scanned using the selected scan type. |
42-
| `scanners` | pickList | | Choose which scanners to run. Options: `license`, `misconfig`, `secret`, `vuln`. Multi-select is supported. |
43-
| `severities` | pickList | | Severities of security issues to be displayed. Options: `UNKNOWN`, `LOW`, `MEDIUM`, `HIGH`, `CRITICAL`. Multi-select is supported. |
44-
| `ignoreUnfixed` | boolean | false | Include only fixed vulnerabilities. |
45-
| `ignoreScanErrors` | boolean | false | Ignore scan errors and continue the pipeline with a `SucceededWithIssues` result. |
38+
| Input | Type | Defaults | Description |
39+
| ------------------------- | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
40+
| `type` | pickList | | The type of scan to perform. Options: `filesystem`, `image`, `repository`. |
41+
| `target` | string | | The specified target will be scanned using the selected scan type. |
42+
| `scanners` | pickList | | Choose which scanners to run. Options: `license`, `misconfig`, `secret`, `vuln`. Multi-select is supported. |
43+
| `severities` | pickList | | Severities of security issues to be displayed. Options: `UNKNOWN`, `LOW`, `MEDIUM`, `HIGH`, `CRITICAL`. Multi-select is supported. |
44+
| `ignoreUnfixed` | boolean | false | Include only fixed vulnerabilities. |
45+
| `failOnSeverityThreshold` | pickList | | Set a threshold for failing the task based on the highest severity level found during the scan. If set, the task will fail if any issue with a severity equal to or higher than this level is found. Options: `UNKNOWN`, `LOW`, `MEDIUM`, `HIGH`, `CRITICAL`. |
46+
| `ignoreScanErrors` | boolean | false | Ignore scan errors and continue the pipeline with a `SucceededWithIssues` result. |
4647

4748
---
4849

trivy-task/trivyV1/task.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
"author": "Aqua Security",
1111
"version": {
1212
"Major": 1,
13-
"Minor": 16,
14-
"Patch": 0
13+
"Minor": 18,
14+
"Patch": 2
1515
},
1616
"instanceNameFormat": "Echo trivy $(version)",
1717
"groups": [

trivy-task/trivyV2/index.ts

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { randomUUID } from 'crypto';
55
import { createRunner, tmpPath } from './runner';
66
import { generateReports } from './reports';
77
import { getTaskInputs, TaskInputs } from './inputs';
8+
import { getHighestSeverityLevel, severityLevels } from './utils';
89

910
const randomPrefix = randomUUID();
1011
const resultsFileName = `trivy-results-${randomPrefix}.json`;
@@ -57,6 +58,7 @@ async function run() {
5758
task.debug('Running Trivy...');
5859

5960
const result = runner.execSync({ env });
61+
6062
checkScanResult(result.code, inputs);
6163

6264
if (inputs.hasAquaAccount && task.exist(assuranceFilePath)) {
@@ -87,13 +89,59 @@ function configureScan(runner: ToolRunner, inputs: TaskInputs) {
8789
runner.arg(inputs.target);
8890
}
8991

92+
function highestSeverityBreached(
93+
inputs: TaskInputs,
94+
resultsFilePath: string
95+
): boolean {
96+
if (!inputs.failOnSeverityThreshold) {
97+
return false;
98+
}
99+
task.debug(`Fail on severity threshold: ${inputs.failOnSeverityThreshold}`);
100+
101+
const severityThreshold = inputs.failOnSeverityThreshold.toUpperCase();
102+
const highestFoundSeverity = getHighestSeverityLevel(resultsFilePath);
103+
104+
const severityThresholdIndex = severityLevels.indexOf(severityThreshold);
105+
const highestIndex = severityLevels.indexOf(highestFoundSeverity);
106+
107+
task.debug(
108+
`Highest severity found: ${highestFoundSeverity} (index: ${highestIndex})`
109+
);
110+
task.debug(
111+
`Severity threshold: ${severityThreshold} (index: ${severityThresholdIndex})`
112+
);
113+
114+
return (
115+
severityThresholdIndex >= 0 &&
116+
highestIndex >= 0 &&
117+
highestIndex >= severityThresholdIndex
118+
);
119+
}
120+
90121
function checkScanResult(exitCode: number, inputs: TaskInputs) {
122+
task.debug(`Trivy scan completed with exit code: ${exitCode}`);
123+
91124
if (exitCode === 0) {
92125
task.setResult(task.TaskResult.Succeeded, 'No issues found.');
93-
} else if (exitCode === 2 && inputs.ignoreScanErrors) {
126+
return;
127+
}
128+
129+
const isHighestSeverityBreached = highestSeverityBreached(
130+
inputs,
131+
resultsFilePath
132+
);
133+
134+
task.debug(`Highest severity breached: ${isHighestSeverityBreached}`);
135+
if (exitCode === 2 && inputs.ignoreScanErrors && isHighestSeverityBreached) {
94136
task.setResult(task.TaskResult.SucceededWithIssues, 'Issues found.');
95-
} else if (exitCode === 2 && !inputs.ignoreScanErrors) {
137+
} else if (
138+
exitCode === 2 &&
139+
!inputs.ignoreScanErrors &&
140+
isHighestSeverityBreached
141+
) {
96142
task.setResult(task.TaskResult.Failed, 'Issues found.');
143+
} else if (exitCode === 2 && !highestSeverityBreached) {
144+
task.setResult(task.TaskResult.Succeeded, 'No issues found.');
97145
} else {
98146
task.setResult(task.TaskResult.Failed, 'Trivy runner error.', true);
99147
}

trivy-task/trivyV2/inputs.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export type TaskInputs = {
1515
ignoreUnfixed: boolean;
1616
showSuppressed: boolean;
1717
ignoreScanErrors: boolean;
18+
failOnSeverityThreshold: string;
1819
hasAquaAccount: boolean;
1920
aquaKey?: string;
2021
aquaSecret?: string;
@@ -45,6 +46,8 @@ export function getTaskInputs(): TaskInputs {
4546
ignoreUnfixed: task.getBoolInput('ignoreUnfixed', false),
4647
showSuppressed: task.getBoolInput('showSuppressed', false),
4748
ignoreScanErrors: task.getBoolInput('ignoreScanErrors', false),
49+
failOnSeverityThreshold:
50+
task.getInput('failOnSeverityThreshold', false) ?? '',
4851
reports: task.getDelimitedInput('reports', ',').map((s) => s.trim()),
4952
publish: task.getBoolInput('publish', false),
5053
templates: task.getInput('templates', false),

trivy-task/trivyV2/task.json

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
"author": "Aqua Security",
1111
"version": {
1212
"Major": 2,
13-
"Minor": 4,
14-
"Patch": 0
13+
"Minor": 5,
14+
"Patch": 2
1515
},
1616
"instanceNameFormat": "Echo trivy $(version)",
1717
"groups": [
@@ -171,6 +171,26 @@
171171
"required": false,
172172
"helpMarkDown": "Ignore scan errors and continue the pipeline with `SucceededWithIssues` result."
173173
},
174+
{
175+
"groupName": "scanInput",
176+
"name": "failOnSeverityThreshold",
177+
"type": "pickList",
178+
"label": "Fail on Severity Level Threshold",
179+
"defaultValue": "",
180+
"required": false,
181+
"options": {
182+
"UNKNOWN": "UNKNOWN",
183+
"LOW": "LOW",
184+
"MEDIUM": "MEDIUM",
185+
"HIGH": "HIGH",
186+
"CRITICAL": "CRITICAL"
187+
},
188+
"helpMarkDown": "Set a threshold for failing the task based on the highest severity level found during the scan. If set, the task will fail if any issue with a severity equal to or higher than this level is found.",
189+
"properties": {
190+
"EditableOptions": "False",
191+
"MultiSelectFlatList": "False"
192+
}
193+
},
174194
{
175195
"groupName": "reportsInput",
176196
"name": "reports",

trivy-task/trivyV2/utils.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,48 @@ export function stripV(version: string): string {
99
}
1010
return version;
1111
}
12+
13+
export const severityLevels = ['UNKNOWN', 'LOW', 'MEDIUM', 'HIGH', 'CRITICAL'];
14+
15+
export function getHighestSeverityLevel(resultFilePath: string): string {
16+
let highestIndex = -1;
17+
18+
try {
19+
const data = require(resultFilePath);
20+
if (data && data.Results) {
21+
for (const result of data.Results) {
22+
if (result.Vulnerabilities) {
23+
for (const vulnerability of result.Vulnerabilities) {
24+
const severity = vulnerability.Severity;
25+
const index = severityLevels.indexOf(severity);
26+
if (index > highestIndex) {
27+
highestIndex = index;
28+
}
29+
}
30+
}
31+
if (result.Misconfigurations) {
32+
for (const misconfiguration of result.Misconfigurations) {
33+
const severity = misconfiguration.Severity;
34+
const index = severityLevels.indexOf(severity);
35+
if (index > highestIndex) {
36+
highestIndex = index;
37+
}
38+
}
39+
}
40+
if (result.Secrets) {
41+
for (const secret of result.Secrets) {
42+
const severity = secret.Severity;
43+
const index = severityLevels.indexOf(severity);
44+
if (index > highestIndex) {
45+
highestIndex = index;
46+
}
47+
}
48+
}
49+
}
50+
}
51+
} catch (error) {
52+
console.error(`Error reading or parsing the result file: ${error}`);
53+
}
54+
55+
return highestIndex >= 0 ? severityLevels[highestIndex] : '';
56+
}

0 commit comments

Comments
 (0)