-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Verify Release URLs using publish attestations #2833
Conversation
tests/unit/packaging/test_models.py
Outdated
@pytest.mark.parametrize( | ||
("url", "trusted_publisher_url", "expected"), | ||
[ | ||
( | ||
"https://github.com/owner/project", | ||
"https://github.com/owner/project", | ||
True, | ||
), | ||
( | ||
"https://github.com/owner/project/", | ||
"https://github.com/owner/project", | ||
True, | ||
), | ||
( | ||
"https://github.com/owner/project/issues", | ||
"https://github.com/owner/project", | ||
True, | ||
), | ||
("https://github.com/owner/", "https://github.com/owner/project", False), | ||
( | ||
"https://gitlab.com/owner/project", | ||
"https://github.com/owner/project", | ||
False, | ||
), | ||
], | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: might be good to have some ActiveState URL examples in here as well 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done!
warehouse/packaging/models.py
Outdated
def verify_url(self, url: str) -> bool: | ||
if not self.trusted_publisher_url: | ||
return False | ||
return url.startswith(self.trusted_publisher_url) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NB: This doesn't do case normalization or anything else, which is probably fine but might be worth calling out in a comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With the new implementation, now we do normalization. I added a detailed docstring to document the behavior
@woodruffw While looking into URL normalization, I realized this implementation has a pretty bad security bug: publisher_url = "https://github.com/org/project"
release_url = "https://github.com/org/project22"
verify_url(release_url) == True # because release_url.startswith(publisher_url) is True Which led me to look into Any ideas on how we can tackle URL comparison here? def verify_url(self, url: str) -> bool:
if not self.trusted_publisher_url:
return False
return (url == self.trusted_publisher_url or
url.startswith(self.trusted_publisher_url + '/')) # if checking with startswith, ensure the character after the publisher URL is a forward slash |
Yeah, I think we should avoid TL;DR: I think we should use |
@woodruffw Done! The |
warehouse/packaging/models.py
Outdated
@@ -22,6 +22,7 @@ | |||
from github_reserved_names import ALL as GITHUB_RESERVED_NAMES | |||
from pyramid.authorization import Allow, Authenticated | |||
from pyramid.threadlocal import get_current_request | |||
from rfc3986 import api |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: api
is a pretty generic import, so maybe we do import rfc3986.api
instead and refer to things with that fully qualified path 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
warehouse/packaging/models.py
Outdated
is_subpath = publisher_uri.path == user_uri.path or user_uri.path.startswith( | ||
publisher_uri.path + "/" | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NB: Probably need to check what happens when either URL doesn't have a path
component; I suspect path
will be None
in that case 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch! Added a check, and a test
50a69a9
to
b9ff4ed
Compare
( # URL path component is empty | ||
"https://github.com", | ||
"https://github.com/owner/project", | ||
False, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A testcase for the inverse (TP has path, expected has no path) would also be good! And same for both having no path.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done!
b4ba06f
to
6d8a0ab
Compare
be61563
to
077a24e
Compare
6d8a0ab
to
f01e393
Compare
f01e393
to
9345a6f
Compare
077a24e
to
37af4ca
Compare
dd1b1bc
to
dc943b5
Compare
Superseeded by pypi#16205 |
This PR adds a new field to
Release
,trusted_publisher_url
, which stores the publisher URL used to create that release.More specifically, this field is only populated when the first file uploaded (the one that creates the release) is uploaded using Trusted Publishing and has a PEP-740 publish attestation.
This allows PyPI to verify a release's URLs (the ones returned by the
Release.urls
property), by comparing them with thetrusted_publisher_url
. This means we put those URLs in theVerified details
section of the project page:(compare with the mockup provided in this comment: pypi#8635 (comment))
This is an alternative implementation to these two PRs:
pypi#15862
pypi#15891
cc @woodruffw