Skip to content

Commit 0e2f41b

Browse files
committed
Set up trusted publishing and harden workflows
1 parent 58171a2 commit 0e2f41b

File tree

7 files changed

+264
-35
lines changed

7 files changed

+264
-35
lines changed

.github/workflows/audit.yaml

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,22 @@ name: Audit
99
- trunk
1010
schedule:
1111
- cron: "0 0 * * TUE"
12+
permissions: {}
13+
concurrency:
14+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
15+
cancel-in-progress: true
1216
jobs:
1317
ruby:
1418
name: Audit Ruby Dependencies
1519
runs-on: ubuntu-latest
20+
permissions:
21+
contents: read # required to checkout repository
1622

1723
steps:
1824
- name: Checkout repository
19-
uses: actions/checkout@v5.0.0
25+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
26+
with:
27+
persist-credentials: false
2028

2129
- name: Install Ruby toolchain
2230
uses: ruby/setup-ruby@d5126b9b3579e429dd52e51e68624dda2e05be25 # v1.267.0
@@ -35,25 +43,48 @@ jobs:
3543
checks:
3644
- advisories
3745
- bans licenses sources
46+
permissions:
47+
contents: read # required to checkout repository
3848

3949
# Prevent sudden announcement of a new advisory from failing ci:
4050
continue-on-error: ${{ matrix.checks == 'advisories' }}
4151

4252
steps:
4353
- name: Checkout repository
44-
uses: actions/checkout@v5.0.0
54+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
55+
with:
56+
persist-credentials: false
4557

4658
- name: Install Rust toolchain
47-
uses: artichoke/setup-rust/audit@v2.0.1
59+
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master
60+
with:
61+
toolchain: stable
4862

4963
- name: Generate Cargo.lock
5064
run: |
5165
if [[ ! -f "Cargo.lock" ]]; then
5266
cargo generate-lockfile --verbose
5367
fi
5468
55-
- uses: EmbarkStudios/cargo-deny-action@f2ba7abc2abebaf185c833c3961145a3c275caad # v2.0.13
69+
- uses: EmbarkStudios/cargo-deny-action@3fd3802e88374d3fe9159b834c7714ec57d6c979 # v2.0.15
5670
with:
5771
arguments: --locked --all-features
5872
command: check ${{ matrix.checks }}
5973
command-arguments: --show-stats
74+
75+
zizmor:
76+
name: Run zizmor 🌈
77+
runs-on: ubuntu-latest
78+
permissions:
79+
contents: read # required to checkout repository
80+
security-events: write # required to add findings via sarif
81+
steps:
82+
- name: Checkout repository
83+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
84+
with:
85+
persist-credentials: false
86+
87+
- name: Run zizmor 🌈
88+
uses: zizmorcore/zizmor-action@135698455da5c3b3e55f73f4419e481ab68cdd95 # v0.4.1
89+
with:
90+
persona: "pedantic"

.github/workflows/block-merge.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,17 @@ name: Merge
33
"on":
44
pull_request:
55
types: [opened, labeled, unlabeled, synchronize]
6+
permissions: {}
7+
concurrency:
8+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
9+
cancel-in-progress: true
610
jobs:
711
labels:
812
name: Labels
913
runs-on: ubuntu-latest
14+
permissions:
15+
issues: write # required to read and write labels
16+
pull-requests: write # required to read and write labels
1017

1118
steps:
1219
- uses: mheap/github-action-required-labels@8afbe8ae6ab7647d0c9f0cfa7c2f939650d22509 # v5.5

.github/workflows/ci.yaml

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ name: CI
99
- trunk
1010
schedule:
1111
- cron: "0 0 * * WED"
12+
concurrency:
13+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
14+
cancel-in-progress: true
15+
permissions:
16+
contents: read # needed to checkout contents
1217
jobs:
1318
build:
1419
name: Build
@@ -22,10 +27,12 @@ jobs:
2227
RUST_BACKTRACE: 1
2328
steps:
2429
- name: Checkout repository
25-
uses: actions/checkout@v5.0.0
30+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
31+
with:
32+
persist-credentials: false
2633

2734
- name: Install Rust toolchain
28-
uses: artichoke/setup-rust/build-and-test@v2.0.1
35+
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master
2936
with:
3037
toolchain: stable
3138

@@ -53,10 +60,12 @@ jobs:
5360
RUST_BACKTRACE: 1
5461
steps:
5562
- name: Checkout repository
56-
uses: actions/checkout@v5.0.0
63+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
64+
with:
65+
persist-credentials: false
5766

5867
- name: Install Rust toolchain
59-
uses: artichoke/setup-rust/build-and-test@v2.0.1
68+
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master
6069
with:
6170
toolchain: "1.76.0"
6271

@@ -83,12 +92,15 @@ jobs:
8392
RUST_BACKTRACE: 1
8493
steps:
8594
- name: Checkout repository
86-
uses: actions/checkout@v5.0.0
95+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
96+
with:
97+
persist-credentials: false
8798

8899
- name: Install Rust toolchain
89-
uses: artichoke/setup-rust/lint-and-format@v2.0.1
100+
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master
90101
with:
91102
toolchain: stable
103+
components: rustfmt,clippy
92104

93105
- name: Check formatting
94106
run: cargo fmt --check
@@ -101,7 +113,9 @@ jobs:
101113
runs-on: ubuntu-latest
102114
steps:
103115
- name: Checkout repository
104-
uses: actions/checkout@v5.0.0
116+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
117+
with:
118+
persist-credentials: false
105119

106120
- name: Install Ruby toolchain
107121
uses: ruby/setup-ruby@d5126b9b3579e429dd52e51e68624dda2e05be25 # v1.267.0
@@ -117,10 +131,12 @@ jobs:
117131
runs-on: ubuntu-latest
118132
steps:
119133
- name: Checkout repository
120-
uses: actions/checkout@v5.0.0
134+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
135+
with:
136+
persist-credentials: false
121137

122138
- name: Setup Node.js runtime
123-
uses: actions/setup-node@v6.0.0
139+
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
124140
with:
125141
node-version: "lts/*"
126142

.github/workflows/code-coverage.yaml

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,40 +7,45 @@ name: Code Coverage
77
pull_request:
88
branches:
99
- trunk
10+
concurrency:
11+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
12+
cancel-in-progress: true
13+
permissions:
14+
contents: read # needed to checkout contents
1015
jobs:
1116
generate:
1217
name: Generate
1318
permissions:
14-
id-token: write
15-
contents: read
19+
id-token: write # needed to do OIDC dance with AWS
20+
contents: read # needed to checkout contents
1621
runs-on: ubuntu-latest
1722
env:
1823
RUST_BACKTRACE: 1
1924
CARGO_NET_GIT_FETCH_WITH_CLI: true
2025
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
2126
steps:
2227
- name: Checkout repository
23-
uses: actions/checkout@v5.0.0
28+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
2429
with:
2530
persist-credentials: false
2631

2732
- name: Install nightly Rust toolchain
28-
uses: artichoke/setup-rust/code-coverage@v2.0.1
33+
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master
34+
with:
35+
toolchain: nightly
36+
components: llvm-tools-preview
2937

3038
- name: Setup grcov
39+
env:
40+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
3141
run: |
32-
release_url="$(curl \
33-
--url https://api.github.com/repos/mozilla/grcov/releases \
34-
--header 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' \
35-
--header 'content-type: application/json' \
36-
--silent \
37-
--fail \
38-
--retry 5 \
39-
| jq -r '.[0].assets
40-
| map(select(.browser_download_url | test(".*x86_64-unknown-linux-musl.tar.bz2$")))
41-
| .[0].browser_download_url'
42-
)"
43-
curl -sL "$release_url" | sudo tar xvj -C /usr/local/bin/
42+
mkdir -p /tmp/grcov
43+
gh release download \
44+
--repo mozilla/grcov \
45+
--pattern '*x86_64-unknown-linux-musl.tar.bz2' \
46+
--dir /tmp/grcov
47+
# Assuming only one matching asset:
48+
tar xvj -C /usr/local/bin/ -f /tmp/grcov/*.tar.bz2
4449
4550
- name: Show grcov version
4651
run: grcov --version
@@ -63,7 +68,7 @@ jobs:
6368
run: grcov raw-parts*.profraw --source-dir . --keep-only 'src/**/*.rs' --binary-path target/debug -t covdir --filter covered -o target/coverage/coverage.json
6469

6570
- name: Configure AWS Credentials
66-
uses: aws-actions/configure-aws-credentials@00943011d9042930efac3dcd3a170e4273319bc8 # v5.1.0
71+
uses: aws-actions/configure-aws-credentials@61815dcd50bd041e203e49132bacad1fd04d2708 # v5.1.1
6772
if: github.ref == 'refs/heads/trunk'
6873
with:
6974
aws-region: us-west-2

.github/workflows/publish.yaml

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
---
2+
name: Publish
3+
"on":
4+
push:
5+
tags:
6+
- "v*.*.*"
7+
8+
permissions: {}
9+
concurrency:
10+
group: ${{ github.workflow }}-${{ github.ref }}
11+
cancel-in-progress: false
12+
13+
jobs:
14+
crates-io:
15+
name: Publish to crates.io
16+
if: github.event.created == true
17+
runs-on: ubuntu-latest
18+
environment:
19+
name: crates-io-publish
20+
permissions:
21+
actions: read # required to inspect CI workflow run status
22+
contents: read # required to checkout repository
23+
id-token: write # required for trusted publishing to crates.io
24+
env:
25+
CARGO_INCREMENTAL: 0
26+
CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
27+
RUST_BACKTRACE: 1
28+
steps:
29+
- name: Checkout repository
30+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
31+
with:
32+
persist-credentials: false
33+
fetch-depth: 1
34+
35+
- name: Configure ephemeral Rust homes
36+
shell: bash
37+
run: |
38+
set -euo pipefail
39+
# Use per-job temporary directories and avoid cross-run caches.
40+
mkdir -p "${RUNNER_TEMP}/cargo-home" "${RUNNER_TEMP}/rustup-home"
41+
echo "CARGO_HOME=${RUNNER_TEMP}/cargo-home" >> "$GITHUB_ENV"
42+
echo "RUSTUP_HOME=${RUNNER_TEMP}/rustup-home" >> "$GITHUB_ENV"
43+
44+
- name: Validate release tag
45+
shell: bash
46+
env:
47+
TAG: ${{ github.ref_name }}
48+
run: |
49+
set -euo pipefail
50+
if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
51+
echo "release tag '$TAG' does not match vX.Y.Z"
52+
exit 1
53+
fi
54+
55+
- name: Install Rust toolchain
56+
shell: bash
57+
run: |
58+
set -euo pipefail
59+
rustup set profile minimal
60+
rustup toolchain install stable --no-self-update
61+
rustup default stable
62+
rustc -Vv
63+
cargo -V
64+
65+
- name: Ensure Cargo.toml version matches tag
66+
shell: bash
67+
env:
68+
TAG_VERSION: ${{ github.ref_name }}
69+
run: |
70+
set -euo pipefail
71+
TAG_VERSION="${TAG_VERSION#v}"
72+
CARGO_VERSION="$(sed -n 's/^version = "\([0-9][0-9.]*\)".*/\1/p' Cargo.toml | head -n1)"
73+
if [[ -z "$CARGO_VERSION" ]]; then
74+
echo "failed to parse crate version from Cargo.toml"
75+
exit 1
76+
fi
77+
if [[ "$TAG_VERSION" != "$CARGO_VERSION" ]]; then
78+
echo "tag version '$TAG_VERSION' does not match crate version '$CARGO_VERSION'"
79+
exit 1
80+
fi
81+
82+
- name: Wait for CI on tagged commit
83+
shell: bash
84+
env:
85+
GH_TOKEN: ${{ github.token }}
86+
REPO: ${{ github.repository }}
87+
SHA: ${{ github.sha }}
88+
CI_WORKFLOW_NAME: CI
89+
POLL_INTERVAL_SECONDS: 20
90+
MAX_WAIT_SECONDS: 10800
91+
run: |
92+
set -euo pipefail
93+
94+
deadline="$(( $(date +%s) + MAX_WAIT_SECONDS ))"
95+
run_list=(
96+
gh run list
97+
--repo "${REPO}"
98+
--workflow "${CI_WORKFLOW_NAME}"
99+
--commit "${SHA}"
100+
--limit 50
101+
)
102+
103+
while true; do
104+
ci_count="$(
105+
"${run_list[@]}" --json status --jq 'length'
106+
)"
107+
108+
if [[ "${ci_count}" -eq 0 ]]; then
109+
echo "no '${CI_WORKFLOW_NAME}' workflow runs found yet for ${SHA}; waiting..."
110+
else
111+
pending_count="$(
112+
"${run_list[@]}" --json status --jq '[.[] | select(.status != "completed")] | length'
113+
)"
114+
if [[ "${pending_count}" -gt 0 ]]; then
115+
echo "${pending_count} '${CI_WORKFLOW_NAME}' runs pending for ${SHA}; waiting..."
116+
else
117+
non_success_count="$(
118+
"${run_list[@]}" --json conclusion --jq '[.[] | select(.conclusion != "success")] | length'
119+
)"
120+
if [[ "${non_success_count}" -eq 0 ]]; then
121+
echo "all '${CI_WORKFLOW_NAME}' runs for ${SHA} completed successfully."
122+
break
123+
fi
124+
echo "'${CI_WORKFLOW_NAME}' is not green for ${SHA}:"
125+
"${run_list[@]}" --json conclusion,url --jq '.[] | select(.conclusion != "success") | "- \(.url) => \(.conclusion)"'
126+
exit 1
127+
fi
128+
fi
129+
130+
if [[ "$(date +%s)" -ge "${deadline}" ]]; then
131+
echo "timed out waiting for '${CI_WORKFLOW_NAME}' to complete for ${SHA}"
132+
exit 1
133+
fi
134+
sleep "${POLL_INTERVAL_SECONDS}"
135+
done
136+
137+
- name: Authenticate with crates.io
138+
id: auth
139+
uses: rust-lang/crates-io-auth-action@b7e9a28eded4986ec6b1fa40eeee8f8f165559ec # v1.0.3
140+
141+
- name: Publish crate
142+
shell: bash
143+
env:
144+
CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}
145+
run: cargo publish

0 commit comments

Comments
 (0)