Skip to content

Commit

Permalink
Pass CA and client TLS certificates to build subprocesses (#13063)
Browse files Browse the repository at this point in the history
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).

Some people do depend on this private envvar in the wild, so the removal
has been called out in the news entry.
  • Loading branch information
ichard26 authored Dec 14, 2024
1 parent 8936fee commit 34fc0e2
Show file tree
Hide file tree
Showing 6 changed files with 53 additions and 30 deletions.
2 changes: 2 additions & 0 deletions news/5502.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Configured TLS server and client certificates are now used while installing build dependencies.
Consequently, the private ``_PIP_STANDALONE_CERT`` environment variable is no longer used.
6 changes: 4 additions & 2 deletions src/pip/_internal/build_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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,
)


Expand Down
14 changes: 14 additions & 0 deletions src/pip/_internal/index/package_finder.py
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,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
Expand Down
9 changes: 1 addition & 8 deletions src/pip/_vendor/requests/certs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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())
32 changes: 32 additions & 0 deletions tests/functional/test_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 0 additions & 20 deletions tools/vendoring/patches/requests.patch
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 34fc0e2

Please sign in to comment.