Skip to content

Commit 3ac11af

Browse files
Support urllib3 1.26.x and 2.x (#121)
* Support urllib3 1.26.x and 2.x This changes the assert_fingerprint hack to more directly tell urllib3 that we'll assert the fingerprint ourselves to add support for pinning root certificates, not only the leaves. * Fix mypy --------- Co-authored-by: Seth Michael Larson <[email protected]>
1 parent 97542b2 commit 3ac11af

File tree

4 files changed

+45
-14
lines changed

4 files changed

+45
-14
lines changed

elastic_transport/_node/_http_urllib3.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@
2121
import warnings
2222
from typing import Any, Dict, Optional, Union
2323

24+
try:
25+
from importlib import metadata
26+
except ImportError:
27+
import importlib_metadata as metadata # type: ignore[import,no-redef]
28+
2429
import urllib3
2530
from urllib3.exceptions import ConnectTimeoutError, NewConnectionError, ReadTimeoutError
2631
from urllib3.util.retry import Retry
@@ -47,7 +52,7 @@
4752
class Urllib3HttpNode(BaseNode):
4853
"""Default synchronous node class using the ``urllib3`` library via HTTP"""
4954

50-
_CLIENT_META_HTTP_CLIENT = ("ur", client_meta_version(urllib3.__version__))
55+
_CLIENT_META_HTTP_CLIENT = ("ur", client_meta_version(metadata.version("urllib3")))
5156

5257
def __init__(self, config: NodeConfig):
5358
super().__init__(config)
@@ -159,13 +164,13 @@ def perform_request(
159164
else:
160165
body_to_send = None
161166

162-
response = self.pool.urlopen( # type: ignore[no-untyped-call]
167+
response = self.pool.urlopen(
163168
method,
164169
target,
165170
body=body_to_send,
166171
retries=Retry(False),
167172
headers=request_headers,
168-
**kw,
173+
**kw, # type: ignore[arg-type]
169174
)
170175
response_headers = HttpHeaders(response.headers)
171176
data = response.data

elastic_transport/_node/_urllib3_chain_certs.py

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,21 @@
3636
__all__ = ["HTTPSConnectionPool"]
3737

3838

39+
class HTTPSConnection(urllib3.connection.HTTPSConnection):
40+
def __init__(self, *args: Any, **kwargs: Any) -> None:
41+
self._elastic_assert_fingerprint: Optional[str] = None
42+
super().__init__(*args, **kwargs)
43+
44+
def connect(self) -> None:
45+
super().connect()
46+
# Hack to prevent a warning within HTTPSConnectionPool._validate_conn()
47+
if self._elastic_assert_fingerprint:
48+
self.is_verified = True
49+
50+
3951
class HTTPSConnectionPool(urllib3.HTTPSConnectionPool):
52+
ConnectionCls = HTTPSConnection
53+
4054
"""HTTPSConnectionPool implementation which supports ``assert_fingerprint``
4155
on certificates within the chain instead of only the leaf cert using private
4256
APIs in CPython 3.10+
@@ -60,18 +74,26 @@ def __init__(
6074
f", should be one of '{valid_lengths}'"
6175
)
6276

63-
if assert_fingerprint:
64-
# Falsey but not None. This is a hack to skip fingerprinting by urllib3
65-
# but still set 'is_verified=True' within HTTPSConnectionPool._validate_conn()
66-
kwargs["assert_fingerprint"] = ""
77+
if self._elastic_assert_fingerprint:
78+
# Skip fingerprinting by urllib3 as we'll do it ourselves
79+
kwargs["assert_fingerprint"] = None
6780

6881
super().__init__(*args, **kwargs)
6982

70-
def _validate_conn(self, conn: urllib3.connection.HTTPSConnection) -> None:
83+
def _new_conn(self) -> HTTPSConnection:
84+
"""
85+
Return a fresh :class:`urllib3.connection.HTTPSConnection`.
86+
"""
87+
conn: HTTPSConnection = super()._new_conn() # type: ignore[assignment]
88+
# Tell our custom connection if we'll assert fingerprint ourselves
89+
conn._elastic_assert_fingerprint = self._elastic_assert_fingerprint
90+
return conn
91+
92+
def _validate_conn(self, conn: HTTPSConnection) -> None: # type: ignore[override]
7193
"""
7294
Called right before a request is made, after the socket is created.
7395
"""
74-
super(HTTPSConnectionPool, self)._validate_conn(conn) # type: ignore[misc]
96+
super(HTTPSConnectionPool, self)._validate_conn(conn)
7597

7698
if self._elastic_assert_fingerprint:
7799
hash_func = _HASHES_BY_LENGTH[len(self._elastic_assert_fingerprint)]
@@ -89,7 +111,7 @@ def _validate_conn(self, conn: urllib3.connection.HTTPSConnection) -> None:
89111
# See: https://github.com/python/cpython/pull/25467
90112
fingerprints = [
91113
hash_func(cert.public_bytes(_ENCODING_DER)).digest()
92-
for cert in conn.sock._sslobj.get_verified_chain()
114+
for cert in conn.sock._sslobj.get_verified_chain() # type: ignore[union-attr]
93115
]
94116
except RERAISE_EXCEPTIONS: # pragma: nocover
95117
raise
@@ -100,7 +122,7 @@ def _validate_conn(self, conn: urllib3.connection.HTTPSConnection) -> None:
100122

101123
# Only add the peercert in front of the chain if it's not there for some reason.
102124
# This is to make sure old behavior of 'ssl_assert_fingerprint' still works.
103-
peercert_fingerprint = hash_func(conn.sock.getpeercert(True)).digest()
125+
peercert_fingerprint = hash_func(conn.sock.getpeercert(True)).digest() # type: ignore[union-attr]
104126
if peercert_fingerprint not in fingerprints: # pragma: nocover
105127
fingerprints.insert(0, peercert_fingerprint)
106128

noxfile.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,14 @@ def lint(session):
4343
"flake8",
4444
"black~=23.0",
4545
"isort",
46-
"mypy==1.0.1",
47-
"types-urllib3",
46+
"mypy==1.5.1",
4847
"types-requests",
4948
"types-certifi",
5049
)
50+
# https://github.com/python/typeshed/issues/10786
51+
session.run(
52+
"python", "-m", "pip", "uninstall", "--yes", "types-urllib3", silent=True
53+
)
5154
session.install(".[develop]")
5255
session.run("black", "--check", "--target-version=py36", *SOURCE_FILES)
5356
session.run("isort", "--check", *SOURCE_FILES)

setup.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,10 @@
4848
package_data={"elastic_transport": ["py.typed"]},
4949
packages=packages,
5050
install_requires=[
51-
"urllib3>=1.26.2, <2",
51+
"urllib3>=1.26.2, <3",
5252
"certifi",
5353
"dataclasses; python_version<'3.7'",
54+
"importlib-metadata; python_version<'3.8'",
5455
],
5556
python_requires=">=3.6",
5657
extras_require={

0 commit comments

Comments
 (0)