Skip to content

Commit ba32254

Browse files
committed
BUG#28295504: Disable SSL when using Unix socket connections
When using Unix socket connections, the communication is secure and the usage of the SSL overhead is unnecessary. This patch disables the usage of SSL for Unix socket connections. It also fixes BUG#28880051. Tests were added/changed for regression. Change-Id: I89c16ce87f9df8749edd98ddc48677383b4e64d3
1 parent c51ba79 commit ba32254

File tree

10 files changed

+92
-55
lines changed

10 files changed

+92
-55
lines changed

CHANGES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ v8.0.30
2020
- BUG#34127959: Add isolation level support in Django backend
2121
- BUG#33923516: Allow tuple of dictionaries as "failover" argument
2222
- BUG#28821983: Fix rounding errors for decimal values
23+
- BUG#28295504: Disable SSL when using Unix socket connections
2324

2425
v8.0.29
2526
=======

lib/mysql/connector/abstracts.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,13 @@ def get_self(self):
182182
"""
183183
return self
184184

185+
@property
186+
def is_secure(self):
187+
"""Return True if is a secure connection."""
188+
return self._ssl_active or (
189+
self._unix_socket is not None and os.name == "posix"
190+
)
191+
185192
@property
186193
def have_next_result(self):
187194
"""Return if have next result."""
@@ -566,12 +573,6 @@ def config(self, **kwargs):
566573
if "ssl_disabled" in config:
567574
self._ssl_disabled = config.pop("ssl_disabled")
568575

569-
if self._ssl_disabled and self._auth_plugin == "mysql_clear_password":
570-
raise InterfaceError(
571-
"Clear password authentication is not "
572-
"supported over insecure channels"
573-
)
574-
575576
# Other configuration
576577
set_ssl_flag = False
577578
for key, value in config.items():
@@ -593,6 +594,15 @@ def config(self, **kwargs):
593594
except AttributeError:
594595
setattr(self, attribute, value)
595596

597+
# Disable SSL for unix socket connections
598+
if self._unix_socket and os.name == "posix":
599+
self._ssl_disabled = True
600+
601+
if self._ssl_disabled and self._auth_plugin == "mysql_clear_password":
602+
raise InterfaceError(
603+
"Clear password authentication is not supported over insecure channels"
604+
)
605+
596606
if set_ssl_flag:
597607
if "verify_cert" not in self._ssl:
598608
self._ssl["verify_cert"] = DEFAULT_CONFIGURATION["ssl_verify_cert"]

lib/mysql/connector/connection.py

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -176,12 +176,11 @@ def _do_handshake(self):
176176
CharacterSet.set_mysql_version(self._server_version)
177177

178178
if not handshake["capabilities"] & ClientFlag.SSL:
179-
if self._auth_plugin == "mysql_clear_password":
180-
err_msg = (
181-
"Clear password authentication is not supported "
182-
"over insecure channels"
179+
if self._auth_plugin == "mysql_clear_password" and not self.is_secure:
180+
raise InterfaceError(
181+
"Clear password authentication is not supported over "
182+
"insecure channels"
183183
)
184-
raise InterfaceError(err_msg)
185184
if self._ssl.get("verify_cert"):
186185
raise InterfaceError(
187186
"SSL is required but the server doesn't support it",
@@ -223,7 +222,7 @@ def _do_auth(
223222
reply back. Raises any error coming from MySQL.
224223
"""
225224
self._ssl_active = False
226-
if client_flags & ClientFlag.SSL:
225+
if not self._ssl_disabled and (client_flags & ClientFlag.SSL):
227226
packet = self._protocol.make_auth_ssl(
228227
charset=charset, client_flags=client_flags
229228
)
@@ -310,14 +309,14 @@ def _auth_switch_request(self, username=None, password=None):
310309
auth_data,
311310
username=username or self._user,
312311
password=password,
313-
ssl_enabled=self._ssl_active,
312+
ssl_enabled=self.is_secure,
314313
)
315314
packet = self._auth_continue(auth, new_auth_plugin, auth_data)
316315

317316
if packet[4] == 1:
318317
auth_data = self._protocol.parse_auth_more_data(packet)
319318
auth = get_auth_plugin(new_auth_plugin)(
320-
auth_data, password=password, ssl_enabled=self._ssl_active
319+
auth_data, password=password, ssl_enabled=self.is_secure
321320
)
322321
if new_auth_plugin == "caching_sha2_password":
323322
response = auth.auth_response()
@@ -352,14 +351,14 @@ def _handle_mfa(self, packet):
352351
None,
353352
username=self._user,
354353
password=password,
355-
ssl_enabled=self._ssl_active,
354+
ssl_enabled=self.is_secure,
356355
)
357356
packet = self._auth_continue(auth, auth_plugin, packet)
358357

359358
if packet[4] == 1:
360359
auth_data = self._protocol.parse_auth_more_data(packet)
361360
auth = get_auth_plugin(auth_plugin)(
362-
auth_data, password=password, ssl_enabled=self._ssl_active
361+
auth_data, password=password, ssl_enabled=self.is_secure
363362
)
364363
if auth_plugin == "caching_sha2_password":
365364
response = auth.auth_response()
@@ -504,7 +503,7 @@ def _get_connection(self):
504503
Returns subclass of MySQLBaseSocket.
505504
"""
506505
conn = None
507-
if self.unix_socket and os.name != "nt":
506+
if self._unix_socket and os.name == "posix":
508507
conn = MySQLUnixSocket(unix_socket=self.unix_socket)
509508
else:
510509
conn = MySQLTCPSocket(

lib/mysql/connector/network.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import os
3333
import socket
3434
import struct
35+
import warnings
3536
import zlib
3637

3738
from collections import deque
@@ -504,6 +505,13 @@ def open_connection(self):
504505
except Exception as err:
505506
raise InterfaceError(str(err)) from err
506507

508+
def switch_to_ssl(self, *args, **kwargs): # pylint: disable=unused-argument
509+
"""Switch the socket to use SSL."""
510+
warnings.warn(
511+
"SSL is disabled when using unix socket connections",
512+
Warning,
513+
)
514+
507515

508516
class MySQLTCPSocket(BaseMySQLSocket):
509517
"""MySQL socket class using TCP/IP

lib/mysql/connector/plugins/__init__.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,10 @@
2828

2929
"""Base Authentication Plugin class."""
3030

31-
import logging
32-
3331
from abc import ABC
3432

3533
from .. import errors
3634

37-
logging.getLogger(__name__).addHandler(logging.NullHandler())
38-
39-
_LOGGER = logging.getLogger(__name__)
40-
4135

4236
class BaseAuthPlugin(ABC):
4337
"""Base class for authentication plugins

lib/mysql/connector/plugins/caching_sha2_password.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,6 @@ def _scramble(self):
7777
hash2 = hash2.digest()
7878
xored = [h1 ^ h2 for (h1, h2) in zip(hash1, hash2)]
7979
hash3 = struct.pack("32B", *xored)
80-
8180
return hash3
8281

8382
def _full_authentication(self):

tests/test_bugs.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5073,6 +5073,8 @@ def try_connect(self, tls_version, expected_ssl_version):
50735073
config = tests.get_mysql_config().copy()
50745074
config["tls_versions"] = tls_version
50755075
config["ssl_ca"] = ""
5076+
if "unix_socket" in config:
5077+
del config["unix_socket"]
50765078
cnx = connection.MySQLConnection(**config)
50775079
query = "SHOW STATUS LIKE 'ssl_version%'"
50785080
cur = cnx.cursor()

tests/test_connection.py

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@
7474
)
7575
from mysql.connector.conversion import MySQLConverter, MySQLConverterBase
7676
from mysql.connector.errors import InterfaceError, NotSupportedError, ProgrammingError
77-
from mysql.connector.network import TLS_V1_3_SUPPORTED
77+
from mysql.connector.network import TLS_V1_3_SUPPORTED, MySQLTCPSocket, MySQLUnixSocket
7878
from mysql.connector.optionfiles import read_option_files
7979
from mysql.connector.pooling import HAVE_DNSPYTHON
8080
from mysql.connector.utils import linux_distribution
@@ -163,6 +163,8 @@ def test_DEFAULT_CONFIGURATION(self):
163163
class MySQLConnectionTests(tests.MySQLConnectorTests):
164164
def setUp(self):
165165
config = tests.get_mysql_config()
166+
if "unix_socket" in config:
167+
del config["unix_socket"]
166168
self.cnx = connection.MySQLConnection(**config)
167169

168170
def tearDown(self):
@@ -1240,7 +1242,7 @@ class TestConverterWrong:
12401242
)
12411243
def test__get_connection(self):
12421244
"""Get connection based on configuration"""
1243-
if os.name != "nt":
1245+
if os.name == "posix" and self.cnx.unix_socket:
12441246
res = self.cnx._get_connection()
12451247
self.assertTrue(isinstance(res, network.MySQLUnixSocket))
12461248

@@ -1292,6 +1294,53 @@ def test_connect(self):
12921294
config["host"] = tests.fake_hostname()
12931295
self.assertRaises(errors.InterfaceError, cnx.connect, **config)
12941296

1297+
@unittest.skipUnless(os.name == "posix", "Platform does not support unix sockets")
1298+
@unittest.skipUnless(tests.SSL_AVAILABLE, "Python has no SSL support")
1299+
def test_connect_with_unix_socket(self):
1300+
"""Test connect with unix_socket and SSL connection options."""
1301+
config = tests.get_mysql_config()
1302+
if tests.MYSQL_EXTERNAL_SERVER:
1303+
if config.get("unix_socket") is None:
1304+
self.skipTest(
1305+
"The 'unix_socket' is not present in the external server "
1306+
"connection arguments"
1307+
)
1308+
return
1309+
else:
1310+
config.update(
1311+
{
1312+
"ssl_ca": os.path.abspath(
1313+
os.path.join(tests.SSL_DIR, "tests_CA_cert.pem")
1314+
),
1315+
"ssl_cert": os.path.abspath(
1316+
os.path.join(tests.SSL_DIR, "tests_client_cert.pem")
1317+
),
1318+
"ssl_key": os.path.abspath(
1319+
os.path.join(tests.SSL_DIR, "tests_client_key.pem")
1320+
),
1321+
}
1322+
)
1323+
config["unix_socket"] = tests.MYSQL_SERVERS[0].unix_socket
1324+
1325+
cnx_classes = [MySQLConnection]
1326+
if CMySQLConnection:
1327+
cnx_classes.append(CMySQLConnection)
1328+
1329+
# SSL should be disabled when using unix socket
1330+
for cls in cnx_classes:
1331+
with cls(**config) as cnx:
1332+
self.assertTrue(cnx._ssl_disabled)
1333+
if isinstance(cnx, MySQLConnection):
1334+
self.assertIsInstance(cnx._socket, MySQLUnixSocket)
1335+
del config["unix_socket"]
1336+
1337+
# SSL should be enabled when unix socket is not being used
1338+
for cls in cnx_classes:
1339+
with cls(**config) as cnx:
1340+
self.assertFalse(cnx._ssl_disabled)
1341+
if isinstance(cnx, MySQLConnection):
1342+
self.assertIsInstance(cnx._socket, MySQLTCPSocket)
1343+
12951344
def test_reconnect(self):
12961345
"""Reconnecting to the MySQL Server"""
12971346
supported_arguments = {

tests/test_network.py

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -321,31 +321,6 @@ def test_open_connection(self):
321321
except errors.Error as err:
322322
self.fail(str(err))
323323

324-
@unittest.skipIf(
325-
tests.MYSQL_EXTERNAL_SERVER,
326-
"Test not available for external MySQL servers",
327-
)
328-
@unittest.skipIf(
329-
not tests.SSL_AVAILABLE,
330-
"Could not test switch to SSL. Make sure Python supports SSL.",
331-
)
332-
def test_switch_to_ssl(self):
333-
"""Switch the socket to use SSL"""
334-
args = {
335-
"ca": os.path.join(tests.SSL_DIR, "tests_CA_cert.pem"),
336-
"cert": os.path.join(tests.SSL_DIR, "tests_client_cert.pem"),
337-
"key": os.path.join(tests.SSL_DIR, "tests_client_key.pem"),
338-
"cipher_suites": "AES256-SHA",
339-
}
340-
self.assertRaises(errors.InterfaceError, self.cnx.switch_to_ssl, **args)
341-
342-
# Handshake failure
343-
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
344-
sock.settimeout(4)
345-
sock.connect(self._unix_socket)
346-
self.cnx.sock = sock
347-
self.assertRaises(errors.InterfaceError, self.cnx.switch_to_ssl, **args)
348-
349324

350325
class MySQLTCPSocketTests(tests.MySQLConnectorTests):
351326

unittests.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,8 @@
373373
("", "--unix-socket"): {
374374
"dest": "unix_socket_folder",
375375
"metavar": "NAME",
376-
"help": "Folder where UNIX Sockets will be created",
376+
"help": "Folder where UNIX Sockets will be created or the unix socket "
377+
"to be used with an external server.",
377378
},
378379
("", "--ipv6"): {
379380
"dest": "ipv6",
@@ -953,12 +954,11 @@ def main():
953954
mysql_server.client_config = {
954955
"host": options.host,
955956
"port": options.port,
956-
"unix_socket": mysql_server.unix_socket,
957957
"user": options.user,
958958
"password": options.password,
959959
"database": "myconnpy",
960960
"connection_timeout": 60,
961-
"unix_socket": None,
961+
"unix_socket": options.unix_socket_folder,
962962
}
963963

964964
mysql_server.xplugin_config = {
@@ -967,7 +967,7 @@ def main():
967967
"user": options.user,
968968
"password": options.password,
969969
"schema": "myconnpy",
970-
"socket": None,
970+
"socket": options.unix_socket_folder,
971971
}
972972

973973
tests.MYSQL_SERVERS.append(mysql_server)

0 commit comments

Comments
 (0)