This repository has been archived by the owner on Sep 9, 2024. It is now read-only.
Vuln. scan #159
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 }} |