xerial / sbt-sonatype

A sbt plugin for publishing Scala/Java projects to the Maven central.
Apache License 2.0
335 stars 65 forks source link

Support `SONATYPE_TOKEN` environment variable for token authentication #464

Open rtyley opened 10 months ago

rtyley commented 10 months ago

As of January 2024, Sonatype is actively discouraging the legacy username & password method of authentication, recommending token authentication instead.

In this new scheme, the token is still split into a username/password format, and both are randomised strings, making the username portion a meaningful secret (ie one that can be revoked).

xerial commented 10 months ago

Thank you for the PR. However, the user token format xxxx:yyyy is compatible with the legacy scheme if you set SONATYPE_USERNAME = xxxx and SONATYPE_PASSWORD = yyyy. Instead of introducing support for the xxxx:yyyy format, it might be easier to maintain if we simply update the documentation to recommend generating a token.

rtyley commented 10 months ago

the user token format xxxx:yyyy is compatible with the legacy scheme if you set SONATYPE_USERNAME = xxxx and SONATYPE_PASSWORD = yyyy

Absolutely yes, there's no essential need for any new technical support in sbt-sonatype to work with the new token authentication scheme, it does work just great with SONATYPE_USERNAME & SONATYPE_PASSWORD.

I would like a SONATYPE_TOKEN environment variable, however...

I've been working on gha-scala-library-release-workflow for the past month, a reusable GitHub workflow for publishing Scala libraries that uses sbt-sonatype for its 'release' stage - it's currently been applied to 20 projects across the Guardian, and eventually it'll be applied to about 50 repos. Each repo needs a relatively small bit of configuration to use the workflow, a release.yml workflow:

name: Release

on:
  workflow_dispatch:

jobs:
  release:
    uses: guardian/gha-scala-library-release-workflow/.github/workflows/reusable-release.yml@main
    permissions: { contents: write, pull-requests: write }
    secrets:
      SONATYPE_PASSWORD: ${{ secrets.AUTOMATED_MAVEN_RELEASE_SONATYPE_PASSWORD }}
      PGP_PRIVATE_KEY: ${{ secrets.AUTOMATED_MAVEN_RELEASE_PGP_SECRET }}

As this file needs to be duplicated across many repos, I've done the best I can to keep its size small- for instance, only SONATYPE_PASSWORD was treated as a GitHub secret, to remove the additional line of configuration required to pass down SONATYPE_USERNAME as a secret too.

However, now that token authentication is a thing, the username is a revocable random string, and really should be treated as a secret. To avoid the extra line of config - a line that will be repeated a lot of times - I would really like to be able to support a SONATYPE_TOKEN field covering both values.

To achieve that, I'm not sure I need support in sbt-sonatype - I've tried having the workflow take a SONATYPE_TOKEN that it then splits into SONATYPE_USERNAME & SONATYPE_PASSWORD to call sbt-sonatype, but so far that hasn't worked - it looks like I'm falling foul of GitHub's attempts to mask secret values and stop them being exfiltrated via bash. It would be much simpler for me if I could pass down a SONATYPE_TOKEN environment variable, and have the sbt plugin code take care of parsing it.

I could also argue that using SONATYPE_TOKEN is also more explicit about using the new form of authentication - and maybe that's true - but the main motivation is reduction of config for this workflow.

xerial commented 10 months ago

Thanks for the background and I understand the pain point. Right. It would not be easy to combine multiple credentials in GitHub Actions.

rtyley commented 5 months ago

Sonatype is now requiring token authentication, which I think maybe makes a stronger case for introducing SONATYPE_TOKEN?

Attempting to release now with a Nexus UI username and password login will get this 401 Content access is protected by token error:

Caused by: java.io.IOException: Unexpected server response: 401 Content access is protected by token
    at org.sonatype.spice.zapper.client.hc4.Hc4Client.upload(Hc4Client.java:107)
    at org.sonatype.spice.zapper.client.hc4.Hc4Track.call(Hc4Track.java:38)
    at org.sonatype.spice.zapper.client.hc4.Hc4Track.call(Hc4Track.java:23)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
    at java.base/java.lang.Thread.run(Thread.java:840)  - (Sonatype.scala:443)