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."