From ecb91a4cb614080ea9dc87ff1c14b2153037a936 Mon Sep 17 00:00:00 2001 From: Jonathan Yu Date: Tue, 6 Apr 2021 03:03:52 +0000 Subject: [PATCH] chore: add container scanning with trivy --- .github/workflows/build.yaml | 23 ++++--- scripts/install_deps.sh | 85 +++++++++++++++++++++++++ scripts/scan_images.sh | 116 +++++++++++++++++++++++++++++++++++ 3 files changed, 217 insertions(+), 7 deletions(-) create mode 100755 scripts/install_deps.sh create mode 100755 scripts/scan_images.sh diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index d8df58e..1c0fa75 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -21,7 +21,7 @@ jobs: runs-on: ubuntu-20.04 steps: - name: Cancel previous runs - if: ${{ github.event_name == 'pull_request' }} + if: github.event_name == 'pull_request' uses: styfle/cancel-workflow-action@0.9.0 - name: Checkout @@ -39,6 +39,7 @@ jobs: run: yarn format:check images: + name: images/${{ matrix.job}} runs-on: ubuntu-20.04 strategy: matrix: @@ -46,29 +47,37 @@ jobs: - arch - centos - ubuntu - name: images/${{ matrix.job}} + fail-fast: false steps: - name: Cancel previous runs - if: ${{ github.event_name == 'pull_request' }} + if: github.event_name == 'pull_request' uses: styfle/cancel-workflow-action@0.9.0 - name: Checkout uses: actions/checkout@v2 + - name: Install dependencies + run: ./scripts/install_deps.sh + - name: Build ${{ matrix.job }} images run: | - ${{ github.workspace }}/scripts/build_images.sh \ + ./scripts/build_images.sh \ + --tag=${{ matrix.job }} + + - name: Scan ${{ matrix.job }} container images + run: | + ./scripts/scan_images.sh \ --tag=${{ matrix.job }} - name: Authenticate to Docker Hub - if: ${{ github.event_name != 'pull_request' }} + if: github.event_name != 'pull_request' uses: docker/login-action@v1 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Push images to Docker Hub - if: ${{ github.event_name != 'pull_request' }} + if: github.event_name != 'pull_request' run: | - ${{ github.workspace}}/scripts/push_images.sh \ + ./scripts/push_images.sh \ --tag=${{ matrix.job}} diff --git a/scripts/install_deps.sh b/scripts/install_deps.sh new file mode 100755 index 0000000..7e2615a --- /dev/null +++ b/scripts/install_deps.sh @@ -0,0 +1,85 @@ +#!/usr/bin/env bash +# +# This script installs dependencies to /usr/local/bin. + +set -euo pipefail +cd "$(dirname "$0")" +source "./lib.sh" + +TMPDIR="$(mktemp -d)" +BINDIR="/usr/local/bin" + +curl_flags=( + --silent + --show-error + --location +) + +DRY_RUN=false + +function usage() { + echo "Usage: $(basename "$0") [options]" + echo + echo "This script installs dependencies required for building" + echo "Coder images." + echo + echo "Options:" + echo " -h, --help Show this help text and exit" + echo " --dry-run Show commands that would run, but" + echo " do not run them" + exit 1 +} + +# Allow a failing exit status, as user input can cause this +set +o errexit +options=$(getopt \ + --name="$(basename "$0")" \ + --longoptions=" \ + help, \ + dry-run" \ + --options="h" \ + -- "$@") +# allow checking the exit code separately here, because we need both +# the response data and the exit code +# shellcheck disable=SC2181 +if [ $? -ne 0 ]; then + usage +fi +set -o errexit + +eval set -- "$options" +while true; do + case "${1:-}" in + --dry-run) + DRY_RUN=true + ;; + -h|--help) + usage + ;; + --) + shift + break + ;; + *) + # Default case, print an error and quit. This code shouldn't be + # reachable, because getopt should return an error exit code. + echo "Unknown option: $1" + usage + ;; + esac + shift +done + +TRIVY_VERSION="0.16.0" + +run_trace $DRY_RUN curl "${curl_flags[@]}" "https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz" \| \ + tar -C "$TMPDIR" -zxf - trivy + +run_trace $DRY_RUN sudo install --mode=0755 --target-directory="$BINDIR" "$TMPDIR/*" + +run_trace $DRY_RUN which \ + trivy + +run_trace false trivy --version + +run_trace false rm -vrf "$TMPDIR" diff --git a/scripts/scan_images.sh b/scripts/scan_images.sh new file mode 100755 index 0000000..98c45de --- /dev/null +++ b/scripts/scan_images.sh @@ -0,0 +1,116 @@ +#!/usr/bin/env bash + +set -euo pipefail + +cd "$(dirname "$0")" +source "./lib.sh" + +check_dependencies \ + docker + +source "./images.sh" + +PROJECT_ROOT="$(git rev-parse --show-toplevel)" +TAG="ubuntu" +DRY_RUN=false +QUIET=false + +function usage() { + echo "Usage: $(basename "$0") [options]" + echo + echo "This script builds Coder's container images." + echo + echo "Options:" + echo " -h, --help Show this help text and exit" + echo " --dry-run Show commands that would run, but" + echo " do not run them" + echo " --tag= Select an image tag group to build," + echo " one of: arch, centos, ubuntu)" + echo " --quiet Suppress container build output" + exit 1 +} + +# Allow a failing exit status, as user input can cause this +set +o errexit +options=$(getopt \ + --name="$(basename "$0")" \ + --longoptions=" \ + help, \ + dry-run, \ + tag:, \ + quiet" \ + --options="h" \ + -- "$@") +# allow checking the exit code separately here, because we need both +# the response data and the exit code +# shellcheck disable=SC2181 +if [ $? -ne 0 ]; then + usage +fi +set -o errexit + +eval set -- "$options" +while true; do + case "${1:-}" in + --dry-run) + DRY_RUN=true + ;; + --tag) + shift + TAG="$1" + ;; + --quiet) + QUIET=true + ;; + -h|--help) + usage + ;; + --) + shift + break + ;; + *) + # Default case, print an error and quit. This code shouldn't be + # reachable, because getopt should return an error exit code. + echo "Unknown option: $1" + usage + ;; + esac + shift +done + +trivy_flags=( + --exit-code=1 + --no-progress + --severity=MEDIUM,HIGH,CRITICAL +) + +failed=false + +for image in "${IMAGES[@]}"; do + image_dir="$PROJECT_ROOT/images/$image" + image_file="Dockerfile.$TAG" + image_ref="codercom/enterprise-$image:$TAG" + image_path="$image_dir/$image_file" + + if [ ! -f "$image_path" ]; then + if [ $QUIET = false ]; then + echo "Path '$image_path' does not exist; skipping" >&2 + fi + continue + fi + + # Allow failing exit codes so that we scan all images + set +o errexit + run_trace $DRY_RUN trivy image \ + "${trivy_flags[@]}" \ + "$image_ref" + if [ $? -ne 0 ]; then + failed=true + fi + set -o errexit +done + +if [ $failed = true ]; then + exit 1 +fi