Skip to content

Commit 1679098

Browse files
milo-minderbinderfrostming
authored andcommitted
fix TLS validation for requirements.txt
Previously, due to a probable typo in the code for importing a requirements file to create a new pipenv project, SSL/TLS validation was disabled by default for any package index servers specified in the requirements file with the `--index-url` or `--extra-index-url` options. In addition, `--trusted-host` options in the requirements file would not work as intended, because any host or host:port pair provided with these options was incorrectly being matched against the full URLs of the configured index server(s) (i.e. including the scheme, path, etc.), instead of extracting and comparing with the host and port parts only, as intended. This PR fixes both of these issues, flipping the existing behavior to require SSL/TLS validation by default, and optionally allowing TLS validation to be disabled explicitly for specific host:port with the `--trusted-host` option if provided.
1 parent 9cb42e1 commit 1679098

File tree

3 files changed

+34
-5
lines changed

3 files changed

+34
-5
lines changed

pipenv/core.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from pipenv.patched import crayons
1818
from pipenv.utils import (
1919
cmd_list_to_shell, convert_deps_to_pip, create_spinner, download_file,
20-
find_python, get_canonical_names, get_source_list, is_pinned,
20+
find_python, get_canonical_names, get_host_and_port, get_source_list, is_pinned,
2121
is_python_command, is_required_version, is_star, is_valid_url,
2222
parse_indexes, pep423_name, prepare_pip_source_args, proper_case,
2323
python_version, run_command, subprocess_run, venv_resolve_deps
@@ -169,7 +169,7 @@ def import_requirements(project, r=None, dev=False):
169169
if extra_index:
170170
indexes.append(extra_index)
171171
if trusted_host:
172-
trusted_hosts.append(trusted_host)
172+
trusted_hosts.append(get_host_and_port(trusted_host))
173173
indexes = sorted(set(indexes))
174174
trusted_hosts = sorted(set(trusted_hosts))
175175
reqs = [install_req_from_parsed_requirement(f) for f in parse_requirements(r, session=pip_requests)]
@@ -185,8 +185,13 @@ def import_requirements(project, r=None, dev=False):
185185
else:
186186
project.add_package_to_pipfile(str(package.req), dev=dev)
187187
for index in indexes:
188-
trusted = index in trusted_hosts
189-
project.add_index_to_pipfile(index, verify_ssl=trusted)
188+
# don't require HTTPS for trusted hosts (see: https://pip.pypa.io/en/stable/cli/pip/#cmdoption-trusted-host)
189+
host_and_port = get_host_and_port(index)
190+
require_valid_https = not any((v in trusted_hosts for v in (
191+
host_and_port,
192+
host_and_port.partition(':')[0], # also check if hostname without port is in trusted_hosts
193+
)))
194+
project.add_index_to_pipfile(index, verify_ssl=require_valid_https)
190195
project.recase_pipfile()
191196

192197

pipenv/utils.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1643,6 +1643,30 @@ def get_url_name(url):
16431643
return urllib3_util.parse_url(url).host
16441644

16451645

1646+
def get_host_and_port(url):
1647+
"""Get the host, or the host:port pair if port is explicitly included, for the given URL.
1648+
1649+
Examples:
1650+
>>> get_host_and_port('example.com')
1651+
'example.com'
1652+
>>> get_host_and_port('example.com:443')
1653+
'example.com:443'
1654+
>>> get_host_and_port('http://example.com')
1655+
'example.com'
1656+
>>> get_host_and_port('https://example.com/')
1657+
'example.com'
1658+
>>> get_host_and_port('https://example.com:8081')
1659+
'example.com:8081'
1660+
>>> get_host_and_port('ssh://example.com')
1661+
'example.com'
1662+
1663+
:param url: the URL string to parse
1664+
:return: a string with the host:port pair if the URL includes port number explicitly; otherwise, returns host only
1665+
"""
1666+
url = urllib3_util.parse_url(url)
1667+
return '{}:{}'.format(url.host, url.port) if url.port else url.host
1668+
1669+
16461670
def get_canonical_names(packages):
16471671
"""Canonicalize a list of packages and return a set of canonical names"""
16481672
from .vendor.packaging.utils import canonicalize_name

tests/unit/test_utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ def test_convert_deps_to_pip_unicode():
142142
("--extra-index-url=https://example.com/simple/", (None, "https://example.com/simple/", None, [])),
143143
("--trusted-host=example.com", (None, None, "example.com", [])),
144144
("# -i https://example.com/simple/", (None, None, None, [])),
145-
("requests", (None, None, None, ["requests"]))
145+
("requests # -i https://example.com/simple/", (None, None, None, ["requests"])),
146146
])
147147
@pytest.mark.utils
148148
def test_parse_indexes(line, result):

0 commit comments

Comments
 (0)