long2ice / asyncmy

A fast asyncio MySQL/MariaDB driver with replication protocol support
https://github.com/long2ice/asyncmy
Apache License 2.0
230 stars 25 forks source link

SSL handshake error with MySQL 8.0.34 #80

Open LeeMendelowitz opened 9 months ago

LeeMendelowitz commented 9 months ago

I'm experiencing an issue where asyncmy 2.8.0 with Python 3.11 is generating an SSL Bad Handshake with MySQL 8.0.34. I'm not experiencing this issue with asyncmy when connecting to MySQL 8.0.26. I'm also not experiencing this issue if I switch to aiomysql and connect to MySQL 8.0.34.

So it appears that the issue is specific to asyncmy 2.8.0 and MySQL 8.0.34.

Here is some example code I'm using to reproduce the issue:

import ssl

from dotenv import dotenv_values
from sqlalchemy import text
from sqlalchemy.ext.asyncio import create_async_engine
import asyncio

env = dotenv_values(".env")

ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ctx.check_hostname = False
ctx.load_verify_locations(cafile=env["SSL_CERT_PATH"])

engine_aiomysql = create_async_engine(
    f"mysql+aiomysql://{env['USERNAME']}:{env['PASSWORD']}@{env['HOST']}:{env['PORT']}",
    connect_args={"ssl": ctx}
)

engine_asyncmy = create_async_engine(
    f"mysql+asyncmy://{env['USERNAME']}:{env['PASSWORD']}@{env['HOST']}:{env['PORT']}",
    connect_args={"ssl": ctx}
)

async def test_connection_aiomysql():
    async with engine_aiomysql.connect() as conn:
        res = await conn.execute(text("SELECT CURRENT_USER();"))
        print(res.fetchall())

async def test_connection_asyncmy():
    async with engine_asyncmy.connect() as conn:
        res = await conn.execute(text("SELECT CURRENT_USER();"))
        print(res.fetchall())

async def main():
    try:
        await test_connection_aiomysql()
        print("Success with aiomysql")
    except Exception as e:
        print(e)
        print("Error with aiomysql")

    try:
        await test_connection_asyncmy()
        print("Success with asyncmy")
    except Exception as e:
        print(e)
        print("Error with asyncmy")

if __name__ == '__main__':
    asyncio.run(main())

which generates output when run against a connection to MySQL 8.0.34:

[('lee@%',)]
Success with aiomysql
(asyncmy.errors.OperationalError) (1043, 'Bad handshake')
(Background on this error at: https://sqlalche.me/e/20/e3q8)
Error with asyncmy

In case it's helpful, here are some outputs showing the differences in TLS versions between the MySQL 8.0.26 and MySQL 8.0.34 instances I'm using:

MySQL 8.0.26

mysql> show variables like '%tls%';
+------------------------+-------------------------------+
| Variable_name          | Value                         |
+------------------------+-------------------------------+
| admin_tls_ciphersuites |                               |
| admin_tls_version      | TLSv1,TLSv1.1,TLSv1.2,TLSv1.3 |
| tls_ciphersuites       |                               |
| tls_version            | TLSv1,TLSv1.1,TLSv1.2,TLSv1.3 |
+------------------------+-------------------------------+
4 rows in set (0.01 sec)

MySQL 8.0.34

mysql> show variables like '%tls%';
+------------------------+-----------------+
| Variable_name          | Value           |
+------------------------+-----------------+
| admin_tls_ciphersuites |                 |
| admin_tls_version      | TLSv1.2,TLSv1.3 |
| tls_ciphersuites       |                 |
| tls_version            | TLSv1.2,TLSv1.3 |
+------------------------+-----------------+
4 rows in set (0.01 sec)
sublai commented 3 months ago

If anybody is experimenting this issue, switch to the aiomysql driver, it works with the following code:

import ssl
settings = YOUR_SETTINGS

ssl_context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
ssl_context.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
ssl_context.load_verify_locations(settings.SQL_SSL_CA)
ssl_context.load_cert_chain(certfile=settings.SQL_SSL_CERT, keyfile=settings.SQL_SSL_KEY)
ssl_context.check_hostname = False
ssl_args = {
    "ssl": ssl_context
}

engine = create_async_engine(
    settings.SQLALCHEMY_DATABASE_URI,
    connect_args=ssl_args
)