Skip to content

Commit 7fc172a

Browse files
authored
fix: use the correct SSL config if cert verification is disabled (#187)
It turned out, disabling the SSL certificate verification didn't work as expected. We passed the right arguments to the `requests` package, but since we use a custom SSL context, it couldn't turn off the verification entirely, because the default SSL context has the `check_hostname` set to `True` and the documentation says the following: "`verify_mode` is now automatically changed to `CERT_REQUIRED` when hostname checking is enabled and `verify_mode` is `CERT_NONE`." This commit adds an addition step which disables the hostname checking in our custom SSL context. Signed-off-by: Norbert Biczo <[email protected]>
1 parent f973bdf commit 7fc172a

File tree

6 files changed

+183
-10
lines changed

6 files changed

+183
-10
lines changed

.secrets.baseline

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"files": "package-lock.json|^.secrets.baseline$",
44
"lines": null
55
},
6-
"generated_at": "2023-12-11T17:30:56Z",
6+
"generated_at": "2024-01-24T12:09:17Z",
77
"plugins_used": [
88
{
99
"name": "AWSKeyDetector"
@@ -207,6 +207,16 @@
207207
"verified_result": null
208208
}
209209
],
210+
"resources/test_ssl.key": [
211+
{
212+
"hashed_secret": "1348b145fa1a555461c1b790a2f66614781091e9",
213+
"is_secret": false,
214+
"is_verified": false,
215+
"line_number": 1,
216+
"type": "Private Key",
217+
"verified_result": null
218+
}
219+
],
210220
"test/test_base_service.py": [
211221
{
212222
"hashed_secret": "da2f27d2c57a0e1ed2dc3a34b4ef02faf2f7a4c2",

ibm_cloud_sdk_core/base_service.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ def __init__(
108108
self.enable_gzip_compression = enable_gzip_compression
109109
self._set_user_agent_header(self._build_user_agent())
110110
self.retry_config = None
111-
self.http_adapter = SSLHTTPAdapter()
111+
self.http_adapter = SSLHTTPAdapter(_disable_ssl_verification=self.disable_ssl_verification)
112112
if not self.authenticator:
113113
raise ValueError('authenticator must be provided')
114114
if not isinstance(self.authenticator, Authenticator):
@@ -138,14 +138,16 @@ def enable_retries(self, max_retries: int = 4, retry_interval: float = 30.0) ->
138138
# Omitting this will default to all methods except POST
139139
allowed_methods=['HEAD', 'GET', 'PUT', 'DELETE', 'OPTIONS', 'TRACE', 'POST'],
140140
)
141-
self.http_adapter = SSLHTTPAdapter(max_retries=self.retry_config)
141+
self.http_adapter = SSLHTTPAdapter(
142+
max_retries=self.retry_config, _disable_ssl_verification=self.disable_ssl_verification
143+
)
142144
self.http_client.mount('http://', self.http_adapter)
143145
self.http_client.mount('https://', self.http_adapter)
144146

145147
def disable_retries(self):
146148
"""Remove retry config from http_adapter"""
147149
self.retry_config = None
148-
self.http_adapter = SSLHTTPAdapter()
150+
self.http_adapter = SSLHTTPAdapter(_disable_ssl_verification=self.disable_ssl_verification)
149151
self.http_client.mount('http://', self.http_adapter)
150152
self.http_client.mount('https://', self.http_adapter)
151153

@@ -223,8 +225,18 @@ def set_disable_ssl_verification(self, status: bool = False) -> None:
223225
Keyword Arguments:
224226
status: set to true to disable ssl verification (default: {False})
225227
"""
228+
if self.disable_ssl_verification == status:
229+
# Do nothing if the state doesn't change.
230+
return
231+
226232
self.disable_ssl_verification = status
227233

234+
self.http_adapter = SSLHTTPAdapter(
235+
max_retries=self.retry_config, _disable_ssl_verification=self.disable_ssl_verification
236+
)
237+
self.http_client.mount('http://', self.http_adapter)
238+
self.http_client.mount('https://', self.http_adapter)
239+
228240
def set_service_url(self, service_url: str) -> None:
229241
"""Set the url the service will make HTTP requests too.
230242

ibm_cloud_sdk_core/utils.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
from typing import List, Union
2626
from urllib.parse import urlparse, parse_qs
2727

28-
from requests.adapters import HTTPAdapter
28+
from requests.adapters import HTTPAdapter, DEFAULT_POOLBLOCK
2929
from urllib3.util.ssl_ import create_urllib3_context
3030

3131
import dateutil.parser as date_parser
@@ -35,14 +35,21 @@ class SSLHTTPAdapter(HTTPAdapter):
3535
"""Wraps the original HTTP adapter and adds additional SSL context."""
3636

3737
def __init__(self, *args, **kwargs):
38+
self._disable_ssl_verification = kwargs.pop('_disable_ssl_verification', None)
39+
3840
super().__init__(*args, **kwargs)
3941

40-
# pylint: disable=arguments-differ
41-
def init_poolmanager(self, connections, maxsize, block):
42-
"""Extends the parent's method by adding minimum SSL version to the args."""
42+
def init_poolmanager(self, connections, maxsize, block=DEFAULT_POOLBLOCK, **pool_kwargs):
43+
"""Create and use custom SSL configuration."""
44+
4345
ssl_context = create_urllib3_context()
4446
ssl_context.minimum_version = ssl.TLSVersion.TLSv1_2
45-
super().init_poolmanager(connections, maxsize, block, ssl_context=ssl_context)
47+
48+
if self._disable_ssl_verification:
49+
ssl_context.check_hostname = False
50+
ssl_context.verify_mode = ssl.CERT_NONE
51+
52+
super().init_poolmanager(connections, maxsize, block, ssl_context=ssl_context, **pool_kwargs)
4653

4754

4855
class GzipStream(io.RawIOBase):
@@ -60,7 +67,7 @@ class GzipStream(io.RawIOBase):
6067
It can be a file-like object, bytes or string.
6168
"""
6269

63-
def __init__(self, source: Union[io.IOBase, bytes, str]) -> 'GzipStream':
70+
def __init__(self, source: Union[io.IOBase, bytes, str]):
6471
self.buffer = b''
6572

6673
if isinstance(source, io.IOBase):

resources/test_ssl.cert

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIFJTCCAw2gAwIBAgIUfElLZwAd6ZSPKCWsVzp5vjUmytQwDQYJKoZIhvcNAQEL
3+
BQAwITELMAkGA1UEBhMCVVMxEjAQBgNVBAMMCWxvY2FsaG9zdDAgFw0yNDAxMjQx
4+
MTM5MzRaGA8yMTIzMTIzMTExMzkzNFowITELMAkGA1UEBhMCVVMxEjAQBgNVBAMM
5+
CWxvY2FsaG9zdDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMT6hPMi
6+
5KO3Jeky+lhsguEqngxKsfSBlicjRGHrYX62ouGafcK4KZJkgwoC7XECi1O//KAJ
7+
EPoOxyesxZKpxa3X+AetI7zUecyfA0J0qQbgtYns4zHOwfpPG4wyprzcEQ9+xlMp
8+
iMJYpY1e0YzqHKaiSlHv6KJBWuNhT/LS7b5P98JJzJtcUQn8fDZ5D7WM+nsHMFn8
9+
Rt9C715GlCyGBS3qe2Eme23h5XvUM/Tqp9f/FmZ0t9GrEovhDScGvtxO3dQ5hLHV
10+
ifhEKyIGhcKoG5+53iYuWaRDhgnLtKZjNg/cAEkmpU0NgORP3tbNKD4wtGJMXQA8
11+
uAc0dXsWfFYqMR9i+rM3F3bqV7VSOaw0rfZSztlQhfgm4XuZTouMF9jHE6xFvLum
12+
Bjvzq2YtR/Xdv1lygRzXA6uMO2ppnAw9ofVn2cBirjdi6qzAzf+X+yiORP2QXcG7
13+
BoGYU3JDLMZVMpQ81/Jm8HuJfX2yBXcKtinXjWCa4up9dI4+RVQg0wyIKtTLpxBh
14+
Gw/iRzyzAto7cjD9NOr7MsqqmhpCwowHeitqCCPJfTmcFxaiKA6SzyUS4+TRJUnf
15+
2DWGjDyb8kfg2NkFVeUkn6WXx8VsbvMYg2VCdpIO0XK40ofBBuVaOrPrQpm6fyFf
16+
6SFusvTOuXJpnI4CeyefO2Nd+2xjN9balXFtAgMBAAGjUzBRMB0GA1UdDgQWBBSx
17+
Lwf6XjMsF54JX6SBpYhwbw8SezAfBgNVHSMEGDAWgBSxLwf6XjMsF54JX6SBpYhw
18+
bw8SezAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQCEgNyk5Bon
19+
F9hDHb6jiR0p0RkNiPZeOkFFHyonmG7WTkQMJElaFJJQN2wuPeok8ky0B65S4V8e
20+
uGRQSvZ9rt/h5/K09G9h/haN/UTWXj1AUWcblC8hw8ZnOSg2tLkOLwqQsb+Q4pUM
21+
eS+Gr10cTENnW0hwG5husiiNj58Mr1+CL+LqfmHPK85vPco2Q0uQ4MEpmtwM5R2g
22+
t4d7hI81vXKeTBTeB6sEI9EkHPTyy1oTk7uEtBK+d62ZJPQCsco1CExNRIkYTUgh
23+
LIIyDfXxO8Vo2fhbLM95rjlDdoM87Db79EQd6REdwLh1lRByD3IvkXouZnbVfkaw
24+
BeCOGtDjcTXcNHTVdjmNZ723sU4OSAqHDyVazeIpKZbOKIRlHgYqylYcxT/MOL0l
25+
Mtzw2o83xoSYrMQ8rcYO5RX1EIPwUijo3k3T8OMPZbYP724hu3YxbEF+kOgCty4A
26+
9UTDIUqlr6Y25C/vf3L792nrhduqrWALXumbENVelqow8u2HBaMj1k+LjVSNMIa+
27+
HFP8ex7bYdNjjikTHDM/a4Rs+yBg2WWkfvcYRdkn/IkjAPt4vjdiuPD5pDmjkIBt
28+
ZFVUM9U6V+DAlPULZXJOb348K0KcnQdbsn9ti+LFWsfLJSaUbAirt4yQ/H6FW35u
29+
iyFlAJPkdeGhoBhtbf5f2IVHqcvrOO34Dw==
30+
-----END CERTIFICATE-----

resources/test_ssl.key

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
-----BEGIN PRIVATE KEY-----
2+
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
51+
l7zlgithSVBlZvD3zalH8RxDX+9NhnHw
52+
-----END PRIVATE KEY-----
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# pylint: disable=missing-docstring
2+
import os
3+
import threading
4+
from http.server import HTTPServer, SimpleHTTPRequestHandler
5+
from ssl import PROTOCOL_TLS_SERVER, SSLContext
6+
7+
import pytest
8+
from requests.exceptions import SSLError
9+
10+
from ibm_cloud_sdk_core.base_service import BaseService
11+
from ibm_cloud_sdk_core.authenticators import NoAuthAuthenticator
12+
13+
14+
# The certificate files that are used in this tests are generated by this command:
15+
# openssl req \
16+
# -new \
17+
# -newkey rsa:4096 \
18+
# -days 36500 \
19+
# -nodes \
20+
# -x509 \
21+
# -subj "/C=US/CN=localhost" \
22+
# -keyout test_ssl.key \
23+
# -out test_ssl.cert
24+
25+
26+
def test_ssl_verification():
27+
# Load the certificate and the key files.
28+
cert = os.path.join(os.path.dirname(__file__), '../resources/test_ssl.cert')
29+
key = os.path.join(os.path.dirname(__file__), '../resources/test_ssl.key')
30+
31+
# Build the SSL context for the server.
32+
ssl_context = SSLContext(PROTOCOL_TLS_SERVER)
33+
ssl_context.load_cert_chain(certfile=cert, keyfile=key)
34+
35+
# Create and start the server on a separate thread.
36+
server = HTTPServer(('127.0.0.1', 3333), SimpleHTTPRequestHandler)
37+
server.socket = ssl_context.wrap_socket(server.socket, server_side=True)
38+
t = threading.Thread(target=server.serve_forever)
39+
t.start()
40+
41+
# We run everything in a big try-except-finally block to make sure we always
42+
# shutdown the HTTP server gracefully.
43+
try:
44+
service = BaseService(service_url='https://127.0.0.1:3333', authenticator=NoAuthAuthenticator())
45+
#
46+
# First call the server with the default configuration.
47+
# It should fail due to the invalid SSL cert.
48+
assert service.disable_ssl_verification is False
49+
prepped = service.prepare_request('GET', url='/')
50+
with pytest.raises(SSLError):
51+
res = service.send(prepped)
52+
53+
# Now disable the SSL verification. The request shouldn't raise any issue.
54+
service.set_disable_ssl_verification(True)
55+
assert service.disable_ssl_verification is True
56+
prepped = service.prepare_request('GET', url='/')
57+
res = service.send(prepped)
58+
assert res is not None
59+
except Exception: # pylint: disable=try-except-raise
60+
raise
61+
finally:
62+
server.shutdown()

0 commit comments

Comments
 (0)