Skip to content

Commit 75c99a9

Browse files
committed
tools/dependency: Add CVE scanner pipeline (#41261)
Signed-off-by: Ryan Northey <[email protected]>
1 parent d99633b commit 75c99a9

File tree

10 files changed

+459
-21
lines changed

10 files changed

+459
-21
lines changed

.github/workflows/_cve_scan.yml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
name: Dependency/Fetch CVE data
2+
3+
permissions:
4+
contents: read
5+
6+
on:
7+
workflow_call:
8+
secrets:
9+
gcs-cve-key:
10+
required: true
11+
inputs:
12+
cve-data-path:
13+
default: tools/dependency/cve_data
14+
type: string
15+
scheduled:
16+
default: false
17+
type: boolean
18+
19+
20+
jobs:
21+
cve-data:
22+
name: Scan dependencies for CVEs
23+
runs-on: ubuntu-24.04
24+
steps:
25+
- name: Checkout repository
26+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
27+
- name: Set vars
28+
id: vars
29+
run: |
30+
echo "cve-data-path=${{ inputs.cve-data-path }}" > $GITHUB_OUTPUT
31+
- uses: envoyproxy/toolshed/gh-actions/gcp/[email protected]
32+
name: Setup GCP
33+
with:
34+
key: ${{ secrets.gcs-cve-key }}
35+
- name: Create CVE data directory
36+
run: |
37+
mkdir -p ${{ steps.vars.outputs.cve-data-path }}
38+
- name: Download (sync) from GCS bucket
39+
run: |
40+
gsutil -mq rsync \
41+
"gs://${{ vars.GCS_CVE_BUCKET }}" \
42+
"${{ steps.vars.outputs.cve-data-path }}"
43+
- name: Run CVE dependency scanner
44+
run: |
45+
bazel test --config=ci //tools/dependency:cve_test

.github/workflows/envoy-cve.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ on:
1414
default: bazel
1515
type: choice
1616
options:
17+
- scan
1718
- fetch
1819

1920
concurrency:
@@ -33,3 +34,10 @@ jobs:
3334
uses: ./.github/workflows/_cve_fetch.yml
3435
with:
3536
scheduled: ${{ github.event_name == 'schedule' }}
37+
scan:
38+
secrets:
39+
gcs-cve-key: ${{ secrets.GCS_CVE_KEY }}
40+
if: >-
41+
github.event_name == 'workflow_dispatch'
42+
&& inputs.task == 'scan'
43+
uses: ./.github/workflows/_cve_scan.yml

tools/dependency/BUILD

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,10 @@ envoy_genjson(
3131
"-L",
3232
"tools/dependency",
3333
],
34-
data = [":cve_utils.jq"],
34+
data = [
35+
":cve_utils.jq",
36+
":version.jq",
37+
],
3538
filter = """
3639
import "cve_utils" as Utils;
3740
.[0] as $deps
@@ -138,3 +141,93 @@ sh_binary(
138141
"CVE_DATA_PATH": "%s/tools/dependency/cve_data" % PATH,
139142
},
140143
)
144+
145+
envoy_genjson(
146+
name = "ignored-cves",
147+
filter = """
148+
.[0].ignored_cves
149+
""",
150+
visibility = ["//visibility:public"],
151+
yaml_srcs = [":cve.yaml"],
152+
)
153+
154+
filegroup(
155+
name = "cve-data",
156+
srcs = glob(["cve_data/*.json"]),
157+
visibility = ["//visibility:public"],
158+
)
159+
160+
sh_binary(
161+
name = "cves",
162+
srcs = ["cves.sh"],
163+
data = [
164+
":cpe-dependencies",
165+
":cve-data",
166+
":cve_matcher.jq",
167+
":cve_utils.jq",
168+
":ignored-cves.json",
169+
":version.jq",
170+
"@jq_toolchains//:resolved_toolchain",
171+
],
172+
env = {
173+
"JQ_BIN": "$(JQ_BIN)",
174+
"CPE_DEPS": "$(location :cpe-dependencies)",
175+
"JQ_CVE_UTILS": "$(location :cve_utils.jq)",
176+
"JQ_CVE_MATCHER": "$(location :cve_matcher.jq)",
177+
"JQ_VERSION_UTILS": "$(location :version.jq)",
178+
"CVES_IGNORED": "$(location :ignored-cves.json)",
179+
"CVES": "$(locations :cve-data)",
180+
},
181+
toolchains = ["@jq_toolchains//:resolved_toolchain"],
182+
)
183+
184+
genrule(
185+
name = "cves-scanned",
186+
outs = ["scanned.json"],
187+
cmd = """
188+
export JQ_BIN="$(JQ_BIN)"
189+
export CPE_DEPS="$(location :cpe-dependencies)"
190+
export JQ_CVE_UTILS="$(location :cve_utils.jq)"
191+
export JQ_CVE_MATCHER="$(location :cve_matcher.jq)"
192+
export JQ_VERSION_UTILS="$(location :version.jq)"
193+
export CVES_IGNORED="$(location :ignored-cves.json)"
194+
export CVES="$(locations :cve-data)"
195+
$(location :cves) \
196+
> $@ || :
197+
""",
198+
tags = ["no-remote-exec"],
199+
toolchains = ["@jq_toolchains//:resolved_toolchain"],
200+
tools = [
201+
":cpe-dependencies",
202+
":cve-data",
203+
":cve_matcher.jq",
204+
":cve_utils.jq",
205+
":cves",
206+
":ignored-cves.json",
207+
":version.jq",
208+
"@jq_toolchains//:resolved_toolchain",
209+
],
210+
)
211+
212+
sh_test(
213+
name = "cve_test",
214+
srcs = [":cve_test.sh"],
215+
args = ["$(location :cves-scanned)"],
216+
data = [
217+
":ansi.jq",
218+
":cve_report.jq",
219+
":cve_utils.jq",
220+
":cves-scanned",
221+
":version.jq",
222+
"@jq_toolchains//:resolved_toolchain",
223+
],
224+
env = {
225+
"JQ_BIN": "$(JQ_BIN)",
226+
"JQ_ANSI_UTILS": "$(location :ansi.jq)",
227+
"JQ_CVE_UTILS": "$(location :cve_utils.jq)",
228+
"JQ_VERSION_UTILS": "$(location :version.jq)",
229+
"JQ_REPORT": "$(location :cve_report.jq)",
230+
},
231+
tags = ["no-remote-exec"],
232+
toolchains = ["@jq_toolchains//:resolved_toolchain"],
233+
)

tools/dependency/ansi.jq

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
2+
def green: "\u001b[32m" ;
3+
def red: "\u001b[31m" ;
4+
def yellow: "\u001b[33m" ;
5+
def blue: "\u001b[34m" ;
6+
def cyan: "\u001b[36m" ;
7+
def bold: "\u001b[1m";
8+
def underline: "\u001b[4m";
9+
def reset: "\u001b[0m" ;

tools/dependency/cve_matcher.jq

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import "cve_utils" as Utils;
2+
3+
Utils::deps_list($deps[0]) as $deps
4+
| Utils::iterate_cves(.vulnerabilities; $deps; $ignored[0])

tools/dependency/cve_report.jq

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import "ansi" as Ansi;
2+
import "cve_utils" as Utils;
3+
4+
def matched_dependencies(cve):
5+
.
6+
| cve as $cve
7+
| cve.matched[]
8+
| "Dep: \(Ansi::green)\(Ansi::bold)\(.dep.name)@\(.dep.version.string)\(Ansi::reset) \(Ansi::green)(\(.dep.release_date)\(Ansi::reset))
9+
10+
Severity: \(Utils::get_severity($cve.cve.metrics))
11+
Published: \(Utils::to_date_string($cve.cve.published))
12+
Version: \(.cpe.cpe.version)
13+
Start (including/excluding): \(.cpe.versions.start_inc // "")/\(.cpe.versions.start_exc // "")
14+
End (including/excluding): \(.cpe.versions.end_inc // "")/\(.cpe.versions.end_exc // "")
15+
16+
---------------------------------
17+
18+
\($cve.cve.descriptions[0].value)
19+
20+
";
21+
22+
def cve_report(cve):
23+
"================ \(Ansi::red)\(Ansi::bold)\(Ansi::underline)\(cve.id)\(Ansi::reset) ================
24+
\(matched_dependencies(cve))
25+
";
26+
27+
def summary(matches):
28+
.
29+
| "
30+
==========================================
31+
\(Ansi::red)\(Ansi::bold)\(matches | length) potential CVE vulnerabilities found\(Ansi::reset)";
32+
33+
def report(matches):
34+
matches
35+
| matches as $matches
36+
| map(cve_report(.))
37+
| join("\n") + summary($matches);
38+
39+
report(.)

tools/dependency/cve_test.sh

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#!/usr/bin/env bash
2+
3+
set -e
4+
5+
ANSI_LIBDIR="$(dirname "$JQ_ANSI_UTILS")"
6+
CVE_LIBDIR="$(dirname "$JQ_CVE_UTILS")"
7+
VERSION_LIBDIR="$(dirname "$JQ_VERSION_UTILS")"
8+
9+
if [[ -s "$1" ]]; then
10+
"$JQ_BIN" -r -f \
11+
-L "$ANSI_LIBDIR" \
12+
-L "$CVE_LIBDIR" \
13+
-L "$VERSION_LIBDIR" \
14+
"$JQ_REPORT" \
15+
"$1"
16+
exit 1
17+
fi

0 commit comments

Comments
 (0)