问题
How can I tell the Python MySQL connector which SSL/TLS protocol to use? Either specific (e.g. TLS1.2) or minimum.
How can I check which protocol is used on an established connection?
I've got an app that uses mysql-connector-python
(8.0.18). I connect something like this:
cnx = mysql.connector.connect(user='x', password='y', host='localhost', database='xyz')
Usually this gives me no trouble, but recently on a web hosting providers server it stopped working. The error I'm now getting is along the lines of:
mysql.connector.errors.InterfaceError: 2026 (HY000): SSL connection error: error:1408F10B:SSL routines:ssl3_get_record:wrong version number
And (connecting through Flask-SQLAlchemy setup):
_mysql_connector.MySQLInterfaceError: SSL connection error: error:140770FC:SSL routines:SSL23_GET_SERVER_HELLO:unknown protocol
What I can confirm is that if I instead do ssl_disabled=True
, as below, it connects properly (but without SSL/TLS I assume):
cnx = mysql.connector.connect(user='x', password='y', host='localhost', database='xyz', ssl_disabled=True)
I cannot alter the providers server, but they say that if I specify a specific version to use, for example TLS1.2, then it should connect properly. They also mention using the ssl.OP_NO_SSLv3 flag, however that is part of the SSLContext setup which I'm unsure how to apply to my connection.
I see that on their MySQL instance (which I cannot edit) they have no value set for:
SHOW VARIABLES LIKE 'tls_version'
SHOW STATUS LIKE 'Ssl_cipher'
SHOW STATUS LIKE 'Ssl_version'
回答1:
According to the MySQL documentation here, there is now a "tls-versions" option in 8.0.18 that allows your to specify the TLS version.
The connections should look something like this.
cnx = mysql.connector.connect(user='x', password='y', host='localhost', database='xyz', tls-versions='tls1.2')
I have not verfied the actual value of tls-versions so you may need to try a couple different values.
回答2:
In the source code I see a reference to ssl_options.get('version', None)
: https://github.com/mysql/mysql-connector-python/blob/b034f25ec8037f5d60015bf2ed4ee278ec12fd17/lib/mysql/connector/connection.py#L197
This variable is referenced in switch_to_ssl
and is passed to ssl.wrap_socket. The value here can be any of the PROTOCOL_*
constants defined in the ssl
module - https://docs.python.org/3/library/ssl.html#ssl.PROTOCOL_TLS
The _do_auth
method is called from _open_connection
, which again is called from the connect
method, and the value of ssl_options
is self._ssl
: https://github.com/mysql/mysql-connector-python/blob/b034f25ec8037f5d60015bf2ed4ee278ec12fd17/lib/mysql/connector/connection.py#L286-L288
There doesn't seem to be a way to control connection._ssl
from the connect
function, so we'll have to construct the object ourselves:
import ssl
try:
from mysql.connector.connection_cext import CMySQLConnection as MySQLConnection
except ImportError:
from mysql.connector.connection import MySQLConnection
connection = MySQLConnection()
connection._ssl['version'] = ssl.PROTOCOL_TLSv1_2
connection.connect(host='localhost', user='root', password='', database='example')
The above code was tested against a mysql Docker container started like this:
docker run --rm -it -e MYSQL_ALLOW_EMPTY_PASSWORD=true -e MYSQL_DATABASE=example -p 127.0.0.1:3306:3306 mysql
回答3:
Based on my experiments, the order of the import is the issue. The following works:
import mysql.connector
import ssl
The following doesn't work:
import ssl
import mysql.connector
no matter if you set the ssl_ca='', ssl_version=ssl.PROTOCOL_TLSv1_2
or not.
Also you get the same if you import requests
instead of import ssl
.
回答4:
From my recent understanding and help from Andreas answer I ended up with the following connect:
cnx = mysql.connector.connect(user='u', password='p', host='localhost', database='db', ssl_ca='', ssl_version=ssl.PROTOCOL_TLSv1_2)
Looking at the source, apparently all kwargs
provided that are also in mysql.connector.constants.DEFAULT_CONFIGURATION are accepted. This includes several that are not included in the documentation, like ssl_version
and ssl_cipher
. This mapping from kwargs
to the connection appears to happen in MySQLConnectionAbstract.connect. Note that setting ssl_version
might require some other kwargs as well. I needed to provide ssl_ca
along with it (might vary depending on what your MySQL instance has in SHOW VARIABLES LIKE '%ssl%'
).
With that in mind, I've got the following MVCE code:
import mysql.connector
import ssl
cnx = mysql.connector.connect(user='u', password='p', host='localhost', database='db', ssl_ca='', ssl_version=ssl.PROTOCOL_TLSv1_2)
cursor = cnx.cursor()
cursor.execute("SELECT 1")
for (number,) in cursor:
print('Number:', number)
print('SSL active:', cnx._ssl_active)
print('Connection SSL version:', cnx._ssl.get("version"))
print("Socket SSL version:", cnx._socket.sock.ssl_version)
cursor.close()
cnx.close()
Which for me outputs:
Number: 1
SSL active: True
Connection SSL version: _SSLMethod.PROTOCOL_TLSv1_2
Socket SSL version: _SSLMethod.PROTOCOL_TLSv1_2
If I do the same connection without specifying ssl_version
I get:
Number: 1
SSL active: True
Connection SSL version: None
Socket SSL version: _SSLMethod.PROTOCOL_TLS
If you replace ssl.PROTOCOL_TLSv1_2
with ssl.PROTOCOL_SSLv23
it should at least attempt a different protocol, but might fail if there are issues (e.g. unsupported).
回答5:
I took a look at our current configuration:
test env:
mysql> SHOW VARIABLES LIKE "%version%";
+-------------------------+------------------------------+
| Variable_name | Value |
+-------------------------+------------------------------+
| innodb_version | 5.7.27 |
| protocol_version | 10 |
| slave_type_conversions | |
| tls_version | TLSv1,TLSv1.1 |
| version | 5.7.27 |
| version_comment | MySQL Community Server (GPL) |
| version_compile_machine | x86_64 |
| version_compile_os | Linux |
+-------------------------+------------------------------+
8 rows in set (0.04 sec)
prodcut env:
mysql> SHOW VARIABLES LIKE "%version%";
+----------------------------------+-----------------------+
| Variable_name | Value |
+----------------------------------+-----------------------+
| innodb_polar_restore_old_version | OFF |
| innodb_version | 8.0.13 |
| protocol_version | 10 |
| rds_audit_log_version | MYSQL_V1 |
| rds_version | 13 |
| slave_type_conversions | |
| tls_version | TLSv1,TLSv1.1,TLSv1.2 |
| version | 8.0.13 |
| version_comment | Source distribution |
| version_compile_machine | x86_64 |
| version_compile_os | Linux |
| version_compile_zlib | 1.2.11 |
+----------------------------------+-----------------------+
12 rows in set (0.98 sec)
The previous version used is 8.0.16 and the release is correct. After the automatic update to 8.0.19, the test environment begins to appear the above error,But the production environment is not wrong。
So I added ssl_disabled = True according to the above operation
ssl_disabled=True
But I don’t know how it will affect those things.
I think this is because the 8.0.19 version uses TLSv1.2 by default, so it caused an error in my test environment.But I didn't find relevant documentation
来源:https://stackoverflow.com/questions/59300128/set-and-verify-ssl-tls-version-used-in-python-mysql-connection