Skip to content

Commit e6768a9

Browse files
authored
Merge 78fd5ea into 7074489
1 parent 7074489 commit e6768a9

File tree

2 files changed

+163
-92
lines changed

2 files changed

+163
-92
lines changed

.github/workflows/stackhpc-container-image-build.yml

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,12 @@ on:
3333
type: boolean
3434
required: false
3535
default: true
36-
push-dirty:
36+
sbom:
37+
description: Generate SBOM?
38+
type: boolean
39+
required: false
40+
default: true
41+
push-critical:
3742
description: Push scanned images that have critical vulnerabilities?
3843
type: boolean
3944
required: false
@@ -150,7 +155,7 @@ jobs:
150155
151156
- name: Install Trivy
152157
run: |
153-
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sudo sh -s -- -b /usr/local/bin v0.49.0
158+
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sudo sh -s -- -b /usr/local/bin v0.62.1
154159
155160
- name: Install yq
156161
run: |
@@ -254,14 +259,14 @@ jobs:
254259
run: if [ $(wc -l < ${{ matrix.distro.name }}-${{ matrix.distro.release }}-container-images) -le 1 ]; then exit 1; fi
255260

256261
- name: Scan built container images
257-
run: src/kayobe-config/tools/scan-images.sh ${{ matrix.distro.name }}-${{ matrix.distro.release }} ${{ steps.write-kolla-tag.outputs.kolla-tag }}
262+
run: src/kayobe-config/tools/scan-images.sh ${{ matrix.distro.name }}-${{ matrix.distro.release }} ${{ steps.write-kolla-tag.outputs.kolla-tag }} ${{ inputs.sbom && '--sbom'}}
258263

259264
- name: Move image scan logs to output artifact
260265
run: mv image-scan-output image-build-logs/image-scan-output
261266

262-
- name: Fail if no images have passed scanning
267+
- name: Fail if any images have critical vulnerabilities
263268
run: if [ $(wc -l < image-build-logs/image-scan-output/critical-images.txt) -gt 0 ]; then exit 1; fi
264-
if: ${{ !inputs.push-dirty }}
269+
if: ${{ !inputs.push-critical }}
265270

266271
- name: Copy clean images to push-attempt-images list
267272
run: cp image-build-logs/image-scan-output/clean-images.txt image-build-logs/push-attempt-images.txt
@@ -271,13 +276,13 @@ jobs:
271276
# This should be reverted when it's decided to filter high level CVEs as well.
272277
- name: Append dirty images to push list
273278
run: |
274-
cat image-build-logs/image-scan-output/dirty-images.txt >> image-build-logs/push-attempt-images.txt
279+
cat image-build-logs/image-scan-output/high-images.txt >> image-build-logs/push-attempt-images.txt
275280
if: ${{ inputs.push }}
276281

277282
- name: Append images with critical vulnerabilities to push list
278283
run: |
279284
cat image-build-logs/image-scan-output/critical-images.txt >> image-build-logs/push-attempt-images.txt
280-
if: ${{ inputs.push && inputs.push-dirty }}
285+
if: ${{ inputs.push && inputs.push-critical }}
281286

282287
- name: Push images
283288
run: |
@@ -326,12 +331,12 @@ jobs:
326331
# This can be used again instead of "Fail when critical vulnerabilities are found" when it's
327332
# decided to fail the job on detecting high CVEs as well.
328333
# - name: Fail when images failed scanning
329-
# run: if [ $(wc -l < image-build-logs/image-scan-output/dirty-images.txt) -gt 0 ]; then cat image-build-logs/image-scan-output/dirty-images.txt && exit 1; fi
330-
# if: ${{ !inputs.push-dirty && !cancelled() }}
334+
# run: if [ $(wc -l < image-build-logs/image-scan-output/high-images.txt) -gt 0 ]; then cat image-build-logs/image-scan-output/high-images.txt && exit 1; fi
335+
# if: ${{ !inputs.push-critical && !cancelled() }}
331336

332337
- name: Fail when critical vulnerabilities are found
333338
run: if [ $(wc -l < image-build-logs/image-scan-output/critical-images.txt) -gt 0 ]; then cat image-build-logs/image-scan-output/critical-images.txt && exit 1; fi
334-
if: ${{ !inputs.push-dirty && !cancelled() }}
339+
if: ${{ !inputs.push-critical && !cancelled() }}
335340

336341
- name: Remove locally built images for this run
337342
if: always() && runner.arch == 'ARM64'

tools/scan-images.sh

Lines changed: 148 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,102 +1,168 @@
11
#!/usr/bin/env bash
22
set -eo pipefail
33

4-
# Check correct usage
5-
if [[ ! $2 ]]; then
6-
echo "Usage: scan-images.sh <os-distribution> <image-tag>"
7-
exit 2
8-
fi
9-
10-
set -u
4+
# Global variables
5+
scan_common_args=" \
6+
--exit-code 1 \
7+
--scanners vuln \
8+
--format json \
9+
--severity HIGH,CRITICAL \
10+
--ignore-unfixed \
11+
--db-repository ghcr.io/aquasecurity/trivy-db:2 \
12+
--db-repository public.ecr.aws/aquasecurity/trivy-db \
13+
--java-db-repository ghcr.io/aquasecurity/trivy-java-db:1 \
14+
--java-db-repository public.ecr.aws/aquasecurity/trivy-java-db "
1115

12-
# Check that trivy is installed
13-
if ! trivy --version; then
14-
echo 'Please install trivy: curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin v0.49.1'
15-
fi
16-
17-
# Clear any previous outputs
18-
rm -rf image-scan-output
16+
# Print usage instructions and error with wrong inputs
17+
usage() {
18+
echo "Usage: scan-images.sh <os-distribution> <image-tag> [--sbom]"
19+
exit 2
20+
}
1921

20-
# Make a fresh output directory
21-
mkdir -p image-scan-output
22+
# Check dependencies are installed, print installation instructions otherwise
23+
check_deps_installed() {
24+
if ! trivy --version > /dev/null; then
25+
echo 'Please install trivy: curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sudo sh -s -- -b /usr/local/bin v0.62.1'
26+
exit 1
27+
fi
28+
if ! yq --version > /dev/null; then
29+
echo 'Please install yq: sudo dnf/apt install yq'
30+
exit 1
31+
fi
32+
}
2233

23-
# Get built container images
24-
docker image ls --filter "reference=ark.stackhpc.com/stackhpc-dev/*:$2*" > $1-scanned-container-images.txt
34+
# Prepare output files
35+
file_prep() {
36+
rm -rf image-scan-output
37+
mkdir -p image-scan-output
38+
touch image-scan-output/clean-images.txt image-scan-output/high-images.txt image-scan-output/critical-images.txt
39+
}
2540

26-
# Make a file of imagename:tag
27-
images=$(grep --invert-match --no-filename ^REPOSITORY $1-scanned-container-images.txt | sed 's/ \+/:/g' | cut -f 1,2 -d:)
41+
# Gather image lists
42+
get_images() {
43+
docker image ls --filter "reference=ark.stackhpc.com/stackhpc-dev/*:$2*" > $1-scanned-container-images.txt
44+
grep --invert-match --no-filename ^REPOSITORY $1-scanned-container-images.txt | sed 's/ \+/:/g' | cut -f 1,2 -d:
45+
}
2846

29-
# Ensure output files exist
30-
touch image-scan-output/clean-images.txt image-scan-output/dirty-images.txt image-scan-output/critical-images.txt
47+
# Generate ignored vulnerabilities file
48+
generate_trivy_ignore() {
49+
local imagename=$1
50+
local global_vulnerabilities=$(yq .global_allowed_vulnerabilities[] src/kayobe-config/etc/kayobe/trivy/allowed-vulnerabilities.yml 2> /dev/null)
51+
local image_vulnerabilities=$(yq .$imagename'_allowed_vulnerabilities[]' src/kayobe-config/etc/kayobe/trivy/allowed-vulnerabilities.yml 2> /dev/null)
3152

32-
# If Trivy detects no vulnerabilities, add the image name to clean-images.txt.
33-
# If there are vulnerabilities detected, add it to dirty-images.txt and
34-
# generate a csv summary
35-
# If the image contains at least one critical vulnerabilities, add it to
36-
# critical-images.txt
37-
for image in $images; do
38-
filename=$(basename $image | sed 's/:/\./g')
39-
imagename=$(echo $filename | cut -d "." -f 1 | sed 's/-/_/g')
40-
global_vulnerabilities=$(yq .global_allowed_vulnerabilities[] src/kayobe-config/etc/kayobe/trivy/allowed-vulnerabilities.yml)
41-
image_vulnerabilities=$(yq .$imagename'_allowed_vulnerabilities[]' src/kayobe-config/etc/kayobe/trivy/allowed-vulnerabilities.yml)
4253
touch .trivyignore
4354
for vulnerability in $global_vulnerabilities; do
4455
echo $vulnerability >> .trivyignore
4556
done
4657
for vulnerability in $image_vulnerabilities; do
4758
echo $vulnerability >> .trivyignore
4859
done
49-
if $(trivy image \
50-
--quiet \
51-
--exit-code 1 \
52-
--scanners vuln \
53-
--format json \
54-
--severity HIGH,CRITICAL \
55-
--output image-scan-output/${filename}.json \
56-
--ignore-unfixed \
57-
--db-repository ghcr.io/aquasecurity/trivy-db:2 \
58-
--db-repository public.ecr.aws/aquasecurity/trivy-db \
59-
--java-db-repository ghcr.io/aquasecurity/trivy-java-db:1 \
60-
--java-db-repository public.ecr.aws/aquasecurity/trivy-java-db \
61-
$image); then
62-
# Clean up the output file for any images with no vulnerabilities
63-
rm -f image-scan-output/${filename}.json
64-
65-
# Add the image to the clean list
60+
}
61+
62+
# Put results into CSV
63+
generate_summary_csv() {
64+
local imagename=$1
65+
local filename=$2
66+
67+
echo '"PkgName","PkgPath","PkgID","VulnerabilityID","FixedVersion","PrimaryURL","Severity"' > image-scan-output/${imagename}/${filename}-summary.csv
68+
69+
jq -r '.Results[]
70+
| select(.Vulnerabilities)
71+
| .Vulnerabilities
72+
| map(select(.PkgName | test("kernel") | not ))
73+
| group_by(.VulnerabilityID)
74+
| map(
75+
[
76+
(map(.PkgName) | unique | join(";")),
77+
(map(.PkgPath | select( . != null )) | join(";")),
78+
.[0].PkgID,
79+
.[0].VulnerabilityID,
80+
.[0].FixedVersion,
81+
.[0].PrimaryURL,
82+
.[0].Severity
83+
]
84+
)
85+
| .[]
86+
| @csv' image-scan-output/${imagename}/${filename}-scan.json >> image-scan-output/${imagename}/${filename}-summary.csv
87+
}
88+
89+
# Categorise images based on severity
90+
categorise_image() {
91+
local imagename=$1
92+
local filename=$2
93+
local image=$3
94+
95+
if [ $(grep "CRITICAL" image-scan-output/${imagename}/${filename}-summary.csv -c) -gt 0 ]; then
96+
echo "${image}" >> image-scan-output/critical-images.txt
97+
else
98+
echo "${image}" >> image-scan-output/high-images.txt
99+
fi
100+
}
101+
102+
# Generate SBOM, return correct scan command for SBOM
103+
generate_sbom() {
104+
local imagename=$1
105+
local filename=$2
106+
local image=$3
107+
trivy image \
108+
--format spdx-json \
109+
--output image-scan-output/${imagename}/${filename}-sbom.json \
110+
$image > /dev/null 2>&1
111+
echo "trivy sbom $scan_common_args \
112+
--output image-scan-output/${imagename}/${filename}-scan.json \
113+
image-scan-output/${imagename}/${filename}-sbom.json"
114+
}
115+
116+
# Scan images, generate SBOMs if requested
117+
scan_image() {
118+
local image=$1
119+
local filename=$(basename $image | sed 's/:/\./g')
120+
local imagename=$(echo $filename | cut -d "." -f 1 | sed 's/-/_/g')
121+
122+
mkdir -p image-scan-output/$imagename
123+
generate_trivy_ignore $imagename
124+
125+
# If SBOM is required, generate it first and scan the results, otherwise we
126+
# scan the image directly.
127+
if $generate_sbom; then
128+
echo "Generating SBOM for $imagename"
129+
scan_command=$(generate_sbom $imagename $filename $image)
130+
else
131+
scan_command="trivy image $scan_common_args \
132+
--output image-scan-output/${imagename}/${filename}-scan.json $image"
133+
fi
134+
135+
# Run scan against image or SBOM, format output. If no results, delete files.
136+
echo "Scanning $imagename for vulnerabilities"
137+
if $scan_command > /dev/null 2>&1; then
138+
rm -f image-scan-output/${imagename}/${filename}-scan.json
66139
echo "${image}" >> image-scan-output/clean-images.txt
67140
else
141+
generate_summary_csv $imagename $filename
142+
categorise_image $imagename $filename $image
143+
fi
144+
}
68145

69-
# Write a header for the summary CSV
70-
echo '"PkgName","PkgPath","PkgID","VulnerabilityID","FixedVersion","PrimaryURL","Severity"' > image-scan-output/${filename}.summary.csv
71-
72-
# Write the summary CSV data
73-
jq -r '.Results[]
74-
| select(.Vulnerabilities)
75-
| .Vulnerabilities
76-
# Ignore packages with "kernel" in the PkgName
77-
| map(select(.PkgName | test("kernel") | not ))
78-
| group_by(.VulnerabilityID)
79-
| map(
80-
[
81-
(map(.PkgName) | unique | join(";")),
82-
(map(.PkgPath | select( . != null )) | join(";")),
83-
.[0].PkgID,
84-
.[0].VulnerabilityID,
85-
.[0].FixedVersion,
86-
.[0].PrimaryURL,
87-
.[0].Severity
88-
]
89-
)
90-
| .[]
91-
| @csv' image-scan-output/${filename}.json >> image-scan-output/${filename}.summary.csv
92-
93-
if [ $(grep "CRITICAL" image-scan-output/${filename}.summary.csv -c) -gt 0 ]; then
94-
# If the image contains critical vulnerabilities, add the image to critical list
95-
echo "${image}" >> image-scan-output/critical-images.txt
96-
else
97-
# Otherwise, add the image to the dirty list
98-
echo "${image}" >> image-scan-output/dirty-images.txt
99-
fi
146+
# Main function
147+
main() {
148+
if [[ ! $2 ]]; then
149+
usage
100150
fi
101-
rm .trivyignore
102-
done
151+
152+
generate_sbom=false
153+
if [[ "$3" == "--sbom" ]]; then
154+
generate_sbom=true
155+
fi
156+
157+
set -u
158+
159+
check_deps_installed
160+
file_prep
161+
162+
images=$(get_images $1 $2)
163+
for image in $images; do
164+
scan_image $image
165+
done
166+
}
167+
168+
main "$@"

0 commit comments

Comments
 (0)