mirror of
https://github.com/pypa/gh-action-pypi-publish.git
synced 2024-11-09 02:13:33 -05:00
commit
ade57f54dc
3 changed files with 61 additions and 12 deletions
25
README.md
25
README.md
|
@ -25,7 +25,8 @@ tag, or opt-in to [use a full Git commit SHA] and Dependabot.
|
||||||
|
|
||||||
### Trusted publishing
|
### Trusted publishing
|
||||||
|
|
||||||
> **NOTE**: Trusted publishing is sometimes referred to by its
|
> [!NOTE]
|
||||||
|
> Trusted publishing is sometimes referred to by its
|
||||||
> underlying technology -- OpenID Connect, or OIDC for short.
|
> underlying technology -- OpenID Connect, or OIDC for short.
|
||||||
> If you see references to "OIDC publishing" in the context of PyPI,
|
> If you see references to "OIDC publishing" in the context of PyPI,
|
||||||
> this is what they're referring to.
|
> this is what they're referring to.
|
||||||
|
@ -61,10 +62,11 @@ jobs:
|
||||||
uses: pypa/gh-action-pypi-publish@release/v1
|
uses: pypa/gh-action-pypi-publish@release/v1
|
||||||
```
|
```
|
||||||
|
|
||||||
> **Pro tip**: instead of using branch pointers, like `unstable/v1`, pin
|
> [!NOTE]
|
||||||
versions of Actions that you use to tagged versions or sha1 commit identifiers.
|
> Pro tip: instead of using branch pointers, like `unstable/v1`, pin versions of
|
||||||
This will make your workflows more secure and better reproducible, saving you
|
> Actions that you use to tagged versions or sha1 commit identifiers.
|
||||||
from sudden and unpleasant surprises.
|
> This will make your workflows more secure and better reproducible, saving you
|
||||||
|
> from sudden and unpleasant surprises.
|
||||||
|
|
||||||
Other indices that support trusted publishing can also be used, like TestPyPI:
|
Other indices that support trusted publishing can also be used, like TestPyPI:
|
||||||
|
|
||||||
|
@ -76,7 +78,8 @@ Other indices that support trusted publishing can also be used, like TestPyPI:
|
||||||
```
|
```
|
||||||
_(don't forget to update the environment name to `testpypi` or similar!)_
|
_(don't forget to update the environment name to `testpypi` or similar!)_
|
||||||
|
|
||||||
> **Pro tip**: only set the `id-token: write` permission in the job that does
|
> [!NOTE]
|
||||||
|
> Pro tip: only set the `id-token: write` permission in the job that does
|
||||||
> publishing, not globally. Also, try to separate building from publishing
|
> publishing, not globally. Also, try to separate building from publishing
|
||||||
> — this makes sure that any scripts maliciously injected into the build
|
> — this makes sure that any scripts maliciously injected into the build
|
||||||
> or test environment won't be able to elevate privileges while flying under
|
> or test environment won't be able to elevate privileges while flying under
|
||||||
|
@ -96,7 +99,8 @@ This GitHub Action [has nothing to do with _building package
|
||||||
distributions_]. Users are responsible for preparing dists for upload
|
distributions_]. Users are responsible for preparing dists for upload
|
||||||
by putting them into the `dist/` folder prior to running this Action.
|
by putting them into the `dist/` folder prior to running this Action.
|
||||||
|
|
||||||
> **IMPORTANT**: Since this GitHub Action is docker-based, it can only
|
> [!IMPORTANT]
|
||||||
|
> Since this GitHub Action is docker-based, it can only
|
||||||
> be used from within GNU/Linux based jobs in GitHub Actions CI/CD
|
> be used from within GNU/Linux based jobs in GitHub Actions CI/CD
|
||||||
> workflows. This is by design and is unlikely to change due to a number
|
> workflows. This is by design and is unlikely to change due to a number
|
||||||
> of considerations we rely on.
|
> of considerations we rely on.
|
||||||
|
@ -187,9 +191,10 @@ default) setting as follows:
|
||||||
skip-existing: true
|
skip-existing: true
|
||||||
```
|
```
|
||||||
|
|
||||||
> **Pro tip**: try to avoid enabling this setting where possible. If you
|
> [!NOTE]
|
||||||
have steps for publishing to both PyPI and TestPyPI, consider only using
|
> Pro tip: try to avoid enabling this setting where possible. If you
|
||||||
it for the latter, having the former fail loudly on duplicates.
|
> have steps for publishing to both PyPI and TestPyPI, consider only using
|
||||||
|
> it for the latter, having the former fail loudly on duplicates.
|
||||||
|
|
||||||
### For Debugging
|
### For Debugging
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import base64
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
|
@ -50,6 +52,25 @@ _SERVER_REFUSED_TOKEN_EXCHANGE_MESSAGE = """
|
||||||
Token request failed: the server refused the request for the following reasons:
|
Token request failed: the server refused the request for the following reasons:
|
||||||
|
|
||||||
{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}
|
||||||
|
"""
|
||||||
|
|
||||||
|
_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}`
|
||||||
|
* `job_workflow_ref`: `{job_workflow_ref}`
|
||||||
|
* `ref`: `{ref}`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Rendered if the package index's token response isn't valid JSON.
|
# Rendered if the package index's token response isn't valid JSON.
|
||||||
|
@ -121,6 +142,23 @@ def assert_successful_audience_call(resp: requests.Response, domain: str):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def render_claims(token: str) -> str:
|
||||||
|
_, payload, _ = token.split(".", 2)
|
||||||
|
claims = json.loads(base64.urlsafe_b64decode(payload))
|
||||||
|
|
||||||
|
def _get(name: str) -> str: # noqa: WPS430
|
||||||
|
return claims.get(name, "MISSING")
|
||||||
|
|
||||||
|
return _RENDERED_CLAIMS.format(
|
||||||
|
sub=_get("sub"),
|
||||||
|
repository=_get("repository"),
|
||||||
|
repository_owner=_get("repository_owner"),
|
||||||
|
repository_owner_id=_get("repository_owner_id"),
|
||||||
|
job_workflow_ref=_get("job_workflow_ref"),
|
||||||
|
ref=_get("ref"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
repository_url = get_normalized_input("repository-url")
|
repository_url = get_normalized_input("repository-url")
|
||||||
repository_domain = urlparse(repository_url).netloc
|
repository_domain = urlparse(repository_url).netloc
|
||||||
token_exchange_url = f"https://{repository_domain}/_/oidc/github/mint-token"
|
token_exchange_url = f"https://{repository_domain}/_/oidc/github/mint-token"
|
||||||
|
@ -165,7 +203,13 @@ if not mint_token_resp.ok:
|
||||||
for error in mint_token_payload["errors"]
|
for error in mint_token_payload["errors"]
|
||||||
)
|
)
|
||||||
|
|
||||||
die(_SERVER_REFUSED_TOKEN_EXCHANGE_MESSAGE.format(reasons=reasons))
|
rendered_claims = render_claims(oidc_token)
|
||||||
|
|
||||||
|
die(
|
||||||
|
_SERVER_REFUSED_TOKEN_EXCHANGE_MESSAGE.format(
|
||||||
|
reasons=reasons, rendered_claims=rendered_claims,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
pypi_token = mint_token_payload.get("token")
|
pypi_token = mint_token_payload.get("token")
|
||||||
if pypi_token is None:
|
if pypi_token is None:
|
||||||
|
|
|
@ -14,7 +14,7 @@ cffi==1.15.1
|
||||||
# via cryptography
|
# via cryptography
|
||||||
charset-normalizer==3.2.0
|
charset-normalizer==3.2.0
|
||||||
# via requests
|
# via requests
|
||||||
cryptography==41.0.2
|
cryptography==41.0.3
|
||||||
# via secretstorage
|
# via secretstorage
|
||||||
docutils==0.20.1
|
docutils==0.20.1
|
||||||
# via readme-renderer
|
# via readme-renderer
|
||||||
|
|
Loading…
Reference in a new issue