Set and verify SSL/TLS version used in Python MySQL connection
Asked Answered
Y

5

5
  • 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'
Yurik answered 12/12, 2019 at 8:7 Comment(0)
Y
1

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).

Yurik answered 20/12, 2019 at 8:29 Comment(0)
B
2

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.

Baugh answered 17/12, 2019 at 21:4 Comment(2)
mysql documentation does not necessarily match 1:1 with python code. - is not a valid character for a keyword argument and what you've provided is invalid Python syntax.Auberta
this is the correct syntax cnx = mysql.connector.connect(user='x', password='y', host='localhost', database='xyz', tls_versions=['TLSv1.1', 'TLSv1.2'])Derosa
A
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
Auberta answered 18/12, 2019 at 14:27 Comment(1)
Thank you for this answer. From this digging I found an approach that suited me, so I won't mark it as accepted answer it since I ended up using a slightly different method, but it provided with me with invaluable input and deserves the bounty.Yurik
Y
1

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).

Yurik answered 20/12, 2019 at 8:29 Comment(0)
K
1

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.

Katar answered 9/1, 2020 at 15:29 Comment(0)
I
1

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

Iron answered 23/4, 2020 at 10:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.