From 3de7fa87072faa6e10531d00379210ca85194fbc Mon Sep 17 00:00:00 2001 From: Kartik Date: Tue, 4 Aug 2026 11:53:07 +0530 Subject: [PATCH] ci: authenticate to crates.io over OIDC Replaces the stored CARGO_TOKEN with a trusted-publishing exchange, so the run mints a credential scoped to itself and nothing long-lived sits in repository secrets. --- .github/workflows/publish-crates.yml | 16 +++++++++------ CONTRIBUTING.md | 30 ++++++++++++++-------------- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/.github/workflows/publish-crates.yml b/.github/workflows/publish-crates.yml index a751630..60e359c 100644 --- a/.github/workflows/publish-crates.yml +++ b/.github/workflows/publish-crates.yml @@ -30,6 +30,7 @@ jobs: environment: crates.io permissions: contents: write # to push the crates-v* tag + id-token: write # to mint a short-lived crates.io token over OIDC steps: - uses: actions/checkout@v6 @@ -62,18 +63,21 @@ jobs: - name: Package and verify crates run: cargo package --locked -p dagron-core -p dagron-ui + # Exchanges this job's OIDC identity for a token scoped to these crates + # and valid for this run only. Requires a trusted publisher configured on + # each crate for ByteVeda/dagron + publish-crates.yml + environment + # crates.io. No registry token is stored in the repository. + - name: Authenticate to crates.io + id: auth + uses: rust-lang/crates-io-auth-action@v1 + - name: Publish crates env: - CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_TOKEN }} + CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }} VERSION: ${{ steps.version.outputs.version }} run: | set -euo pipefail - if [ -z "${CARGO_REGISTRY_TOKEN:-}" ]; then - echo "::error::CARGO_TOKEN is not set in the 'crates.io' environment" - exit 1 - fi - # Re-running a release must be a no-op for versions already up. already_published() { local crate=$1 code diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0cc8e77..6d50704 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -159,21 +159,21 @@ unsupported platforms build from, and a green wheel job proves nothing about it. ### Registry credentials -Each workflow runs in a GitHub environment that holds its own credential, so -neither is reachable from an ordinary PR build. - -- **PyPI** — the `pypi` environment, using OIDC Trusted Publishing. No stored - token. -- **crates.io** — the `crates.io` environment, using a `CARGO_TOKEN` secret - (a crates.io API token with publish scope for `dagron-core` and - `dagron-ui`). - -crates.io also supports OIDC Trusted Publishing, which would remove the stored -token. It can only be configured for a crate that already exists, so it is a -worthwhile follow-up once the first version of each crate is published: add a -trusted publisher on each crate's settings page pointing at `ByteVeda/dagron` / -`publish-crates.yml` / environment `crates.io`, then swap the `Publish crates` -step over to `rust-lang/crates-io-auth-action` and drop the secret. +Both registries use OIDC Trusted Publishing. **No registry token is stored in +the repository**, so there is nothing to leak or rotate: each job exchanges its +own OIDC identity for a credential valid only for that run. + +- **PyPI** — the `pypi` environment, trusted publisher for `publish.yml`. +- **crates.io** — the `crates.io` environment. Each of `dagron-core` and + `dagron-ui` needs a trusted publisher on its crates.io settings page + pointing at `ByteVeda/dagron` / `publish-crates.yml` / environment + `crates.io`. A crate with none configured fails to publish — the trust is + per crate, not per repository. + +Both crates should also have **Require trusted publishing for all new +versions** enabled, which makes crates.io reject API tokens outright. Note +that this closes the manual `cargo publish` break-glass path too: with it on, +a release can only go out through this workflow. ## Project Structure