Skip to content

Commit 357b01c

Browse files
cursoragentasutermo
andcommitted
Harden CI/CD for trusted publishing (ENG-5308)
- Switch from API token auth to PyPI trusted publishing (OIDC) - Add id-token: write permission for OIDC token generation - Remove UV_PUBLISH_TOKEN secret references; uv publish uses OIDC - Add GitHub environment (pypi/testpypi) for deployment protection rules - Pin all GitHub Actions to immutable commit SHAs - Add branch guard: publish job only runs from main - Set deny-by-default permissions at workflow level (permissions: {}) - Add SLSA build provenance attestation via attest-build-provenance - Add attestations: write permission for provenance generation Co-authored-by: Andrew Suter-Morris <asutermo@users.noreply.github.com>
1 parent 8d6fa89 commit 357b01c

2 files changed

Lines changed: 28 additions & 25 deletions

File tree

‎.github/workflows/publish.yml‎

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,30 +14,36 @@ on:
1414
use_testpypi:
1515
description: 'Use TestPyPI for testing'
1616
required: false
17-
default: false # Change to true when testing publish workflow
17+
default: false
1818
type: boolean
1919

20+
permissions: {}
21+
2022
concurrency:
2123
group: publish-pypi
2224
cancel-in-progress: false
2325

2426
jobs:
2527
publish:
28+
if: github.ref == 'refs/heads/main'
2629
runs-on: ubuntu-latest
30+
environment: ${{ github.event.inputs.use_testpypi == 'true' && 'testpypi' || 'pypi' }}
2731
permissions:
28-
contents: write # Needed for creating releases
32+
contents: write
33+
id-token: write
34+
attestations: write
2935

3036
steps:
3137
- name: Checkout code
32-
uses: actions/checkout@v4
38+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
3339

3440
- name: Set up Python
35-
uses: actions/setup-python@v5
41+
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
3642
with:
3743
python-version: '3.8'
3844

3945
- name: Install uv
40-
uses: astral-sh/setup-uv@v4
46+
uses: astral-sh/setup-uv@38f3f104447c67c051c4a08e39b64a148898af3a # v4
4147
with:
4248
version: "latest"
4349

@@ -69,13 +75,11 @@ jobs:
6975
echo "repository_name=TestPyPI" >> $GITHUB_OUTPUT
7076
echo "publish_url=https://test.pypi.org/legacy/" >> $GITHUB_OUTPUT
7177
echo "view_url=https://test.pypi.org/project/freeplay" >> $GITHUB_OUTPUT
72-
echo "token_secret=TEST_PYPI_API_TOKEN" >> $GITHUB_OUTPUT
7378
else
7479
echo "repository=pypi" >> $GITHUB_OUTPUT
7580
echo "repository_name=PyPI" >> $GITHUB_OUTPUT
7681
echo "publish_url=https://upload.pypi.org/legacy/" >> $GITHUB_OUTPUT
7782
echo "view_url=https://pypi.org/project/freeplay" >> $GITHUB_OUTPUT
78-
echo "token_secret=PYPI_API_TOKEN" >> $GITHUB_OUTPUT
7983
fi
8084
8185
- name: Get latest version from repository
@@ -117,27 +121,24 @@ jobs:
117121
run: |
118122
echo "Built packages:"
119123
ls -la dist/
120-
124+
125+
- name: Generate build provenance attestation
126+
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4
127+
with:
128+
subject-path: dist/*
129+
121130
- name: Publish to TestPyPI
122131
if: steps.repo_config.outputs.repository == 'testpypi'
123132
env:
124-
UV_PUBLISH_TOKEN: ${{ secrets.TEST_PYPI_API_TOKEN }}
125-
UV_PUBLISH_URL: ${{ steps.repo_config.outputs.publish_url }}
126-
run: |
127-
echo "🧪 Publishing to TestPyPI for testing..."
128-
uv publish
133+
UV_PUBLISH_URL: https://test.pypi.org/legacy/
134+
run: uv publish
129135

130136
- name: Publish to PyPI
131137
if: steps.repo_config.outputs.repository != 'testpypi'
132-
env:
133-
UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
134-
UV_PUBLISH_URL: ${{ steps.repo_config.outputs.publish_url }}
135-
run: |
136-
echo "📦 Publishing to PyPI..."
137-
uv publish
138+
run: uv publish
138139

139140
- name: Create GitHub Release
140-
uses: softprops/action-gh-release@v2
141+
uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2
141142
with:
142143
tag_name: v${{ steps.version.outputs.version }}
143144
name: Release v${{ steps.version.outputs.version }}

‎.github/workflows/test.yml‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ on:
66
pull_request:
77
branches: [main]
88

9+
permissions: {}
10+
911
concurrency:
1012
group: ${{ github.workflow }}-${{ github.ref }}
1113
cancel-in-progress: true
@@ -18,21 +20,21 @@ jobs:
1820

1921
steps:
2022
- name: Checkout code
21-
uses: actions/checkout@v4
23+
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
2224

2325
- name: Set up Python
24-
uses: actions/setup-python@v5
26+
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
2527
with:
2628
python-version: '3.8'
2729

2830
- name: Install uv
29-
uses: astral-sh/setup-uv@v4
31+
uses: astral-sh/setup-uv@38f3f104447c67c051c4a08e39b64a148898af3a # v4
3032
with:
3133
version: "latest"
3234

3335
- name: Load cached venv
3436
id: cached-uv-dependencies
35-
uses: actions/cache@v4
37+
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
3638
with:
3739
path: .venv
3840
key: venv-${{ runner.os }}-${{ hashFiles('**/uv.lock') }}
@@ -48,4 +50,4 @@ jobs:
4850
run: make lint-check
4951

5052
- name: Run tests (PR)
51-
run: make test-ci
53+
run: make test-ci

0 commit comments

Comments
 (0)