Skip to content

Commit 8509a20

Browse files
author
Julien Bouquillon
authored
feat: add trivy-ghcr action (#75)
* feat: add trivy-ghcr action * fix: dashbord grades color * fix * fix
1 parent be6ace1 commit 8509a20

29 files changed

Lines changed: 2180 additions & 515 deletions

README.md

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ Basic GitHub actions used in [dashlord](https://github.com/socialgouv/dashlord)
44

55
Actions
66

7-
| Action | Usage |
8-
| ------- | ------------------------------------------------- |
9-
| init | read dashloard.yml |
10-
| save | save a single url scan result for dashlord |
11-
| report | build a report.json and website from latest scans |
7+
| Action | Usage |
8+
| ------ | ------------------------------------------------- |
9+
| init | read dashloard.yml |
10+
| save | save a single url scan result for dashlord |
11+
| report | build a report.json and website from latest scans |
1212

1313
[![](./workflows.png)](https://excalidraw.com/#json=5097005936279552,BIdgMf7vmfpdFCKoCVegXg)
1414

@@ -26,12 +26,21 @@ yarn
2626
yarn start
2727
```
2828

29-
### Build a `report.json`
29+
### Build a new report
3030

31-
Build a new report.json on a local repo with scan results :
31+
This will add `report.json`, `config.json`, `trends.json` in `report/www/src`
32+
based on the content of `DASHLORD_REPO_PATH` :
3233

3334
```sh
3435
DASHLORD_URLS=http://test1.com,http://test1.com \ # optional
3536
DASHLORD_REPO_PATH=/path/to/some/dashlord-repo \ #optional
3637
node report/src > report/www/src/report.json
3738
```
39+
40+
### Add a new dashlord action
41+
42+
1. create a GitHub action that can produce some JSON in your `scans/myaction.json`
43+
2. In `report/src/generateUrlReport`, import the minimum from your action JSON to serve it to the frontend via the generated `report.json`
44+
3. In `report/src/summary`, add compute logic for your scanner score.
45+
4. Build a new report.json, see above
46+
5. Run `yarn start` in the `report/www` folder to start adding types from your action and UI for your component

report/src/__snapshots__/generateUrlReport.test.js.snap

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Object {
2020
},
2121
"testssl": null,
2222
"thirdparties": null,
23+
"trivy": null,
2324
"updownio": null,
2425
"url": "https://www.test.com",
2526
"wappalyzer": Object {
@@ -79,6 +80,7 @@ Object {
7980
"thirdparties": Object {
8081
"report": "thirdparties.json",
8182
},
83+
"trivy": null,
8284
"updownio": Object {
8385
"report": "updownio.json",
8486
},

report/src/generateUrlReport.js

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -69,14 +69,8 @@ const lhrCleanup = (result) => {
6969
if (!result) {
7070
return null;
7171
}
72-
const {
73-
requestedUrl,
74-
finalUrl,
75-
fetchTime,
76-
runWarnings,
77-
categories,
78-
audits,
79-
} = result;
72+
const { requestedUrl, finalUrl, fetchTime, runWarnings, categories, audits } =
73+
result;
8074

8175
/** @type {LighthouseReportCategories} */
8276
// @ts-ignore
@@ -109,13 +103,45 @@ const lhrCleanup = (result) => {
109103
*
110104
* Minify wget spider report
111105
*
112-
* @param {{broken?:Wget404Report}} result Lighthouse JSON content
106+
* @param {{broken?:Wget404Report}} result wget JSON content
113107
*
114108
* @returns {Wget404Report|undefined} minified JSON content
115109
*
116110
*/
117111
const wget404Cleanup = (result) => result && result.broken;
118112

113+
/**
114+
*
115+
* Minify trivy report
116+
*
117+
* @param {TrivyReport} result trivy JSON content
118+
*
119+
* @returns {TrivyReport} minified JSON content
120+
*
121+
*/
122+
const trivyCleanup = (result) =>
123+
result &&
124+
result.map((image) => ({
125+
name: image.name,
126+
url: image.url,
127+
image: image.image,
128+
trivy: image.trivy && {
129+
Target: image.trivy.Target,
130+
Vulnerabilities:
131+
image.trivy &&
132+
image.trivy.Vulnerabilities &&
133+
image.trivy.Vulnerabilities.map(
134+
({ VulnerabilityID, PkgName, PrimaryURL, Title, Severity }) => ({
135+
VulnerabilityID,
136+
PkgName,
137+
PrimaryURL,
138+
Title,
139+
Severity,
140+
})
141+
),
142+
},
143+
}));
144+
119145
//@ts-expect-error
120146
const requireToolData = (filename) => (basePath) =>
121147
requireJson(path.join(basePath, filename));
@@ -138,6 +164,7 @@ const tools = {
138164
},
139165
stats: { data: requireToolData("stats.json") },
140166
404: { data: requireToolData("404.json"), cleanup: wget404Cleanup },
167+
trivy: { data: requireToolData("trivy.json"), cleanup: trivyCleanup },
141168
};
142169

143170
//@ts-expect-error

report/src/summary/index.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
// each tool can output multiple values
32
const tools = {
43
/** @param {CodescanReport} report */
@@ -19,8 +18,10 @@ const tools = {
1918
updownio: (report) => require("./updownio")(report),
2019
/** @param {StatsReport} report */
2120
stats: (report) => require("./stats")(report),
22-
/** @param {Error404Report} report */
23-
404: report => report && report.length && ({ 404: report.length })
21+
/** @param {Wget404Report} report */
22+
404: (report) => report && report.length && { 404: report.length },
23+
/** @param {TrivyReport} report */
24+
trivy: (report) => require("./trivy")(report),
2425
};
2526

2627
/**

report/src/summary/trivy.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/**
2+
* sum an array
3+
*
4+
* @param {number[]} arr
5+
*
6+
**/
7+
const sum = (arr) => arr.reduce((a, c) => a + c, 0);
8+
9+
/** @param {TrivyReport} report */
10+
const summary = (report) => {
11+
if (report && report.length) {
12+
const allVulns = report.flatMap(
13+
(image) => (image.trivy && image.trivy.Vulnerabilities) || []
14+
);
15+
const vulnsCount = allVulns.length;
16+
const critical = allVulns.filter(
17+
(vuln) => vuln.Severity === "CRITICAL"
18+
).length;
19+
const high = allVulns.filter((vuln) => vuln.Severity === "HIGH").length;
20+
const medium = allVulns.filter((vuln) => vuln.Severity === "MEDIUM").length;
21+
const trivyGrade = critical ? "F" : high ? "E" : medium ? "C" : "A";
22+
return { trivy: vulnsCount, trivyGrade };
23+
}
24+
};
25+
26+
module.exports = summary;

report/src/summary/trivy.test.js

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
const summary = require("./trivy");
2+
3+
const tests = [
4+
{
5+
title: "invalid report",
6+
report: null,
7+
expected: undefined,
8+
},
9+
{
10+
title: "critical report",
11+
report: [
12+
{
13+
name: "app",
14+
url: "https://github.com//orgs/SocialGouv/packages/container/package/sample-next-app%2Fapp",
15+
image: "ghcr.io/socialgouv/sample-next-app/app",
16+
trivy: {
17+
Target:
18+
"ghcr.io/socialgouv/sample-next-app/app:latest (alpine 3.11.12)",
19+
Vulnerabilities: [
20+
{
21+
VulnerabilityID: "CVE-2018-17360",
22+
PkgName: "binutils",
23+
PrimaryURL: "https://avd.aquasec.com/nvd/cve-2018-17360",
24+
Title:
25+
"binutils: heap-based buffer over-read in bfd_getl32 in libbfd.c",
26+
Severity: "MEDIUM",
27+
},
28+
],
29+
},
30+
},
31+
{
32+
name: "hasura",
33+
url: "https://github.com//orgs/SocialGouv/packages/container/package/sample-next-app%2Fhasura",
34+
image: "ghcr.io/socialgouv/sample-next-app/hasura",
35+
trivy: {
36+
Target:
37+
"ghcr.io/socialgouv/sample-next-app/hasura:latest (debian 10.9)",
38+
Vulnerabilities: [
39+
{
40+
VulnerabilityID: "CVE-2019-18276",
41+
PkgName: "bash",
42+
PrimaryURL: "https://avd.aquasec.com/nvd/cve-2019-18276",
43+
Title:
44+
"bash: when effective UID is not equal to its real UID the saved UID is not dropped",
45+
Severity: "HIGH",
46+
},
47+
{
48+
VulnerabilityID: "CVE-2018-12699",
49+
PkgName: "binutils",
50+
PrimaryURL: "https://avd.aquasec.com/nvd/cve-2018-12699",
51+
Title:
52+
"binutils: heap-based buffer overflow in finish_stab in stabs.c",
53+
Severity: "CRITICAL",
54+
},
55+
],
56+
},
57+
},
58+
],
59+
expected: {
60+
trivy: 3,
61+
trivyGrade: "F",
62+
},
63+
},
64+
{
65+
title: "medium report",
66+
report: [
67+
{
68+
name: "app",
69+
url: "https://github.com//orgs/SocialGouv/packages/container/package/sample-next-app%2Fapp",
70+
image: "ghcr.io/socialgouv/sample-next-app/app",
71+
trivy: {
72+
Target:
73+
"ghcr.io/socialgouv/sample-next-app/app:latest (alpine 3.11.12)",
74+
Vulnerabilities: [
75+
{
76+
VulnerabilityID: "CVE-2018-17360",
77+
PkgName: "binutils",
78+
PrimaryURL: "https://avd.aquasec.com/nvd/cve-2018-17360",
79+
Title:
80+
"binutils: heap-based buffer over-read in bfd_getl32 in libbfd.c",
81+
Severity: "MEDIUM",
82+
},
83+
],
84+
},
85+
},
86+
{
87+
name: "hasura",
88+
url: "https://github.com//orgs/SocialGouv/packages/container/package/sample-next-app%2Fhasura",
89+
image: "ghcr.io/socialgouv/sample-next-app/hasura",
90+
trivy: {
91+
Target:
92+
"ghcr.io/socialgouv/sample-next-app/hasura:latest (debian 10.9)",
93+
},
94+
},
95+
],
96+
expected: {
97+
trivy: 1,
98+
trivyGrade: "C",
99+
},
100+
},
101+
];
102+
103+
describe("trivy", () => {
104+
tests.forEach((t) => {
105+
test(`${t.title} should return ${JSON.stringify(t.expected)}`, () => {
106+
//@ts-expect-error
107+
expect(summary(t.report)).toEqual(t.expected);
108+
});
109+
});
110+
});

report/src/trends/index.js

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -33,22 +33,15 @@ async function generateTrends(gitPath, latestReport, maxDaysHistory = 30) {
3333
const commits = await Promise.all(
3434
history
3535
// include only relevant commits (in the date range)
36-
.reduce(
37-
(
38-
filteredCommits,
39-
entry,
40-
i
41-
) => {
42-
const isAfter = entry.commit.date() >= startDate;
43-
const isFirstBefore = !isAfter && i === filteredCommits.length;
44-
// include relevant commits
45-
if (isAfter || isFirstBefore) {
46-
filteredCommits.push(entry);
47-
}
48-
return filteredCommits;
49-
},
50-
reduceInit
51-
)
36+
.reduce((filteredCommits, entry, i) => {
37+
const isAfter = entry.commit.date() >= startDate;
38+
const isFirstBefore = !isAfter && i === filteredCommits.length;
39+
// include relevant commits
40+
if (isAfter || isFirstBefore) {
41+
filteredCommits.push(entry);
42+
}
43+
return filteredCommits;
44+
}, reduceInit)
5245
// extract summary content for each commit
5346
.map(async ({ commit }) => {
5447
//@ts-expect-error

report/tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"resolveJsonModule": true,
1111
"skipLibCheck": true,
1212
"strictNullChecks": true,
13-
"target": "ES6"
13+
"target": "es2019"
1414
},
1515
"include": ["src/**/*.js", "../types/index.d.ts"]
1616
}

0 commit comments

Comments
 (0)