Skip to content

Commit fcfff10

Browse files
JohnGarbuttk-s-deanm-bullAlex-Welsh
authored
Add Trivy image scanning (#436)
Trivy scanning on container image build --------- Co-authored-by: k-s-dean <[email protected]> Co-authored-by: Matt Anson <[email protected]> Co-authored-by: Alex-Welsh <[email protected]>
1 parent a062a8c commit fcfff10

File tree

4 files changed

+201
-26
lines changed

4 files changed

+201
-26
lines changed

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

Lines changed: 105 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ on:
3838
type: boolean
3939
required: false
4040
default: true
41+
push-dirty:
42+
description: Push scanned images that have vulnerabilities?
43+
type: boolean
44+
required: false
45+
# NOTE(Alex-Welsh): This default should be flipped once we resolve existing failures
46+
default: true
4147

4248
env:
4349
ANSIBLE_FORCE_COLOR: True
@@ -109,7 +115,15 @@ jobs:
109115
- name: Install package dependencies
110116
run: |
111117
sudo apt update
112-
sudo apt install -y build-essential git unzip nodejs python3-wheel python3-pip python3-venv
118+
sudo apt install -y build-essential git unzip nodejs python3-wheel python3-pip python3-venv curl jq wget
119+
120+
- name: Install gh
121+
run: |
122+
sudo mkdir -p -m 755 /etc/apt/keyrings && wget -qO- https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo tee /etc/apt/keyrings/githubcli-archive-keyring.gpg > /dev/null
123+
sudo chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg
124+
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null
125+
sudo apt update
126+
sudo apt install gh -y
113127
114128
- name: Checkout
115129
uses: actions/checkout@v4
@@ -127,6 +141,10 @@ jobs:
127141
run: |
128142
docker ps
129143
144+
- name: Install Trivy
145+
run: |
146+
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sudo sh -s -- -b /usr/local/bin v0.49.0
147+
130148
- name: Install Kayobe
131149
run: |
132150
mkdir -p venvs &&
@@ -162,65 +180,124 @@ jobs:
162180
env:
163181
KAYOBE_VAULT_PASSWORD: ${{ secrets.KAYOBE_VAULT_PASSWORD }}
164182

165-
- name: Build and push kolla overcloud images
183+
- name: Create build logs output directory
184+
run: mkdir image-build-logs
185+
186+
- name: Build kolla overcloud images
187+
id: build_overcloud_images
188+
continue-on-error: true
166189
run: |
167-
args="${{ github.event.inputs.regexes }}"
190+
args="${{ inputs.regexes }}"
168191
args="$args -e kolla_base_distro=${{ matrix.distro }}"
169192
args="$args -e kolla_tag=${{ needs.generate-tag.outputs.kolla_tag }}"
170193
args="$args -e stackhpc_repo_mirror_auth_proxy_enabled=true"
171-
if ${{ inputs.push }} == 'true'; then
172-
args="$args --push"
173-
fi
174194
source venvs/kayobe/bin/activate &&
175195
source src/kayobe-config/kayobe-env --environment ci-builder &&
176196
kayobe overcloud container image build $args
177197
env:
178198
KAYOBE_VAULT_PASSWORD: ${{ secrets.KAYOBE_VAULT_PASSWORD }}
179-
if: github.event.inputs.overcloud == 'true'
199+
if: inputs.overcloud
200+
201+
- name: Copy overcloud container image build logs to output directory
202+
run: sudo mv /var/log/kolla-build.log image-build-logs/kolla-build-overcloud.log
203+
if: inputs.overcloud
180204

181-
- name: Build and push kolla seed images
205+
- name: Build kolla seed images
206+
id: build_seed_images
207+
continue-on-error: true
182208
run: |
183209
args="-e kolla_base_distro=${{ matrix.distro }}"
184210
args="$args -e kolla_tag=${{ needs.generate-tag.outputs.kolla_tag }}"
185211
args="$args -e stackhpc_repo_mirror_auth_proxy_enabled=true"
186-
if ${{ inputs.push }} == 'true'; then
187-
args="$args --push"
188-
fi
189212
source venvs/kayobe/bin/activate &&
190213
source src/kayobe-config/kayobe-env --environment ci-builder &&
191214
kayobe seed container image build $args
192215
env:
193216
KAYOBE_VAULT_PASSWORD: ${{ secrets.KAYOBE_VAULT_PASSWORD }}
194-
if: github.event.inputs.seed == 'true'
217+
if: inputs.seed
218+
219+
- name: Copy seed container image build logs to output directory
220+
run: sudo mv /var/log/kolla-build.log image-build-logs/kolla-build-seed.log
221+
if: inputs.seed
195222

196223
- name: Get built container images
197-
run: |
198-
docker image ls --filter "reference=ark.stackhpc.com/stackhpc-dev/${{ matrix.distro }}-*:${{ needs.generate-tag.outputs.kolla_tag }}" > ${{ matrix.distro }}-container-images
224+
run: docker image ls --filter "reference=ark.stackhpc.com/stackhpc-dev/${{ matrix.distro }}-*:${{ needs.generate-tag.outputs.kolla_tag }}" > ${{ matrix.distro }}-container-images
199225

200226
- name: Fail if no images have been built
201227
run: if [ $(wc -l < ${{ matrix.distro }}-container-images) -le 1 ]; then exit 1; fi
202228

203-
- name: Upload container images artifact
229+
- name: Scan built container images
230+
run: src/kayobe-config/tools/scan-images.sh ${{ matrix.distro }} ${{ needs.generate-tag.outputs.kolla_tag }}
231+
232+
- name: Move image scan logs to output artifact
233+
run: mv image-scan-output image-build-logs/image-scan-output
234+
235+
- name: Fail if no images have passed scanning
236+
run: if [ $(wc -l < image-build-logs/image-scan-output/clean-images.txt) -le 0 ]; then exit 1; fi
237+
if: ${{ !inputs.push-dirty }}
238+
239+
- name: Copy clean images to push-attempt-images list
240+
run: cp image-build-logs/image-scan-output/clean-images.txt image-build-logs/push-attempt-images.txt
241+
if: inputs.push
242+
243+
- name: Append dirty images to push list
244+
run: |
245+
cat image-build-logs/image-scan-output/dirty-images.txt >> image-build-logs/push-attempt-images.txt
246+
if: ${{ inputs.push && inputs.push-dirty }}
247+
248+
- name: Push images
249+
run: |
250+
touch image-build-logs/push-failed-images.txt
251+
source venvs/kayobe/bin/activate &&
252+
source src/kayobe-config/kayobe-env --environment ci-builder &&
253+
kayobe playbook run ${KAYOBE_CONFIG_PATH}/ansible/docker-registry-login.yml &&
254+
255+
while read -r image; do
256+
# Retries!
257+
for i in {1..5}; do
258+
if docker push $image; then
259+
echo "Pushed $image"
260+
break
261+
elif $i == 5; then
262+
echo "Failed to push $image"
263+
echo $image >> image-build-logs/push-failed-images.txt
264+
else
265+
echo "Failed on retry $i"
266+
sleep 5
267+
fi;
268+
done
269+
done < image-build-logs/push-attempt-images.txt
270+
shell: bash
271+
env:
272+
KAYOBE_VAULT_PASSWORD: ${{ secrets.KAYOBE_VAULT_PASSWORD }}
273+
if: inputs.push
274+
275+
- name: Upload output artifact
204276
uses: actions/upload-artifact@v4
205277
with:
206-
name: ${{ matrix.distro }} container images
207-
path: ${{ matrix.distro }}-container-images
278+
name: ${{ matrix.distro }}-logs
279+
path: image-build-logs
208280
retention-days: 7
281+
if: ${{ !cancelled() }}
282+
283+
- name: Fail when images failed to build
284+
run: echo "An image build failed. Check the workflow artifact for build logs" && exit 1
285+
if: ${{ steps.build_overcloud_images.outcome == 'failure' || steps.build_seed_images.outcome == 'failure' }}
286+
287+
- name: Fail when images failed to push
288+
run: if [ $(wc -l < image-build-logs/push-failed-images.txt) -gt 0 ]; then cat image-build-logs/push-failed-images.txt && exit 1; fi
289+
if: ${{ !cancelled() }}
290+
291+
- name: Fail when images failed scanning
292+
run: if [ $(wc -l < image-build-logs/dirty-images.txt) -gt 0 ]; then cat image-build-logs/dirty-images.txt && exit 1; fi
293+
if: ${{ !inputs.push-dirty && !cancelled() }}
209294

210-
sync-container-repositories:
211-
name: Trigger container image repository sync
212-
needs:
213-
- container-image-build
214-
if: github.repository == 'stackhpc/stackhpc-kayobe-config' && inputs.push
215-
runs-on: ubuntu-latest
216-
permissions: {}
217-
steps:
218295
# NOTE(mgoddard): Trigger another CI workflow in the
219296
# stackhpc-release-train repository.
220297
- name: Trigger container image repository sync
221298
run: |
222299
filter='${{ inputs.regexes }}'
223-
if [[ -n $filter ]] && [[ ${{ github.event.inputs.seed }} == 'true' ]]; then
300+
if [[ -n $filter ]] && [[ ${{ inputs.seed }} == 'true' ]]; then
224301
filter="$filter bifrost"
225302
fi
226303
gh workflow run \
@@ -231,7 +308,9 @@ jobs:
231308
-f sync-new-images=false
232309
env:
233310
GITHUB_TOKEN: ${{ secrets.STACKHPC_RELEASE_TRAIN_TOKEN }}
311+
if: ${{ github.repository == 'stackhpc/stackhpc-kayobe-config' && inputs.push && !cancelled() }}
234312

235313
- name: Display link to container image repository sync workflows
236314
run: |
237315
echo "::notice Container image repository sync workflows: https://github.com/stackhpc/stackhpc-release-train/actions/workflows/container-sync.yml"
316+
if: ${{ github.repository == 'stackhpc/stackhpc-kayobe-config' && inputs.push && !cancelled() }}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
- name: Login to docker registry
3+
gather_facts: false
4+
hosts: container-image-builders
5+
tasks:
6+
- name: Login to docker registry
7+
docker_login:
8+
registry_url: "{{ kolla_docker_registry or omit }}"
9+
username: "{{ kolla_docker_registry_username }}"
10+
password: "{{ kolla_docker_registry_password }}"
11+
reauthorize: yes
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
security:
3+
- |
4+
Kolla container images created using the
5+
``stackhpc-container-image-build.yml`` workflow are now automatically
6+
scanned for vulnerablilities.

tools/scan-images.sh

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
#!/usr/bin/env bash
2+
set -eo pipefail
3+
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
11+
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
19+
20+
# Make a fresh output directory
21+
mkdir -p image-scan-output
22+
23+
# Get built container images
24+
docker image ls --filter "reference=ark.stackhpc.com/stackhpc-dev/$1-*:$2" > $1-scanned-container-images.txt
25+
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:)
28+
29+
# Ensure output files exist
30+
touch image-scan-output/clean-images.txt image-scan-output/dirty-images.txt
31+
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+
for image in $images; do
36+
filename=$(basename $image | sed 's/:/\./g')
37+
if $(trivy image \
38+
--quiet \
39+
--exit-code 1 \
40+
--scanners vuln \
41+
--format json \
42+
--severity HIGH,CRITICAL \
43+
--output image-scan-output/${filename}.json \
44+
--ignore-unfixed \
45+
$image); then
46+
# Clean up the output file for any images with no vulnerabilities
47+
rm -f image-scan-output/${filename}.json
48+
49+
# Add the image to the clean list
50+
echo "${image}" >> image-scan-output/clean-images.txt
51+
else
52+
# Add the image to the dirty list
53+
echo "${image}" >> image-scan-output/dirty-images.txt
54+
55+
# Write a header for the summary CSV
56+
echo '"PkgName","PkgPath","PkgID","VulnerabilityID","FixedVersion","PrimaryURL","Severity"' > image-scan-output/${filename}.summary.csv
57+
58+
# Write the summary CSV data
59+
jq -r '.Results[]
60+
| select(.Vulnerabilities)
61+
| .Vulnerabilities
62+
# Ignore packages with "kernel" in the PkgName
63+
| map(select(.PkgName | test("kernel") | not ))
64+
| group_by(.VulnerabilityID)
65+
| map(
66+
[
67+
(map(.PkgName) | unique | join(";")),
68+
(map(.PkgPath | select( . != null )) | join(";")),
69+
.[0].PkgID,
70+
.[0].VulnerabilityID,
71+
.[0].FixedVersion,
72+
.[0].PrimaryURL,
73+
.[0].Severity
74+
]
75+
)
76+
| .[]
77+
| @csv' image-scan-output/${filename}.json >> image-scan-output/${filename}.summary.csv
78+
fi
79+
done

0 commit comments

Comments
 (0)