Skip to content

Commit

Permalink
Use OIDC @ GHA to PyPI publishing guide
Browse files Browse the repository at this point in the history
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
  • Loading branch information
webknjaz authored Sep 14, 2023
2 parents 68115b5 + 922208b commit 8eab89a
Show file tree
Hide file tree
Showing 4 changed files with 235 additions and 77 deletions.
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ sphinx-inline-tabs==2021.4.11b9
python-docs-theme==2022.1
sphinx-copybutton==0.5.0
pypa-docs-theme @ git+https://github.com/pypa/pypa-docs-theme.git
sphinx-toolbox==3.5.0
1 change: 1 addition & 0 deletions source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
'sphinx.ext.todo',
'sphinx_inline_tabs',
'sphinx_copybutton',
'sphinx_toolbox.collapse',
]

# config for copy button
Expand Down
105 changes: 87 additions & 18 deletions source/guides/github-actions-ci-cd-sample/publish-to-test-pypi.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
name: Publish Python 🐍 distributions πŸ“¦ to PyPI and TestPyPI
name: Publish Python 🐍 distribution πŸ“¦ to PyPI and TestPyPI

on: push

jobs:
build-n-publish:
name: Build and publish Python 🐍 distributions πŸ“¦ to PyPI and TestPyPI
build:
name: Build distribution πŸ“¦
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
with:
Expand All @@ -20,21 +20,90 @@ jobs:
build
--user
- name: Build a binary wheel and a source tarball
run: >-
python3 -m
build
--sdist
--wheel
--outdir dist/
.
# Actually publish to PyPI/TestPyPI
- name: Publish distribution πŸ“¦ to Test PyPI
uses: pypa/gh-action-pypi-publish@release/v1
run: python3 -m build
- name: Store the distribution packages
uses: actions/upload-artifact@v3
with:
password: ${{ secrets.TEST_PYPI_API_TOKEN }}
repository-url: https://test.pypi.org/legacy/
name: python-package-distributions
path: dist/

publish-to-pypi:
name: >-
Publish Python 🐍 distribution πŸ“¦ to PyPI
if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes
needs:
- build
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/p/<package-name> # Replace <package-name> with your PyPI project name
permissions:
id-token: write # IMPORTANT: mandatory for trusted publishing

steps:
- name: Download all the dists
uses: actions/download-artifact@v3
with:
name: python-package-distributions
path: dist/
- name: Publish distribution πŸ“¦ to PyPI
if: startsWith(github.ref, 'refs/tags')
uses: pypa/gh-action-pypi-publish@release/v1

github-release:
name: >-
Sign the Python 🐍 distribution πŸ“¦ with Sigstore
and upload them to GitHub Release
needs:
- publish-to-pypi
runs-on: ubuntu-latest

permissions:
contents: write # IMPORTANT: mandatory for making GitHub Releases
id-token: write # IMPORTANT: mandatory for sigstore

steps:
- name: Download all the dists
uses: actions/download-artifact@v3
with:
name: python-package-distributions
path: dist/
- name: Sign the dists with Sigstore
uses: sigstore/gh-action-sigstore-python@v1.2.3
with:
password: ${{ secrets.PYPI_API_TOKEN }}
inputs: >-
./dist/*.tar.gz
./dist/*.whl
- name: Upload artifact signatures to GitHub Release
env:
GITHUB_TOKEN: ${{ github.token }}
# Upload to GitHub Release using the `gh` CLI.
# `dist/` contains the built packages, and the
# sigstore-produced signatures and certificates.
run: >-
gh release upload
'${{ github.ref_name }}' dist/**
--repo '${{ github.repository }}'
publish-to-testpypi:
name: Publish Python 🐍 distribution πŸ“¦ to TestPyPI
needs:
- build
runs-on: ubuntu-latest

environment:
name: testpypi
url: https://test.pypi.org/p/<package-name>

permissions:
id-token: write # IMPORTANT: mandatory for trusted publishing

steps:
- name: Download all the dists
uses: actions/download-artifact@v3
with:
name: python-package-distributions
path: dist/
- name: Publish distribution πŸ“¦ to TestPyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
repository-url: https://test.pypi.org/legacy/
Loading

0 comments on commit 8eab89a

Please sign in to comment.