ci.yml 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  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. jobs:
  42. fmt:
  43. name: fmt
  44. runs-on: ubuntu-24.04
  45. steps:
  46. - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
  47. - uses: dtolnay/rust-toolchain@686976e191b89faba57d3206551f0f330d8cb249 # stable
  48. with:
  49. components: rustfmt
  50. - name: Check formatting
  51. run: cargo fmt --check
  52. clippy:
  53. name: clippy
  54. runs-on: ubuntu-24.04
  55. steps:
  56. - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
  57. - uses: dtolnay/rust-toolchain@686976e191b89faba57d3206551f0f330d8cb249 # stable
  58. with:
  59. components: clippy
  60. - name: Clippy (all targets, warnings denied)
  61. run: cargo clippy --all-targets -- -D warnings
  62. audit:
  63. name: audit
  64. runs-on: ubuntu-24.04
  65. steps:
  66. - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
  67. - uses: dtolnay/rust-toolchain@686976e191b89faba57d3206551f0f330d8cb249 # stable
  68. - name: Install cargo-audit
  69. run: cargo install cargo-audit --locked
  70. - name: Audit advisories
  71. run: cargo audit
  72. deny:
  73. name: deny
  74. runs-on: ubuntu-24.04
  75. steps:
  76. - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
  77. - uses: dtolnay/rust-toolchain@686976e191b89faba57d3206551f0f330d8cb249 # stable
  78. - name: Install cargo-deny
  79. run: cargo install cargo-deny --locked
  80. - name: Check supply chain (ring-only TLS enforced in deny.toml)
  81. run: cargo deny check
  82. sqlx-prepare-check:
  83. name: sqlx-prepare-check
  84. runs-on: ubuntu-24.04
  85. services:
  86. postgres:
  87. image: postgres:16
  88. env:
  89. POSTGRES_USER: postgres
  90. POSTGRES_PASSWORD: sqlxcheck
  91. POSTGRES_DB: postgres
  92. ports:
  93. - 5432:5432
  94. options: >-
  95. --health-cmd "pg_isready -U postgres"
  96. --health-interval 5s
  97. --health-timeout 5s
  98. --health-retries 10
  99. steps:
  100. - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
  101. - uses: dtolnay/rust-toolchain@686976e191b89faba57d3206551f0f330d8cb249 # stable
  102. - name: Install sqlx-cli
  103. run: cargo install sqlx-cli --locked --version 0.9.0 --no-default-features --features postgres,rustls
  104. - name: Apply migrations to the check database
  105. env:
  106. DATABASE_URL: postgres://postgres:sqlxcheck@127.0.0.1:5432/postgres
  107. run: cargo sqlx migrate run
  108. - name: Fail if the committed .sqlx cache is stale
  109. env:
  110. DATABASE_URL: postgres://postgres:sqlxcheck@127.0.0.1:5432/postgres
  111. run: cargo sqlx prepare --check --workspace
  112. doc-links:
  113. name: doc-links
  114. runs-on: ubuntu-24.04
  115. steps:
  116. - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
  117. - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
  118. with:
  119. python-version: "3.x"
  120. - name: Check documentation links (markdown-link-check equivalent)
  121. run: python3 scripts/check_doc_links.py
  122. nextest:
  123. name: nextest
  124. runs-on: ubuntu-24.04
  125. steps:
  126. - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
  127. - uses: dtolnay/rust-toolchain@686976e191b89faba57d3206551f0f330d8cb249 # stable
  128. - name: Install cargo-nextest
  129. uses: taiki-e/install-action@ab6895351a88b908e96a4697860d4ffea5e57dbe # v2
  130. with:
  131. tool: nextest
  132. - name: Install redis-server (in-process Sentinel/Cluster fixtures)
  133. run: sudo apt-get update && sudo apt-get install -y redis-server
  134. - name: Start shared test services (Postgres + Redis)
  135. run: docker compose -f compose.test.yaml up -d
  136. - name: Full test suite (shared services; testcontainers for lifecycle)
  137. run: cargo nextest run --all-targets
  138. - name: Stop shared test services
  139. if: always()
  140. run: docker compose -f compose.test.yaml down -v
  141. doc-tests:
  142. name: doc-tests
  143. runs-on: ubuntu-24.04
  144. steps:
  145. - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
  146. - uses: dtolnay/rust-toolchain@686976e191b89faba57d3206551f0f330d8cb249 # stable
  147. - name: Documentation tests
  148. run: cargo test --doc
  149. fuzz-build:
  150. name: fuzz-build
  151. runs-on: ubuntu-24.04
  152. defaults:
  153. run:
  154. working-directory: fuzz
  155. steps:
  156. - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
  157. # The `fuzz/rust-toolchain.toml` in the working directory selects
  158. # nightly via rustup override; the root stable pin is untouched.
  159. - name: Install cargo-fuzz (nightly, per fuzz/rust-toolchain.toml)
  160. run: cargo install cargo-fuzz --locked
  161. - name: Build all fuzz targets
  162. run: cargo fuzz build
  163. fuzz-run:
  164. name: fuzz-run
  165. # Scheduled bounded fuzzing. `fuzz-build` (above) only compiles the
  166. # targets per PR; this leg actually executes them, so a crash is found
  167. # without a human starting it. It is bounded and scheduled, not 24/7:
  168. # continuous coverage is ClusterFuzzLite (`.clusterfuzzlite/`, docs in
  169. # `docs/CONTRIBUTING.md`); the remaining owner step is enabling it.
  170. if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
  171. runs-on: ubuntu-24.04
  172. timeout-minutes: 70
  173. strategy:
  174. fail-fast: false
  175. matrix:
  176. target: [token_form, dcr_json, jar_claims, dpop_proof, jar_verify, redirect_uri, scope_parse]
  177. defaults:
  178. run:
  179. working-directory: fuzz
  180. steps:
  181. - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
  182. # `fuzz/rust-toolchain.toml` selects nightly; the root stable pin is
  183. # untouched, exactly as in `fuzz-build`.
  184. - name: Install cargo-fuzz (nightly)
  185. run: cargo install cargo-fuzz --locked
  186. # `uses` steps resolve paths from the workspace root, not the job's
  187. # `working-directory`; keep the fuzz/ prefix explicit.
  188. - name: Restore corpus for ${{ matrix.target }}
  189. uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5
  190. with:
  191. path: fuzz/corpus/${{ matrix.target }}
  192. key: fuzz-corpus-${{ matrix.target }}-${{ github.run_id }}
  193. restore-keys: |
  194. fuzz-corpus-${{ matrix.target }}-
  195. - name: Fuzz ${{ matrix.target }} (bounded)
  196. run: cargo fuzz run ${{ matrix.target }} -- -max_total_time=600 -rss_limit_mb=2048
  197. # Save only on success; a per-run key grows the corpus while the prefix
  198. # `restore-keys` always pick the newest entry.
  199. - name: Save corpus for ${{ matrix.target }}
  200. if: success()
  201. uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5
  202. with:
  203. path: fuzz/corpus/${{ matrix.target }}
  204. key: fuzz-corpus-${{ matrix.target }}-${{ github.run_id }}
  205. - name: Upload crash artifacts
  206. if: failure()
  207. uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
  208. with:
  209. name: fuzz-crash-${{ matrix.target }}
  210. path: fuzz/artifacts/${{ matrix.target }}
  211. if-no-files-found: ignore
  212. mutants:
  213. name: mutants
  214. # Scheduled budgeted job only: never blocks a PR.
  215. if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
  216. runs-on: ubuntu-24.04
  217. timeout-minutes: 60
  218. steps:
  219. - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
  220. - uses: dtolnay/rust-toolchain@686976e191b89faba57d3206551f0f330d8cb249 # stable
  221. - name: Install cargo-mutants
  222. run: cargo install cargo-mutants --locked
  223. - name: Budgeted mutation run (see .cargo/mutants.toml)
  224. run: cargo mutants --timeout 1800 --test-tool nextest
  225. coverage:
  226. name: coverage
  227. # Report-only coverage signal (T4-4). Non-blocking by design: the upload
  228. # is the deliverable; there is no `--fail-under-*` threshold yet.
  229. # Services mirror `nextest` (shared Postgres/Redis plus redis-server),
  230. # because several lib unit tests are testcontainers-backed.
  231. # Follow-up: add a threshold and extend beyond `--lib` once
  232. # the first reports land.
  233. runs-on: ubuntu-24.04
  234. continue-on-error: true
  235. steps:
  236. - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
  237. - uses: dtolnay/rust-toolchain@686976e191b89faba57d3206551f0f330d8cb249 # stable
  238. with:
  239. components: llvm-tools-preview
  240. - name: Install cargo-llvm-cov
  241. uses: taiki-e/install-action@ab6895351a88b908e96a4697860d4ffea5e57dbe # v2
  242. with:
  243. tool: cargo-llvm-cov
  244. - name: Install redis-server (in-process Sentinel/Cluster fixtures)
  245. run: sudo apt-get update && sudo apt-get install -y redis-server
  246. - name: Start shared test services (Postgres + Redis)
  247. run: docker compose -f compose.test.yaml up -d
  248. - name: Coverage report (lib unit tests only; integration tests stay in `nextest`)
  249. run: cargo llvm-cov --lib --lcov --output-path lcov.info
  250. - name: Stop shared test services
  251. if: always()
  252. run: docker compose -f compose.test.yaml down -v
  253. - name: Upload coverage report
  254. if: always()
  255. uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
  256. with:
  257. name: coverage-lcov
  258. path: lcov.info
  259. if-no-files-found: error
  260. miri:
  261. name: miri
  262. # Scoped miri leg (T4-4). Scheduled only: the interpreter is slow and
  263. # nightly-only, so this never runs per-PR and never gates
  264. # (`continue-on-error`). Nightly is confined to this job; the root
  265. # `rust-toolchain.toml` stable pin is untouched.
  266. # Scope is the sync unit tests of the dependency-free helpers, plus the
  267. # sync subset of `jose::jar` (fetch-decode, request-URI-membership, and
  268. # alg-rejection tests only — never the async `resolve` path).
  269. # Explicitly excluded, per the header constraint: tokio (`#[tokio::test]`), sqlx, and redis
  270. # (DPoP replay store, async claim paths), plus the argon2 KDF
  271. # (`crypto::secret`, cost prohibitive under the interpreter) and the
  272. # `ring`-backed modules (`crypto::field`, `crypto::totp`, whose C/asm
  273. # calls Miri cannot interpret).
  274. if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
  275. runs-on: ubuntu-24.04
  276. timeout-minutes: 60
  277. continue-on-error: true
  278. steps:
  279. - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
  280. - uses: dtolnay/rust-toolchain@f0d5300ba8908d942c808f4d31c08471d7798ba3 # nightly
  281. with:
  282. components: miri
  283. - name: Scoped miri run (sync crypto/JOSE helpers only)
  284. env:
  285. # Relaxed isolation for the first scheduled runs (wall-clock/env
  286. # access); tighten in a follow-up once green.
  287. MIRIFLAGS: -Zmiri-disable-isolation
  288. run: >
  289. cargo miri test --lib --
  290. crypto::ct crypto::pkce crypto::tokens
  291. jose::jar