Skip to content

Conversation

facutuesca
Copy link
Contributor

@facutuesca facutuesca commented May 19, 2025

This PR removes the OIDC token exchange logic, replacing it with twine's built-in support for Trusted Publishing present in version 6.1.0.

In practice, this just means running twine without setting the TWINE_PASSWORD env variable whenever we detect the Trusted Publishing flow.

cc @woodruffw

@woodruffw
Copy link
Member

Nice, thanks @facutuesca! I'll do a full review in a bit.

@facutuesca facutuesca force-pushed the ft/twine-trusted-publish branch from dfb8951 to f2d4b3e Compare May 19, 2025 17:45
Copy link
Member

@woodruffw woodruffw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks @facutuesca!

@webknjaz
Copy link
Member

webknjaz commented Sep 3, 2025

I'm going to wait until pypa/twine#1249 gets released (twine 6.1.1?)

cc @woodruffw perhaps you could make that happen?

@woodruffw
Copy link
Member

cc @woodruffw perhaps you could make that happen?

Yeah, I'm OK with a release -- I'll prep one tomorrow and see if Ian's okay with one 🙂

@woodruffw
Copy link
Member

I've opened pypa/twine#1264 for the twine release.

Comment on lines -15 to -111
# The top-level error message that gets rendered.
# This message wraps one of the other templates/messages defined below.
_ERROR_SUMMARY_MESSAGE = """
Trusted publishing exchange failure:
{message}
You're seeing this because the action wasn't given the inputs needed to
perform password-based or token-based authentication. If you intended to
perform one of those authentication methods instead of trusted
publishing, then you should double-check your secret configuration and variable
names.
Read more about trusted publishers at https://docs.pypi.org/trusted-publishers/
Read more about how this action uses trusted publishers at
https://github.com/marketplace/actions/pypi-publish#trusted-publishing
"""

# Rendered if OIDC identity token retrieval fails for any reason.
_TOKEN_RETRIEVAL_FAILED_MESSAGE = """
OpenID Connect token retrieval failed: {identity_error}
This generally indicates a workflow configuration error, such as insufficient
permissions. Make sure that your workflow has `id-token: write` configured
at the job level, e.g.:
```yaml
permissions:
id-token: write
```
Learn more at https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#adding-permissions-settings.
""" # noqa: S105; not a password

# Specialization of the token retrieval failure case, when we know that
# the failure cause is use within a third-party PR.
_TOKEN_RETRIEVAL_FAILED_FORK_PR_MESSAGE = """
OpenID Connect token retrieval failed: {identity_error}
The workflow context indicates that this action was called from a
pull request on a fork. GitHub doesn't give these workflows OIDC permissions,
even if `id-token: write` is explicitly configured.
To fix this, change your publishing workflow to use an event that
forks of your repository cannot trigger (such as tag or release
creation, or a manually triggered workflow dispatch).
""" # noqa: S105; not a password

# Rendered if the package index refuses the given OIDC token.
_SERVER_REFUSED_TOKEN_EXCHANGE_MESSAGE = """
Token request failed: the server refused the request for the following reasons:
{reasons}
This generally indicates a trusted publisher configuration error, but could
also indicate an internal error on GitHub or PyPI's part.
{rendered_claims}
""" # noqa: S105; not a password

_RENDERED_CLAIMS = """
The claims rendered below are **for debugging purposes only**. You should **not**
use them to configure a trusted publisher unless they already match your expectations.
If a claim is not present in the claim set, then it is rendered as `MISSING`.
* `sub`: `{sub}`
* `repository`: `{repository}`
* `repository_owner`: `{repository_owner}`
* `repository_owner_id`: `{repository_owner_id}`
* `workflow_ref`: `{workflow_ref}`
* `job_workflow_ref`: `{job_workflow_ref}`
* `ref`: `{ref}`
* `environment`: `{environment}`
See https://docs.pypi.org/trusted-publishers/troubleshooting/ for more help.
"""

# Rendered if the package index's token response isn't valid JSON.
_SERVER_TOKEN_RESPONSE_MALFORMED_JSON = """
Token request failed: the index produced an unexpected
{status_code} response.
This strongly suggests a server configuration or downtime issue; wait
a few minutes and try again.
You can monitor PyPI's status here: https://status.python.org/
""" # noqa: S105; not a password

# Rendered if the package index's token response isn't a valid API token payload.
_SERVER_TOKEN_RESPONSE_MALFORMED_MESSAGE = """
Token response error: the index gave us an invalid response.
This strongly suggests a server configuration or downtime issue; wait
a few minutes and try again.
""" # noqa: S105; not a password
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@facutuesca could you confirm we're not loosing all this troubleshooting detail?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The troubleshooting details are lost. I checked, and here's what each error looks like now when failing from twine:

  • _TOKEN_RETRIEVAL_FAILED_MESSAGE -> "Unable to retrieve an OIDC token from the CI platform for trusted publishing {e}" (src)
  • _TOKEN_RETRIEVAL_FAILED_FORK_PR_MESSAGE -> Same as above
  • _SERVER_REFUSED_TOKEN_EXCHANGE_MESSAGE -> "The token request failed; the index server gave the following reasons:\n\n{reasons}" (src)
  • _SERVER_TOKEN_RESPONSE_MALFORMED_JSON -> "The token-minting request returned invalid JSON" (src)
  • _SERVER_TOKEN_RESPONSE_MALFORMED_MESSAGE -> "Expected a trusted publishing token but got None" (src)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to find a way to keep this in. Twine might be providing generic info but we need to be more precise, specific to GHA.

Comment on lines +4 to 5
# and also uploading via Trusted Publishing
twine >= 6.1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@facutuesca time to bump to 6.2

Comment on lines -6 to -8
# NOTE: Used to detect an ambient OIDC credential for OIDC publishing,
# NOTE: as well as PEP 740 attestations.
id ~= 1.0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@facutuesca upon rebase, you'll probably need to remove the type stub that #381 added and drop this dep from the pre-commit config.

@webknjaz
Copy link
Member

@woodruffw any chance you could resurrect the Twine API effort?

@facutuesca
Copy link
Contributor Author

@woodruffw any chance you could resurrect the Twine API effort?

As an intermediate solution, would having Twine exit with different status codes depending on the error be possible/desirable? We could disambiguate the error type from the exit code and print the corresponding troubleshooting message (including Twine's stderr output)

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.

3 participants