Skip to content

ci: nuget publish via Trusted Publishing (OIDC), drop long-lived API key - #629

Open
Nucs wants to merge 1 commit into
masterfrom
ci/nuget-trusted-publishing
Open

ci: nuget publish via Trusted Publishing (OIDC), drop long-lived API key#629
Nucs wants to merge 1 commit into
masterfrom
ci/nuget-trusted-publishing

Conversation

@Nucs

@Nucs Nucs commented Sep 6, 2026

Copy link
Copy Markdown
Member

Summary

Switches the publish-nuget job to NuGet.org Trusted Publishing (OIDC) — no more long-lived NUGETAPIKEY. GitHub mints a short-lived, signed OIDC token (id-token: write), NuGet/login@v1 exchanges it at nuget.org's token endpoint for a temporary API key (valid 1 hour), and dotnet nuget push uses that key.

nuget.org validates the token's repo-owner / repo / workflow-file claims against the Trusted Publishing policy already registered under the SciSharp owner (workflow file .github/workflows/build-and-release.yml, no environment, scope Push new packages and package versions, glob *NumSharp*).

Why: removes the long-lived, rotate-by-hand credential and the new-package first-publish authorization risk. Keyless publishing is the OpenSSF-recommended posture and cannot leak a standing secret.

Changes (one file)

.github/workflows/build-and-release.ymlpublish-nuget:

  • job-level permissions (id-token: write + contents: read + actions: read) — replaces top-level write-all for this job so id-token is granted explicitly and the rest stays least-privilege;
  • a NuGet/login@v1 step (user: ${{ secrets.NUGET_USER }}, id: nuget-login) requested immediately before the push so the 1-hour key can't expire;
  • push now uses ${{ steps.nuget-login.outputs.NUGET_API_KEY }} instead of ${{ secrets.NUGETAPIKEY }}.

This PR publishes nothing — the release jobs (validate-release/build-nuget/publish-nuget/create-release) run only on a v* tag.

⚠️ Required before the next release

  1. Add repo secret NUGET_USER = the nuget.org profile username (NOT email) of the account that owns the Trusted Publishing policy. Without it the login step fails with an empty user.
  2. Once a trusted publish is verified end to end, the NUGETAPIKEY secret is unused and can be deleted.

Notes

  • NuGet/login@v1 pinned by major tag, matching the repo convention (actions/*@v4, softprops/*@v2, setup-python@v5).

The publish-nuget job in .github/workflows/build-and-release.yml no longer
authenticates with the NUGETAPIKEY secret. It now uses NuGet.org Trusted
Publishing: GitHub mints a short-lived, signed OIDC token (new job permission
id-token: write), NuGet/login@v1 exchanges it at nuget.org's token endpoint for
a temporary API key (valid 1 hour), and `dotnet nuget push` uses that key.
nuget.org validates the token's repo-owner / repo / workflow-file claims against
the Trusted Publishing policy registered under the SciSharp owner (workflow file
.github/workflows/build-and-release.yml, no environment, scope "Push new
packages and package versions", glob *NumSharp*), then returns the key.

Why: removes the long-lived, rotate-by-hand credential (the source of the
new-package-first-publish authorization risk and the manual key rotation just
performed). Keyless publishing is the OpenSSF-recommended posture and cannot
leak a standing secret.

Details:
- A job-level permissions block REPLACES the top-level `write-all` for
  publish-nuget, so id-token:write is granted explicitly (write-all's coverage
  of id-token is not something to depend on); contents:read (unused here, no
  checkout) and actions:read (download-artifact reads this run's nuget-packages
  artifact) keep the job least-privilege.
- `user` is the nuget.org account username (profile name, NOT an email) that
  owns the policy, read from the NUGET_USER repo secret — no username hardcoded.
- Login is requested immediately before the push so the 1-hour temp key cannot
  expire; the push loop, source URL and --skip-duplicate are unchanged.

Operational follow-ups (NOT in this commit):
- Add the NUGET_USER repo secret (the nuget.org profile name of the policy
  owner) before the next release, or the login step fails with an empty user.
- The NUGETAPIKEY secret is now unused by this workflow; delete it once a
  trusted publish is verified end to end.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant