-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathaction.yml
325 lines (290 loc) · 12.1 KB
/
action.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
name: stordco/actions-trivy
branding:
color: blue
icon: list
description: >-
GitHub Composite Action for running Trivy scans
inputs:
github-token:
description: "GitHub token for authentication."
required: false
image-ref:
description: "Specify the local Docker image to be scanned. This value needs to be set if the scan-type = image."
required: false
matrix-id:
description: "Specify the matrix indicator to be leveraged on notification steps."
required: false
scan-type:
description: >-
"Specify the type of scan to be perforemed (e.g., 'fs' for filesystem scan, `image` for image scan)."
required: true
slack-bot-token:
description: "Slackbot token for sending notifications."
required: false
slack-channel-id:
description: "Slack channel ID for sending notifications."
required: false
update-db:
description: "Update Trivy vulnerability database."
required: false
default: "true"
outputs:
artifact-url:
description: "Link to the Trivy scan artifact"
value: ${{ steps.trivy_artifact_upload.outputs.artifact-url }}
runs:
using: composite
steps:
- name: Configure configuration file
id: configuration_file
run: |
if [[ '${{ inputs.scan-type }}' == 'fs' ]]; then
echo "config_file_type=fs" >> "$GITHUB_OUTPUT"
fi
if [[ '${{ inputs.scan-type }}' == 'image' ]]; then
echo "config_file_type=image" >> "$GITHUB_OUTPUT"
fi
shell: bash
- name: Run Trivy vulnerability scanner in ${{ inputs.scan-type }} mode
id: trivy_scan
uses: aquasecurity/[email protected]
env:
TRIVY_SKIP_DB_UPDATE: ${{ inputs.update-db == 'false' && 'true' || 'false' }}
with:
scan-type: ${{ inputs.scan-type }}
image-ref: ${{ inputs.image-ref }}
trivy-config: .trivy/${{ steps.configuration_file.outputs.config_file_type }}-config.yaml
continue-on-error: true
- name: Set artifact upload metadata
id: artifact_metadata
run: |
if [[ '${{ github.ref_name }}' == 'main' ]]; then
echo "Keep trivy artifact for 90 days on main branch merge"
echo "days=90" >> "$GITHUB_OUTPUT"
else
echo "Keep trivy artifact for 1 day on PR builds"
echo "days=1" >> "$GITHUB_OUTPUT"
fi
sha_short=$(git rev-parse --short HEAD)
sanitized_ref_name=$(echo "${{ github.ref_name }}" | sed 's/[\\\/:*?<>|]/-/g')
if [[ -z '${{ inputs.matrix-id }}' ]]; then
echo "name=trivy-${{ inputs.scan-type }}-results-${sha_short}-${sanitized_ref_name}" >> $GITHUB_OUTPUT
else
echo "name=trivy-${{ inputs.scan-type }}-results-${sha_short}-${sanitized_ref_name}-${{ inputs.matrix-id }}" >> $GITHUB_OUTPUT
fi
shell: bash
- name: Upload Trivy report to artifacts
uses: actions/upload-artifact@v4
id: trivy_artifact_upload
with:
name: ${{ steps.artifact_metadata.outputs.name }}
path: trivy-${{ inputs.scan-type }}-results.json
retention-days: ${{ steps.artifact_metadata.outputs.days }}
if-no-files-found: ignore
overwrite: true
- name: Upload Trivy Report as PR Comment and parse Critical vulnerabilities
id: trivy_report_notification
if: ${{ inputs.github-token && github.event_name == 'pull_request' && steps.trivy_artifact_upload.outputs.artifact-url }}
uses: actions/github-script@v7
env:
GITHUB_TOKEN: ${{ inputs.github-token }}
SCAN_TYPE: ${{ inputs.scan-type }}
MATRIX_ID: ${{ inputs.matrix-id }}
with:
script: |
const fs = require('fs');
const path = require('path');
if (context.payload.pull_request == null) {
core.info('No pull request found, skipping comment creation');
return;
}
const issue_number = context.payload.pull_request.number;
const owner = context.repo.owner;
const repo = context.repo.repo;
let commentIdentifier;
if (!process.env.MATRIX_ID) {
commentIdentifier = `<!-- trivyReportComment-${process.env.SCAN_TYPE}-->`;
} else {
commentIdentifier = `<!-- trivyReportComment-${process.env.SCAN_TYPE}-${process.env.MATRIX_ID}-->`;
}
const filePath = path.join(process.env.GITHUB_WORKSPACE, `trivy-${process.env.SCAN_TYPE}-results.json`);
const fileContent = fs.readFileSync(filePath, 'utf8');
const jsonData = JSON.parse(fileContent);
function formatVulnerabilities(results) {
const headers = ["Library", "Vulnerability", "Severity", "Status", "Installed Version", "Fixed Version", "Title", "Target"];
const headerRow = `| ${headers.join(" | ")} |`;
const separatorRow = `| ${headers.map(() => "---").join(" | ")} |`;
const rows = results.flatMap(result => {
if (result.Vulnerabilities && result.Vulnerabilities.length > 0) {
return result.Vulnerabilities.map(vuln => {
const titleWithUrl = `${vuln.Title || ""} (${vuln.PrimaryURL || ""})`;
return `| ${[
vuln.PkgName || "",
vuln.VulnerabilityID || "",
vuln.Severity || "",
vuln.Status || "",
vuln.InstalledVersion || "",
vuln.FixedVersion || "",
titleWithUrl,
result.Target || ""
].join(" | ")} |`;
});
} else {
return [];
}
});
return [headerRow, separatorRow, ...rows].join("\n");
}
function parseTrivyResults(data) {
return data.Results.map(result => ({
Target: result.Target,
Vulnerabilities: result.Vulnerabilities || []
}));
}
const vulnerabilities = parseTrivyResults(jsonData);
const criticalVulnerabilitiesCount = vulnerabilities.flatMap(result => result.Vulnerabilities).filter(vuln => vuln.Severity === 'CRITICAL').length;
core.setOutput('critical_vulnerabilities_count', criticalVulnerabilitiesCount.toString());
core.info(`Found ${criticalVulnerabilitiesCount} critical vulnerabilities`);
const comments = await github.rest.issues.listComments({
owner,
repo,
issue_number,
});
const botComment = comments.data.find(comment => comment.body.includes(commentIdentifier));
core.info(`Parsed Vulnerabilities: ${JSON.stringify(vulnerabilities)}`)
const scanTypeName = process.env.SCAN_TYPE === 'fs' ? 'filesystem' : 'image';
if (vulnerabilities.flatMap(result => result.Vulnerabilities).length === 0) {
if (botComment) {
const noErrorsComment = `No Trivy ${scanTypeName} vulnerabilities to be reported${process.env.MATRIX_ID ? ` for ${process.env.MATRIX_ID}` : ''}.\n${commentIdentifier}`;
await github.rest.issues.updateComment({
owner,
repo,
comment_id: botComment.id,
body: noErrorsComment,
});
core.info('Updated existing PR comment to indicate no vulnerabilities detected anymore');
} else {
core.info('No vulnerabilities found, no comment posted');
}
} else {
const formattedContent = `
<details>
<summary>Trivy Scan Report</summary>
${formatVulnerabilities(vulnerabilities)}
${commentIdentifier}
</details>
`;
const MAX_COMMENT_LENGTH = 65536;
let fullCommentBody;
if (formattedContent.length > MAX_COMMENT_LENGTH) {
fullCommentBody = `
The Trivy ${scanTypeName} scan report${process.env.MATRIX_ID ? ` for ${process.env.MATRIX_ID}` : ''} is too large to display here. Please view the detailed output from the job:
[View Trivy Report](https://github.com/${owner}/${repo}/actions/runs/${context.runId})
${commentIdentifier}
`;
} else {
fullCommentBody = `
View the Trivy ${scanTypeName} scan report${process.env.MATRIX_ID ? ` for ${process.env.MATRIX_ID}` : ''} below. Click on the dropdown to expand the report.
${formattedContent}
`;
}
if (botComment) {
await github.rest.issues.updateComment({
owner,
repo,
comment_id: botComment.id,
body: fullCommentBody,
});
core.info('Updated existing PR comment');
} else {
await github.rest.issues.createComment({
owner,
repo,
issue_number,
body: fullCommentBody,
});
core.info('Created new PR comment');
}
}
- name: Notify Slack of critical vulnerabilities
if: ${{ steps.trivy_report_notification.outputs.critical_vulnerabilities_count != '0' && github.ref_name == 'main' && inputs.slack-bot-token && !inputs.matrix-id }}
uses: slackapi/[email protected]
env:
SLACK_BOT_TOKEN: ${{ inputs.slack-bot-token }}
with:
channel-id: ${{ inputs.slack-channel-id }}
payload: |
{
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": ":alert: *Critical vulnerabilities detected* in Trivy ${{ inputs.scan-type }} scan. `${{ steps.trivy_report_notification.outputs.critical_vulnerabilities_count }}` critical vulnerabilities detected."
}
},
{
"type": "section",
"fields": [
{
"type": "mrkdwn",
"text": "*Repository:*\n`${{ github.repository }}`"
},
{
"type": "mrkdwn",
"text": "*Branch:*\n`${{ github.ref_name }}`"
}
]
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "<${{ steps.trivy_artifact_upload.outputs.artifact-url }}|View Artifacts>"
}
}
]
}
- name: Notify Slack of critical vulnerabilities (${{ inputs.matrix-id }})
if: ${{ steps.trivy_report_notification.outputs.critical_vulnerabilities_count != '0' && github.ref_name == 'main' && inputs.slack-bot-token && inputs.matrix-id }}
uses: slackapi/[email protected]
env:
SLACK_BOT_TOKEN: ${{ inputs.slack-bot-token }}
with:
channel-id: ${{ inputs.slack-channel-id }}
payload: |
{
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": ":alert: *Critical vulnerabilities detected* in Trivy ${{ inputs.scan-type }} scan. `${{ steps.trivy_report_notification.outputs.critical_vulnerabilities_count }}` critical vulnerabilities detected."
}
},
{
"type": "section",
"fields": [
{
"type": "mrkdwn",
"text": "*Repository:*\n`${{ github.repository }}`"
},
{
"type": "mrkdwn",
"text": "*Branch:*\n`${{ github.ref_name }}`"
},
{
"type": "mrkdwn",
"text": "*Matrix ID:*\n`${{ inputs.matrix-id }}`"
}
]
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "<${{ steps.trivy_artifact_upload.outputs.artifact-url }}|View Artifacts>"
}
}
]
}