Skip to content

Commit

Permalink
Add support PEP-740 attestations for GitLab CI/CD (#17125)
Browse files Browse the repository at this point in the history
* bump pypi-attestattions to 0.0.17

Signed-off-by: Facundo Tuesca <[email protected]>

* attestations: add support for upload from GitLab CI/CD

Signed-off-by: Facundo Tuesca <[email protected]>

* locale: re-generate translations

Signed-off-by: Facundo Tuesca <[email protected]>

---------

Signed-off-by: Facundo Tuesca <[email protected]>
Co-authored-by: Dustin Ingram <[email protected]>
  • Loading branch information
facutuesca and di authored Nov 20, 2024
1 parent 5c39da5 commit 69f3af7
Show file tree
Hide file tree
Showing 8 changed files with 69 additions and 28 deletions.
2 changes: 1 addition & 1 deletion requirements/main.in
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ redis>=2.8.0,<6.0.0
rfc3986
sentry-sdk
setuptools
pypi-attestations==0.0.16
pypi-attestations==0.0.17
sqlalchemy[asyncio]>=2.0,<3.0
stdlib-list
stripe
Expand Down
6 changes: 3 additions & 3 deletions requirements/main.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1779,9 +1779,9 @@ pyparsing==3.2.0 \
--hash=sha256:93d9577b88da0bbea8cc8334ee8b918ed014968fd2ec383e868fb8afb1ccef84 \
--hash=sha256:cbf74e27246d595d9a74b186b810f6fbb86726dbf3b9532efb343f6d7294fe9c
# via linehaul
pypi-attestations==0.0.16 \
--hash=sha256:1d816719b5067ef49ac47c7ae229bd03752c57f957daf95b19b2ba19a66220e4 \
--hash=sha256:cbd2b946fe160793606dee4516ef58ac5959456e69630a7f03e7de73aa7f2737
pypi-attestations==0.0.17 \
--hash=sha256:5936c0c69af4e31d69543d03c9809c53c3f1c12b7eed6d83fe1bc81bf6a58c2e \
--hash=sha256:5a8a6a89f146d97357284fb6f467ea095273cf385f2f62ce49ad70b0a2057841
# via -r requirements/main.in
pyqrcode==1.2.1 \
--hash=sha256:1b2812775fa6ff5c527977c4cd2ccb07051ca7d0bc0aecf937a43864abe5eff6 \
Expand Down
19 changes: 13 additions & 6 deletions tests/unit/attestations/test_services.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,12 @@ class TestNullIntegrityService:
def test_interface_matches(self):
assert verifyClass(IIntegrityService, services.NullIntegrityService)

def test_build_provenance(self, db_request, dummy_attestation):
db_request.oidc_publisher = GitHubPublisherFactory.create()
@pytest.mark.parametrize(
"publisher_factory",
[GitHubPublisherFactory, GitLabPublisherFactory],
)
def test_build_provenance(self, db_request, dummy_attestation, publisher_factory):
db_request.oidc_publisher = publisher_factory.create()

file = FileFactory.create()
service = services.NullIntegrityService.create_service(None, db_request)
Expand Down Expand Up @@ -89,7 +93,6 @@ def test_parse_attestations_fails_no_publisher(self, db_request):
@pytest.mark.parametrize(
"publisher_factory",
[
GitLabPublisherFactory,
GooglePublisherFactory,
ActiveStatePublisherFactory,
],
Expand Down Expand Up @@ -267,7 +270,7 @@ def test_parse_attestations_succeeds(

@pytest.mark.parametrize(
"publisher_factory",
[GitHubPublisherFactory],
[GitHubPublisherFactory, GitLabPublisherFactory],
)
def test_build_provenance_succeeds(
self, metrics, db_request, publisher_factory, dummy_attestation
Expand Down Expand Up @@ -295,8 +298,12 @@ def test_build_provenance_succeeds(
]


def test_extract_attestations_from_request_empty_list(db_request):
db_request.oidc_publisher = GitHubPublisherFactory.create()
@pytest.mark.parametrize(
"publisher_factory",
[GitHubPublisherFactory, GitLabPublisherFactory],
)
def test_extract_attestations_from_request_empty_list(db_request, publisher_factory):
db_request.oidc_publisher = publisher_factory.create()
db_request.POST = {"attestations": json.dumps([])}

with pytest.raises(
Expand Down
19 changes: 19 additions & 0 deletions tests/unit/oidc/models/test_gitlab.py
Original file line number Diff line number Diff line change
Expand Up @@ -710,6 +710,25 @@ def test_gitlab_publisher_verify_url(
)
assert publisher.verify_url(url) == expected

@pytest.mark.parametrize("environment", ["", "some-env"])
def test_gitlab_publisher_attestation_identity(self, environment):
publisher = gitlab.GitLabPublisher(
project="project",
namespace="group/subgroup",
workflow_filepath="workflow_filename.yml",
environment=environment,
)

identity = publisher.attestation_identity
assert identity is not None
assert identity.repository == publisher.project_path
assert identity.workflow_filepath == publisher.workflow_filepath

if not environment:
assert identity.environment is None
else:
assert identity.environment == publisher.environment


class TestPendingGitLabPublisher:
def test_reify_does_not_exist_yet(self, db_request):
Expand Down
1 change: 0 additions & 1 deletion warehouse/attestations/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,6 @@ def parse_attestations(
artifact. Attestations are only allowed when uploading via a Trusted
Publisher, because a Trusted Publisher provides the identity that will be
used to verify the attestations.
Only GitHub Actions Trusted Publishers are supported.
"""

attestations = _extract_attestations_from_request(request)
Expand Down
34 changes: 17 additions & 17 deletions warehouse/locale/messages.pot
Original file line number Diff line number Diff line change
Expand Up @@ -826,7 +826,7 @@ msgstr ""
#: warehouse/templates/base.html:321 warehouse/templates/base.html:331
#: warehouse/templates/base.html:344
#: warehouse/templates/includes/accounts/profile-callout.html:18
#: warehouse/templates/includes/file-details.html:94
#: warehouse/templates/includes/file-details.html:101
#: warehouse/templates/index.html:100 warehouse/templates/index.html:104
#: warehouse/templates/manage/account.html:228
#: warehouse/templates/manage/account.html:234
Expand Down Expand Up @@ -2707,51 +2707,51 @@ msgstr ""
msgid "Public profile"
msgstr ""

#: warehouse/templates/includes/file-details.html:27
#: warehouse/templates/includes/file-details.html:34
msgid "File details"
msgstr ""

#: warehouse/templates/includes/file-details.html:38
#: warehouse/templates/includes/file-details.html:45
#, python-format
msgid "Upload date: %(upload_time)s"
msgstr ""

#: warehouse/templates/includes/file-details.html:39
#: warehouse/templates/includes/file-details.html:46
#, python-format
msgid "Size: %(size)s"
msgstr ""

#: warehouse/templates/includes/file-details.html:40
#: warehouse/templates/includes/file-details.html:47
#, python-format
msgid "Tags: %(tags)s"
msgstr ""

#: warehouse/templates/includes/file-details.html:42
#: warehouse/templates/includes/file-details.html:49
#, python-format
msgid "Uploaded using Trusted Publishing? %(is_tp)s"
msgstr ""

#: warehouse/templates/includes/file-details.html:47
#: warehouse/templates/includes/file-details.html:54
#, python-format
msgid "Uploaded via: %(uploaded_via)s"
msgstr ""

#: warehouse/templates/includes/file-details.html:55
#: warehouse/templates/includes/file-details.html:62
#, python-format
msgid "Hashes for %(filename)s"
msgstr ""

#: warehouse/templates/includes/file-details.html:58
#: warehouse/templates/includes/file-details.html:65
msgid "Algorithm"
msgstr ""

#: warehouse/templates/includes/file-details.html:59
#: warehouse/templates/includes/file-details.html:66
msgid "Hash digest"
msgstr ""

#: warehouse/templates/includes/file-details.html:68
#: warehouse/templates/includes/file-details.html:77
#: warehouse/templates/includes/file-details.html:86
#: warehouse/templates/includes/file-details.html:75
#: warehouse/templates/includes/file-details.html:84
#: warehouse/templates/includes/file-details.html:93
#: warehouse/templates/manage/account.html:206
#: warehouse/templates/manage/account/recovery_codes-provision.html:58
#: warehouse/templates/manage/account/totp-provision.html:57
Expand All @@ -2761,9 +2761,9 @@ msgstr ""
msgid "Copy to clipboard"
msgstr ""

#: warehouse/templates/includes/file-details.html:69
#: warehouse/templates/includes/file-details.html:78
#: warehouse/templates/includes/file-details.html:87
#: warehouse/templates/includes/file-details.html:76
#: warehouse/templates/includes/file-details.html:85
#: warehouse/templates/includes/file-details.html:94
#: warehouse/templates/manage/account.html:207
#: warehouse/templates/manage/account/recovery_codes-provision.html:59
#: warehouse/templates/manage/account/totp-provision.html:58
Expand All @@ -2772,7 +2772,7 @@ msgstr ""
msgid "Copy"
msgstr ""

#: warehouse/templates/includes/file-details.html:94
#: warehouse/templates/includes/file-details.html:101
#, python-format
msgid ""
"<a href=\"%(href)s\" title=\"%(title)s\" target=\"_blank\" "
Expand Down
9 changes: 9 additions & 0 deletions warehouse/oidc/models/gitlab.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

from typing import Any

from pypi_attestations import GitLabPublisher as GitLabIdentity, Publisher
from sqlalchemy import ForeignKey, String, UniqueConstraint
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import Query, mapped_column
Expand Down Expand Up @@ -258,6 +259,14 @@ def publisher_url(self, claims=None):
base = self.publisher_base_url
return f"{base}/commit/{claims['sha']}" if claims else base

@property
def attestation_identity(self) -> Publisher | None:
return GitLabIdentity(
repository=self.project_path,
workflow_filepath=self.workflow_filepath,
environment=self.environment if self.environment else None,
)

def stored_claims(self, claims=None):
claims = claims if claims else {}
return {"ref_path": claims.get("ref_path"), "sha": claims.get("sha")}
Expand Down
7 changes: 7 additions & 0 deletions warehouse/templates/includes/file-details.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@
<code>{{ publ.workflow }}</code> on {{ publ.repository }}
</a>
</p>
{% elif publ.kind == "GitLab" %}
<p>
Publisher: <a href="https://gitlab.com/{{ publ.repository }}/blob/HEAD/{{ publ.workflow_filepath }}">
<i class="fa-brands fa-gitlab" aria-hidden="true"></i>
<code>{{ publ.workflow_filepath }}</code> on {{ publ.repository }}
</a>
</p>
{% endif %}
{%- endmacro %}

Expand Down

0 comments on commit 69f3af7

Please sign in to comment.