Skip to content

Commit a9c7f80

Browse files
didependabot[bot]woodruffw
authored
Support attestations from Google Cloud publishers (#18013)
* chore(deps): bump pypi-attestations from 0.0.23 to 0.0.24 Bumps [pypi-attestations](https://github.com/trailofbits/pypi-attestations) from 0.0.23 to 0.0.24. - [Release notes](https://github.com/trailofbits/pypi-attestations/releases) - [Changelog](https://github.com/trailofbits/pypi-attestations/blob/main/CHANGELOG.md) - [Commits](trailofbits/pypi-attestations@v0.0.23...v0.0.24) --- updated-dependencies: - dependency-name: pypi-attestations dependency-version: 0.0.24 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <[email protected]> * chore(deps): bump pypi-attestations from 0.0.24 to 0.0.25 * Support attestations from Google Cloud publishers * Update docs * Update docs/user/attestations/producing-attestations.md Co-authored-by: William Woodruff <[email protected]> --------- Signed-off-by: dependabot[bot] <[email protected]> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: William Woodruff <[email protected]>
1 parent fcdd05d commit a9c7f80

File tree

8 files changed

+50
-9
lines changed

8 files changed

+50
-9
lines changed

docs/user/attestations/index.md

+12
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,13 @@ attestations per file: one for each of the allowed predicates. Uploads with more
3737
than two attestations per file, or with attestations with repeated predicates will
3838
be rejected.
3939

40+
Currently, PyPI allows for attestations to be signed by the following Trusted
41+
Publisher identities:
42+
43+
* [GitHub Actions]
44+
* [GitLab CI/CD]
45+
* [Google Cloud]
46+
4047
[in-toto Attestation Framework]: https://github.com/in-toto/attestation/blob/main/spec/README.md
4148

4249
[PEP 740]: https://peps.python.org/pep-0740/
@@ -49,4 +56,9 @@ be rejected.
4956

5057
[SLSA Provenance]: https://slsa.dev/spec/v1.0/provenance
5158

59+
[GitHub Actions]: /trusted-publishers/using-a-publisher/#github-actions
60+
61+
[GitLab CI/CD]: /trusted-publishers/using-a-publisher/#gitlab-cicd
62+
63+
[Google Cloud]: /trusted-publishers/using-a-publisher/#google-cloud
5264

docs/user/attestations/producing-attestations.md

+20
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,25 @@ Before uploading attestations to the index, please:
203203
generated attestations from it.
204204
- The publish job now calls `twine` passing the `--attestations` flag, to enable attestation upload.
205205

206+
=== "Google Cloud"
207+
208+
[`pypi-attestations`][pypi-attestations] is a convenience library and CLI
209+
for generating and interacting with attestation objects. You can use
210+
either interface to produce attestations.
211+
212+
For example, to generate attestations for all distributions in `dist/`:
213+
214+
```bash
215+
python -m pip install pypi-attestations
216+
python -m pypi_attestations sign dist/*
217+
```
218+
219+
If the above is run within a Google Cloud service with a [workload identity]
220+
(such as Cloud Build, Compute Engine, etc.), it will use the [ambient
221+
identity] of the service that invoked it.
222+
223+
See [pypi-attestations' documentation] for usage as a Python library.
224+
206225

207226
[Trusted Publishing]: /trusted-publishers/
208227

@@ -228,3 +247,4 @@ Before uploading attestations to the index, please:
228247

229248
[GitLab Trusted Publishing]: /trusted-publishers/using-a-publisher/#gitlab-cicd
230249
[Linux Foundation Immutable Record notice]: https://lfprojects.org/policies/hosted-project-tools-immutable-records/
250+
[workload identity]: https://cloud.google.com/iam/docs/workload-identity-federation

docs/user/attestations/publish/v1.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ that project to verify the following:
2424
(such as a locally-held API token).
2525
2. That a *specific* Trusted Publisher identity was used to publish to the
2626
project, such as a particular GitHub Actions workflow, GitLab identity,
27-
etc.
27+
Google Cloud service account, etc.
2828

2929
Put together, these allow users to assert a higher degree of confidence in
3030
the integrity (but not necessarily trustworthiness) of projects published to PyPI,

requirements/main.in

+1-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ redis>=2.8.0,<6.0.0
6464
rfc3986
6565
sentry-sdk
6666
setuptools
67-
pypi-attestations==0.0.23
67+
pypi-attestations==0.0.25
6868
sqlalchemy[asyncio]>=2.0,<3.0
6969
stdlib-list
7070
stripe

requirements/main.txt

+3-3
Original file line numberDiff line numberDiff line change
@@ -1816,9 +1816,9 @@ pyparsing==3.2.3 \
18161816
--hash=sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf \
18171817
--hash=sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be
18181818
# via linehaul
1819-
pypi-attestations==0.0.23 \
1820-
--hash=sha256:2eb89bf121d983ad58dcee70a550982bb2221b0c447b191a9291c9841c7f1ed6 \
1821-
--hash=sha256:f8530f4d0aa2aab335130b9ba1cfbadc06b118c73a3836fa74d00b94c4678163
1819+
pypi-attestations==0.0.25 \
1820+
--hash=sha256:5afd0fb151445b9ad154b5a41a7097b010d881e994173022033e913eab36a974 \
1821+
--hash=sha256:9f62b34c35b481e5931979f3f222340001041127bd82945b36e34b198075d331
18221822
# via -r requirements/main.in
18231823
pyqrcode==1.2.1 \
18241824
--hash=sha256:1b2812775fa6ff5c527977c4cd2ccb07051ca7d0bc0aecf937a43864abe5eff6 \

tests/unit/attestations/test_services.py

+3-4
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def test_interface_matches(self):
4343

4444
@pytest.mark.parametrize(
4545
"publisher_factory",
46-
[GitHubPublisherFactory, GitLabPublisherFactory],
46+
[GitHubPublisherFactory, GitLabPublisherFactory, GooglePublisherFactory],
4747
)
4848
def test_build_provenance(self, db_request, dummy_attestation, publisher_factory):
4949
db_request.oidc_publisher = publisher_factory.create()
@@ -94,7 +94,6 @@ def test_parse_attestations_fails_no_publisher(self, db_request):
9494
@pytest.mark.parametrize(
9595
"publisher_factory",
9696
[
97-
GooglePublisherFactory,
9897
ActiveStatePublisherFactory,
9998
],
10099
)
@@ -317,7 +316,7 @@ def test_parse_attestations_succeeds(
317316

318317
@pytest.mark.parametrize(
319318
"publisher_factory",
320-
[GitHubPublisherFactory, GitLabPublisherFactory],
319+
[GitHubPublisherFactory, GitLabPublisherFactory, GooglePublisherFactory],
321320
)
322321
def test_build_provenance_succeeds(
323322
self, metrics, db_request, publisher_factory, dummy_attestation
@@ -347,7 +346,7 @@ def test_build_provenance_succeeds(
347346

348347
@pytest.mark.parametrize(
349348
"publisher_factory",
350-
[GitHubPublisherFactory, GitLabPublisherFactory],
349+
[GitHubPublisherFactory, GitLabPublisherFactory, GooglePublisherFactory],
351350
)
352351
def test_extract_attestations_from_request_empty_list(db_request, publisher_factory):
353352
db_request.oidc_publisher = publisher_factory.create()

tests/unit/oidc/models/test_google.py

+5
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,11 @@ def test_exists(self, db_request, exists_in_db):
209209

210210
assert publisher.exists(db_request.db) == exists_in_db
211211

212+
def test_google_publisher_attestation_identity(self):
213+
publisher = google.GooglePublisher(email="[email protected]")
214+
identity = publisher.attestation_identity
215+
assert identity.email == publisher.email
216+
212217

213218
class TestPendingGooglePublisher:
214219
@pytest.mark.parametrize("sub", ["fakesubject", None])

warehouse/oidc/models/google.py

+5
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
from typing import Any
1414

15+
from pypi_attestations import GooglePublisher as GoogleIdentity, Publisher
1516
from sqlalchemy import ForeignKey, String, UniqueConstraint, and_, exists
1617
from sqlalchemy.dialects.postgresql import UUID
1718
from sqlalchemy.orm import Query, mapped_column
@@ -93,6 +94,10 @@ def publisher_base_url(self):
9394
def publisher_url(self, claims=None):
9495
return None
9596

97+
@property
98+
def attestation_identity(self) -> Publisher | None:
99+
return GoogleIdentity(email=self.email)
100+
96101
def stored_claims(self, claims=None):
97102
return {}
98103

0 commit comments

Comments
 (0)