Skip to content

Vuln. scan

Vuln. scan #784

Workflow file for this run

# This is a GitHub workflow defining a set of jobs with a set of steps.
# ref: https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions
#
# This workflow use aquasecurity/trivy to scan the images we have published for
# known vulnerabilities. If there are such that can be patched, we let this
# workflow fail to signal that unless we make an exception, which we do for the
# singleuser-sample image only.
#
#
# About environment: watch-dependencies
#
# To reduce the exposure of the secrets.jupyterhub_bot_pat token that was setup
# for the environment watch-dependencies, we have setup a dedicated environment
# according to steps in
# https://github.com/jupyterhub/team-compass/issues/516#issuecomment-1129961954.
#
name: Vuln. scan
on:
pull_request:
paths:
- ".github/workflows/vuln-scan.yaml"
push:
paths:
- ".github/workflows/vuln-scan.yaml"
branches: ["main"]
schedule:
# At 05:00 on Monday - https://crontab.guru
- cron: "0 5 * * 1"
workflow_dispatch:
jobs:
trivy_image_scan:
if: github.repository == 'jupyterhub/zero-to-jupyterhub-k8s'
runs-on: ubuntu-22.04
environment: watch-dependencies
strategy:
fail-fast: false
matrix:
include:
- image_ref: hub
accept_failure: false
- image_ref: secret-sync
accept_failure: false
- image_ref: network-tools
accept_failure: false
- image_ref: image-awaiter
accept_failure: false
- image_ref: singleuser-sample
accept_failure: false
steps:
- uses: actions/checkout@v4
with:
# chartpress requires git history to set chart version and image tags
# correctly
fetch-depth: 0
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install chartpress
run: |
pip install chartpress
# charpress --list-images output lines of name:tag format. We use it with
# a search string in matrix.image_ref to find the specific image to scan
# in this job.
- name: Identify image name:tag
id: image
run: |
IMAGE_SPEC=$(
chartpress --list-images \
| grep ${{ matrix.image_ref }}:
)
echo "Identified image: $IMAGE_SPEC"
echo "spec=$IMAGE_SPEC" >> $GITHUB_OUTPUT
echo "name=$(echo $IMAGE_SPEC | sed 's/\(.*\):.*/\1/')" >> $GITHUB_OUTPUT
echo "tag=$(echo $IMAGE_SPEC | sed 's/.*:\(.*\)/\1/')" >> $GITHUB_OUTPUT
- name: Create ./tmp dir
run: mkdir ./tmp
# Action reference: https://github.com/aquasecurity/trivy-action
- name: Scan latest published image
id: scan_1
uses: aquasecurity/trivy-action@6e7b7d1fd3e4fef0c5fa8cce1229c54b2c9bd0d8
with:
image-ref: ${{ steps.image.outputs.spec }}
format: json # ref: https://github.com/aquasecurity/trivy#save-the-results-as-json
output: tmp/scan_1.json
ignore-unfixed: true
exit-code: "1"
# Keep running the subsequent steps of the job, they are made to
# explicitly adjust based on this step's outcome.
continue-on-error: true
# Steps below is only executing if vulnerabilities have been detected.
# -----------------------------------------------------------------------
- name: Rebuild image
id: rebuild
if: steps.scan_1.outcome == 'failure'
env:
DOCKER_BUILDKIT: "1"
run: |
docker build -t rebuilt-image images/${{ matrix.image_ref }}
- name: Scan rebuilt image
id: scan_2
if: steps.rebuild.outcome == 'success'
uses: aquasecurity/trivy-action@6e7b7d1fd3e4fef0c5fa8cce1229c54b2c9bd0d8
with:
image-ref: rebuilt-image
format: json # ref: https://github.com/aquasecurity/trivy#save-the-results-as-json
output: tmp/scan_2.json
ignore-unfixed: true
# Analyze the scan reports. If they differ, we want to proceed and create
# or update a PR. We use a hash from the final scan report as an
# indication to rebuild or not.
- name: Analyze scan reports
id: analyze
if: steps.rebuild.outcome == 'success'
run: |
echo "utc_time=$(date --utc +'%F_%T')" >> $GITHUB_OUTPUT
json_to_misc() {
# Count vulnerabilities
VULNERABILITY_COUNT="$(cat tmp/scan_$1.json | jq -r '[.Results[].Vulnerabilities | select(type != null)] | add | select(. != null) | length')"
echo "VULNERABILITY_COUNT_$1=$VULNERABILITY_COUNT" >> $GITHUB_ENV
# Construct a markdown summary
if [[ "$VULNERABILITY_COUNT" == "0" ]]; then
echo "No vulnerabilities! :tada:" >> tmp/md_summary_$1.md
else
echo "Target | Vuln. ID | Package Name | Installed v. | Fixed v." >> tmp/md_summary_$1.md
echo "-|-|-|-|-" >> tmp/md_summary_$1.md
cat tmp/scan_$1.json | jq -r '.Results[] | select(.Vulnerabilities != null) | .Type + " | " + (.Vulnerabilities[] | .VulnerabilityID + " | " + .PkgName + " | " + .InstalledVersion + " | " + .FixedVersion)' | sort >> tmp/md_summary_$1.md
fi
# Set a multiline string output with the following technique:
# ref: https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#multiline-strings
#
eof_marker=EOF_$RANDOM
echo "md_summary_$1<<$eof_marker" >> $GITHUB_OUTPUT
cat tmp/md_summary_$1.md >> $GITHUB_OUTPUT
echo "$eof_marker" >> $GITHUB_OUTPUT
# Calculate a hash of the markdown summary
HASH=$(cat tmp/md_summary_$1.md | sha1sum)
HASH=${HASH:0:10}
export HASH_$1=$HASH
echo "hash_$1=$HASH" >> $GITHUB_OUTPUT
}
json_to_misc 1
json_to_misc 2
# Did rebuilding the image change anything?
if [ "$HASH_1" == "$HASH_2" ]; then
echo "proceed=no" >> $GITHUB_OUTPUT
echo "No vulnerabilities were patched by rebuilding the image - won't proceed!"
else
echo "proceed=yes" >> $GITHUB_OUTPUT
echo "Vulnerabilities were patched by rebuilding the image - will proceed!"
fi
- name: Describe vulnerabilities
if: steps.rebuild.outcome == 'success'
uses: aquasecurity/trivy-action@6e7b7d1fd3e4fef0c5fa8cce1229c54b2c9bd0d8
with:
image-ref: rebuilt-image
format: table
ignore-unfixed: true
- name: Decision to not proceed
if: steps.analyze.outputs.proceed == 'no'
run: |
echo "::warning::None of the $VULNERABILITY_COUNT_1 vulnerabilities got patched by rebuilding the image :("
continue-on-error: ${{ matrix.accept_failure == true }}
# Steps below are executed if the analyze step decided to proceed.
# -----------------------------------------------------------------------
# Searches for the "# VULN_SCAN_TIME=..." in the Dockerfile and sets a new
# value, which can be used to submit a PR that when merged will trigger a
# rebuild of the image.
- name: Update VULN_SCAN_TIME in Dockerfile
if: steps.analyze.outputs.proceed == 'yes'
run: |
value_to_set="${{ steps.analyze.outputs.utc_time }}"
file_to_update="images/${{ matrix.image_ref }}/Dockerfile"
sed --in-place "s/\(#.*VULN_SCAN_TIME=\)\(.*\)/\1${value_to_set}/" "$file_to_update"
git --no-pager diff --color=always
# The create-pull-request action is smart enough to only create/update a
# PR if there is a change to anything not .gitignored. A change will be
# made only if the analyze steps outputted hash is changed.
#
# ref: https://github.com/peter-evans/create-pull-request
- name: Create or update a PR
if: steps.analyze.outputs.proceed == 'yes' && github.event_name != 'pull_request'
uses: peter-evans/create-pull-request@v6
with:
token: "${{ secrets.jupyterhub_bot_pat }}"
author: JupterHub Bot Account <[email protected]>
committer: JupterHub Bot Account <[email protected]>
reviewers: consideratio
branch: vuln-scan-${{ matrix.image_ref }}
title: Vulnerability patch in ${{ matrix.image_ref }}
labels: image:rebuild-to-patch-vuln
body: |
A rebuild of `${{ steps.image.outputs.name }}` has been found to influence the detected vulnerabilities! This PR will trigger a rebuild because it has updated a comment in the Dockerfile.
## About
This scan for known vulnerabilities has been made by [aquasecurity/trivy](https://github.com/aquasecurity/trivy). Trivy was configured to filter the vulnerabilities with the following settings:
- ignore-unfixed: `true`
## Before
Before trying to rebuild the image, the following vulnerabilities was detected in `${{ steps.image.outputs.spec }}`.
${{ steps.analyze.outputs.md_summary_1 }}
## After
${{ steps.analyze.outputs.md_summary_2 }}
commit-message: |
Patch known vulnerability in ${{ matrix.image_ref }}