Skip to content

Commit

Permalink
Support for client to verify server with custom CA bundle
Browse files Browse the repository at this point in the history
  • Loading branch information
bhast authored and miguelgrinberg committed Feb 14, 2021
1 parent 1d174b7 commit 792bdd0
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 3 deletions.
13 changes: 10 additions & 3 deletions engineio/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class Client(object):
:param http_session: an initialized ``requests.Session`` object to be used
when sending requests to the server. Use it if you
need to add special client options such as proxy
servers, SSL certificates, etc.
servers, SSL certificates, custom CA bundle, etc.
:param ssl_verify: ``True`` to verify SSL certificates, or ``False`` to
skip SSL certificate verification, allowing
connections to servers with self signed certificates.
Expand Down Expand Up @@ -405,7 +405,12 @@ def _connect_websocket(self, url, headers, engineio_path):
else None)

# verify
if not self.http.verify:
if isinstance(self.http.verify, str):
if 'sslopt' in extra_options:
extra_options['sslopt']['ca_certs'] = self.http.verify
else:
extra_options['sslopt'] = {'ca_certs': self.http.verify}
elif not self.http.verify:
self.ssl_verify = False

if not self.ssl_verify:
Expand Down Expand Up @@ -515,9 +520,11 @@ def _send_request(
timeout=None): # pragma: no cover
if self.http is None:
self.http = requests.Session()
if not self.ssl_verify:
self.http.verify = False
try:
return self.http.request(method, url, headers=headers, data=body,
timeout=timeout, verify=self.ssl_verify)
timeout=timeout)
except requests.exceptions.RequestException as exc:
self.logger.info('HTTP %s request to %s failed with error %s.',
method, url, exc)
Expand Down
72 changes: 72 additions & 0 deletions tests/common/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -897,6 +897,45 @@ def test_websocket_connection_with_cert_and_key(self, create_connection):
'enable_multithread': True,
}

@mock.patch('engineio.client.websocket.create_connection')
def test_websocket_connection_verify_with_cert_and_key(
self, create_connection
):
create_connection.return_value.recv.return_value = packet.Packet(
packet.OPEN,
{
'sid': '123',
'upgrades': [],
'pingInterval': 1000,
'pingTimeout': 2000,
},
).encode()
http = mock.MagicMock()
http.cookies = []
http.auth = None
http.proxies = None
http.cert = ('foo.crt', 'key.pem')
http.verify = 'ca-bundle.crt'
c = client.Client(http_session=http)
c._read_loop_polling = mock.MagicMock()
c._read_loop_websocket = mock.MagicMock()
c._write_loop = mock.MagicMock()
on_connect = mock.MagicMock()
c.on('connect', on_connect)
c.connect('ws://foo', transports=['websocket'])

assert len(create_connection.call_args_list) == 1
assert create_connection.call_args[1] == {
'sslopt': {
'certfile': 'foo.crt',
'keyfile': 'key.pem',
'ca_certs': 'ca-bundle.crt'
},
'header': {},
'cookie': '',
'enable_multithread': True,
}

@mock.patch('engineio.client.websocket.create_connection')
def test_websocket_connection_with_proxies(self, create_connection):
all_urls = [
Expand Down Expand Up @@ -996,6 +1035,39 @@ def test_websocket_connection_without_verify(self, create_connection):
'enable_multithread': True,
}

@mock.patch('engineio.client.websocket.create_connection')
def test_websocket_connection_with_verify(self, create_connection):
create_connection.return_value.recv.return_value = packet.Packet(
packet.OPEN,
{
'sid': '123',
'upgrades': [],
'pingInterval': 1000,
'pingTimeout': 2000,
},
).encode()
http = mock.MagicMock()
http.cookies = []
http.auth = None
http.proxies = None
http.cert = None
http.verify = 'ca-bundle.crt'
c = client.Client(http_session=http)
c._read_loop_polling = mock.MagicMock()
c._read_loop_websocket = mock.MagicMock()
c._write_loop = mock.MagicMock()
on_connect = mock.MagicMock()
c.on('connect', on_connect)
c.connect('ws://foo', transports=['websocket'])

assert len(create_connection.call_args_list) == 1
assert create_connection.call_args[1] == {
'sslopt': {'ca_certs': 'ca-bundle.crt'},
'header': {},
'cookie': '',
'enable_multithread': True,
}

@mock.patch('engineio.client.websocket.create_connection')
def test_websocket_upgrade_no_pong(self, create_connection):
create_connection.return_value.recv.return_value = packet.Packet(
Expand Down

0 comments on commit 792bdd0

Please sign in to comment.