Skip to content

Commit b09d7d1

Browse files
authored
ci: publish via NuGet Trusted Publishing (keyless OIDC) (#2)
Replace the long-lived NUGET_API_KEY secret with NuGet.org Trusted Publishing per the official docs. The publish job now requests a GitHub OIDC token (id-token: write) and NuGet/login@v1 exchanges it for a short-lived (1 h) API key used by dotnet nuget push. The only secret needed is NUGET_USER (the nuget.org profile name). The old "Require NUGET_API_KEY secret" guard is replaced by a NUGET_USER guard, and the header comment's outdated "not GA / not supported" rationale is removed. All other behavior (master gate, version resolution, tag check, pack, dry_run, tag + GitHub release) is preserved. CHANGELOG updated.
1 parent 047a6f0 commit b09d7d1

2 files changed

Lines changed: 29 additions & 17 deletions

File tree

.github/workflows/dotnet-publish.yml

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ name: Publish .NET client
55
# of truth in src/Apify.Client/Apify.Client.csproj (<Version>). This workflow packs the library,
66
# pushes it to NuGet.org, tags the release, and creates the GitHub release.
77
#
8-
# Publishing uses a NuGet API key read from the NUGET_API_KEY repository secret. NuGet.org Trusted
9-
# Publishing (OIDC) is not used because, per the official docs, it is still being rolled out gradually
10-
# and is not generally available, so it is not an officially supported publishing mechanism we can rely
11-
# on. If/when Trusted Publishing reaches GA, switch to the NuGet/login OIDC flow and drop this secret.
8+
# Publishing uses NuGet.org Trusted Publishing (keyless OIDC): the job requests a GitHub OIDC token
9+
# (permissions.id-token: write) and the NuGet/login action exchanges it for a short-lived (1 hour)
10+
# NuGet API key, so no long-lived NUGET_API_KEY secret is stored in the repository. The only secret
11+
# needed is NUGET_USER, the nuget.org username (profile name, NOT an email) the trusted publishing
12+
# policy is registered under. This requires a Trusted Publishing policy configured on nuget.org for
13+
# this repository/workflow (Repository Owner, Repository, Workflow File = dotnet-publish.yml).
1214
on:
1315
workflow_dispatch:
1416
inputs:
@@ -22,12 +24,12 @@ concurrency:
2224
group: dotnet-publish
2325
cancel-in-progress: false
2426

25-
permissions:
26-
contents: write # create the tagged GitHub release
27-
2827
jobs:
2928
publish:
3029
runs-on: ubuntu-latest
30+
permissions:
31+
contents: write # create the tagged GitHub release
32+
id-token: write # request the GitHub OIDC token that NuGet Trusted Publishing exchanges for a short-lived key
3133
steps:
3234
- name: Checkout
3335
uses: actions/checkout@v4
@@ -83,25 +85,34 @@ jobs:
8385
- name: Pack
8486
run: dotnet pack src/Apify.Client/Apify.Client.csproj --configuration Release --no-build --output ./artifacts
8587

86-
# Fail fast if the publish secret is missing so a run cannot silently pass without publishing.
87-
- name: Require NUGET_API_KEY secret
88+
# Fail fast if the trusted-publishing username is missing so a run cannot silently pass without publishing.
89+
# Note: this only checks the secret. The other prerequisite — a Trusted Publishing policy registered on
90+
# nuget.org for this repo/workflow (Workflow File = dotnet-publish.yml) — cannot be checked here; if it is
91+
# missing the NuGet/login step below fails with nuget.org's own error.
92+
- name: Require NUGET_USER secret
8893
if: ${{ github.event.inputs.dry_run != 'true' }}
8994
env:
90-
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
95+
NUGET_USER: ${{ secrets.NUGET_USER }}
9196
run: |
92-
if [ -z "${NUGET_API_KEY}" ]; then
93-
echo "::error::NUGET_API_KEY secret is empty or missing; cannot publish to NuGet.org."
97+
if [ -z "${NUGET_USER}" ]; then
98+
echo "::error::NUGET_USER secret is empty or missing (set it to the nuget.org profile name, not an email). A NuGet Trusted Publishing policy for this repo/workflow must also be registered on nuget.org; see https://learn.microsoft.com/en-us/nuget/nuget-org/trusted-publishing"
9499
exit 1
95100
fi
96101
102+
# Exchange the GitHub OIDC token for a short-lived NuGet API key. Trusted Publishing keys expire
103+
# after 1 hour, so this runs immediately before the push (not earlier in the job).
104+
- name: NuGet login (OIDC -> short-lived API key)
105+
id: login
106+
if: ${{ github.event.inputs.dry_run != 'true' }}
107+
uses: NuGet/login@v1
108+
with:
109+
user: ${{ secrets.NUGET_USER }}
110+
97111
- name: Push to NuGet
98112
if: ${{ github.event.inputs.dry_run != 'true' }}
99-
env:
100-
# The NuGet.org API key is stored as a repository secret.
101-
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
102113
run: |
103114
dotnet nuget push "./artifacts/*.nupkg" \
104-
--api-key "${NUGET_API_KEY}" \
115+
--api-key "${{ steps.login.outputs.NUGET_API_KEY }}" \
105116
--source https://api.nuget.org/v3/index.json \
106117
--skip-duplicate
107118

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,5 @@
4343
- Public `ApifyClientVersion.ClientVersion` and `ApifyClientVersion.ApiSpecVersion` constants.
4444
- Integration test suite, documentation with runnable examples, a data-model property reference
4545
(`docs/models.md`), and CI workflows for integration tests and publishing (manual NuGet.org publish
46-
using an API key from a repository secret).
46+
via Trusted Publishing: the `NuGet/login` action exchanges a GitHub OIDC token for a short-lived
47+
key, using only the `NUGET_USER` repository secret — no long-lived NuGet API key is stored).

0 commit comments

Comments
 (0)