From dee08976ef311ec2b1844016b3fb56aa62e64d27 Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Tue, 16 Jun 2026 15:01:23 -0400 Subject: [PATCH] ci: classic auth via group-scoped TWINE_USERNAME/TWINE_PASSWORD, v0.1.4 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Trusted Publishing OIDC is blocked for git.unturf.com — PyPI's GitLab provider hardcodes the issuer to gitlab.com (no self-hosted instance field in the Add publisher form). Until PyPI lights up self-hosted GitLab support, classic API token auth is our path. Auth source: TWINE_USERNAME=__token__ + TWINE_PASSWORD= set as group-level CI variables on python/. Both python/ago and python/erldistpy inherit them automatically; engineering/ group gets the same vars for make-post-sell + remarkbox. What this commit changes: - .gitlab-ci.yml pypi-twine stage drops the id_tokens block we briefly tried (OIDC), uses latest twine (no <6 pin) — twine 6 reads TWINE_USERNAME/TWINE_PASSWORD env vars and prefers them over the OIDC attempt - pyproject.toml build-system requires drops the setuptools<77 cap (latest setuptools emits Metadata-Version 2.4 which twine 6 reads fine; locally verified) - docs/PYPI-TRUSTED-PUBLISHING.md updated to note the issuer- hardcoded blocker and the recipe to migrate later when it unblocks (or if we mirror to gitlab.com) --- .gitlab-ci.yml | 18 ++++++++---------- docs/PYPI-TRUSTED-PUBLISHING.md | 25 +++++++++++++++++-------- 2 files changed, 25 insertions(+), 18 deletions(-) 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.