From f24547ecb16a4c32b43921b436907c5240f0e322 Mon Sep 17 00:00:00 2001 From: Eric Paulsen Date: Tue, 7 Feb 2023 18:09:33 -0500 Subject: [PATCH] feat: add iron bank Dockerfile & manifest (#5934) * feat: add iron bank Dockerfile & manifest Co-authored-by: Dean Sheather * add: tfrc file * mv: ironbank/ /scripts * fixup! Merge branch 'main' into iron-bank * feat: add ironbank trivy scanning * fixup! feat: add ironbank trivy scanning * fixup! feat: add ironbank trivy scanning * fixup! feat: add ironbank trivy scanning * fixup! feat: add ironbank trivy scanning --------- Co-authored-by: Dean Sheather --- .github/workflows/security.yaml | 42 ++++++- scripts/ironbank/.gitignore | 6 + scripts/ironbank/Dockerfile | 95 ++++++++++++++++ scripts/ironbank/build_ironbank.sh | 106 ++++++++++++++++++ scripts/ironbank/hardening_manifest.yaml | 65 +++++++++++ .../ironbank/terraform-filesystem-mirror.tfrc | 5 + scripts/lib.sh | 2 +- 7 files changed, 319 insertions(+), 2 deletions(-) create mode 100644 scripts/ironbank/.gitignore create mode 100644 scripts/ironbank/Dockerfile create mode 100755 scripts/ironbank/build_ironbank.sh create mode 100644 scripts/ironbank/hardening_manifest.yaml create mode 100644 scripts/ironbank/terraform-filesystem-mirror.tfrc diff --git a/.github/workflows/security.yaml b/.github/workflows/security.yaml index 4e00e8ff33..a80c1ef555 100644 --- a/.github/workflows/security.yaml +++ b/.github/workflows/security.yaml @@ -92,6 +92,9 @@ jobs: restore-keys: | js-${{ runner.os }}- + - name: Install yq + run: go run github.com/mikefarah/yq/v4@v4.30.6 + - name: Build Coder linux amd64 Docker image id: build run: | @@ -112,6 +115,17 @@ jobs: make -j "$image_job" echo "image=$(cat "$image_job")" >> $GITHUB_OUTPUT + - name: Build Coder linux amd64 Docker image (ironbank) + id: build-ironbank + run: | + set -euo pipefail + # NOTE: This is not a real image tag we publish. + image_tag="${{ steps.build.outputs.image }}-ironbank" + ./scripts/ironbank/build_ironbank.sh \ + --target "$image_tag" \ + "build/coder_$(./scripts/version.sh)_linux_amd64" + echo "image=$image_tag" >> $GITHUB_OUTPUT + - name: Run Trivy vulnerability scanner uses: aquasecurity/trivy-action@9ab158e8597f3b310480b9a69402b419bc03dbd5 with: @@ -124,10 +138,36 @@ jobs: uses: github/codeql-action/upload-sarif@v2 with: sarif_file: trivy-results.sarif + category: "Trivy" + + - name: Run Trivy vulnerability scanner (ironbank) + uses: aquasecurity/trivy-action@7b7aa264d83dc58691451798b4d117d53d21edfe + with: + image-ref: ${{ steps.build-ironbank.outputs.image }} + format: sarif + output: trivy-results-ironbank.sarif + severity: "CRITICAL,HIGH" + + # Update the tool name field in the ironbank SARIF file so it's not + # indistinguishable from findings in the non-ironbank SARIF file in the + # GitHub UI. Without this, findings from both scans would show up as + # "Trivy". + - name: Update tool name in SARIF file (ironbank) + run: | + set -euo pipefail + yq eval -i '.runs[0].tool.driver.name = "Trivy Ironbank"' trivy-results-ironbank.sarif + + - name: Upload Trivy scan results to GitHub Security tab (ironbank) + uses: github/codeql-action/upload-sarif@v2 + with: + sarif_file: trivy-results-ironbank.sarif + category: "Trivy Ironbank" - name: Upload Trivy scan results as an artifact uses: actions/upload-artifact@v2 with: name: trivy - path: trivy-results.sarif + path: | + trivy-results.sarif + trivy-results-ironbank.sarif retention-days: 7 diff --git a/scripts/ironbank/.gitignore b/scripts/ironbank/.gitignore new file mode 100644 index 0000000000..ae23ac0abf --- /dev/null +++ b/scripts/ironbank/.gitignore @@ -0,0 +1,6 @@ +coder.tar.gz +terraform.zip +terraform-provider-coder.zip + +.terraform.zip.* +.terraform-provider-coder.zip.* diff --git a/scripts/ironbank/Dockerfile b/scripts/ironbank/Dockerfile new file mode 100644 index 0000000000..8aa0a9eac8 --- /dev/null +++ b/scripts/ironbank/Dockerfile @@ -0,0 +1,95 @@ +ARG BASE_REGISTRY=registry1.dso.mil +ARG BASE_IMAGE=ironbank/redhat/ubi/ubi8-minimal +ARG BASE_TAG=8.7 + +FROM ${BASE_REGISTRY}/${BASE_IMAGE}:${BASE_TAG} + +SHELL ["/bin/bash", "-c"] + +ENV LANG=en_US.UTF-8 + +RUN microdnf update --assumeyes && \ + microdnf install --assumeyes \ + ca-certificates \ + git \ + gzip \ + shadow-utils \ + tar \ + unzip && \ + microdnf clean all + +# Configure the cryptography policy manually. These policies likely +# have no impact, since Go doesn't link against these libraries. +# +# Normally, one uses the update-crypto-policies script to create these +# links, which is included in the crypto-policies-scripts package, but +# that pulls in Python, so we create the links manually here. This +# list of links comes from running strace on the update-crypto-policies +# script (strace update-crypto-policies --set FIPS) in Fedora, since +# RHEL and UBI do not provide an strace package by default. +RUN echo "FIPS" >/etc/crypto-policies/config && \ + cp --force /usr/share/crypto-policies/policies/FIPS.pol /etc/crypto-policies/state/CURRENT.pol && \ + echo "FIPS" >/etc/crypto-policies/state/current && \ + ln --symbolic --force /usr/share/crypto-policies/FIPS/bind.txt /etc/crypto-policies/back-ends/bind.config && \ + ln --symbolic --force /usr/share/crypto-policies/FIPS/gnutls.txt /etc/crypto-policies/back-ends/gnutls.config && \ + ln --symbolic --force /usr/share/crypto-policies/FIPS/java.txt /etc/crypto-policies/back-ends/java.config && \ + ln --symbolic --force /usr/share/crypto-policies/FIPS/krb5.txt /etc/crypto-policies/back-ends/krb5.config && \ + ln --symbolic --force /usr/share/crypto-policies/FIPS/libreswan.txt /etc/crypto-policies/back-ends/libreswan.config && \ + ln --symbolic --force /usr/share/crypto-policies/FIPS/libssh.txt /etc/crypto-policies/back-ends/libssh.config && \ + ln --symbolic --force /usr/share/crypto-policies/FIPS/nss.txt /etc/crypto-policies/back-ends/nss.config && \ + ln --symbolic --force /usr/share/crypto-policies/FIPS/openssh.txt /etc/crypto-policies/back-ends/openssh.config && \ + ln --symbolic --force /usr/share/crypto-policies/FIPS/opensshserver.txt /etc/crypto-policies/back-ends/opensshserver.config && \ + ln --symbolic --force /usr/share/crypto-policies/FIPS/openssl.txt /etc/crypto-policies/back-ends/openssl.config && \ + ln --symbolic --force /usr/share/crypto-policies/FIPS/opensslcnf.txt /etc/crypto-policies/back-ends/opensslcnf.config + +# Copy and extract Coder binary from tar file. We have to put this in /opt to +# match the Dockerfile. +ARG CODER_BIN=/opt/coder +ARG CODER_BIN_TAR_GZ=coder.tar.gz +COPY "$CODER_BIN_TAR_GZ" /tmp/coder.tar.gz +RUN mkdir -p /opt && \ + tar -xzvf /tmp/coder.tar.gz --directory /opt --strip-components=1 ./coder && \ + rm /tmp/coder.tar.gz +ENV PATH="/opt:${PATH}" + +# Copy and extract Terraform binary from zip file. +ARG TERRAFORM_BIN_DIR=/opt/terraform +ARG TERRAFORM_BIN_ZIP=terraform.zip +COPY "$TERRAFORM_BIN_ZIP" /tmp/terraform.zip +RUN mkdir -p "$TERRAFORM_BIN_DIR" && \ + unzip /tmp/terraform.zip -d "$TERRAFORM_BIN_DIR" && \ + rm /tmp/terraform.zip +ENV PATH="${TERRAFORM_BIN_DIR}:${PATH}" + +# Install the Coder Terraform provider to a well-known location. +ARG TERRAFORM_PLUGINS_DIR=/opt/terraform/plugins +ARG TERRAFORM_CODER_PROVIDER_VERSION +ARG TERRAFORM_CODER_PROVIDER_ZIP=terraform-provider-coder.zip +COPY "$TERRAFORM_CODER_PROVIDER_ZIP" "${TERRAFORM_PLUGINS_DIR}/registry.terraform.io/coder/coder/terraform-provider-coder_${TERRAFORM_CODER_PROVIDER_VERSION}_linux_amd64.zip" + +# Configure Terraform to use plugins from this dir. +COPY terraform-filesystem-mirror.tfrc /opt/terraform/config.tfrc +ENV TF_CLI_CONFIG_FILE=/opt/terraform/config.tfrc + +# Uninstall the build dependencies. +RUN microdnf remove --assumeyes \ + tar \ + unzip && \ + microdnf clean all + +# Transfer ownership of the binaries to the 'coder' user. +RUN useradd coder \ + --create-home \ + --shell=/bin/bash \ + --uid=1000 \ + --user-group && \ + chown --recursive --quiet coder:coder "$CODER_BIN" && \ + chown --recursive --quiet coder:coder "$TERRAFORM_BIN_DIR" && \ + chown --recursive --quiet coder:coder "$TERRAFORM_PLUGINS_DIR" && \ + chmod 0755 /home/coder + +USER 1000 +ENV HOME /home/coder +ENV USER=coder + +ENTRYPOINT [ "/opt/coder", "server" ] diff --git a/scripts/ironbank/build_ironbank.sh b/scripts/ironbank/build_ironbank.sh new file mode 100755 index 0000000000..8af8431d93 --- /dev/null +++ b/scripts/ironbank/build_ironbank.sh @@ -0,0 +1,106 @@ +#!/usr/bin/env bash + +# This script builds the ironbank Docker image of Coder containing the given +# binary. Other dependencies will be automatically downloaded and cached. +# +# Usage: ./build_ironbank.sh --target image_tag path/to/coder + +set -euo pipefail +# shellcheck source=scripts/lib.sh +source "$(dirname "${BASH_SOURCE[0]}")/../lib.sh" + +image_tag="" + +args="$(getopt -o "" -l target: -- "$@")" +eval set -- "$args" +while true; do + case "$1" in + --target) + image_tag="$2" + shift 2 + ;; + --) + shift + break + ;; + *) + error "Unrecognized option: $1" + ;; + esac +done + +if [[ "$image_tag" == "" ]]; then + error "The --image-tag parameter is required" +fi + +# Check dependencies +dependencies docker sha256sum yq +if [[ $(yq --version) != *" v4."* ]]; then + error "yq version 4 is required" +fi + +if [[ "$#" != 1 ]]; then + error "Exactly one argument must be provided to this script, $# were supplied" +fi +if [[ ! -f "$1" ]]; then + error "File '$1' does not exist or is not a regular file" +fi +input_file="$(realpath "$1")" + +# Make temporary dir for Docker build context. +tmpdir="$(mktemp -d)" +trap 'rm -rf "$tmpdir"' EXIT +pushd "$(dirname "${BASH_SOURCE[0]}")" +cp Dockerfile "$tmpdir/" +cp terraform-filesystem-mirror.tfrc "$tmpdir/" +popd + +# Create a coder.tar.gz file. +execrelative ../archive.sh \ + --format tar.gz \ + --os linux \ + --output "$tmpdir/coder.tar.gz" \ + "$input_file" + +# Download all resources in the hardening_manifest.yaml file except for +# coder.tar.gz (which we will make ourselves). +manifest_path="$(dirname "${BASH_SOURCE[0]}")/hardening_manifest.yaml" +resources="$(yq e '.resources[] | select(.filename != "coder.tar.gz") | [.filename, .url, .validation.value] | @tsv' "$manifest_path")" +while read -r line; do + filename="$(echo "$line" | cut -f1)" + url="$(echo "$line" | cut -f2)" + sha256_hash="$(echo "$line" | cut -f3)" + + pushd "$(dirname "${BASH_SOURCE[0]}")" + target=".${filename}.${sha256_hash}" + if [[ ! -f "$target" ]]; then + log "Downloading $filename" + curl -sSL "$url" -o "$target" + fi + + sum="$(sha256sum "$target" | cut -d' ' -f1)" + if [[ "$sum" != "$sha256_hash" ]]; then + rm "$target" + error "Downloaded $filename has hash $sum, but expected $sha256_hash" + fi + cp "$target" "$tmpdir/$filename" + popd +done <<<"$resources" + +terraform_coder_provider_version="$(yq e '.args.TERRAFORM_CODER_PROVIDER_VERSION' "$manifest_path")" +if [[ "$terraform_coder_provider_version" == "" ]]; then + error "TERRAFORM_CODER_PROVIDER_VERSION not found in hardening_manifest.yaml" +fi + +# Build the image. +pushd "$tmpdir" +docker build \ + --build-arg BASE_REGISTRY=registry.access.redhat.com \ + --build-arg BASE_IMAGE=ubi8/ubi-minimal \ + --build-arg BASE_TAG=8.7 \ + --build-arg TERRAFORM_CODER_PROVIDER_VERSION="$terraform_coder_provider_version" \ + -t "$image_tag" \ + . >&2 +popd + +echo "$image_tag" diff --git a/scripts/ironbank/hardening_manifest.yaml b/scripts/ironbank/hardening_manifest.yaml new file mode 100644 index 0000000000..dfcbd75d66 --- /dev/null +++ b/scripts/ironbank/hardening_manifest.yaml @@ -0,0 +1,65 @@ +apiVersion: v1 + +# The repository name in registry1, excluding /ironbank/ +name: "coder/coder-enterprise/coder-service-2" + +# List of tags to push for the repository in registry1 +# The most specific version should be the first tag and will be shown +# on ironbank.dso.mil +tags: + - "0.15.3" + - "latest" + +# Build args passed to Dockerfile ARGs +args: + # Needs to be kept in sync with the resource below. + TERRAFORM_CODER_PROVIDER_VERSION: "0.6.10" + +# Docker image labels +labels: + org.opencontainers.image.title: "coder-service-v2" + # Human-readable description of the software packaged in the image + org.opencontainers.image.description: "Coder server binary, includes REST API, Terraform, and dashboard" + # License(s) under which contained software is distributed + org.opencontainers.image.licenses: "AGPL" + # URL to find more information on the image + org.opencontainers.image.url: "https://coder.com/docs" + # Name of the distributing entity, organization or individual + org.opencontainers.image.vendor: "Coder Technologies" + org.opencontainers.image.version: "0.15.3" + # Keywords to help with search (ex. "cicd,gitops,golang") + mil.dso.ironbank.image.keywords: "remote, workspaces" + +# List of resources to make available to the offline build context +resources: + # Coder binary + - url: "https://github.com/coder/coder/releases/download/v0.15.3/coder_0.15.3_linux_amd64.tar.gz" + filename: "coder.tar.gz" + validation: + type: sha256 + value: 2c88555777f1d9cc77a8f049093f4002472dc43d52b026e6784ef477bdced4a2 + # Terraform binary, bundled inside of Coder to support air-gapped installs. + - url: https://releases.hashicorp.com/terraform/1.3.7/terraform_1.3.7_linux_amd64.zip + filename: "terraform.zip" + validation: + type: sha256 + value: b8cf184dee15dfa89713fe56085313ab23db22e17284a9a27c0999c67ce3021e + # Coder Terraform provider, bundled inside of Coder to support air-gapped + # installs. + # + # The version of this provider needs to be kept in sync with the + # TERRAFORM_CODER_PROVIDER_VERSION build arg. + - url: https://github.com/coder/terraform-provider-coder/releases/download/v0.6.10/terraform-provider-coder_0.6.10_linux_amd64.zip + filename: "terraform-provider-coder.zip" + validation: + type: sha256 + value: 4c2a16010621e146251f6fb5e27105dde9213d85ca8f3c8866c3f5a4159b81b0 + +# List of project maintainers +maintainers: + - email: "eric@coder.com" + name: "Eric Paulsen" + username: "ericpaulsen" + - email: "dean@coder.com" + name: "Dean Sheather" + username: "cdrdean" diff --git a/scripts/ironbank/terraform-filesystem-mirror.tfrc b/scripts/ironbank/terraform-filesystem-mirror.tfrc new file mode 100644 index 0000000000..d3585e39ea --- /dev/null +++ b/scripts/ironbank/terraform-filesystem-mirror.tfrc @@ -0,0 +1,5 @@ +provider_installation { + filesystem_mirror { + path = "/opt/terraform/plugins" + } +} diff --git a/scripts/lib.sh b/scripts/lib.sh index 61d67b3cda..fcb5a72067 100644 --- a/scripts/lib.sh +++ b/scripts/lib.sh @@ -39,7 +39,7 @@ realpath() { } # We have to define realpath before these otherwise it fails on Mac's bash. -SCRIPT_DIR="$(realpath "$(dirname "${BASH_SOURCE[0]}")")" +SCRIPT_DIR="$(realpath "$(dirname "${BASH_SOURCE[1]}")")" PROJECT_ROOT="$(cd "$SCRIPT_DIR" && realpath "$(git rev-parse --show-toplevel)")" # pushd is a silent alternative to the real pushd shell command.