Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changes/authenticate-ci-tool-downloads-6253.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"rscrypto" = "patch"
---

Authenticate CI tool downloads and package-manager installs.
7 changes: 7 additions & 0 deletions .config/ci-tool-archives.tsv
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# tool version os architecture filename url sha256
wasmtime v46.0.1 linux x86_64 wasmtime-v46.0.1-x86_64-linux.tar.xz https://github.com/bytecodealliance/wasmtime/releases/download/v46.0.1/wasmtime-v46.0.1-x86_64-linux.tar.xz 9ae0b17ea298bcc52277a8208d6ab7fae8e1a89579672f9d82f9d86c116edb62
wasmtime v46.0.1 linux aarch64 wasmtime-v46.0.1-aarch64-linux.tar.xz https://github.com/bytecodealliance/wasmtime/releases/download/v46.0.1/wasmtime-v46.0.1-aarch64-linux.tar.xz 071c4def2a08f0ebc95c52dfd4f2886eb697ba495804217cf76e13b09d70a1be
wasmtime v46.0.1 macos x86_64 wasmtime-v46.0.1-x86_64-macos.tar.xz https://github.com/bytecodealliance/wasmtime/releases/download/v46.0.1/wasmtime-v46.0.1-x86_64-macos.tar.xz 0513db67e7089c7e5f743a01427782bc4def83854222f4bc9b1d75f0b925240b
wasmtime v46.0.1 macos aarch64 wasmtime-v46.0.1-aarch64-macos.tar.xz https://github.com/bytecodealliance/wasmtime/releases/download/v46.0.1/wasmtime-v46.0.1-aarch64-macos.tar.xz acee50be70dbe90b0ab2ac7db1321fc44715153a1b1cc58291c97b6d7cffc558
zig 0.17.0-dev.1282+c0f9b51d8 linux x86_64 zig-x86_64-linux-0.17.0-dev.1282+c0f9b51d8.tar.xz https://ziglang.org/builds/zig-x86_64-linux-0.17.0-dev.1282+c0f9b51d8.tar.xz 6d81dec0152f6f11f8a12a84a73535b65070fe01b14a1ce75423870d65b3f270
codecov v11.3.1 linux x86_64 codecov https://cli.codecov.io/v11.3.1/linux/codecov ca1d64196d2d34771084afe76ea657d581bf628e31d993ff8e52ea09cc88a56d
34 changes: 34 additions & 0 deletions .github/actions/scorecard/action.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Run OpenSSF Scorecard
description: Run the exact OpenSSF Scorecard container authenticated by its OCI digest.

inputs:
results_file:
description: Path to the result file.
required: true
results_format:
description: Result format (json or sarif).
required: true
repo_token:
description: GitHub token with read access.
required: false
default: ${{ github.token }}
publish_results:
description: Publish results to the Scorecard API.
required: false
default: "false"
file_mode:
description: Method used to fetch repository files.
required: false
default: archive
internal_publish_base_url:
description: Scorecard publication endpoint.
required: false
default: https://api.scorecard.dev
internal_default_token:
description: Default token used by the Scorecard image.
required: false
default: ${{ github.token }}

runs:
using: docker
image: docker://ghcr.io/ossf/scorecard-action@sha256:2dd6a6d60100f78ef24e14a47941d0087a524b4d3642041558239b1c6097c941 # v2.4.3
18 changes: 6 additions & 12 deletions .github/actions/setup-toolchain/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,9 @@ runs:
echo "toolchain=$TOOLCHAIN" >> "$GITHUB_OUTPUT"
echo "Using toolchain: $TOOLCHAIN"

- name: Install Rust Toolchain (with components)
if: inputs.components != ''
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # v1
with:
toolchain: ${{ steps.read-toolchain.outputs.toolchain }}
components: ${{ inputs.components }}

- name: Install Rust Toolchain (no components)
if: inputs.components == ''
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # v1
with:
toolchain: ${{ steps.read-toolchain.outputs.toolchain }}
- name: Install Rust Toolchain
shell: bash
env:
TOOLCHAIN: ${{ steps.read-toolchain.outputs.toolchain }}
TOOLCHAIN_COMPONENTS: ${{ inputs.components }}
run: scripts/ci/setup-toolchain.sh "$TOOLCHAIN" "$TOOLCHAIN_COMPONENTS"
23 changes: 1 addition & 22 deletions .github/actions/setup/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,6 @@ inputs:
runs:
using: composite
steps:
- name: Detect Host Architecture
id: host
shell: bash
run: echo "uname_m=$(uname -m)" >> "$GITHUB_OUTPUT"

# runs-on.com S3-backed cache intercept. No-op on non-runs-on pools.
- name: Setup MagicCache
if: inputs.enable-magic-cache == 'true'
Expand All @@ -43,27 +38,10 @@ runs:
with:
components: ${{ inputs.toolchain-components }}

# Cargo tools cache — mode in the key so mode swaps don't contaminate a shared slot.
- name: Cache Cargo Tools
if: inputs.tools-mode != 'none'
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cargo/bin
key: cargo-tools-${{ runner.os }}-${{ steps.host.outputs.uname_m }}-${{ inputs.tools-mode }}-v11

- name: Cache BINSEC OPAM Switch
if: inputs.tools-mode == 'ct-linux'
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.opam
key: binsec-opam-${{ runner.os }}-${{ steps.host.outputs.uname_m }}-v2

- name: Install Cargo Tools
if: inputs.tools-mode != 'none'
shell: bash
env:
CARGO_RAIL_VERSION: "0.18.0"
CARGO_SEMVER_CHECKS_VERSION: "0.48.0"
TOOLS_MODE: ${{ inputs.tools-mode }}
run: scripts/ci/install-tools.sh "$TOOLS_MODE"

Expand All @@ -73,6 +51,7 @@ runs:
with:
shared-key: "rscrypto-v1-${{ inputs.cache-key }}"
cache-directories: ${{ inputs.tools-mode == 'rail' && 'target/cargo-rail/cache' || '' }}
cache-bin: false
cache-on-failure: true
# Populate cache on main, manual dispatch, and PRs (so slow platforms
# like IBM/RISC-V warm their cache before merge).
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/_ci-suite.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ jobs:
uses: ./.github/workflows/_rust-job.yaml
with:
runner: runs-on=${{ github.run_id }}/runner=linux-x64-ci
timeout_minutes: 15
timeout_minutes: 45
cache_key: ${{ inputs.cache_key_prefix }}-supply-chain
tools_mode: supply-chain
enable_magic_cache: true
Expand Down
22 changes: 13 additions & 9 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,28 +33,32 @@ jobs:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 1
fetch-depth: 0
persist-credentials: false

- name: Build Plan (PR)
- name: Setup Planner (PR)
if: github.event_name == 'pull_request'
id: rail
uses: loadingalias/cargo-rail-action@90f7f739028887508c6e8d8b7cc510dc612eb653 # v5.1.0
id: rail_setup
uses: ./.github/actions/setup
with:
version: "0.18.0"
cache-key: rail-plan
tools-mode: rail
toolchain-components: ""
enable-magic-cache: false
enable-rust-cache: true

- name: Resolve Build Plan
if: always()
id: resolve
env:
RAIL_PLAN_STEP_OUTCOME: ${{ steps.rail.outcome }}
RAIL_SCOPE_JSON: ${{ steps.rail.outputs.scope-json }}
RAIL_PLAN_STEP_OUTCOME: ${{ steps.rail_setup.outcome }}
RAIL_SINCE: origin/${{ github.base_ref }}
run: scripts/ci/resolve-rail-plan.sh

- name: Check Release Intent Coverage
if: github.event_name == 'pull_request'
env:
RAIL_BASE_REF: ${{ steps.rail.outputs.base-ref }}
RAIL_BASE_REF: origin/${{ github.base_ref }}
run: cargo rail change check --since "$RAIL_BASE_REF" --required

dependency-smoke:
Expand All @@ -63,7 +67,7 @@ jobs:
uses: ./.github/workflows/_rust-job.yaml
with:
runner: ubuntu-latest
timeout_minutes: 20
timeout_minutes: 45
cache_key: dependabot-smoke
tools_mode: supply-chain
toolchain_components: "clippy, rustfmt"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
preflight:
name: Preflight
runs-on: ubuntu-latest
timeout-minutes: 45
timeout-minutes: 90
outputs:
crate_name: ${{ steps.preflight.outputs.crate_name }}
crate_sha256: ${{ steps.preflight.outputs.crate_sha256 }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/scorecard.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
persist-credentials: false

- name: Run Scorecard
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
uses: ./.github/actions/scorecard
with:
results_file: scorecard.sarif
results_format: sarif
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/weekly.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -199,9 +199,14 @@ jobs:
- name: Total Coverage (nextest + fuzz corpus replay)
run: just test-coverage

- name: Install authenticated Codecov CLI
id: codecov
run: scripts/ci/install-codecov.sh

- name: Upload to Codecov
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with:
binary: ${{ steps.codecov.outputs.binary }}
token: ${{ secrets.CODECOV_TOKEN }}
files: coverage/total.lcov
flags: total
Expand Down
1 change: 1 addition & 0 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ update-check:
check-actions:
@scripts/ci/check-action-pins.sh
@scripts/ci/check-action-pins-test.sh
@scripts/ci/tool-integrity-test.sh
@scripts/ci/dependabot-smoke-test.sh
@scripts/ci/check-ci-ownership.sh
@scripts/ci/check-ci-ownership-test.sh
Expand Down
24 changes: 24 additions & 0 deletions scripts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ its caller.
| `bench/bench.sh` | `just bench`, `just bench-quick` |
| `ci/check-action-pins.sh` | `just check-actions`, `ci/ci-check.sh`, `ci/dependabot-smoke.sh` |
| `ci/check-action-pins-test.sh` | `just check-actions`, `ci/dependabot-smoke.sh` |
| `ci/tool-integrity-test.sh` | `just check-actions` |
| `ci/dependabot-smoke-test.sh` | `just check-actions` |
| `ci/check-ci-ownership.sh` | `just check-actions`, `ci/check-ci-ownership-test.sh` |
| `ci/check-ci-ownership-test.sh`| `just check-actions` |
Expand Down Expand Up @@ -77,6 +78,8 @@ claim in [`docs/secret-lifecycle.md`](../docs/secret-lifecycle.md).
| Script | Callers |
|--------|---------|
| `ci/install-tools.sh` | `.github/actions/setup/action.yaml` |
| `ci/install-codecov.sh` | `weekly.yaml` |
| `ci/setup-toolchain.sh` | `.github/actions/setup-toolchain/action.yaml` |
| `ci/run-rust-job.sh` | `.github/workflows/_rust-job.yaml` |
| `ci/resolve-rail-plan.sh` | `.github/workflows/ci.yaml` |
| `ci/dependabot-smoke.sh` | `ci/run-rust-job.sh` |
Expand All @@ -95,6 +98,27 @@ claim in [`docs/secret-lifecycle.md`](../docs/secret-lifecycle.md).
| `lib/targets.sh` | `scripts/check/check-all.sh`, `scripts/check/check-linux.sh`, `scripts/check/check-ibm.sh` |
| `lib/target-matrix.sh` | `scripts/lib/targets.sh`, `_ci-suite.yaml` (target-matrix job) |
| `lib/toolchain.sh` | `.github/actions/setup-toolchain/action.yaml` |
| `lib/ci-tool-integrity.sh` | `ci/cross-targets.sh`, `ci/install-codecov.sh`, `ci/nostd-wasm-suite.sh` |

## CI tool integrity

Direct executable downloads are declared in
`.config/ci-tool-archives.tsv`. Each supported host has one exact version,
filename, HTTPS URL, and repository-owned SHA-256; the shared verifier checks
the digest before extraction, installation, or execution. OCI tools use an
image digest in their local action definition.

Package-manager tools install into a fresh runner-temporary root; CI never
restores Cargo binaries, Cargo install metadata, Go module state, or OPAM
switches from a cache. Cargo installs exact crates from crates.io and
authenticates crate contents against registry checksums. Go installs an exact
module through the public checksum database. Ubuntu 24.04 APT packages are
exact-versioned and authenticated by signed repository metadata. OPAM uses
exact packages from a repository pinned to a full Git commit and verifies
package source hashes from that immutable metadata. Rustup receives only the
exact toolchain declared in `rust-toolchain.toml`; runner images must provide
rustup, which verifies component downloads against the exact distribution
manifest, because network bootstrap installers are rejected.

## Results layout

Expand Down
3 changes: 0 additions & 3 deletions scripts/check/check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@ set -euo pipefail
# optional deny/audit, doc
# Usage: check.sh [--feature-matrix] [--all] [crate1 crate2 ...]

# Prefer cargo-installed tools over any preinstalled runner tools.
export PATH="$HOME/.cargo/bin:$PATH"

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=../lib/common.sh
source "$SCRIPT_DIR/../lib/common.sh"
Expand Down
88 changes: 86 additions & 2 deletions scripts/ci/check-ci-ownership-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,19 @@ trap 'rm -rf "$TMP_ROOT"' EXIT

make_fixture() {
local fixture=$1
mkdir -p "$fixture/.github" "$fixture/.config" "$fixture/scripts/check" "$fixture/scripts/test"
mkdir -p "$fixture/.github" "$fixture/.config" "$fixture/scripts/check" "$fixture/scripts/lib" "$fixture/scripts/test"
cp -R "$REPO_ROOT/.github/workflows" "$fixture/.github/workflows"
cp -R "$REPO_ROOT/.github/actions" "$fixture/.github/actions"
cp -R "$REPO_ROOT/.github/rulesets" "$fixture/.github/rulesets"
cp -R "$REPO_ROOT/.github/repository-settings" "$fixture/.github/repository-settings"
cp "$REPO_ROOT/.github/dependabot.yaml" "$fixture/.github/dependabot.yaml"
cp "$REPO_ROOT/.config/target-matrix.json" "$fixture/.config/target-matrix.json"
cp "$REPO_ROOT/.config/ci-tool-archives.tsv" "$fixture/.config/ci-tool-archives.tsv"
cp -R "$REPO_ROOT/scripts/ci" "$fixture/scripts/ci"
cp "$REPO_ROOT/scripts/check/check-all.sh" "$REPO_ROOT/scripts/check/check-feature-matrix.sh" "$fixture/scripts/check/"
cp "$REPO_ROOT/scripts/lib/ci-tool-integrity.sh" "$REPO_ROOT/scripts/lib/common.sh" \
"$fixture/scripts/lib/"
cp "$REPO_ROOT/scripts/check/check-all.sh" "$REPO_ROOT/scripts/check/check-feature-matrix.sh" \
"$REPO_ROOT/scripts/check/check.sh" "$fixture/scripts/check/"
cp "$REPO_ROOT/scripts/test/test-feature-matrix.sh" "$fixture/scripts/test/"
}

Expand All @@ -33,6 +38,85 @@ baseline="$TMP_ROOT/baseline"
make_fixture "$baseline"
"$CHECKER" --root "$baseline" >/dev/null

invalid_tool_digest="$TMP_ROOT/invalid-tool-digest"
make_fixture "$invalid_tool_digest"
sed -i.bak 's/ca1d64196d2d34771084afe76ea657d581bf628e31d993ff8e52ea09cc88a56d/not-a-digest/' \
"$invalid_tool_digest/.config/ci-tool-archives.tsv"
rm -f "$invalid_tool_digest/.config/ci-tool-archives.tsv.bak"
expect_failure "$invalid_tool_digest" "direct tool digest is malformed"

mutable_tool_url="$TMP_ROOT/mutable-tool-url"
make_fixture "$mutable_tool_url"
sed -i.bak 's#/download/v46\.0\.1/#/download/Latest/#' \
"$mutable_tool_url/.config/ci-tool-archives.tsv"
rm -f "$mutable_tool_url/.config/ci-tool-archives.tsv.bak"
expect_failure "$mutable_tool_url" "direct tool URL resolves a mutable release"

unexpected_tool_filename="$TMP_ROOT/unexpected-tool-filename"
make_fixture "$unexpected_tool_filename"
awk -F '\t' -v OFS='\t' '$1 == "codecov" { $5 = "codecov-substitute" } { print }' \
"$unexpected_tool_filename/.config/ci-tool-archives.tsv" \
>"$unexpected_tool_filename/.config/ci-tool-archives.tsv.tmp"
mv "$unexpected_tool_filename/.config/ci-tool-archives.tsv.tmp" \
"$unexpected_tool_filename/.config/ci-tool-archives.tsv"
expect_failure "$unexpected_tool_filename" "direct tool URL and filename disagree"

unauthenticated_cargo_installer="$TMP_ROOT/unauthenticated-cargo-installer"
make_fixture "$unauthenticated_cargo_installer"
printf '\ncargo binstall cargo-nextest\n' \
>>"$unauthenticated_cargo_installer/scripts/ci/install-tools.sh"
expect_failure "$unauthenticated_cargo_installer" "Cargo-binstall bypasses package integrity"

poisonable_tool_cache="$TMP_ROOT/poisonable-tool-cache"
make_fixture "$poisonable_tool_cache"
yq eval '.runs.steps += [{"name": "Restore poisonable tools", "uses": "actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9", "with": {"path": "~/.cargo/bin", "key": "known"}}]' -i \
"$poisonable_tool_cache/.github/actions/setup/action.yaml"
expect_failure "$poisonable_tool_cache" "CI tool executables can be restored from a poisonable cache"

rust_cache_binaries="$TMP_ROOT/rust-cache-binaries"
make_fixture "$rust_cache_binaries"
yq eval '(.runs.steps[] | select(.name == "Setup Rust Cache") | .with."cache-bin") = true' -i \
"$rust_cache_binaries/.github/actions/setup/action.yaml"
expect_failure "$rust_cache_binaries" "the Rust build cache can restore Cargo tool executables"

unauthenticated_rustup="$TMP_ROOT/unauthenticated-rustup"
make_fixture "$unauthenticated_rustup"
printf '\n - uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9\n' \
>>"$unauthenticated_rustup/.github/actions/setup-toolchain/action.yaml"
expect_failure "$unauthenticated_rustup" "toolchain setup can run a network bootstrap installer"

unpinned_scorecard="$TMP_ROOT/unpinned-scorecard"
make_fixture "$unpinned_scorecard"
sed -i.bak 's#@sha256:[0-9a-f]*#:v2.4.3#' \
"$unpinned_scorecard/.github/actions/scorecard/action.yaml"
rm -f "$unpinned_scorecard/.github/actions/scorecard/action.yaml.bak"
expect_failure "$unpinned_scorecard" "Scorecard container uses a mutable tag"

floating_codecov="$TMP_ROOT/floating-codecov"
make_fixture "$floating_codecov"
sed -i.bak '/binary:.*steps\.codecov\.outputs\.binary/d' \
"$floating_codecov/.github/workflows/weekly.yaml"
rm -f "$floating_codecov/.github/workflows/weekly.yaml.bak"
expect_failure "$floating_codecov" "Codecov action can download its floating default CLI"

unowned_download="$TMP_ROOT/unowned-download"
make_fixture "$unowned_download"
printf '\ncurl --output /tmp/tool https://example.invalid/tool\n' \
>>"$unowned_download/scripts/ci/run-rust-job.sh"
expect_failure "$unowned_download" "a direct download exists outside the integrity owner"

unowned_package_install="$TMP_ROOT/unowned-package-install"
make_fixture "$unowned_package_install"
printf '\ncargo install ripgrep\n' \
>>"$unowned_package_install/scripts/ci/run-rust-job.sh"
expect_failure "$unowned_package_install" "a package-manager install exists outside the integrity owner"

unowned_test_download="$TMP_ROOT/unowned-test-download"
make_fixture "$unowned_test_download"
printf '#!/usr/bin/env bash\ncurl --output /tmp/tool https://example.invalid/tool\n' \
>"$unowned_test_download/scripts/ci/unreviewed-test.sh"
expect_failure "$unowned_test_download" "a test script bypasses downloader ownership"

missing_action_updates="$TMP_ROOT/missing-action-updates"
make_fixture "$missing_action_updates"
yq eval 'del(.updates[] | select(."package-ecosystem" == "github-actions"))' -i \
Expand Down
Loading