diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 0e7ee62..9ef54e3 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,3 +1,20 @@ +FROM golang:1.26.1-bookworm AS build +ARG TARGETARCH +RUN apt-get update && apt-get install -y \ + jq \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* +COPY src/base/.devcontainer/scripts/install_cosign.sh /tmp/install_cosign.sh +COPY src/base/.devcontainer/scripts/install_trivy.sh /tmp/install_trivy.sh +RUN INSTALL_DIR=/usr/local/bin /tmp/install_cosign.sh +RUN case "${TARGETARCH}" in \ + x86_64|amd64) TRIVY_ARCH=64bit ;; \ + aarch64|arm64) TRIVY_ARCH=ARM64 ;; \ + *) echo "Unsupported TARGETARCH: ${TARGETARCH}" && exit 1 ;; \ + esac \ + && INSTALL_DIR=/tmp/trivy/ ARCH="${TRIVY_ARCH}" /tmp/install_trivy.sh + + FROM mcr.microsoft.com/devcontainers/base:ubuntu-22.04 ARG TARGETARCH ENV TARGETARCH=${TARGETARCH} @@ -64,11 +81,13 @@ RUN git clone https://github.com/awslabs/git-secrets.git /tmp/git-secrets && \ chmod 755 /usr/share/secrets-scanner && \ curl -L https://raw.githubusercontent.com/NHSDigital/software-engineering-quality-framework/main/tools/nhsd-git-secrets/nhsd-rules-deny.txt -o /usr/share/secrets-scanner/nhsd-rules-deny.txt +COPY --from=build /tmp/trivy/trivy /usr/local/bin/trivy + USER vscode -ENV PATH="/home/vscode/.asdf/shims/:$PATH:/workspaces/eps-devcontainers/node_modules/.bin" +ENV PATH="/home/vscode/.asdf/shims:/home/vscode/.local/bin:$PATH:/workspaces/eps-devcontainers/node_modules/.bin" RUN \ - echo 'PATH="/home/vscode/.asdf/shims/:$PATH:/workspaces/eps-devcontainers/node_modules/.bin"' >> ~/.bashrc; \ + echo 'PATH="/home/vscode/.asdf/shims:/home/vscode/.local/bin:$PATH:/workspaces/eps-devcontainers/node_modules/.bin"' >> ~/.bashrc; \ echo '. <(asdf completion bash)' >> ~/.bashrc; \ echo '# Install Ruby Gems to ~/gems' >> ~/.bashrc; \ echo 'export GEM_HOME="$HOME/gems"' >> ~/.bashrc; \ @@ -83,8 +102,7 @@ RUN asdf plugin add python; \ asdf plugin add actionlint; \ asdf plugin add ruby https://github.com/asdf-vm/asdf-ruby.git; \ asdf plugin add trivy https://github.com/zufardhiyaulhaq/asdf-trivy.git; \ - asdf plugin add yq https://github.com/sudermanjr/asdf-yq.git - + asdf plugin add yq https://github.com/sudermanjr/asdf-yq.git; WORKDIR /workspaces/eps-devcontainers COPY .tool-versions /workspaces/eps-devcontainers/.tool-versions diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index b08221a..645e5c2 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -12,7 +12,8 @@ "source=${env:HOME}${env:USERPROFILE}/.aws,target=/home/vscode/.aws,type=bind", "source=${env:HOME}${env:USERPROFILE}/.ssh,target=/home/vscode/.ssh,type=bind", "source=${env:HOME}${env:USERPROFILE}/.gnupg,target=/home/vscode/.gnupg,type=bind", - "source=${env:HOME}${env:USERPROFILE}/.npmrc,target=/home/vscode/.npmrc,type=bind" + "source=${env:HOME}${env:USERPROFILE}/.npmrc,target=/home/vscode/.npmrc,type=bind", + "source=${env:HOME}${env:USERPROFILE}/.gitconfig,target=/home/vscode/.gitconfig,type=bind" ], "runArgs": [ "--network=host" diff --git a/.github/workflows/build_all_images.yml b/.github/workflows/build_all_images.yml index 111abf8..fe3279a 100644 --- a/.github/workflows/build_all_images.yml +++ b/.github/workflows/build_all_images.yml @@ -33,6 +33,7 @@ jobs: echo "node_24_languages=$node_24_language_folders" echo "projects=$project_folders" } >> "$GITHUB_OUTPUT" + package_base_docker_image: uses: ./.github/workflows/build_multi_arch_image.yml with: diff --git a/.github/workflows/build_multi_arch_image.yml b/.github/workflows/build_multi_arch_image.yml index b9d5334..11c1857 100644 --- a/.github/workflows/build_multi_arch_image.yml +++ b/.github/workflows/build_multi_arch_image.yml @@ -64,9 +64,10 @@ jobs: with: fetch-depth: 0 - name: setup trivy - uses: aquasecurity/setup-trivy@3fb12ec12f41e471780db15c232d5dd185dcb514 - with: - version: v0.69.3 + run: | + docker build --output=/usr/local/bin/ -f "src/base/.devcontainer/Dockerfile.trivy.${ARCH}" . + env: + ARCH: '${{ matrix.arch }}' - name: setup node uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f with: diff --git a/.gitignore b/.gitignore index 35bc1fd..7c362b6 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ src/base/.devcontainer/language_versions/ .trivyignore_combined.yaml .out/ .envrc +.trivy_out/ diff --git a/.tool-versions b/.tool-versions index 1aed182..2500101 100644 --- a/.tool-versions +++ b/.tool-versions @@ -5,5 +5,4 @@ shellcheck 0.11.0 direnv 2.37.1 actionlint 1.7.10 ruby 3.3.0 -trivy 0.69.3 yq 4.52.2 diff --git a/Makefile b/Makefile index 8b40ad8..f5767ed 100644 --- a/Makefile +++ b/Makefile @@ -10,6 +10,9 @@ guard-%: exit 1; \ fi +.PHONY: install install-python install-node install-hooks build-base-image build-node-24-image build-node-24-python-3-10-image build-node-24-python-3-12-image build-node-24-python-3-13-image build-node-24-python-3-14-image \ + build-eps-storage-terraform-image build-fhir-facade-image build-node-24-python-3-14-golang-1-24-image build-node-24-python-3-14-java-24-image \ + build-regression-tests-image build-all build-image build-githubactions-image scan-image scan-image-json shell-image lint test lint-githubactions lint-githubaction-scripts github-login clean install: install-python install-node install-hooks install-python: @@ -129,13 +132,9 @@ test: lint-githubactions: actionlint -github-login: - gh auth login --scopes read:packages - lint-githubaction-scripts: shellcheck .github/scripts/*.sh clean: rm -rf .out find . -type f -name '.trivyignore_combined.yaml' -delete - diff --git a/src/base/.devcontainer/.tool-versions b/src/base/.devcontainer/.tool-versions index bac5b7b..7aaf5f8 100644 --- a/src/base/.devcontainer/.tool-versions +++ b/src/base/.devcontainer/.tool-versions @@ -2,5 +2,4 @@ shellcheck 0.11.0 direnv 2.37.1 actionlint 1.7.11 ruby 3.3.0 -trivy 0.69.3 yq 4.52.4 diff --git a/src/base/.devcontainer/Dockerfile b/src/base/.devcontainer/Dockerfile index 1d39021..69d2bf8 100644 --- a/src/base/.devcontainer/Dockerfile +++ b/src/base/.devcontainer/Dockerfile @@ -1,3 +1,19 @@ +FROM golang:1.26.1-bookworm AS build +ARG TARGETARCH +RUN apt-get update && apt-get install -y \ + jq \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* +COPY scripts/install_cosign.sh /tmp/install_cosign.sh +COPY scripts/install_trivy.sh /tmp/install_trivy.sh +RUN INSTALL_DIR=/usr/local/bin /tmp/install_cosign.sh +RUN case "${TARGETARCH}" in \ + x86_64|amd64) TRIVY_ARCH=64bit ;; \ + aarch64|arm64) TRIVY_ARCH=ARM64 ;; \ + *) echo "Unsupported TARGETARCH: ${TARGETARCH}" && exit 1 ;; \ + esac \ + && INSTALL_DIR=/tmp/trivy/ ARCH="${TRIVY_ARCH}" /tmp/install_trivy.sh + FROM mcr.microsoft.com/devcontainers/base:ubuntu-22.04 ARG SCRIPTS_DIR=/usr/local/share/eps @@ -16,6 +32,8 @@ COPY --chmod=755 Mk ${SCRIPTS_DIR}/Mk WORKDIR ${SCRIPTS_DIR}/${CONTAINER_NAME} RUN ./root_install.sh +COPY --from=build /tmp/trivy/trivy /usr/local/bin/trivy + COPY --chmod=755 scripts/vscode_install.sh ${SCRIPTS_DIR}/${CONTAINER_NAME}/vscode_install.sh USER vscode COPY --chown=vscode:vscode .tool-versions.asdf /home/vscode/.tool-versions.asdf diff --git a/src/base/.devcontainer/Dockerfile.trivy.amd64 b/src/base/.devcontainer/Dockerfile.trivy.amd64 new file mode 100644 index 0000000..169855e --- /dev/null +++ b/src/base/.devcontainer/Dockerfile.trivy.amd64 @@ -0,0 +1,13 @@ +FROM golang:1.26.1-bookworm AS build +RUN apt-get update && apt-get install -y \ + jq \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* +COPY src/base/.devcontainer/scripts/install_cosign.sh /tmp/install_cosign.sh +COPY src/base/.devcontainer/scripts/install_trivy.sh /tmp/install_trivy.sh +RUN INSTALL_DIR=/usr/local/bin /tmp/install_cosign.sh +RUN INSTALL_DIR=/tmp/trivy/ ARCH=64bit /tmp/install_trivy.sh + +FROM scratch +COPY --from=build /tmp/trivy/trivy / +ENTRYPOINT ["/trivy"] diff --git a/src/base/.devcontainer/Dockerfile.trivy.arm64 b/src/base/.devcontainer/Dockerfile.trivy.arm64 new file mode 100644 index 0000000..379dc5d --- /dev/null +++ b/src/base/.devcontainer/Dockerfile.trivy.arm64 @@ -0,0 +1,13 @@ +FROM golang:1.26.1-bookworm AS build +RUN apt-get update && apt-get install -y \ + jq \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* +COPY scripts/install_cosign.sh /tmp/install_cosign.sh +COPY scripts/install_trivy.sh /tmp/install_trivy.sh +RUN INSTALL_DIR=/usr/local/bin /tmp/install_cosign.sh +RUN INSTALL_DIR=/tmp/trivy/ ARCH=ARM64 /tmp/install_trivy.sh + +FROM scratch +COPY --from=build /tmp/trivy/trivy / +ENTRYPOINT ["/trivy"] diff --git a/src/base/.devcontainer/scripts/install_cosign.sh b/src/base/.devcontainer/scripts/install_cosign.sh new file mode 100755 index 0000000..d0d3d4f --- /dev/null +++ b/src/base/.devcontainer/scripts/install_cosign.sh @@ -0,0 +1,109 @@ +#!/usr/bin/env bash +set -euo pipefail + +DEFAULT_INSTALL_DIR="/usr/local/bin" +INSTALL_DIR="${INSTALL_DIR:-$DEFAULT_INSTALL_DIR}" +REQUESTED_VERSION="${1:-latest}" +OS="$(uname -s)" +ARCH="$(uname -m)" +API_URL="https://api.github.com/repos/sigstore/cosign/releases" + +usage() { + cat <<'EOF' +Usage: install_cosign.sh [version] + +Downloads the requested cosign release (default: latest) for Linux amd64, verifies +its signature, and installs it into $INSTALL_DIR (override via INSTALL_DIR env var). +EOF +} + +if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then + usage + exit 0 +fi + +if [[ "$OS" != "Linux" ]]; then + echo "Error: This installer currently supports Linux only" >&2 + exit 1 +fi + +case "$ARCH" in + x86_64|amd64) + BINARY_NAME="cosign-linux-amd64" + ;; + aarch64|arm64) + BINARY_NAME="cosign-linux-arm64" + ;; + *) + echo "Error: Unsupported architecture $ARCH" >&2 + exit 1 + ;; +esac + +for cmd in curl openssl install go jq; do + if ! command -v "$cmd" >/dev/null 2>&1; then + echo "Error: $cmd is required but not found in PATH" >&2 + exit 1 + fi +done + +get_latest_tag() { + local response + response="$(curl -fsSL "$API_URL/latest")" + awk -F'"' '/tag_name/ {print $4; exit}' <<<"$response" +} + +VERSION="$REQUESTED_VERSION" +if [[ "$VERSION" == "latest" ]]; then + VERSION="$(get_latest_tag)" +fi + +if [[ -z "$VERSION" ]]; then + echo "Error: Unable to determine cosign version" >&2 + exit 1 +fi + +BASE_URL="https://github.com/sigstore/cosign/releases/download/${VERSION}" +TMP_DIR="$(mktemp -d)" +trap 'rm -rf "$TMP_DIR"' EXIT + +download() { + local url="${1}" dest="${2}" + echo "Downloading ${dest} ..." + curl -fsSL "${url}" -o "${dest}" +} + +BIN_PATH="$TMP_DIR/${BINARY_NAME}" +SIGSTORE_PATH="$TMP_DIR/${BINARY_NAME}-kms.sigstore.json" +ARTIFACT_PATH="$TMP_DIR/artifact.pub" +DECODED_SIGSTORE_PATH="$TMP_DIR/cosign-kms.sig.decoded" + +download "${BASE_URL}/${BINARY_NAME}" "$BIN_PATH" +download "${BASE_URL}/${BINARY_NAME}-kms.sigstore.json" "$SIGSTORE_PATH" + +# install tuf-client +go install github.com/theupdateframework/go-tuf/cmd/tuf-client@latest + +# setup tuf-client +SIGSTORE_ROOT_PATH="$TMP_DIR/sigstore-root.json" +curl -o "$SIGSTORE_ROOT_PATH" https://raw.githubusercontent.com/sigstore/root-signing/refs/heads/main/metadata/root_history/10.root.json +tuf-client init https://tuf-repo-cdn.sigstore.dev "$SIGSTORE_ROOT_PATH" + +tuf-client get https://tuf-repo-cdn.sigstore.dev artifact.pub > "$ARTIFACT_PATH" + +cat "$SIGSTORE_PATH" | jq -r .messageSignature.signature | base64 -d > "$DECODED_SIGSTORE_PATH" +pushd "$TMP_DIR" >/dev/null +echo "verifying signature with artifact.pub" +openssl dgst -sha256 -verify "$ARTIFACT_PATH" -signature "$DECODED_SIGSTORE_PATH" "$BIN_PATH" +popd >/dev/null + +echo "verifying signature with cosign verify-blob" +chmod +x "$BIN_PATH" +${BIN_PATH} verify-blob --bundle "${SIGSTORE_PATH}" --key "$ARTIFACT_PATH" "$BIN_PATH" + +mkdir -p "$INSTALL_DIR" +install -m 0755 "$BIN_PATH" "${INSTALL_DIR}/cosign" + +"${INSTALL_DIR}/cosign" version + +echo "cosign ${VERSION} installed to ${INSTALL_DIR}" diff --git a/src/base/.devcontainer/scripts/install_trivy.sh b/src/base/.devcontainer/scripts/install_trivy.sh new file mode 100755 index 0000000..9e0588e --- /dev/null +++ b/src/base/.devcontainer/scripts/install_trivy.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +set -euo pipefail + +DEFAULT_INSTALL_DIR="/usr/local/bin" +INSTALL_DIR="${INSTALL_DIR:-$DEFAULT_INSTALL_DIR}" +VERSION="v0.69.3" +DEFAULT_ARCH="64bit" +ARCH="${ARCH:-$DEFAULT_ARCH}" +RELEASE_NUMBER="${VERSION#v}" +BASE_URL="https://github.com/aquasecurity/trivy/releases/download/${VERSION}" +ARCHIVE="trivy_${RELEASE_NUMBER}_Linux-${ARCH}.tar.gz" +BUNDLE="${ARCHIVE}.sigstore.json" +CERT_IDENTITY="https://github.com/aquasecurity/trivy/.github/workflows/reusable-release.yaml@refs/tags/${VERSION}" + +usage() { + cat <<'EOF' +Usage: install_trivy.sh [output_dir] + +Downloads Trivy, its sigstore bundle, and checksum into output_dir (default: current directory), +then verifies the checksum and the sigstore bundle, following +https://github.com/aquasecurity/trivy/blob/main/docs/getting-started/signature-verification.md. +EOF +} + +if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then + usage + exit 0 +fi + +for cmd in curl cosign sha256sum; do + if ! command -v "$cmd" >/dev/null 2>&1; then + echo "Error: $cmd is required but not found in PATH" >&2 + exit 1 + fi +done + +TMP_DIR="$(mktemp -d)" +trap 'rm -rf "$TMP_DIR"' EXIT + +download() { + local url="${1}" dest="${2}" + echo "Downloading ${dest} ..." + curl -fsSL "${url}" -o "${dest}" +} +ARCHIVE_PATH="${TMP_DIR}/${ARCHIVE}" +BUNDLE_PATH="${TMP_DIR}/${BUNDLE}" +download "${BASE_URL}/${ARCHIVE}" "${ARCHIVE_PATH}" +download "${BASE_URL}/${BUNDLE}" "${BUNDLE_PATH}" + + +cosign verify-blob-attestation "${ARCHIVE_PATH}" \ + --bundle "${BUNDLE_PATH}" \ + --certificate-oidc-issuer="https://token.actions.githubusercontent.com" \ + --certificate-identity "${CERT_IDENTITY}" + +echo "Sigstore verification passed" +tar -xzf "${ARCHIVE_PATH}" -C "${TMP_DIR}" + +mkdir -p "$INSTALL_DIR" +install -m 0755 "$TMP_DIR/trivy" "${INSTALL_DIR}/trivy" + +echo "trivy ${VERSION} installed to ${INSTALL_DIR}"