@smbcloud / proses / commits / d40967d

Enable trusted publishing to crates.io via OIDC in workflow

Seto committed Apr 5, 2026 at 23:07 UTC d40967dce6866f726db5188ab6f63f801c1f276d
1 file changed +50 -1
.github/workflows/release-crate.yml
+50 -1
@@ -15,6 +15,9 @@ on:
15
16 permissions:
17 contents: read
18 + # Required for crates.io trusted publishing — allows the job to request
19 + # a GitHub OIDC token and exchange it for a short-lived crates.io token.
20 + id-token: write
21
22 env:
23 CARGO_TERM_COLOR: always
@@ -71,10 +74,56 @@ jobs:
74 shell: bash
75 run: cargo test --locked
76
77 + # ── Trusted publishing ────────────────────────────────────────────────
78 + # 1. Request a GitHub OIDC token scoped to crates.io.
79 + # 2. Exchange it with the crates.io trusted-publishing endpoint for a
80 + # short-lived API token (valid for one publish operation only).
81 + # 3. Mask the token immediately so it never appears in logs.
82 + #
83 + # Prerequisites (one-time setup):
84 + # • On crates.io → your crate → "Trusted Publishing" tab, add a publisher:
85 + # Repository owner : smbcloudXYZ
86 + # Repository name : proses
87 + # Workflow filename: release-crate.yml
88 + # Environment name : (leave blank, or match the `environment:` key below)
89 + # • No repository secret is required — the OIDC handshake replaces it.
90 + - name: Obtain crates.io publish token via OIDC
91 + id: crates-token
92 + shell: bash
93 + run: |
94 + # Request a GitHub OIDC JWT scoped to crates.io.
95 + OIDC_TOKEN=$(curl --silent --fail \
96 + -H "Authorization: bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}" \
97 + "${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=crates.io" \
98 + | jq -r '.value')
99 +
100 + if [[ -z "${OIDC_TOKEN}" || "${OIDC_TOKEN}" == "null" ]]; then
101 + echo "::error::Failed to obtain OIDC token — ensure id-token: write is set."
102 + exit 1
103 + fi
104 +
105 + # Exchange the OIDC JWT for a short-lived crates.io publish token.
106 + RESPONSE=$(curl --silent --fail \
107 + -X POST "https://crates.io/api/v1/trusted_publishing/tokens" \
108 + -H "Content-Type: application/json" \
109 + -d "{\"jwt\": \"${OIDC_TOKEN}\"}")
110 +
111 + CRATES_TOKEN=$(echo "${RESPONSE}" | jq -r '.token')
112 +
113 + if [[ -z "${CRATES_TOKEN}" || "${CRATES_TOKEN}" == "null" ]]; then
114 + echo "::error::Failed to exchange OIDC token — check your trusted publisher config on crates.io."
115 + echo "Response: ${RESPONSE}"
116 + exit 1
117 + fi
118 +
119 + # Mask the token so it is redacted in all subsequent log output.
120 + echo "::add-mask::${CRATES_TOKEN}"
121 + echo "token=${CRATES_TOKEN}" >> "$GITHUB_OUTPUT"
122 +
123 - name: Publish
124 shell: bash
125 env:
77 - CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
126 + CARGO_REGISTRY_TOKEN: ${{ steps.crates-token.outputs.token }}
127 run: |
128 if [[ "${{ github.event.inputs.dry_run }}" == "true" ]]; then
129 echo "Dry run — package will not be published."