Skip to content

Commit bbecab9

Browse files
authored
Merge pull request #182 from NMRhub/master
Add HTTPAdapter to allow connection to old servers with archaic SSL h…
2 parents 40bf84b + 68a2192 commit bbecab9

File tree

2 files changed

+87
-10
lines changed

2 files changed

+87
-10
lines changed

examples/http_adapter.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#!/usr/bin/env python3
2+
import argparse
3+
from pathlib import Path
4+
from typing import Optional
5+
import ssl
6+
from requests.adapters import HTTPAdapter
7+
from urllib3.poolmanager import PoolManager
8+
9+
from redfish import redfish_client
10+
11+
12+
def make_dhe_compatible_context(
13+
cafile: Optional[Path] = None,
14+
*,
15+
seclevel: int = 1,
16+
verify: bool = True,
17+
tls12_only: bool = True,
18+
) -> ssl.SSLContext:
19+
"""
20+
Build an SSLContext that accepts legacy DHE handshakes (small DH groups).
21+
- seclevel=1 usually permits 1024-bit DH. Use 0 only as a last resort.
22+
- If verify=True and the server is self-signed, pass its PEM as `cafile`.
23+
- DHE is TLS<=1.2; set tls12_only=True to pin TLS 1.2.
24+
"""
25+
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
26+
ctx.set_ciphers(f"DEFAULT:@SECLEVEL={seclevel}:DHE")
27+
ctx.options |= ssl.OP_NO_COMPRESSION
28+
if tls12_only:
29+
ctx.minimum_version = ssl.TLSVersion.TLSv1_2
30+
ctx.maximum_version = ssl.TLSVersion.TLSv1_2
31+
32+
if verify:
33+
if cafile:
34+
ctx.load_verify_locations(str(cafile))
35+
else:
36+
ctx.load_default_certs()
37+
else:
38+
ctx.check_hostname = False
39+
ctx.verify_mode = ssl.CERT_NONE
40+
return ctx
41+
42+
class SSLContextAdapter(HTTPAdapter):
43+
"""requests adapter that injects a custom ssl_context into urllib3."""
44+
def __init__(self, ssl_context: ssl.SSLContext, **kwargs):
45+
self._ssl_context = ssl_context
46+
super().__init__(**kwargs)
47+
48+
def init_poolmanager(self, connections, maxsize, block=False, **pool_kwargs):
49+
pool_kwargs["ssl_context"] = self._ssl_context
50+
self.poolmanager = PoolManager(
51+
num_pools=connections, maxsize=maxsize, block=block, **pool_kwargs
52+
)
53+
54+
def proxy_manager_for(self, proxy, **proxy_kwargs):
55+
proxy_kwargs["ssl_context"] = self._ssl_context
56+
return super().proxy_manager_for(proxy, **proxy_kwargs)
57+
58+
59+
# Test dhe adapter
60+
ctx = make_dhe_compatible_context(seclevel=0, verify=False)
61+
adapter = SSLContextAdapter(ctx)
62+
parser = argparse.ArgumentParser( )
63+
parser.add_argument('url',help="Server with DHE encryption")
64+
args = parser.parse_args()
65+
66+
client = redfish_client(args.url,https_adapter=adapter)
67+
68+

src/redfish/rest/v1.py

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -468,7 +468,8 @@ class RestClientBase(object):
468468
def __init__(self, base_url, username=None, password=None,
469469
default_prefix='/redfish/v1/', sessionkey=None,
470470
capath=None, cafile=None, timeout=None,
471-
max_retry=None, proxies=None, check_connectivity=True):
471+
max_retry=None, proxies=None, check_connectivity=True,
472+
https_adapter = None):
472473
"""Initialization of the base class RestClientBase
473474
474475
:param base_url: The URL of the remote system
@@ -494,7 +495,7 @@ def __init__(self, base_url, username=None, password=None,
494495
:param check_connectivity: A boolean to determine whether the client immediately checks for
495496
connectivity to the base_url or not.
496497
:type check_connectivity: bool
497-
498+
:type https_adapter: requests.adpaters.HTTPAdapter
498499
"""
499500

500501
self.__base_url = base_url.rstrip('/')
@@ -507,6 +508,8 @@ def __init__(self, base_url, username=None, password=None,
507508
self._session = requests_unixsocket.Session()
508509
else:
509510
self._session = requests.Session()
511+
if https_adapter:
512+
self._session.mount('https://',https_adapter)
510513
self._timeout = timeout
511514
self._max_retry = max_retry if max_retry is not None else 10
512515
self._proxies = proxies
@@ -1079,7 +1082,8 @@ def __init__(self, base_url, username=None, password=None,
10791082
default_prefix='/redfish/v1/',
10801083
sessionkey=None, capath=None,
10811084
cafile=None, timeout=None,
1082-
max_retry=None, proxies=None, check_connectivity=True):
1085+
max_retry=None, proxies=None, check_connectivity=True,
1086+
https_adapter=None):
10831087
"""Initialize HttpClient
10841088
10851089
:param base_url: The url of the remote system
@@ -1105,14 +1109,15 @@ def __init__(self, base_url, username=None, password=None,
11051109
:param check_connectivity: A boolean to determine whether the client immediately checks for
11061110
connectivity to the base_url or not.
11071111
:type check_connectivity: bool
1108-
1112+
:param https_adapter session adapter for HTTPS
1113+
:type https_adapter: requests.adpaters.HTTPAdapter
11091114
"""
11101115
super(HttpClient, self).__init__(base_url, username=username,
11111116
password=password, default_prefix=default_prefix,
11121117
sessionkey=sessionkey, capath=capath,
11131118
cafile=cafile, timeout=timeout,
11141119
max_retry=max_retry, proxies=proxies,
1115-
check_connectivity=check_connectivity)
1120+
check_connectivity=check_connectivity,https_adapter=https_adapter)
11161121

11171122
try:
11181123
self.login_url = self.root.Links.Sessions['@odata.id']
@@ -1169,7 +1174,8 @@ def redfish_client(base_url=None, username=None, password=None,
11691174
default_prefix='/redfish/v1/',
11701175
sessionkey=None, capath=None,
11711176
cafile=None, timeout=None,
1172-
max_retry=None, proxies=None, check_connectivity=True):
1177+
max_retry=None, proxies=None, check_connectivity=True,
1178+
https_adapter=None):
11731179
"""Create and return appropriate REDFISH client instance."""
11741180
""" Instantiates appropriate Redfish object based on existing"""
11751181
""" configuration. Use this to retrieve a pre-configured Redfish object
@@ -1196,14 +1202,17 @@ def redfish_client(base_url=None, username=None, password=None,
11961202
:type proxies: dict
11971203
:param check_connectivity: A boolean to determine whether the client immediately checks for
11981204
connectivity to the base_url or not.
1199-
:type check_connectivity: bool
1205+
:type check_connectivity: bo#ol
1206+
:param https_adapter session adapter for HTTPS
1207+
:type https_adapter: requests.adpaters.HTTPAdapter
12001208
:returns: a client object.
12011209
12021210
"""
12031211
if "://" not in base_url:
1204-
warnings.warn("Scheme not specified for '{}'; adding 'https://'".format(base_url))
1205-
base_url = "https://" + base_url
1212+
warnings.warn("Scheme not specified for '{}'; adding 'https://'".format(base_url))
1213+
base_url = "https://" + base_url
12061214
return HttpClient(base_url=base_url, username=username, password=password,
12071215
default_prefix=default_prefix, sessionkey=sessionkey,
12081216
capath=capath, cafile=cafile, timeout=timeout,
1209-
max_retry=max_retry, proxies=proxies, check_connectivity=check_connectivity)
1217+
max_retry=max_retry, proxies=proxies, check_connectivity=check_connectivity,
1218+
https_adapter=https_adapter)

0 commit comments

Comments
 (0)