ci.yml 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. # Main quality gate (task 42).
  2. #
  3. # Per-PR legs (all must be green, except `coverage` which is report-only):
  4. # fmt, clippy, audit, deny, sqlx-prepare-check, nextest, doc-tests,
  5. # coverage (llvm-cov report upload over lib unit tests; non-blocking),
  6. # fuzz-build (nightly leg inside `fuzz/`, so the pinned stable
  7. # `rust-toolchain.toml` at the root is never violated).
  8. # Scheduled legs (not per-PR):
  9. # mutants (weekly budgeted mutation run over the pure security modules),
  10. # fuzz-run (bounded run of every fuzz target; the per-PR leg only builds),
  11. # miri (scoped interpreter run over the sync crypto/JOSE helpers;
  12. # allow-failure, never gates).
  13. #
  14. # Must NOT gate on miri for tokio/sqlx/redis; no lint suppressions anywhere.
  15. # The `miri` leg therefore runs only sync unit tests (`crypto::{ct, pkce,
  16. # tokens}` and the sync subset of `jose::jar` — the fetch-decode,
  17. # request-URI-membership, and alg-rejection tests, which short-circuit before
  18. # any crypto or I/O), excluding the argon2 KDF
  19. # (`crypto::secret`, cost prohibitive under the interpreter), the `ring`-backed
  20. # modules (`crypto::field`, `crypto::totp`, whose C/asm calls the interpreter
  21. # cannot execute), and every
  22. # `#[tokio::test]` / redis-backed test (DPoP replay store, async claim
  23. # paths, including the async `jose::jar::resolve` path itself — only its sync
  24. # unit tests are selected); the whole leg is `continue-on-error` so it can
  25. # never gate a PR. If a `#[tokio::test]` or a real-verification test is ever
  26. # added under `jose::jar`, narrow the filter to the explicit sync test paths.
  27. name: ci
  28. on:
  29. push:
  30. branches: ["master"]
  31. pull_request: {}
  32. schedule:
  33. # Weekly budgeted mutation run (Sunday 03:00 UTC).
  34. - cron: "0 3 * * 0"
  35. workflow_dispatch: {}
  36. permissions:
  37. contents: read
  38. env:
  39. CARGO_TERM_COLOR: always
  40. SQLX_OFFLINE: "true"
  41. # Shared compilation cache (each compiling job below starts it via
  42. # sccache-action, except `miri`: cargo-miri overrides RUSTC_WRAPPER
  43. # itself). Incremental is useless on ephemeral runners, and disabling it
  44. # maximizes cache hits.
  45. SCCACHE_GHA_ENABLED: "true"
  46. CARGO_INCREMENTAL: "0"
  47. jobs:
  48. fmt:
  49. name: fmt
  50. runs-on: ubuntu-24.04
  51. steps:
  52. - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
  53. - uses: dtolnay/rust-toolchain@686976e191b89faba57d3206551f0f330d8cb249 # stable
  54. with:
  55. components: rustfmt
  56. - name: Check formatting
  57. run: cargo fmt --check
  58. clippy:
  59. name: clippy
  60. runs-on: ubuntu-24.04
  61. env:
  62. RUSTC_WRAPPER: sccache
  63. steps:
  64. - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
  65. - uses: dtolnay/rust-toolchain@686976e191b89faba57d3206551f0f330d8cb249 # stable
  66. with:
  67. components: clippy
  68. - name: Start sccache (shared compilation cache)
  69. uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
  70. with:
  71. version: "v0.18.0"
  72. - name: Clippy (all targets, warnings denied)
  73. run: cargo clippy --all-targets -- -D warnings
  74. audit:
  75. name: audit
  76. runs-on: ubuntu-24.04
  77. steps:
  78. - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
  79. - uses: dtolnay/rust-toolchain@686976e191b89faba57d3206551f0f330d8cb249 # stable
  80. - name: Install cargo-audit
  81. uses: taiki-e/install-action@ab6895351a88b908e96a4697860d4ffea5e57dbe # v2
  82. with:
  83. tool: cargo-audit
  84. - name: Audit advisories
  85. run: cargo audit
  86. deny:
  87. name: deny
  88. runs-on: ubuntu-24.04
  89. steps:
  90. - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
  91. - uses: dtolnay/rust-toolchain@686976e191b89faba57d3206551f0f330d8cb249 # stable
  92. - name: Install cargo-deny
  93. uses: taiki-e/install-action@ab6895351a88b908e96a4697860d4ffea5e57dbe # v2
  94. with:
  95. tool: cargo-deny
  96. - name: Check supply chain (ring-only TLS enforced in deny.toml)
  97. run: cargo deny check
  98. sqlx-prepare-check:
  99. name: sqlx-prepare-check
  100. runs-on: ubuntu-24.04
  101. env:
  102. RUSTC_WRAPPER: sccache
  103. services:
  104. postgres:
  105. image: postgres:16
  106. env:
  107. POSTGRES_USER: postgres
  108. POSTGRES_PASSWORD: sqlxcheck
  109. POSTGRES_DB: postgres
  110. ports:
  111. - 5432:5432
  112. options: >-
  113. --health-cmd "pg_isready -U postgres"
  114. --health-interval 5s
  115. --health-timeout 5s
  116. --health-retries 10
  117. steps:
  118. - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
  119. - uses: dtolnay/rust-toolchain@686976e191b89faba57d3206551f0f330d8cb249 # stable
  120. - name: Start sccache (shared compilation cache)
  121. uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
  122. with:
  123. version: "v0.18.0"
  124. - name: Install sqlx-cli
  125. uses: taiki-e/install-action@ab6895351a88b908e96a4697860d4ffea5e57dbe # v2
  126. with:
  127. tool: sqlx-cli@0.9.0
  128. - name: Apply migrations to the check database
  129. env:
  130. DATABASE_URL: postgres://postgres:sqlxcheck@127.0.0.1:5432/postgres
  131. run: cargo sqlx migrate run
  132. - name: Fail if the committed .sqlx cache is stale
  133. env:
  134. DATABASE_URL: postgres://postgres:sqlxcheck@127.0.0.1:5432/postgres
  135. run: cargo sqlx prepare --check --workspace
  136. doc-links:
  137. name: doc-links
  138. runs-on: ubuntu-24.04
  139. steps:
  140. - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
  141. - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
  142. with:
  143. python-version: "3.x"
  144. - name: Check documentation links (markdown-link-check equivalent)
  145. run: python3 scripts/check_doc_links.py
  146. nextest:
  147. name: nextest
  148. runs-on: ubuntu-24.04
  149. env:
  150. RUSTC_WRAPPER: sccache
  151. steps:
  152. - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
  153. - uses: dtolnay/rust-toolchain@686976e191b89faba57d3206551f0f330d8cb249 # stable
  154. - name: Start sccache (shared compilation cache)
  155. uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
  156. with:
  157. version: "v0.18.0"
  158. - name: Install cargo-nextest
  159. uses: taiki-e/install-action@ab6895351a88b908e96a4697860d4ffea5e57dbe # v2
  160. with:
  161. tool: nextest
  162. - name: Install redis-server (in-process Sentinel/Cluster fixtures)
  163. run: sudo apt-get update && sudo apt-get install -y redis-server
  164. - name: Start shared test services (Postgres + Redis)
  165. run: docker compose -f compose.test.yaml up -d
  166. - name: Full test suite (shared services; testcontainers for lifecycle)
  167. run: cargo nextest run --all-targets
  168. - name: Stop shared test services
  169. if: always()
  170. run: docker compose -f compose.test.yaml down -v
  171. doc-tests:
  172. name: doc-tests
  173. runs-on: ubuntu-24.04
  174. env:
  175. RUSTC_WRAPPER: sccache
  176. steps:
  177. - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
  178. - uses: dtolnay/rust-toolchain@686976e191b89faba57d3206551f0f330d8cb249 # stable
  179. - name: Start sccache (shared compilation cache)
  180. uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
  181. with:
  182. version: "v0.18.0"
  183. - name: Documentation tests
  184. run: cargo test --doc
  185. fuzz-build:
  186. name: fuzz-build
  187. runs-on: ubuntu-24.04
  188. env:
  189. RUSTC_WRAPPER: sccache
  190. defaults:
  191. run:
  192. working-directory: fuzz
  193. steps:
  194. - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
  195. # The `fuzz/rust-toolchain.toml` in the working directory selects
  196. # nightly via rustup override; the root stable pin is untouched.
  197. - name: Install cargo-fuzz (nightly, per fuzz/rust-toolchain.toml)
  198. uses: taiki-e/install-action@ab6895351a88b908e96a4697860d4ffea5e57dbe # v2
  199. with:
  200. tool: cargo-fuzz
  201. - name: Start sccache (shared compilation cache)
  202. uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
  203. with:
  204. version: "v0.18.0"
  205. # Explicit gnu target: the prebuilt cargo-fuzz binary is musl-static,
  206. # so its default target is musl (ASan-incompatible, and musl std is
  207. # not installed). Never drop this flag without checking the default.
  208. - name: Build all fuzz targets
  209. run: cargo fuzz build --target x86_64-unknown-linux-gnu
  210. fuzz-run:
  211. name: fuzz-run
  212. # Scheduled bounded fuzzing. `fuzz-build` (above) only compiles the
  213. # targets per PR; this leg actually executes them, so a crash is found
  214. # without a human starting it. It is bounded and scheduled, not 24/7:
  215. # continuous coverage is ClusterFuzzLite (`.clusterfuzzlite/`, docs in
  216. # `docs/CONTRIBUTING.md`); the remaining owner step is enabling it.
  217. if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
  218. runs-on: ubuntu-24.04
  219. env:
  220. RUSTC_WRAPPER: sccache
  221. timeout-minutes: 70
  222. strategy:
  223. fail-fast: false
  224. matrix:
  225. target: [token_form, dcr_json, jar_claims, dpop_proof, jar_verify, redirect_uri, scope_parse]
  226. defaults:
  227. run:
  228. working-directory: fuzz
  229. steps:
  230. - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
  231. # `fuzz/rust-toolchain.toml` selects nightly; the root stable pin is
  232. # untouched, exactly as in `fuzz-build`.
  233. - name: Install cargo-fuzz (nightly)
  234. uses: taiki-e/install-action@ab6895351a88b908e96a4697860d4ffea5e57dbe # v2
  235. with:
  236. tool: cargo-fuzz
  237. - name: Start sccache (shared compilation cache)
  238. uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
  239. with:
  240. version: "v0.18.0"
  241. # `uses` steps resolve paths from the workspace root, not the job's
  242. # `working-directory`; keep the fuzz/ prefix explicit.
  243. - name: Restore corpus for ${{ matrix.target }}
  244. uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5
  245. with:
  246. path: fuzz/corpus/${{ matrix.target }}
  247. key: fuzz-corpus-${{ matrix.target }}-${{ github.run_id }}
  248. restore-keys: |
  249. fuzz-corpus-${{ matrix.target }}-
  250. # Same explicit target as `fuzz-build`: cargo-fuzz defaults to the
  251. # installer binary's own platform (musl), not the runner's (gnu).
  252. - name: Fuzz ${{ matrix.target }} (bounded)
  253. run: cargo fuzz run --target x86_64-unknown-linux-gnu ${{ matrix.target }} -- -max_total_time=600 -rss_limit_mb=2048
  254. # Save only on success; a per-run key grows the corpus while the prefix
  255. # `restore-keys` always pick the newest entry.
  256. - name: Save corpus for ${{ matrix.target }}
  257. if: success()
  258. uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5
  259. with:
  260. path: fuzz/corpus/${{ matrix.target }}
  261. key: fuzz-corpus-${{ matrix.target }}-${{ github.run_id }}
  262. - name: Upload crash artifacts
  263. if: failure()
  264. uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
  265. with:
  266. name: fuzz-crash-${{ matrix.target }}
  267. path: fuzz/artifacts/${{ matrix.target }}
  268. if-no-files-found: ignore
  269. mutants:
  270. name: mutants
  271. # Scheduled budgeted job only: never blocks a PR.
  272. if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
  273. runs-on: ubuntu-24.04
  274. env:
  275. RUSTC_WRAPPER: sccache
  276. timeout-minutes: 60
  277. steps:
  278. - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
  279. - uses: dtolnay/rust-toolchain@686976e191b89faba57d3206551f0f330d8cb249 # stable
  280. - name: Start sccache (shared compilation cache)
  281. uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
  282. with:
  283. version: "v0.18.0"
  284. - name: Install cargo-mutants
  285. uses: taiki-e/install-action@ab6895351a88b908e96a4697860d4ffea5e57dbe # v2
  286. with:
  287. tool: cargo-mutants
  288. - name: Budgeted mutation run (see .cargo/mutants.toml)
  289. run: cargo mutants --timeout 1800 --test-tool nextest
  290. coverage:
  291. name: coverage
  292. # Report-only coverage signal (T4-4). Non-blocking by design: the upload
  293. # is the deliverable; there is no `--fail-under-*` threshold yet.
  294. # Services mirror `nextest` (shared Postgres/Redis plus redis-server),
  295. # because several lib unit tests are testcontainers-backed.
  296. # Follow-up: add a threshold and extend beyond `--lib` once
  297. # the first reports land.
  298. runs-on: ubuntu-24.04
  299. env:
  300. RUSTC_WRAPPER: sccache
  301. continue-on-error: true
  302. steps:
  303. - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
  304. - uses: dtolnay/rust-toolchain@686976e191b89faba57d3206551f0f330d8cb249 # stable
  305. with:
  306. components: llvm-tools-preview
  307. - name: Start sccache (shared compilation cache)
  308. uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
  309. with:
  310. version: "v0.18.0"
  311. - name: Install cargo-llvm-cov
  312. uses: taiki-e/install-action@ab6895351a88b908e96a4697860d4ffea5e57dbe # v2
  313. with:
  314. tool: cargo-llvm-cov
  315. - name: Install redis-server (in-process Sentinel/Cluster fixtures)
  316. run: sudo apt-get update && sudo apt-get install -y redis-server
  317. - name: Start shared test services (Postgres + Redis)
  318. run: docker compose -f compose.test.yaml up -d
  319. - name: Coverage report (lib unit tests only; integration tests stay in `nextest`)
  320. run: cargo llvm-cov --lib --lcov --output-path lcov.info
  321. - name: Stop shared test services
  322. if: always()
  323. run: docker compose -f compose.test.yaml down -v
  324. - name: Upload coverage report
  325. if: always()
  326. uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
  327. with:
  328. name: coverage-lcov
  329. path: lcov.info
  330. if-no-files-found: error
  331. miri:
  332. name: miri
  333. # Scoped miri leg (T4-4). Scheduled only: the interpreter is slow and
  334. # nightly-only, so this never runs per-PR and never gates
  335. # (`continue-on-error`). Nightly is confined to this job; the root
  336. # `rust-toolchain.toml` stable pin is untouched.
  337. # Scope is the sync unit tests of the dependency-free helpers, plus the
  338. # sync subset of `jose::jar` (fetch-decode, request-URI-membership, and
  339. # alg-rejection tests only — never the async `resolve` path).
  340. # Explicitly excluded, per the header constraint: tokio (`#[tokio::test]`), sqlx, and redis
  341. # (DPoP replay store, async claim paths), plus the argon2 KDF
  342. # (`crypto::secret`, cost prohibitive under the interpreter) and the
  343. # `ring`-backed modules (`crypto::field`, `crypto::totp`, whose C/asm
  344. # calls Miri cannot interpret).
  345. if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
  346. runs-on: ubuntu-24.04
  347. timeout-minutes: 60
  348. continue-on-error: true
  349. steps:
  350. - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
  351. - uses: dtolnay/rust-toolchain@f0d5300ba8908d942c808f4d31c08471d7798ba3 # nightly
  352. with:
  353. components: miri
  354. - name: Scoped miri run (sync crypto/JOSE helpers only)
  355. env:
  356. # Relaxed isolation for the first scheduled runs (wall-clock/env
  357. # access); tighten in a follow-up once green.
  358. MIRIFLAGS: -Zmiri-disable-isolation
  359. run: >
  360. cargo miri test --lib --
  361. crypto::ct crypto::pkce crypto::tokens
  362. jose::jar