Skip to content

Commit 465d951

Browse files
committed
fix ssl: respect SSL_CERT_FILE and REQUESTS_CA_BUNDLE for CA bundle
1 parent 481e5c2 commit 465d951

3 files changed

Lines changed: 61 additions & 3 deletions

File tree

‎massive/rest/base.py‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import certifi
22
import json
3+
import os
34
import urllib3
45
import inspect
56
from urllib3.util.retry import Retry
@@ -21,6 +22,14 @@
2122
pass
2223

2324

25+
def _default_ca_bundle() -> str:
26+
for env_var in ("SSL_CERT_FILE", "REQUESTS_CA_BUNDLE"):
27+
bundle = os.environ.get(env_var)
28+
if bundle:
29+
return bundle
30+
return certifi.where()
31+
32+
2433
class BaseClient:
2534
def __init__(
2635
self,
@@ -76,7 +85,7 @@ def __init__(
7685
self.client = urllib3.PoolManager(
7786
num_pools=num_pools,
7887
headers=self.headers, # default headers sent with each request.
79-
ca_certs=certifi.where(),
88+
ca_certs=_default_ca_bundle(),
8089
cert_reqs="CERT_REQUIRED",
8190
retries=retry_strategy, # use the customized Retry instance
8291
timeout=self.timeout, # set timeout for each request

‎massive/websocket/__init__.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@
55
import json
66
import asyncio
77
import ssl
8-
import certifi
98
from .models import *
109
from websockets.asyncio.client import connect, ClientConnection
1110
from websockets.exceptions import ConnectionClosedOK, ConnectionClosedError
1211
from ..logging import get_logger
1312
import logging
1413
from ..exceptions import AuthError
14+
from ..rest.base import _default_ca_bundle
1515

1616
env_key = "MASSIVE_API_KEY"
1717
logger = get_logger("WebSocketClient")
@@ -98,7 +98,7 @@ async def connect(
9898
ssl_context = None
9999
if self.url.startswith("wss://"):
100100
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
101-
ssl_context.load_verify_locations(certifi.where())
101+
ssl_context.load_verify_locations(_default_ca_bundle())
102102

103103
last_exc = None
104104
async for s in connect(

‎test_rest/test_ssl_ca_bundle.py‎

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import os
2+
import unittest
3+
from unittest import mock
4+
5+
import certifi
6+
7+
from massive import RESTClient
8+
from massive.rest.base import _default_ca_bundle
9+
10+
11+
class SSLCaBundleTest(unittest.TestCase):
12+
def tearDown(self):
13+
for var in ("SSL_CERT_FILE", "REQUESTS_CA_BUNDLE"):
14+
os.environ.pop(var, None)
15+
16+
def test_ssl_cert_file_takes_precedence(self):
17+
with mock.patch.dict(
18+
os.environ,
19+
{
20+
"SSL_CERT_FILE": "/tmp/custom-ca.pem",
21+
"REQUESTS_CA_BUNDLE": "/tmp/other-ca.pem",
22+
},
23+
):
24+
self.assertEqual(_default_ca_bundle(), "/tmp/custom-ca.pem")
25+
client = RESTClient("key")
26+
self.assertEqual(
27+
client.client.connection_pool_kw["ca_certs"], "/tmp/custom-ca.pem"
28+
)
29+
30+
def test_requests_ca_bundle_fallback(self):
31+
os.environ.pop("SSL_CERT_FILE", None)
32+
with mock.patch.dict(os.environ, {"REQUESTS_CA_BUNDLE": "/tmp/other-ca.pem"}):
33+
self.assertEqual(_default_ca_bundle(), "/tmp/other-ca.pem")
34+
client = RESTClient("key")
35+
self.assertEqual(
36+
client.client.connection_pool_kw["ca_certs"], "/tmp/other-ca.pem"
37+
)
38+
39+
def test_certifi_default_without_env(self):
40+
with mock.patch.dict(os.environ, {}, clear=True):
41+
self.assertEqual(_default_ca_bundle(), certifi.where())
42+
client = RESTClient("key")
43+
self.assertEqual(
44+
client.client.connection_pool_kw["ca_certs"], certifi.where()
45+
)
46+
47+
48+
if __name__ == "__main__":
49+
unittest.main()

0 commit comments

Comments
 (0)