diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7b7439f..d01220a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -25,22 +25,20 @@ test: # --------------------------------------------------------------------------- # Ship to PyPI on tag pushes. # -# Tag the commit (``git tag -a v0.0.1 -m 'release'`` then -# ``git push --tags``) and CI builds sdist + wheel and uploads with -# twine. Credentials come from GitLab CI variables ``TWINE_USERNAME`` -# (typically ``__token__``) and ``TWINE_PASSWORD`` (the PyPI API token). +# Auth: TWINE_USERNAME + TWINE_PASSWORD env vars come from project-scoped +# GitLab CI variables (Settings → CI/CD → Variables, masked + protected). +# Standard values: TWINE_USERNAME=__token__ and TWINE_PASSWORD=. +# +# Trusted Publishing OIDC would be cleaner but PyPI's GitLab provider is +# hardcoded to gitlab.com — git.unturf.com self-hosted isn't supported. +# See docs/PYPI-TRUSTED-PUBLISHING.md for the migration recipe whenever +# PyPI adds custom-issuer support (or whenever we mirror to gitlab.com). # --------------------------------------------------------------------------- pypi-twine: stage: pypi-twine tags: ["build"] only: - tags - # GitLab mints a short-lived OIDC ID token (audience=pypi) and injects - # it as PYPI_ID_TOKEN. Twine v6+ auto-detects it and exchanges it with - # PyPI for a scoped upload token. No long-lived secret on the runner. - id_tokens: - PYPI_ID_TOKEN: - aud: pypi script: - python3 -m venv .venv - . .venv/bin/activate diff --git a/docs/PYPI-TRUSTED-PUBLISHING.md b/docs/PYPI-TRUSTED-PUBLISHING.md index de35245..2c8673c 100644 --- a/docs/PYPI-TRUSTED-PUBLISHING.md +++ b/docs/PYPI-TRUSTED-PUBLISHING.md @@ -1,15 +1,24 @@ # Migrating to PyPI Trusted Publishing (OIDC) -Today's auth path: classic API token in `/home/gitlab-runner/.pypirc` -on `build.unturf.com`, used by twine 5.x. Works, but every project that -ships from this runner shares one token, rotation is manual, and we're -pinning `twine<6` + `setuptools<77` to keep the legacy fallback alive. +**Status: BLOCKED for self-hosted git.unturf.com** -Trusted Publishing replaces that with short-lived OIDC tokens that -GitLab mints per-job and PyPI exchanges for an upload-only API token. -No long-lived secret on the runner. Per-project, audited per-pipeline. +PyPI's GitLab Trusted Publisher provider has the issuer URL **hardcoded +to `https://gitlab.com`**. There's no "Issuer URL" / "GitLab instance" +field in the "Add publisher" form. Until PyPI ships support for custom +GitLab issuers (or we mirror releases to gitlab.com), Trusted +Publishing is off the table for our python/* repos. -## When to migrate +Today's working auth path: project-scoped GitLab CI variables +``TWINE_USERNAME=__token__`` + ``TWINE_PASSWORD=`` (masked + +protected). Twine 6 reads them as env vars and skips the OIDC attempt. +Per-project tokens; can be rotated independently. + +When PyPI lights up self-hosted GitLab support (track: +https://github.com/pypi/warehouse/issues — search "self-hosted gitlab +trusted publisher"), or if we move to gitlab.com, the recipe below +applies. + +## When to migrate (once unblocked) Single coordinated change across all four python/* repos. Each repo needs its PyPI pending publisher set up *before* its CI YAML switches.