Skip to content

Commit 8eab89a

Browse files
authored
Use OIDC @ GHA to PyPI publishing guide
This patch rewrites the "Publishing package distribution releases using GitHub Actions CI/CD workflows" guide to use trusted publishing instead of long-lived PyPI API tokens stored as GitHub repository secrets. It also shows how to protect the PyPI publishing job by creating a GitHub Environment that requires a human to manually approve the job run. Besides, it demonstrates how to avoid accidental privilege escalation by employing the principle of least privilege and separating the package build process with reduced privileges from the publishing jobs with elevated privileges that are mandatory for Trusted Publishing to work by being able to access OIDC. As a bonus, this change introduces a job that uploads the dists to GitHub Releases and signs them using Sigstore, which is of special interest as PyPI dropped support for GPG. PR #1261 Refs: * https://github.com/marketplace/actions/pypi-publish#trusted-publishing * https://blog.pypi.org/posts/2023-04-20-introducing-trusted-publishers/ * https://blog.trailofbits.com/2023/05/23/trusted-publishing-a-new-benchmark-for-packaging-security/ * https://docs.pypi.org/trusted-publishers/ * https://blog.pypi.org/posts/2023-05-23-removing-pgp/ * https://www.sigstore.dev
2 parents 68115b5 + 922208b commit 8eab89a

File tree

4 files changed

+235
-77
lines changed

4 files changed

+235
-77
lines changed

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ sphinx-inline-tabs==2021.4.11b9
44
python-docs-theme==2022.1
55
sphinx-copybutton==0.5.0
66
pypa-docs-theme @ git+https://github.com/pypa/pypa-docs-theme.git
7+
sphinx-toolbox==3.5.0

source/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
'sphinx.ext.todo',
3737
'sphinx_inline_tabs',
3838
'sphinx_copybutton',
39+
'sphinx_toolbox.collapse',
3940
]
4041

4142
# config for copy button
Lines changed: 87 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
name: Publish Python 🐍 distributions 📦 to PyPI and TestPyPI
1+
name: Publish Python 🐍 distribution 📦 to PyPI and TestPyPI
22

33
on: push
44

55
jobs:
6-
build-n-publish:
7-
name: Build and publish Python 🐍 distributions 📦 to PyPI and TestPyPI
6+
build:
7+
name: Build distribution 📦
88
runs-on: ubuntu-latest
99

1010
steps:
11-
- uses: actions/checkout@v3
11+
- uses: actions/checkout@v4
1212
- name: Set up Python
1313
uses: actions/setup-python@v4
1414
with:
@@ -20,21 +20,90 @@ jobs:
2020
build
2121
--user
2222
- name: Build a binary wheel and a source tarball
23-
run: >-
24-
python3 -m
25-
build
26-
--sdist
27-
--wheel
28-
--outdir dist/
29-
.
30-
# Actually publish to PyPI/TestPyPI
31-
- name: Publish distribution 📦 to Test PyPI
32-
uses: pypa/gh-action-pypi-publish@release/v1
23+
run: python3 -m build
24+
- name: Store the distribution packages
25+
uses: actions/upload-artifact@v3
3326
with:
34-
password: ${{ secrets.TEST_PYPI_API_TOKEN }}
35-
repository-url: https://test.pypi.org/legacy/
27+
name: python-package-distributions
28+
path: dist/
29+
30+
publish-to-pypi:
31+
name: >-
32+
Publish Python 🐍 distribution 📦 to PyPI
33+
if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes
34+
needs:
35+
- build
36+
runs-on: ubuntu-latest
37+
environment:
38+
name: pypi
39+
url: https://pypi.org/p/<package-name> # Replace <package-name> with your PyPI project name
40+
permissions:
41+
id-token: write # IMPORTANT: mandatory for trusted publishing
42+
43+
steps:
44+
- name: Download all the dists
45+
uses: actions/download-artifact@v3
46+
with:
47+
name: python-package-distributions
48+
path: dist/
3649
- name: Publish distribution 📦 to PyPI
37-
if: startsWith(github.ref, 'refs/tags')
3850
uses: pypa/gh-action-pypi-publish@release/v1
51+
52+
github-release:
53+
name: >-
54+
Sign the Python 🐍 distribution 📦 with Sigstore
55+
and upload them to GitHub Release
56+
needs:
57+
- publish-to-pypi
58+
runs-on: ubuntu-latest
59+
60+
permissions:
61+
contents: write # IMPORTANT: mandatory for making GitHub Releases
62+
id-token: write # IMPORTANT: mandatory for sigstore
63+
64+
steps:
65+
- name: Download all the dists
66+
uses: actions/download-artifact@v3
67+
with:
68+
name: python-package-distributions
69+
path: dist/
70+
- name: Sign the dists with Sigstore
71+
uses: sigstore/[email protected]
3972
with:
40-
password: ${{ secrets.PYPI_API_TOKEN }}
73+
inputs: >-
74+
./dist/*.tar.gz
75+
./dist/*.whl
76+
- name: Upload artifact signatures to GitHub Release
77+
env:
78+
GITHUB_TOKEN: ${{ github.token }}
79+
# Upload to GitHub Release using the `gh` CLI.
80+
# `dist/` contains the built packages, and the
81+
# sigstore-produced signatures and certificates.
82+
run: >-
83+
gh release upload
84+
'${{ github.ref_name }}' dist/**
85+
--repo '${{ github.repository }}'
86+
87+
publish-to-testpypi:
88+
name: Publish Python 🐍 distribution 📦 to TestPyPI
89+
needs:
90+
- build
91+
runs-on: ubuntu-latest
92+
93+
environment:
94+
name: testpypi
95+
url: https://test.pypi.org/p/<package-name>
96+
97+
permissions:
98+
id-token: write # IMPORTANT: mandatory for trusted publishing
99+
100+
steps:
101+
- name: Download all the dists
102+
uses: actions/download-artifact@v3
103+
with:
104+
name: python-package-distributions
105+
path: dist/
106+
- name: Publish distribution 📦 to TestPyPI
107+
uses: pypa/gh-action-pypi-publish@release/v1
108+
with:
109+
repository-url: https://test.pypi.org/legacy/

0 commit comments

Comments
 (0)