From f44fc883eabe30fe4d19b179e60d965bf2f738f7 Mon Sep 17 00:00:00 2001 From: Richard Si Date: Fri, 1 Nov 2024 15:56:24 -0400 Subject: [PATCH] Pass HTTPS and client TLS certificates to build subprocesses The _PIP_STANDALONE_CERT environment variable hack is no longer required as pip doesn't run a zip archive of itself to provision build dependencies these days (which due to a CPython bug would leave behind temporary certifi files). --- news/5502.bugfix.rst | 1 + src/pip/_internal/build_env.py | 6 +++-- src/pip/_internal/index/package_finder.py | 14 ++++++++++ src/pip/_vendor/requests/certs.py | 9 +------ tests/functional/test_install.py | 32 +++++++++++++++++++++++ tools/vendoring/patches/requests.patch | 20 -------------- 6 files changed, 52 insertions(+), 30 deletions(-) create mode 100644 news/5502.bugfix.rst diff --git a/news/5502.bugfix.rst b/news/5502.bugfix.rst new file mode 100644 index 00000000000..52c47a4b2cf --- /dev/null +++ b/news/5502.bugfix.rst @@ -0,0 +1 @@ +Configured TLS server and client certificates are now used while installing build dependencies. diff --git a/src/pip/_internal/build_env.py b/src/pip/_internal/build_env.py index 0f1e2667caf..b2cc6682291 100644 --- a/src/pip/_internal/build_env.py +++ b/src/pip/_internal/build_env.py @@ -246,6 +246,8 @@ def _install_requirements( # target from config file or env var should be ignored "--target", "", + "--cert", + finder.custom_cert or where(), ] if logger.getEffectiveLevel() <= logging.DEBUG: args.append("-vv") @@ -272,19 +274,19 @@ def _install_requirements( for host in finder.trusted_hosts: args.extend(["--trusted-host", host]) + if finder.client_cert: + args.extend(["--client-cert", finder.client_cert]) if finder.allow_all_prereleases: args.append("--pre") if finder.prefer_binary: args.append("--prefer-binary") args.append("--") args.extend(requirements) - extra_environ = {"_PIP_STANDALONE_CERT": where()} with open_spinner(f"Installing {kind}") as spinner: call_subprocess( args, command_desc=f"pip subprocess to install {kind}", spinner=spinner, - extra_environ=extra_environ, ) diff --git a/src/pip/_internal/index/package_finder.py b/src/pip/_internal/index/package_finder.py index 0d65ce35f37..fc6535d1035 100644 --- a/src/pip/_internal/index/package_finder.py +++ b/src/pip/_internal/index/package_finder.py @@ -680,6 +680,20 @@ def trusted_hosts(self) -> Iterable[str]: for host_port in self._link_collector.session.pip_trusted_origins: yield build_netloc(*host_port) + @property + def custom_cert(self) -> Optional[str]: + # session.verify is either a boolean (use default bundle/no SSL + # verification) or a string path to a custom CA bundle to use. We only + # care about the latter. + verify = self._link_collector.session.verify + return verify if isinstance(verify, str) else None + + @property + def client_cert(self) -> Optional[str]: + cert = self._link_collector.session.cert + assert not isinstance(cert, tuple), "pip only supports PEM client certs" + return cert + @property def allow_all_prereleases(self) -> bool: return self._candidate_prefs.allow_all_prereleases diff --git a/src/pip/_vendor/requests/certs.py b/src/pip/_vendor/requests/certs.py index 38696a1fb34..2743144b994 100644 --- a/src/pip/_vendor/requests/certs.py +++ b/src/pip/_vendor/requests/certs.py @@ -11,14 +11,7 @@ environment, you can change the definition of where() to return a separately packaged CA bundle. """ - -import os - -if "_PIP_STANDALONE_CERT" not in os.environ: - from pip._vendor.certifi import where -else: - def where(): - return os.environ["_PIP_STANDALONE_CERT"] +from pip._vendor.certifi import where if __name__ == "__main__": print(where()) diff --git a/tests/functional/test_install.py b/tests/functional/test_install.py index 4718beb948c..35d4e58b65e 100644 --- a/tests/functional/test_install.py +++ b/tests/functional/test_install.py @@ -2361,6 +2361,38 @@ def test_install_sends_client_cert( assert environ["SSL_CLIENT_CERT"] +def test_install_sends_certs_for_pep518_deps( + script: PipTestEnvironment, + cert_factory: CertFactory, + data: TestData, + common_wheels: Path, +) -> None: + cert_path = cert_factory() + ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23) + ctx.load_cert_chain(cert_path, cert_path) + ctx.load_verify_locations(cafile=cert_path) + ctx.verify_mode = ssl.CERT_REQUIRED + + setuptools_pkg = next(common_wheels.glob("setuptools*")).name + server = make_mock_server(ssl_context=ctx) + server.mock.side_effect = [ + package_page({setuptools_pkg: f"/files/{setuptools_pkg}"}), + file_response(common_wheels / setuptools_pkg), + ] + url = f"https://{server.host}:{server.port}/simple" + + args = ["install", str(data.packages / "pep517_setup_and_pyproject")] + args.extend(["--index-url", url]) + args.extend(["--cert", cert_path, "--client-cert", cert_path]) + + with server_running(server): + script.pip(*args) + + for call_args in server.mock.call_args_list: + environ, _ = call_args.args + assert environ.get("SSL_CLIENT_CERT", "") + + def test_install_skip_work_dir_pkg(script: PipTestEnvironment, data: TestData) -> None: """ Test that install of a package in working directory diff --git a/tools/vendoring/patches/requests.patch b/tools/vendoring/patches/requests.patch index c205250eeb1..715f8f5a39b 100644 --- a/tools/vendoring/patches/requests.patch +++ b/tools/vendoring/patches/requests.patch @@ -84,26 +84,6 @@ index 8fbcd656..094e2046 100644 try: from urllib3.contrib import pyopenssl -diff --git a/src/pip/_vendor/requests/certs.py b/src/pip/_vendor/requests/certs.py -index be422c3e..3daf06f6 100644 ---- a/src/pip/_vendor/requests/certs.py -+++ b/src/pip/_vendor/requests/certs.py -@@ -11,7 +11,14 @@ If you are packaging Requests, e.g., for a Linux distribution or a managed - environment, you can change the definition of where() to return a separately - packaged CA bundle. - """ --from certifi import where -+ -+import os -+ -+if "_PIP_STANDALONE_CERT" not in os.environ: -+ from certifi import where -+else: -+ def where(): -+ return os.environ["_PIP_STANDALONE_CERT"] - - if __name__ == "__main__": - print(where()) diff --git a/src/pip/_vendor/requests/__init__.py b/src/pip/_vendor/requests/__init__.py index 9d4e72c60..04230fc8d 100644 --- a/src/pip/_vendor/requests/__init__.py