diff --git a/.github/workflows/build-checks.yml b/.github/workflows/build-checks.yml index 83bfb2c..a03bacb 100644 --- a/.github/workflows/build-checks.yml +++ b/.github/workflows/build-checks.yml @@ -9,6 +9,7 @@ on: - '*/smoke-test.sh' - '*/node-keyring.kbx' - '*/githubcli-archive-keyring.gpg' + - '*/rustup-init-*.sha256' - build-config.sh - .trivyignore.yaml push: @@ -20,6 +21,7 @@ on: - '*/smoke-test.sh' - '*/node-keyring.kbx' - '*/githubcli-archive-keyring.gpg' + - '*/rustup-init-*.sha256' - build-config.sh permissions: {} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e355707..6286ca3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -286,8 +286,8 @@ jobs: fi # Commit each changed README via the contents API (one commit per - # file, same pattern as update-keys.yml). Commits are created by - # the app, so they are verified. + # file, same pattern as update-material.yml). Commits are created + # by the app, so they are verified. while IFS= read -r path; do image="${path%%/*}" existing_sha=$(gh api "repos/${REPO}/contents/${path}?ref=${BRANCH}" \ diff --git a/.github/workflows/update-keys.yml b/.github/workflows/update-material.yml similarity index 67% rename from .github/workflows/update-keys.yml rename to .github/workflows/update-material.yml index 100e966..0dba8f6 100644 --- a/.github/workflows/update-keys.yml +++ b/.github/workflows/update-material.yml @@ -1,9 +1,19 @@ -name: Update Trusted Keys +name: Update Trusted Material + +# Keeps trust material committed to this repository — signing keys, +# keyrings, and per-version checksum files with no signature of their +# own — in sync with each upstream. Format-specific verification (GPG, +# minisign, sha256sum) happens in each image's Dockerfile against the +# committed file; this workflow only keeps that file's content current. on: pull_request: paths: - - .github/workflows/update-keys.yml + - .github/workflows/update-material.yml + # Entries with a version_file re-run promptly when Renovate bumps the + # pinned version, since the currently committed checksum would + # otherwise go stale until the next scheduled run. + - rust/build.yaml schedule: - cron: '0 6 * * 3' workflow_dispatch: @@ -72,6 +82,33 @@ jobs: [cli.github.com](https://cli.github.com/packages/githubcli-archive-keyring.gpg), the GPG keyring that signs GitHub's official apt repository used to install the `gh` CLI in the uv builder stage. + - id: rust-rustup-init-sha256-amd64 + path: rust/rustup-init-amd64.sha256 + # VERSION is replaced with RUSTUP_VERSION, read from version_file below. + url: https://static.rust-lang.org/rustup/archive/VERSION/x86_64-unknown-linux-gnu/rustup-init.sha256 + version_file: rust/build.yaml + version_key: RUSTUP_VERSION + title: "chore(rust): update rustup-init checksum (amd64)" + body: >- + Automated sync of `rust/rustup-init-amd64.sha256` from + [static.rust-lang.org](https://static.rust-lang.org/rustup/), matching the + currently pinned `RUSTUP_VERSION`. rustup publishes no GPG/minisign signature + for `rustup-init`, so this checksum file, committed to the repo and reviewed + like any other change, is the trust anchor `rust/Dockerfile` verifies the + downloaded binary against. + - id: rust-rustup-init-sha256-arm64 + path: rust/rustup-init-arm64.sha256 + url: https://static.rust-lang.org/rustup/archive/VERSION/aarch64-unknown-linux-gnu/rustup-init.sha256 + version_file: rust/build.yaml + version_key: RUSTUP_VERSION + title: "chore(rust): update rustup-init checksum (arm64)" + body: >- + Automated sync of `rust/rustup-init-arm64.sha256` from + [static.rust-lang.org](https://static.rust-lang.org/rustup/), matching the + currently pinned `RUSTUP_VERSION`. rustup publishes no GPG/minisign signature + for `rustup-init`, so this checksum file, committed to the repo and reviewed + like any other change, is the trust anchor `rust/Dockerfile` verifies the + downloaded binary against. steps: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 @@ -81,7 +118,14 @@ jobs: - name: Download remote file env: URL: ${{ matrix.url }} - run: wget -q -T 30 -t 3 -O /tmp/downloaded "${URL}" + VERSION_FILE: ${{ matrix.version_file }} + VERSION_KEY: ${{ matrix.version_key }} + run: | + if [ -n "$VERSION_FILE" ]; then + VERSION=$(KEY="$VERSION_KEY" yq '.variants[0].build_args.[strenv(KEY)]' "$VERSION_FILE") + URL="${URL//VERSION/${VERSION}}" + fi + wget -q -T 30 -t 3 -O /tmp/downloaded "${URL}" - name: Check for changes id: diff diff --git a/rust/Dockerfile b/rust/Dockerfile index 0cde00f..b6951b2 100644 --- a/rust/Dockerfile +++ b/rust/Dockerfile @@ -21,17 +21,22 @@ ENV PATH=/home/dev/.cargo/bin:${PATH} USER dev +# rustup publishes no signature for rustup-init, so verify it against a +# checksum file committed to this repository (kept in sync by +# .github/workflows/update-material.yml and reviewed like any other change) +# instead of one fetched from the same server as the binary. # hadolint ignore=DL3003 # cd only used in subshell -RUN case "${TARGETARCH}" in \ - amd64) RUST_ARCH="x86_64" ;; \ - arm64) RUST_ARCH="aarch64" ;; \ +RUN --mount=type=bind,source=rustup-init-amd64.sha256,target=/tmp/rustup-init-amd64.sha256,ro \ + --mount=type=bind,source=rustup-init-arm64.sha256,target=/tmp/rustup-init-arm64.sha256,ro \ + case "${TARGETARCH}" in \ + amd64) RUST_ARCH="x86_64"; RUSTUP_SHA256_FILE="/tmp/rustup-init-amd64.sha256" ;; \ + arm64) RUST_ARCH="aarch64"; RUSTUP_SHA256_FILE="/tmp/rustup-init-arm64.sha256" ;; \ *) echo "Unsupported TARGETARCH: ${TARGETARCH}" >&2; exit 1 ;; \ esac \ && RUSTUP_TMPDIR="$(mktemp -d)" \ - && RUSTUP_URL="https://static.rust-lang.org/rustup/archive/${RUSTUP_VERSION}/${RUST_ARCH}-unknown-linux-gnu" \ - && wget -q -T 30 -t 3 -P "${RUSTUP_TMPDIR}" "${RUSTUP_URL}/rustup-init" \ - && wget -q -T 30 -t 3 -P "${RUSTUP_TMPDIR}" "${RUSTUP_URL}/rustup-init.sha256" \ - && (cd "${RUSTUP_TMPDIR}" && sha256sum -c rustup-init.sha256) \ + && wget -q -T 30 -t 3 -P "${RUSTUP_TMPDIR}" \ + "https://static.rust-lang.org/rustup/archive/${RUSTUP_VERSION}/${RUST_ARCH}-unknown-linux-gnu/rustup-init" \ + && (cd "${RUSTUP_TMPDIR}" && sha256sum -c "${RUSTUP_SHA256_FILE}") \ && chmod +x "${RUSTUP_TMPDIR}/rustup-init" \ && "${RUSTUP_TMPDIR}/rustup-init" -y --no-modify-path --default-toolchain none \ && rm -rf "${RUSTUP_TMPDIR}" \ diff --git a/rust/README.md b/rust/README.md index 4c608f4..d1900d3 100644 --- a/rust/README.md +++ b/rust/README.md @@ -2,7 +2,7 @@ Dev container image with Rust installed via `rustup`, built on the [debian](../debian) base image. -`rustup` is downloaded directly from the official [rustup release archive](https://static.rust-lang.org/rustup/) and verified against its published SHA-256 checksum. Rust toolchains are managed by `rustup` and stored in `~/.cargo`. +`rustup` is downloaded directly from the official [rustup release archive](https://static.rust-lang.org/rustup/). rustup publishes no signature for `rustup-init`, so it is verified against a SHA-256 checksum file committed to this repository (`rust/rustup-init-.sha256`) rather than one fetched from the same server as the binary. The committed checksum files are kept in sync with the pinned `RUSTUP_VERSION` by an automated workflow and reviewed like any other change, so later tampering with the download channel cannot affect builds. Rust toolchains are managed by `rustup` and stored in `~/.cargo`. ## Image diff --git a/rust/rustup-init-amd64.sha256 b/rust/rustup-init-amd64.sha256 new file mode 100644 index 0000000..5f77da3 --- /dev/null +++ b/rust/rustup-init-amd64.sha256 @@ -0,0 +1 @@ +4acc9acc76d5079515b46346a485974457b5a79893cfb01112423c89aeb5aa10 *./rustup-init diff --git a/rust/rustup-init-arm64.sha256 b/rust/rustup-init-arm64.sha256 new file mode 100644 index 0000000..cb5aac0 --- /dev/null +++ b/rust/rustup-init-arm64.sha256 @@ -0,0 +1 @@ +9732d6c5e2a098d3521fca8145d826ae0aaa067ef2385ead08e6feac88fa5792 *./rustup-init diff --git a/uv/Dockerfile b/uv/Dockerfile index 66740c9..a5bffc0 100644 --- a/uv/Dockerfile +++ b/uv/Dockerfile @@ -16,7 +16,7 @@ USER root SHELL ["/bin/bash", "-o", "pipefail", "-c"] # Install gh from GitHub's official apt repository to verify uv's GitHub # Artifact Attestation below. The repository keyring is committed to this repo -# and kept in sync by the update-keys workflow. +# and kept in sync by .github/workflows/update-material.yml. RUN --mount=type=bind,source=githubcli-archive-keyring.gpg,target=/etc/apt/keyrings/githubcli-archive-keyring.gpg,readonly \ --mount=type=cache,target=/var/cache/apt,sharing=locked \ --mount=type=cache,target=/var/lib/apt,sharing=locked \