Skip to content

Commit ff14a4b

Browse files
authored
fix: always load the default certs on our custom SSL context (#196)
This is a follow-up PR of a previous one where we started restricting the maximum version of the `request` package to avoid a recently introduced regression. This PR contains the fix for that, so we can continue using always the latest version. Furthermore, the HTTPS related tests have been improved and moved to the directory of the unit tests, to always include them in our builds. Signed-off-by: Norbert Biczo <[email protected]>
1 parent 94f7321 commit ff14a4b

File tree

9 files changed

+197
-171
lines changed

9 files changed

+197
-171
lines changed

ibm_cloud_sdk_core/base_service.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from ibm_cloud_sdk_core.authenticators import Authenticator
3131
from .api_exception import ApiException
3232
from .detailed_response import DetailedResponse
33+
from .http_adapter import SSLHTTPAdapter
3334
from .token_managers.token_manager import TokenManager
3435
from .utils import (
3536
has_bad_first_or_last_char,
@@ -38,7 +39,6 @@
3839
cleanup_values,
3940
read_external_sources,
4041
strip_extra_slashes,
41-
SSLHTTPAdapter,
4242
GzipStream,
4343
)
4444
from .private_helpers import _build_user_agent

ibm_cloud_sdk_core/http_adapter.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import ssl
2+
3+
from requests.adapters import HTTPAdapter, DEFAULT_POOLBLOCK
4+
from urllib3.util.ssl_ import create_urllib3_context
5+
6+
7+
# pylint: disable=fixme
8+
class SSLHTTPAdapter(HTTPAdapter):
9+
"""Wraps the original HTTP adapter and adds additional SSL context."""
10+
11+
def __init__(self, *args, **kwargs):
12+
self._disable_ssl_verification = kwargs.pop('_disable_ssl_verification', None)
13+
14+
super().__init__(*args, **kwargs)
15+
16+
def init_poolmanager(self, connections, maxsize, block=DEFAULT_POOLBLOCK, **pool_kwargs):
17+
"""Create and use custom SSL configuration."""
18+
19+
ssl_context = create_urllib3_context()
20+
ssl_context.load_default_certs()
21+
ssl_context.minimum_version = ssl.TLSVersion.TLSv1_2
22+
23+
if self._disable_ssl_verification:
24+
ssl_context.check_hostname = False
25+
ssl_context.verify_mode = ssl.CERT_NONE
26+
27+
super().init_poolmanager(connections, maxsize, block, ssl_context=ssl_context, **pool_kwargs)

ibm_cloud_sdk_core/utils.py

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -19,41 +19,14 @@
1919
import io
2020
import json as json_import
2121
import re
22-
import ssl
2322
from os import getenv, environ, getcwd
2423
from os.path import isfile, join, expanduser
2524
from typing import List, Union
2625
from urllib.parse import urlparse, parse_qs
2726

28-
from requests.adapters import HTTPAdapter, DEFAULT_POOLBLOCK
29-
from urllib3.util.ssl_ import create_urllib3_context
30-
3127
import dateutil.parser as date_parser
3228

3329

34-
# pylint: disable=fixme
35-
# TODO: revert the change in the `requirement.txt` once this class become deprecated!
36-
class SSLHTTPAdapter(HTTPAdapter):
37-
"""Wraps the original HTTP adapter and adds additional SSL context."""
38-
39-
def __init__(self, *args, **kwargs):
40-
self._disable_ssl_verification = kwargs.pop('_disable_ssl_verification', None)
41-
42-
super().__init__(*args, **kwargs)
43-
44-
def init_poolmanager(self, connections, maxsize, block=DEFAULT_POOLBLOCK, **pool_kwargs):
45-
"""Create and use custom SSL configuration."""
46-
47-
ssl_context = create_urllib3_context()
48-
ssl_context.minimum_version = ssl.TLSVersion.TLSv1_2
49-
50-
if self._disable_ssl_verification:
51-
ssl_context.check_hostname = False
52-
ssl_context.verify_mode = ssl.CERT_NONE
53-
54-
super().init_poolmanager(connections, maxsize, block, ssl_context=ssl_context, **pool_kwargs)
55-
56-
5730
class GzipStream(io.RawIOBase):
5831
"""Compress files on the fly.
5932

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
requests>=2.31.0,<2.32.3
1+
requests>=2.31.0,<3.0.0
22
urllib3>=2.1.0,<3.0.0
33
python_dateutil>=2.8.2,<3.0.0
44
PyJWT>=2.8.0,<3.0.0

resources/test_ssl.cert

Lines changed: 0 additions & 30 deletions
This file was deleted.

resources/test_ssl.crt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIDETCCAfmgAwIBAgIUVQfATsBxBkHqAgmrv9Eb/KQhA2IwDQYJKoZIhvcNAQEL
3+
BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MCAXDTI0MDcwMjA5MTkyN1oYDzIxMjQw
4+
NjA4MDkxOTI3WjAUMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEB
5+
AQUAA4IBDwAwggEKAoIBAQCx7eyXu0A1IH18U0fmVbHlC0OpzpGfGOQayrlrxyxN
6+
kNc8T2ehnt7W33FHI2tjcmO11wgsN+U2+uB2aq0q1OYaqo3OlZtTF4A91CgAdbGd
7+
Ix5aEEzjogiQIBvrBQhaU6uFTzUBQ5tWs+pLcorVrp8G/ONN/1e4Z3NCg036ibSs
8+
Vkfdw1zX6vTR674uTq8aIG7sH3DCF1Q+CzvxhQrhjkZOha+u0H+OhZ9yd30hU/xy
9+
AKZsoGHNY65bVSYAPxP6XLw5inF534TLriggFDonEk16eHjAi7SxcjdKcyxhfX7u
10+
DefD/s9cKUY4Tf1JeAx2F1Y++ffqWlQSde5DKkfC6xU5AgMBAAGjWTBXMBQGA1Ud
11+
EQQNMAuCCWxvY2FsaG9zdDALBgNVHQ8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUH
12+
AwEwHQYDVR0OBBYEFIHsshXxnhcdXRSPxHvTvfMWhfTgMA0GCSqGSIb3DQEBCwUA
13+
A4IBAQCnEU3NtmskVv2/3U0DQM8FM8jKc/V7WCfK/PWPFUZgCkj8pUg4yYxjOZ4K
14+
pFV7cPJjtArmjyU9lB0g2wQvlpXEYbDJ5K0LK9GsdhowZQatZaTB0nVZeG87mlV7
15+
kxrQTMsMTYf5I6S3SW63SorlJQiuaQjOKwvommCS+6Q5goEOodZpGr+5sQSuRLlw
16+
XMKzcU7ZDfe1jidjjcWSyf6UMKB/mhMQVMTTDURt/jS7koA5lXiU+m0XCSi28wTr
17+
lV9ZRzZiE4mRDANlEkoqUCYPG/PDF0KOgDROiavlrBcBycsqBD5iRdGYGaYwUmSo
18+
UpaVGGLYBguqwEeQ2ixC2rTGkyg7
19+
-----END CERTIFICATE-----

resources/test_ssl.key

Lines changed: 26 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,28 @@
11
-----BEGIN PRIVATE KEY-----
2-
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
51-
l7zlgithSVBlZvD3zalH8RxDX+9NhnHw
2+
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCx7eyXu0A1IH18
3+
U0fmVbHlC0OpzpGfGOQayrlrxyxNkNc8T2ehnt7W33FHI2tjcmO11wgsN+U2+uB2
4+
aq0q1OYaqo3OlZtTF4A91CgAdbGdIx5aEEzjogiQIBvrBQhaU6uFTzUBQ5tWs+pL
5+
corVrp8G/ONN/1e4Z3NCg036ibSsVkfdw1zX6vTR674uTq8aIG7sH3DCF1Q+Czvx
6+
hQrhjkZOha+u0H+OhZ9yd30hU/xyAKZsoGHNY65bVSYAPxP6XLw5inF534TLrigg
7+
FDonEk16eHjAi7SxcjdKcyxhfX7uDefD/s9cKUY4Tf1JeAx2F1Y++ffqWlQSde5D
8+
KkfC6xU5AgMBAAECggEABbqBSYlP0eYP5DbSM8pChftM3GS4L4UfovUv7xZkiMLH
9+
CzwLPBrfVc+v1/h99p+yMiKQMsxB5vlAzM82cBCWr/kZw7LxY0V4bYUtHIathz+g
10+
NIodz55h5DIEdBafZDkZZptcO4QvtiTowDEZ4zNSD2mI7/PuoRNDlLqhghV46at3
11+
nfJmOgbWxzciOhpYUXtXl1+n1ywkXIbL58sfp5HgkuM9dL2BTZQdUwbS58TgyYla
12+
DBYZQfiZ+MgrCqJKCYEy+lycVHz9LaSCVXgEYmstfcLqdC7X3/nQuUn587rV5FDA
13+
75bDZ7r2jvrQT7/MEGm3b2fgk0+0gMew52wrDsncuQKBgQDygvVlm6BU2cIm8qvR
14+
l1LqU06jbEVW2fzrXi73RVgCR+ZQoy+hB8LviqNO0ayAOWIH/6iMGU09bK72DlJ0
15+
K4P58X6iJaop1ntdYa2DApPzrz3/4weMl4ZVjz5tPBUxhkfJCMfZF2q+CoHOsdlQ
16+
rTXNFQgfq4XNGpj3YX0L94pkgwKBgQC702bRFk7lHoMyn8hs8MQqumSsefpTsc6v
17+
3fLl6DgwhU9/FAEymIt+wEIchoU0GkVfZ16OmO1whZAgj3zsBr/zG7C7Din9s7hB
18+
KV57o7KIRP3IUo+N6qQ2xNPG8lrCd7Xz+430RHT5BRudqNXWsZziFOrPXZz7Yh8/
19+
TMy4ZwzKkwKBgQCOTToh/Uf/gifjItKfkeQdi/TBAG9Pn2pB0mpMvmv+KqKC/r6c
20+
Bynj1b4uKerG8uULPIFydAZW3MdtqsnHUSGIMKTWELPhCPIqwX5HOeQHQfVniZiM
21+
bv1shzlib7cf8GN/G5/pS0xfZ1r0JngWVw0S4hx6OPOyfsDzqEjwFLkocQKBgBOQ
22+
2xYO19sgSZR9dph6oES/M/uPnVcYn6pMWaA/h5LuYDChudo2b9mdV4W3MasSzYU5
23+
tGzwW1OsZi4uJFpF/brqeIeT2yX1kc0f7Rq+G7v8S9+RUij7d23JJTKFTpUReV/Y
24+
JZp7gx/pu026J8R8rhYTDb7aRp8dQpoKewz+lyOHAoGAUGOfsLybzE2w/gL1gZ8W
25+
L6sdkQ7zq/IF04cqM5jw1g82f9CGdZlWrT6wv5D++O9zpZSmuwJ2p/os5Tsygmb4
26+
M0IjG23Mw5IWLCC6n2riYwpQ8sjuL3SOhqgt4k5mnu4H0RpVLz69JPBJkaGuE1Ld
27+
k4zeVfI1+X2clcHVfDS6xhc=
5228
-----END PRIVATE KEY-----

test/test_http_adapter.py

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
# pylint: disable=missing-docstring
2+
import os
3+
import threading
4+
import warnings
5+
from http.server import HTTPServer, SimpleHTTPRequestHandler
6+
from ssl import get_default_verify_paths, SSLContext, PROTOCOL_TLSv1_1, PROTOCOL_TLSv1_2
7+
from typing import Callable
8+
9+
import pytest
10+
import urllib3
11+
from requests.exceptions import SSLError
12+
13+
from ibm_cloud_sdk_core.base_service import BaseService
14+
from ibm_cloud_sdk_core.authenticators import NoAuthAuthenticator
15+
16+
17+
# The certificate files that are used in this tests are generated by this command:
18+
# pylint: disable=line-too-long,pointless-string-statement
19+
"""
20+
openssl req -x509 -out test_ssl.crt -keyout test_ssl.key \
21+
-newkey rsa:2048 -nodes -sha256 -days 36500 \
22+
-subj '/CN=localhost' -extensions EXT -config <( \
23+
printf "[dn]\nCN=localhost\n[req]\ndistinguished_name = dn\n[EXT]\nsubjectAltName=DNS:localhost\nkeyUsage=digitalSignature\nextendedKeyUsage=serverAuth")
24+
"""
25+
26+
27+
# Load the certificate and the key files.
28+
cert = os.path.join(os.path.dirname(__file__), '../resources/test_ssl.crt')
29+
key = os.path.join(os.path.dirname(__file__), '../resources/test_ssl.key')
30+
31+
32+
def _local_server(tls_version: int, port: int) -> Callable:
33+
def decorator(test_function: Callable) -> Callable:
34+
def inner():
35+
# Disable warnings caused by the self-signed certificate.
36+
urllib3.disable_warnings()
37+
38+
# Build the SSL context for the server.
39+
ssl_context = SSLContext(tls_version)
40+
ssl_context.load_cert_chain(certfile=cert, keyfile=key)
41+
42+
# Create and start the server on a separate thread.
43+
server = HTTPServer(('localhost', port), SimpleHTTPRequestHandler)
44+
server.socket = ssl_context.wrap_socket(server.socket, server_side=True)
45+
t = threading.Thread(target=server.serve_forever)
46+
t.start()
47+
48+
# We run everything in a big try-except-finally block to make sure we always
49+
# shutdown the HTTP server gracefully.
50+
try:
51+
test_function()
52+
except Exception: # pylint: disable=try-except-raise
53+
raise
54+
finally:
55+
server.shutdown()
56+
t.join()
57+
# Re-enable warnings.
58+
warnings.resetwarnings()
59+
60+
return inner
61+
62+
return decorator
63+
64+
65+
@_local_server(PROTOCOL_TLSv1_1, 3333)
66+
def test_tls_v1_1():
67+
service = BaseService(service_url='https://localhost:3333', authenticator=NoAuthAuthenticator())
68+
prepped = service.prepare_request('GET', url='/')
69+
# The following request should fail, because the server will try
70+
# to use TLS v1.1 but that's not allowed in our client.
71+
with pytest.raises(Exception) as exception:
72+
service.send(prepped, verify=cert)
73+
# Errors can be differ based on the Python version.
74+
assert exception.type is SSLError or exception.type is ConnectionError
75+
76+
77+
@_local_server(PROTOCOL_TLSv1_2, 3334)
78+
def test_tls_v1_2():
79+
service = BaseService(service_url='https://localhost:3334', authenticator=NoAuthAuthenticator())
80+
81+
# First call the server with the default configuration.
82+
# It should fail due to the self-signed SSL cert.
83+
assert service.disable_ssl_verification is False
84+
prepped = service.prepare_request('GET', url='/')
85+
with pytest.raises(SSLError, match='certificate verify failed: self-signed certificate'):
86+
res = service.send(prepped)
87+
88+
# Next configure it to validate by using our local certificate. Should raise no exception.
89+
res = service.send(prepped, verify=cert)
90+
assert res is not None
91+
92+
# Now disable the SSL verification. The request shouldn't raise any issue.
93+
service.set_disable_ssl_verification(True)
94+
assert service.disable_ssl_verification is True
95+
prepped = service.prepare_request('GET', url='/')
96+
res = service.send(prepped)
97+
assert res is not None
98+
99+
# Lastly, try with an external URL.
100+
# This test case is mainly here to reproduce the regression
101+
# in the `requests` package that was introduced in `2.32.3`.
102+
# More details on the issue can be found here: https://github.com/psf/requests/issues/6730
103+
service = BaseService(service_url='https://cloud.ibm.com', authenticator=NoAuthAuthenticator())
104+
assert service.disable_ssl_verification is False
105+
106+
ssl_context = service.http_adapter.poolmanager.connection_pool_kw.get("ssl_context")
107+
assert ssl_context is not None
108+
# In some cases (especially in Ubuntu containers that we use for testing on Travis)
109+
# the default CA certificates are stored in a different place, so let's try to
110+
# load those before making the final decision for this test case.
111+
if len(ssl_context.get_ca_certs()) == 0:
112+
try:
113+
default_ca_path = get_default_verify_paths().capath
114+
ssl_context.load_verify_locations(os.path.join(default_ca_path, 'ca-certificates.crt'))
115+
except:
116+
# Errors are ignored, let's jump straight to the assertion.
117+
pass
118+
119+
assert len(ssl_context.get_ca_certs()) > 0
120+
121+
prepped = service.prepare_request('GET', url='/status')
122+
res = service.send(prepped)
123+
assert res is not None

0 commit comments

Comments
 (0)