-
Notifications
You must be signed in to change notification settings - Fork 2.3k
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
Issue #3483 Add cert retrieval for requests #3490
Issue #3483 Add cert retrieval for requests #3490
Conversation
Forgive my ignorance. Would this work for |
@tbennett6421 Yes, that's specifically what this is for. |
I just noticed that when cert_verify is called by _download_archive, cert's type is Path(), so the test if not isinstance(cert, basestring): yields False, and we mistakenly treat the cert as a list. Oddly, when cert_verify winds up being called by legacy_repository.find_links_for_package, the certs name is passed as a string. I'll see if I can figure out a fix |
This seems to do the trick (apologies for the edits, I found an issue with the previous patch)
the requests module will not accept Path instances for either cert or verify, but it will accept cert as a tuple of (cert, key), in cases where the cert and key are not combined into a single file. I'm assuming that poetry expects the combined format since pip has the same expectation. I'm guessing that if poetry ever opts to support separate cert/key files, it would entail a different key in auth.yaml, so I doubt we'll need to check here whether cert is a tuple or not. |
@GooseYArd it seems like you could just apply |
hah funny you mention it- that was what I did in the initial patch without realizing that the values will be None if no cert/key or calist are specified, and request's cert verification was trying to locate the literal 'None' :) |
@GooseYArd good point there. i'll apply this patch |
Any chance this will be merged anytime soon? I'd love to promote Poetry as the default package manager, but work with enterprises that have an internal PyPi repository, and am unable to pull those packages without hacky workarounds. |
The issue resolved here is also blocking adoption for our project |
@abn this was the second of the two private repo + cert related issues that were preventing us from using Artifactory pypi servers. I'm hoping it can be reviewed for 1.2, but am not sure who to ping. We've been using this PR internally for the past few months without issue, fwiw. |
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.
Over all looks okay; can we get this rebased and compatibility code removed please?
poetry/installation/authenticator.py
Outdated
self._get_certs_for_netloc_from_config(netloc), | ||
) | ||
|
||
def _get_repository_netlocs(self): # type: () -> Generator[[str, str], None, None] |
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.
We can drop compatibily type hints now.
@GooseYArd @dusktreader apologies this slipped through the radar. If you can get it rebased and updated, I can get it reviewed and merged. |
@abn thanks a bunch, if @dusktreader doesn't beat me to it I'll make the updates you requested tomorrow AM |
@abn Rebased and force-pushed! thanks @GooseYArd got it covered, I think |
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.
Minor suggestions
poetry/installation/authenticator.py
Outdated
verify = kwargs.get("verify", certs.get("verify")) | ||
cert = kwargs.get("cert", certs.get("cert")) | ||
|
||
if isinstance(cert, pathlib.PurePath): |
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.
PurePath might be problematic. Since the only case we want to prevent here is str(None)
I'd probably suggest we update the check to be an is none check.
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.
I changed this check to if cert:
and things seem to work as expected for me on Ubuntu with only a client cert specified (verify should be None
for me).
poetry/installation/authenticator.py
Outdated
continue | ||
def _get_certs_for_netloc_from_config( | ||
self, netloc: str | ||
) -> Dict[str, pathlib.PosixPath]: |
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.
Should this be pathlib.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.
I also made this change locally and also on line 167 for get_certs_for_url
.
@dusktreader thanks for this PR, also a blocker for us since we use a private repository with client certificates. I manually made these changes to my Poetry 1.1.6 install and my private repo is working now. Are you able to update and merge this soon? Would love to get this fix into a version I can update to with |
@abn Applied suggested changes and pushed commit. Thanks! |
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.
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.
Functionally looks okay. Some minor suggestions added.
def test_authenticator_uses_certs_from_config_if_not_provided( | ||
config, mock_remote, http, mocker | ||
): | ||
config.merge( | ||
{ | ||
"repositories": {"foo": {"url": "https://foo.bar/simple/"}}, | ||
"http-basic": {"foo": {"username": "bar", "password": "baz"}}, | ||
"certificates": { | ||
"foo": {"cert": "/path/to/cert", "client-cert": "/path/to/client-cert"} | ||
}, | ||
} | ||
) | ||
|
||
authenticator = Authenticator(config, NullIO()) | ||
session_send = mocker.patch.object(authenticator.session, "send") | ||
authenticator.request("get", "https://foo.bar/files/foo-0.1.0.tar.gz") | ||
kwargs = session_send.call_args[1] | ||
assert pathlib.Path(kwargs["verify"]) == pathlib.Path("/path/to/cert") | ||
assert pathlib.Path(kwargs["cert"]) == pathlib.Path("/path/to/client-cert") | ||
|
||
|
||
def test_authenticator_uses_provided_certs_instead_of_config_certs( | ||
config, mock_remote, http, mocker | ||
): | ||
config.merge( | ||
{ | ||
"repositories": {"foo": {"url": "https://foo.bar/simple/"}}, | ||
"http-basic": {"foo": {"username": "bar", "password": "baz"}}, | ||
"certificates": { | ||
"foo": {"cert": "/path/to/cert", "client-cert": "/path/to/client-cert"} | ||
}, | ||
} | ||
) | ||
|
||
authenticator = Authenticator(config, NullIO()) | ||
session_send = mocker.patch.object(authenticator.session, "send") | ||
authenticator.request( | ||
"get", | ||
"https://foo.bar/files/foo-0.1.0.tar.gz", | ||
verify="/path/to/provided/cert", | ||
cert="/path/to/provided/client-cert", | ||
) | ||
kwargs = session_send.call_args[1] | ||
assert pathlib.Path(kwargs["verify"]) == pathlib.Path("/path/to/provided/cert") | ||
assert pathlib.Path(kwargs["cert"]) == pathlib.Path("/path/to/provided/client-cert") |
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.
def test_authenticator_uses_certs_from_config_if_not_provided( | |
config, mock_remote, http, mocker | |
): | |
config.merge( | |
{ | |
"repositories": {"foo": {"url": "https://foo.bar/simple/"}}, | |
"http-basic": {"foo": {"username": "bar", "password": "baz"}}, | |
"certificates": { | |
"foo": {"cert": "/path/to/cert", "client-cert": "/path/to/client-cert"} | |
}, | |
} | |
) | |
authenticator = Authenticator(config, NullIO()) | |
session_send = mocker.patch.object(authenticator.session, "send") | |
authenticator.request("get", "https://foo.bar/files/foo-0.1.0.tar.gz") | |
kwargs = session_send.call_args[1] | |
assert pathlib.Path(kwargs["verify"]) == pathlib.Path("/path/to/cert") | |
assert pathlib.Path(kwargs["cert"]) == pathlib.Path("/path/to/client-cert") | |
def test_authenticator_uses_provided_certs_instead_of_config_certs( | |
config, mock_remote, http, mocker | |
): | |
config.merge( | |
{ | |
"repositories": {"foo": {"url": "https://foo.bar/simple/"}}, | |
"http-basic": {"foo": {"username": "bar", "password": "baz"}}, | |
"certificates": { | |
"foo": {"cert": "/path/to/cert", "client-cert": "/path/to/client-cert"} | |
}, | |
} | |
) | |
authenticator = Authenticator(config, NullIO()) | |
session_send = mocker.patch.object(authenticator.session, "send") | |
authenticator.request( | |
"get", | |
"https://foo.bar/files/foo-0.1.0.tar.gz", | |
verify="/path/to/provided/cert", | |
cert="/path/to/provided/client-cert", | |
) | |
kwargs = session_send.call_args[1] | |
assert pathlib.Path(kwargs["verify"]) == pathlib.Path("/path/to/provided/cert") | |
assert pathlib.Path(kwargs["cert"]) == pathlib.Path("/path/to/provided/client-cert") | |
@pytest.mark.parametrize("cert,client_cert", [ | |
(None, None), | |
("/path/to/provided/cert", "/path/to/provided/client-cert") | |
]) | |
def test_authenticator_uses_certs_from_config_if_not_provided( | |
cert, client_cert, config, mock_remote, http, mocker | |
): | |
configured_cert = "/path/to/cert" | |
configured_client_cert = "/path/to/client-cert" | |
config.merge( | |
{ | |
"repositories": {"foo": {"url": "https://foo.bar/simple/"}}, | |
"http-basic": {"foo": {"username": "bar", "password": "baz"}}, | |
"certificates": { | |
"foo": {"cert": configured_cert, "client-cert": configured_client_cert} | |
}, | |
} | |
) | |
authenticator = Authenticator(config, NullIO()) | |
session_send = mocker.patch.object(authenticator.session, "send") | |
authenticator.request("get", "https://foo.bar/files/foo-0.1.0.tar.gz", verify=cert, cert=client_cert) | |
kwargs = session_send.call_args[1] | |
assert Path(kwargs["verify"]) == Path(cert or configured_cert) | |
assert Path(kwargs["cert"]) == Path(client_cert or configured_client_cert) |
Nothing wroing with the implementation, just wanted to avoid some duplicated code.
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.
I think it makes sense to have the tests separated since the test name clearly defines the conditions under test. In this suggestion, the parameterization makes the test cases less clear.
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.
I am not wedded to parametrised tests. But I am much more keen on adding to the already large sets of duplicated code in our test suites. It is becoming harder to maintain. Additionally you can add a comment next to the parameters if further clarity is required. Personally, I do think the parameters are clear enough.
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.
OK. Will update
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.
This has been updated as requested
@@ -1,3 +1,4 @@ | |||
import pathlib |
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.
import pathlib | |
from pathlib import 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.
There's a few reasons to prefer import pathlib
to from pathlib import Path
. The primary one is that it follows the import pattern used in this module already with requests, pytest, and other. Secondly, pathlib.Path
is more clear and explicit the type of Path
object being used since the name Path is common and can be overloaded. The last reason, though it isn't really relevant here, is that using the import path
style makes it much easier to mock internal functions in pathlib if necessary.
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.
I would say those reasons are rather subjective and not necessarily universal. Further, I do not think we use anything other than pathlib.Path
as Path
through the code base. I recommended the change as this is how Path
is import through the rest of the code. As for mocking, I suspect we will have to patch anyway within poetry.*
in most cases.
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.
OK. Will update
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.
Since 1.2.0 looks imminent I thought I'd bump this thread- this is the last cert-related issue that is blocking my company from being able to use poetry generally for our internal packaging, and since I've already converted a number of our packages (and have been using a private fork with a couple of cert fixes) I'm desperate to get this change merged. Is there a compromise we could reach here?
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.
I will try to get some cycles to work on this today. The latest requests for changes broke a bunch of unit tests, so I will have to figure out what happened there. Sorry it's been slow to merge.
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.
Updates pushed. Tests passed locally; will see what the pipeline does when it's greenlit to run
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.
The python3.6 tests on FreeBSD failed with a strange (or missing) error: https://github.com/python-poetry/poetry/pull/3490/checks?check_run_id=2657871763
Maybe that test just needs to be re-run and the other workflows could be approved by a maintainer see if there are other issues this PR needs to address? @abn
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.
Yes, that failure seems to be unrelated to my change. I will try to act on any more feedback on this quickly, since folks are waiting on this feature.
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.
This has been updated as requested.
poetry/installation/authenticator.py
Outdated
@@ -1,9 +1,12 @@ | |||
import logging | |||
import pathlib |
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.
import pathlib | |
from pathlib import 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.
This has been adjusted as requested
@abn Can you trigger the pipelines, please? Also, it seems like the "Tests / FreeBSD / PYTHON:python3.6" is failing due to an unrelated issue. Also, I've addressed your comments above |
Can we get a maintainer to look at this please? Just waiting for pipelines |
I pinged some of the devs on Discord to get the pipelines going, so you might be able to get more traction there. @abn would you be able to take another look now that the checks pass? This PR would really help users that rely on client certificates to access private Pypi repositories. |
Any idea when this would be implemented? Project stuck at 1.0.10 until this is fixed for use of our private repo. |
Any updates on this one? We wanted to use poetry for our internal build but we need the client cert functional. No we have to do a really convoluted build step with extracting requirements.txt, etc. |
Rebased to fix merge conflict. |
Rebased and force pushed again. @abn or anyone else: does this have any traction? |
@abn @finswimmer @neersighted @sdispater @stephsamson Could one of the maintainers please look at this? I'm running into problems with multiple of our projects due to being stuck at an old version of Poetry. |
This needs another rebase from @dusktreader, but I am happy to land it once it's rebased onto current |
Yes, I noticed some of the underlying logic changed a bit. I'm going to work on getting it rebased eventually, but I've been busy lately. Also, it seemed like wasted effort rebasing over and over. Now that a maintainer is looking at it, I'll try to get it done. |
The completion of this PR is holding up full adoption for 100s of developers at my company. It's been in-progress for over a year. Any chance we can see this completed soon? Is there some method to the prioritization of these requests? It seems like being able to use a certificate for requests made by Poetry would be a major blocker to widespread adoption for many professional developers. |
Would it be possible to release this as a patch to 1.1 to get it out the door faster? It is a major blocker for many and is desperately needed ASAP. |
I simply don't have time to do this right now, and it's not in my current critical path. If someone else has time to pick up the mantle and rebase it, I (and apparently many others) would greatly appreciate it. |
For those blocked by this, can you not just set CURL_CA_BUNDLE env variable before running poetry to your CA bundle file? This is what I'm doing for hosted Artifactory pypi mirror and private repos. |
@chriswhite199 - yes and no. Yes, that workaround works for me as someone that is familiar with poetry and its quirks. No, that workaround will not work for the >100 developers I work with that have never used poetry. |
@ITProKyle - I hear you, and we just add a poetry wrapper script in the base of our repositories to do this - It handles detection of whether poetry is installed globally (and if not pip installs it into a .poetry-venv folder). Developers are taught to use it (in the same way they might use a maven or gradle wrapper if they were coding in Java). |
Superseded by: #5320 |
Oohhhhhhh thank you for teaching me this. Where did you find about this |
This pull request has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. |
The authenticator.py code already retrieves credentials from the config
for every request based on url matching. This change makes the
authenticator also retrieve certs from the config for each request based
on url matching.
Also includes unit tests.
Pull Request Check List
Resolves: #3483