Skip to content
This repository has been archived by the owner on Sep 9, 2024. It is now read-only.

Vuln. scan

Vuln. scan #168

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/learn-github-actions/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.
#
name: Vuln. scan
on:
pull_request:
paths:
- ".github/workflows/vuln-scan.yaml"
schedule:
# At 00:00 - https://crontab.guru
- cron: "0 0 * * *"
workflow_dispatch:
jobs:
trivy_image_scan:
if: github.repository == 'jupyterhub/zero-to-jupyterhub-k8s'
runs-on: ubuntu-20.04
# Write permissions granted for the peter-evans/create-pull-request action
# to push to a branch and create/update a PR
permissions:
contents: write
pull-requests: write
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@v2
with:
# chartpress requires git history to set chart version and image tags
# correctly
fetch-depth: 0
- uses: actions/setup-python@v2
with:
python-version: "3.8"
- 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 "::set-output name=spec::$IMAGE_SPEC"
echo "::set-output name=name::$(echo $IMAGE_SPEC | sed 's/\(.*\):.*/\1/')"
echo "::set-output name=tag::$(echo $IMAGE_SPEC | sed 's/.*:\(.*\)/\1/')"
- 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@8f4c7160b470bafe4299efdc1c8a1fb495f8325a
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'
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@8f4c7160b470bafe4299efdc1c8a1fb495f8325a
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 "::set-output name=utc_time::$(date --utc +'%F_%T')"
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
# Use hack to set a multiline string output
# ref: https://github.com/actions/toolkit/issues/403#issue-593398879
TMP=$(cat tmp/md_summary_$1.md)
TMP="${TMP//'%'/'%25'}"
TMP="${TMP//$'\n'/'%0A'}"
TMP="${TMP//$'\r'/'%0D'}"
echo "::set-output name=md_summary_$1::$TMP"
# Calculate a hash of the markdown summary
HASH=$(cat tmp/md_summary_$1.md | sha1sum)
HASH=${HASH:0:10}
export HASH_$1=$HASH
echo "::set-output name=hash_$1::$HASH"
}
json_to_misc 1
json_to_misc 2
# Did rebuilding the image change anything?
if [ "$HASH_1" == "$HASH_2" ]; then
echo "::set-output name=proceed::no"
echo "No vulnerabilities were patched by rebuilding the image - won't proceed!"
else
echo "::set-output name=proceed::yes"
echo "Vulnerabilities were patched by rebuilding the image - will proceed!"
fi
- name: Describe vulnerabilities
if: steps.rebuild.outcome == 'success'
uses: aquasecurity/trivy-action@8f4c7160b470bafe4299efdc1c8a1fb495f8325a
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@dcd5fd746d53dd8de555c0f10bca6c35628be47a
with:
token: "${{ secrets.GITHUB_TOKEN }}"
author: jupyterhub vuln-scan bot <noreply@github.com>
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 }}