-
Notifications
You must be signed in to change notification settings - Fork 0
251 lines (222 loc) · 12.1 KB
/
ci.yml
File metadata and controls
251 lines (222 loc) · 12.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
# ──────────────────────────────────────────────────────────────────────
# CI — runs on every PR, push to main, and weekly on a schedule.
#
# PURPOSE:
# Gate merges behind automated checks: lint, test, and security
# scanning. This prevents regressions and catches vulnerabilities
# before code reaches the release workflow.
#
# JOBS (run in parallel where possible):
# changes — detects which files changed (dorny/paths-filter)
# check — fmt → clippy → test (skipped for docs-only changes)
# supply-chain — cargo-deny + cargo-audit (dep changes OR schedule)
# secrets — gitleaks (always runs — any file can contain secrets)
# unsafe-audit — cargo-geiger (dep changes OR schedule)
#
# OPTIMIZATIONS:
# • Path filtering: dorny/paths-filter skips expensive Rust jobs when
# only docs/scripts/config changed (saves ~35s+ per docs-only PR).
# • Concurrency: cancel-in-progress prevents wasted runs when you
# push again quickly or when push+PR events both fire.
# • Fail-fast: steps in `check` ordered cheapest-first (fmt → clippy
# → test) so broken PRs fail in seconds, not minutes.
#
# SUPPLY CHAIN SECURITY:
# • All Actions pinned to full commit SHAs (see release.yml header).
# • `cargo deny` checks licenses and advisories on every PR.
# • `cargo audit` catches known CVEs in the dependency tree.
# • `gitleaks` scans for accidentally committed secrets.
# • `cargo-geiger` audits unsafe code surface area.
# • Minimal permissions — only `contents: read` for checkout.
# • Audit tool versions are pinned and verified via SHA-256 checksums
# before execution — no silent upgrades from upstream `latest`.
# • Scheduled weekly run ensures new advisories are caught even when
# no dependency files changed.
#
# RUNNERS:
# • Blacksmith runners for Linux (2× faster, colocated cache).
# • Swatinem/rust-cache automatically uses Blacksmith's cache backend
# when running on Blacksmith runners — no special config needed.
# ──────────────────────────────────────────────────────────────────────
name: CI
on:
pull_request:
push:
branches: [main]
schedule:
# Weekly on Monday at 06:00 UTC — catches new advisories even
# when no dependency files have changed since the last PR.
- cron: '0 6 * * 1'
# Cancel in-progress runs when a new push arrives on the same branch.
# This prevents wasted compute when you push rapid fixes, and deduplicates
# the push + pull_request events that GitHub fires simultaneously.
# On main/schedule, use run_id so runs don't cancel each other.
concurrency:
group: ci-${{ (github.event_name == 'push' || github.event_name == 'schedule') && github.run_id || github.head_ref }}
cancel-in-progress: true
# Least-privilege: CI only needs to read the repo
permissions:
contents: read
env:
CARGO_TERM_COLOR: always
jobs:
# ── Path Detection ───────────────────────────────────────────────
# Runs in <5s on a minimal runner. Downstream jobs use its outputs
# to skip expensive work when only docs/scripts/config changed.
changes:
name: Detect changes
runs-on: blacksmith-2vcpu-ubuntu-2204
outputs:
rust: ${{ steps.filter.outputs.rust }}
deps: ${{ steps.filter.outputs.deps }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
id: filter
with:
filters: |
rust:
- 'crates/**'
- 'Cargo.toml'
- 'Cargo.lock'
- 'rust-toolchain.toml'
- 'deny.toml'
- '.github/workflows/**'
deps:
- 'Cargo.toml'
- 'Cargo.lock'
- 'crates/**/Cargo.toml'
- 'deny.toml'
check:
name: Lint & test
needs: changes
if: needs.changes.outputs.rust == 'true'
runs-on: blacksmith-4vcpu-ubuntu-2204
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
with:
components: clippy, rustfmt
# rust-toolchain.toml overrides action inputs, so ensure
# components are installed. This is a no-op if they're
# already listed in rust-toolchain.toml (belt + suspenders).
- name: Ensure toolchain components
run: rustup component add clippy rustfmt
# Blacksmith intercepts cache calls on its runners — this
# uses the colocated cache automatically, no extra config.
- name: Cache Cargo
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
# ── Fail-fast: cheapest checks first ────────────────────
# ~1s — pure text check, no compilation. Catches formatting
# issues before waiting for a full compile.
- name: Formatting
run: cargo fmt --check
# ~30s — compiles all targets AND lints in a single pass.
# No separate `cargo build` step needed — clippy does both.
# -D warnings ensures zero warnings merge.
- name: Clippy
run: cargo clippy --locked --all-targets -- -D warnings
# ~3s — reuses build artifacts from clippy above, so this
# is essentially just running the test harness.
- name: Test
run: cargo test --locked
# Runs in parallel with check — no dependency between them.
# Uses pre-built binaries (not `cargo install`) to avoid the 60s+
# compile-from-source overhead of building cargo-audit.
supply-chain:
name: Supply chain audit
needs: changes
if: needs.changes.outputs.deps == 'true' || github.event_name == 'schedule'
runs-on: blacksmith-2vcpu-ubuntu-2204
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
# Download pre-built binaries (~2s each) instead of
# `cargo install` which compiles from source (~60s+).
# Versions are pinned; SHA-256 checksums are verified before
# extraction to detect tampering or download corruption.
- name: Install cargo-deny
run: |
DENY_VERSION="0.19.1"
DENY_SHA256="baa0d618ce06bd1fb352115de47e31acba439c3e639b990dc43429c948364d70"
curl -sSL "https://github.com/EmbarkStudios/cargo-deny/releases/download/${DENY_VERSION}/cargo-deny-${DENY_VERSION}-x86_64-unknown-linux-musl.tar.gz" \
-o cargo-deny.tar.gz
echo "${DENY_SHA256} cargo-deny.tar.gz" | sha256sum --check --strict
tar xzf cargo-deny.tar.gz --strip-components=1 -C /usr/local/bin
rm cargo-deny.tar.gz
cargo-deny --version
# rustsec/rustsec is a monorepo; cargo-audit releases don't
# include a checksums file, so the SHA-256 is embedded here
# (computed from the official release asset at pin time).
- name: Install cargo-audit
run: |
AUDIT_VERSION="0.22.1"
AUDIT_SHA256="c32506f338bdcdaef5a17fb9f33abb6ecf9561324cfd34237fd335f9283a1eab"
curl -sSL "https://github.com/rustsec/rustsec/releases/download/cargo-audit%2Fv${AUDIT_VERSION}/cargo-audit-x86_64-unknown-linux-musl-v${AUDIT_VERSION}.tgz" \
-o cargo-audit.tgz
echo "${AUDIT_SHA256} cargo-audit.tgz" | sha256sum --check --strict
tar xzf cargo-audit.tgz --strip-components=1 -C /usr/local/bin
rm cargo-audit.tgz
cargo-audit --version
# cargo-deny checks: advisories, licenses, bans, sources
- name: cargo deny check
run: cargo deny check
# cargo-audit is a second layer — checks the RustSec advisory DB
- name: cargo audit
run: cargo audit
# ── Secrets Scanning ─────────────────────────────────────────────
# Gitleaks detects hardcoded secrets (API keys, tokens, passwords)
# in the repo history and staged changes. Runs on a minimal runner
# since it's I/O-bound, not CPU-bound. Blocks the build on any
# finding — secrets in the repo are always a hard stop.
secrets:
name: Secrets scan
runs-on: blacksmith-2vcpu-ubuntu-2204
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0 # full history for scanning all commits
- name: Install gitleaks
run: |
GL_VERSION="8.30.1"
GL_SHA256="551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb"
curl -sSL "https://github.com/gitleaks/gitleaks/releases/download/v${GL_VERSION}/gitleaks_${GL_VERSION}_linux_x64.tar.gz" \
-o gitleaks.tar.gz
echo "${GL_SHA256} gitleaks.tar.gz" | sha256sum --check --strict
tar xzf gitleaks.tar.gz -C /usr/local/bin gitleaks
rm gitleaks.tar.gz
gitleaks version
# Scan the full repo history for secrets. On PRs, you could
# scope to just the diff with --log-opts="origin/main..HEAD"
# but scanning everything catches pre-existing issues too.
- name: Scan for secrets
run: gitleaks detect --source . --config .gitleaks.toml --verbose
# ── Unsafe Code Audit ────────────────────────────────────────────
# cargo-geiger counts `unsafe` usage across the entire dependency
# tree. This is informational (non-blocking) — many safe crates
# use unsafe internally (e.g. ring for crypto, libc for FFI).
# The value is visibility: track unsafe surface area over time
# and catch unexpected new unsafe dependencies in PRs.
unsafe-audit:
name: Unsafe code audit
needs: changes
if: needs.changes.outputs.deps == 'true' || github.event_name == 'schedule'
runs-on: blacksmith-4vcpu-ubuntu-2204
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
- name: Ensure toolchain components
run: rustup component add clippy rustfmt
# Blacksmith caches the compiled cargo-geiger binary after
# the first run — subsequent runs skip the 60s+ compile.
- name: Cache Cargo
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
- name: Install cargo-geiger
run: cargo install cargo-geiger --locked
# Non-blocking (|| true) — geiger can exit non-zero if it
# encounters crates it can't analyze. The output itself is
# the valuable part: review it in PR checks to spot new
# unsafe dependencies entering the tree.
- name: Audit unsafe code usage
run: cargo geiger --all-targets --output-format ascii 2>&1 || true