Skip to content

Commit 52cfd5e

Browse files
committed
Add implicit_tls connect arg to support non-standard implicit TLS connections, such as Google Cloud SQL
fixes aio-libs#757
1 parent a09398f commit 52cfd5e

File tree

7 files changed

+161
-16
lines changed

7 files changed

+161
-16
lines changed

.github/workflows/ci-cd.yml

+29-4
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,10 @@ jobs:
449449
options: '--name=mysqld'
450450
env:
451451
MYSQL_ROOT_PASSWORD: rootpw
452+
haproxy:
453+
image: haproxytech/haproxy-alpine:2.6
454+
ports:
455+
- 13306:13306
452456

453457
steps:
454458
- name: Setup Python ${{ matrix.py }}
@@ -569,6 +573,14 @@ jobs:
569573
# unfortunately we need this hacky workaround as GitHub Actions service containers can't reference data from our repo.
570574
- name: Prepare mysql
571575
run: |
576+
# inject HAproxy configuration
577+
docker container stop haproxy
578+
579+
docker container cp "${{ github.workspace }}/tests/ssl_resources/haproxy.cfg" haproxy:/usr/local/etc/haproxy/haproxy.cfg
580+
docker container cp "${{ github.workspace }}/tests/ssl_resources/ssl/server-combined.pem" haproxy:/usr/local/etc/haproxy/haproxy.pem
581+
582+
docker container start haproxy
583+
572584
# ensure server is started up
573585
while :
574586
do
@@ -599,10 +611,23 @@ jobs:
599611
mysql -h127.0.0.1 -uroot "-p$MYSQL_ROOT_PASSWORD" -e "SET GLOBAL local_infile=on"
600612
601613
- name: Run tests
602-
run: |
603-
# timeout ensures a more or less clean stop by sending a KeyboardInterrupt which will still provide useful logs
604-
timeout --preserve-status --signal=INT --verbose 570s \
605-
pytest --capture=no --verbosity 2 --cov-report term --cov-report xml --cov aiomysql --cov tests ./tests --mysql-unix-socket "unix-${{ join(matrix.db, '') }}=/tmp/run-${{ join(matrix.db, '-') }}/mysql.sock" --mysql-address "tcp-${{ join(matrix.db, '') }}=127.0.0.1:3306"
614+
# timeout ensures a more or less clean stop by sending a KeyboardInterrupt which will still provide useful logs
615+
run: >-
616+
timeout
617+
--preserve-status
618+
--signal=INT
619+
--verbose 570s
620+
pytest
621+
--capture=no
622+
--verbosity 2
623+
--cov-report term
624+
--cov-report xml
625+
--cov aiomysql
626+
--cov tests
627+
./tests
628+
--mysql-unix-socket "unix-${{ join(matrix.db, '') }}=/tmp/run-${{ join(matrix.db, '-') }}/mysql.sock"
629+
--mysql-address "tcp-${{ join(matrix.db, '') }}=127.0.0.1:3306"
630+
--mysql-address-tls "tcp-${{ join(matrix.db, '') }}=127.0.0.1:13306"
606631
env:
607632
PYTHONUNBUFFERED: 1
608633
timeout-minutes: 10

CHANGES.txt

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ next (unreleased)
88

99
* Remove deprecated Pool.get #706
1010

11+
* Add `implicit_tls` connect arg to support non-standard implicit TLS connections, such as Google Cloud SQL #757
12+
1113
0.1.1 (2022-05-08)
1214
^^^^^^^^^^^^^^^^^^
1315

aiomysql/connection.py

+20-8
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def connect(host="localhost", user=None, password="",
5353
connect_timeout=None, read_default_group=None,
5454
autocommit=False, echo=False,
5555
local_infile=False, loop=None, ssl=None, auth_plugin='',
56-
program_name='', server_public_key=None):
56+
program_name='', server_public_key=None, implicit_tls=False):
5757
"""See connections.Connection.__init__() for information about
5858
defaults."""
5959
coro = _connect(host=host, user=user, password=password, db=db,
@@ -66,7 +66,8 @@ def connect(host="localhost", user=None, password="",
6666
read_default_group=read_default_group,
6767
autocommit=autocommit, echo=echo,
6868
local_infile=local_infile, loop=loop, ssl=ssl,
69-
auth_plugin=auth_plugin, program_name=program_name)
69+
auth_plugin=auth_plugin, program_name=program_name,
70+
implicit_tls=implicit_tls)
7071
return _ConnectionContextManager(coro)
7172

7273

@@ -142,7 +143,7 @@ def __init__(self, host="localhost", user=None, password="",
142143
connect_timeout=None, read_default_group=None,
143144
autocommit=False, echo=False,
144145
local_infile=False, loop=None, ssl=None, auth_plugin='',
145-
program_name='', server_public_key=None):
146+
program_name='', server_public_key=None, implicit_tls=False):
146147
"""
147148
Establish a connection to the MySQL database. Accepts several
148149
arguments:
@@ -184,6 +185,9 @@ def __init__(self, host="localhost", user=None, password="",
184185
handshaking with MySQL. (omitted by default)
185186
:param server_public_key: SHA256 authentication plugin public
186187
key value.
188+
:param implicit_tls: Establish TLS immediately, skipping non-TLS
189+
preamble before upgrading to TLS.
190+
(default: False)
187191
:param loop: asyncio loop
188192
"""
189193
self._loop = loop or asyncio.get_event_loop()
@@ -218,6 +222,7 @@ def __init__(self, host="localhost", user=None, password="",
218222
self._auth_plugin_used = ""
219223
self._secure = False
220224
self.server_public_key = server_public_key
225+
self._implicit_tls = implicit_tls
221226
self.salt = None
222227

223228
from . import __version__
@@ -241,7 +246,7 @@ def __init__(self, host="localhost", user=None, password="",
241246
self.use_unicode = use_unicode
242247

243248
self._ssl_context = ssl
244-
if ssl:
249+
if ssl and not implicit_tls:
245250
client_flag |= CLIENT.SSL
246251

247252
self._encoding = charset_by_name(self._charset).encoding
@@ -536,7 +541,8 @@ async def _connect(self):
536541

537542
self._next_seq_id = 0
538543

539-
await self._get_server_information()
544+
if not self._implicit_tls:
545+
await self._get_server_information()
540546
await self._request_authentication()
541547

542548
self.connected_time = self._loop.time()
@@ -727,7 +733,8 @@ async def _execute_command(self, command, sql):
727733

728734
async def _request_authentication(self):
729735
# https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::HandshakeResponse
730-
if int(self.server_version.split('.', 1)[0]) >= 5:
736+
# FIXME: change this before merge
737+
if self._implicit_tls or int(self.server_version.split('.', 1)[0]) >= 5:
731738
self.client_flag |= CLIENT.MULTI_RESULTS
732739

733740
if self.user is None:
@@ -737,8 +744,10 @@ async def _request_authentication(self):
737744
data_init = struct.pack('<iIB23s', self.client_flag, MAX_PACKET_LEN,
738745
charset_id, b'')
739746

740-
if self._ssl_context and self.server_capabilities & CLIENT.SSL:
741-
self.write_packet(data_init)
747+
if self._ssl_context and \
748+
(self._implicit_tls or self.server_capabilities & CLIENT.SSL):
749+
if not self._implicit_tls:
750+
self.write_packet(data_init)
742751

743752
# Stop sending events to data_received
744753
self._writer.transport.pause_reading()
@@ -760,6 +769,9 @@ async def _request_authentication(self):
760769
server_hostname=self._host
761770
)
762771

772+
if self._implicit_tls:
773+
await self._get_server_information()
774+
763775
self._secure = True
764776

765777
if isinstance(self.user, str):

docs/connection.rst

+6-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ Example::
4747
connect_timeout=None, read_default_group=None,
4848
autocommit=False, echo=False
4949
ssl=None, auth_plugin='', program_name='',
50-
server_public_key=None, loop=None)
50+
server_public_key=None, loop=None, implicit_tls=False)
5151

5252
A :ref:`coroutine <coroutine>` that connects to MySQL.
5353

@@ -93,6 +93,11 @@ Example::
9393
``sys.argv[0]`` is no longer passed by default
9494
:param server_public_key: SHA256 authenticaiton plugin public key value.
9595
:param loop: asyncio event loop instance or ``None`` for default one.
96+
:param implicit_tls: Establish TLS immediately, skipping non-TLS
97+
preamble before upgrading to TLS.
98+
(default: False)
99+
100+
.. versionadded:: 0.2
96101
:returns: :class:`Connection` instance.
97102

98103

tests/conftest.py

+23-3
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,26 @@ def pytest_generate_tests(metafunc):
6363

6464
if ":" in addr:
6565
addr = addr.split(":", 1)
66-
mysql_addresses.append((addr[0], int(addr[1])))
66+
mysql_addresses.append((addr[0], int(addr[1]), False))
6767
else:
68-
mysql_addresses.append((addr, 3306))
68+
mysql_addresses.append((addr, 3306, False))
69+
70+
opt_mysql_address_tls =\
71+
list(metafunc.config.getoption("mysql_address_tls"))
72+
for i in range(len(opt_mysql_address_tls)):
73+
if "=" in opt_mysql_address_tls[i]:
74+
label, addr = opt_mysql_address_tls[i].split("=", 1)
75+
ids.append(label)
76+
else:
77+
addr = opt_mysql_address_tls[i]
78+
ids.append("tls{}".format(i))
79+
80+
addr = addr.split(":", 1)
81+
mysql_addresses.append((addr[0], int(addr[1]), True))
6982

7083
# default to connecting to localhost
7184
if len(mysql_addresses) == 0:
72-
mysql_addresses = [("127.0.0.1", 3306)]
85+
mysql_addresses = [("127.0.0.1", 3306, False)]
7386
ids = ["tcp-local"]
7487

7588
assert len(mysql_addresses) == len(set(mysql_addresses)), \
@@ -153,6 +166,12 @@ def pytest_addoption(parser):
153166
default=[],
154167
help="list of addresses to connect to: [name=]host[:port]",
155168
)
169+
parser.addoption(
170+
"--mysql-address-tls",
171+
action="append",
172+
default=[],
173+
help="list of addresses to connect to using implicit TLS: [name=]host:port",
174+
)
156175
parser.addoption(
157176
"--mysql-unix-socket",
158177
action="append",
@@ -271,6 +290,7 @@ def mysql_server(mysql_address):
271290
server_params["host"] = mysql_address[0]
272291
server_params["port"] = mysql_address[1]
273292
server_params["ssl"] = ctx
293+
server_params["implicit_tls"] = mysql_address[2]
274294

275295
try:
276296
connection = pymysql.connect(

tests/ssl_resources/haproxy.cfg

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# partially based on https://github.com/haproxytech/haproxy-docker-alpine/blob/8df4bf3078a338759ae484ab26908f7a4ba9484e/2.6/haproxy.cfg
2+
3+
global
4+
log stdout format raw local0
5+
6+
chroot /var/lib/haproxy
7+
pidfile /var/run/haproxy.pid
8+
maxconn 4000
9+
user haproxy
10+
group haproxy
11+
12+
ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
13+
ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
14+
ssl-default-bind-options prefer-client-ciphers no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets
15+
ssl-default-server-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384
16+
ssl-default-server-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
17+
ssl-default-server-options no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets
18+
19+
20+
defaults
21+
mode tcp
22+
log global
23+
timeout connect 10s
24+
timeout client 1m
25+
timeout server 1m
26+
27+
28+
frontend tcp-13306-front
29+
bind 127.0.0.1:13306 ssl crt haproxy.pem
30+
bind [::1]:13306 ssl crt haproxy.pem
31+
default_backend tcp-13306-backend
32+
33+
34+
backend tcp-13306-backend
35+
server mysql mysqld:3306
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
-----BEGIN RSA PRIVATE KEY-----
2+
MIIEogIBAAKCAQEAn1LwhlVDVK3k0W3LgUENsPflTN0zzDa94HWVdV9coN07RCXP
3+
5AWJ0j34c90tEWq/mPKXcQi765RiXgWnr2l8M4/LVvOUv95r+QRbRu9ceWwf3bnh
4+
uMIBGhBIblySEB7PPZ8xoIIAm4WQrg6dpasKbV9lPz6/WCQ20rpKN6adBLzIu6n5
5+
zcs62WPYC6lJE3dUB/mzVkFuRgzCvX2w17xpnv8cg+DiyxB8uwnPishgc0jhaizV
6+
aZqaQuDOY3/WkzNrOe3xGgxPraU+BPCynIl2CgFtN5/qX6BAn9SZ3rJRpbZoW7wD
7+
Ri7gOJXzq/yOr+X9kLDUvCElf3e/mEfjvZOaFQIDAQABAoIBAFNubFP8LEEYut1M
8+
4Kez+EZ22hXRNEG5XN9A095d7LS0hUefgWkH2W9GUmfiJ6qaOvEOAG4Jw9aOoqBX
9+
18LMu2SI5VOIRJnhEKubM21HBSb0jw9eOqy0sz0Bz9wzD63vZFkBl0xVJ5pJbEUp
10+
lDZgBhrWPL/MzQiMFkVtllXkIw+KNRIokV0HJn0VNUm+ORaDO0TnTAiqL7Wv553+
11+
lWvGaeI4NpMZOPtlgqym1neQmllkeB07pSEtTopO0iINwuTuDUU7IMleN4eOomfh
12+
GwirEHUban8BDL5djckS0GrkUq9EuJbvjEikHAFuNwz7D1Sn3LsDYygD6pKWoXxh
13+
8Ng/AZUCgYEAy1ma5xKvRZZ6QUFssZjtwbruxhj0j3lPA3t9VGlp4SQb3g6FgeOV
14+
6dyJLuuFfSLT9ps+k8D1Er+v85OqO0IM8TR12rPjWMzSjBz/xHb+0uRbEoPwArfn
15+
wcPj3NN6M+tcZH//djogrpphN7u+BudBG6YWzUOQjLwdStM52s8hFD8CgYEAyJM2
16+
M5AWPYL57CI1lqzN135aS6OFyG4N2+rtEWEGAmoeP+NoSGFmQPLTWBdT4ZOGl59/
17+
fSBrWRKted6/H9frjZuSdsGXFMb7e71DDyYZq7tJbYEExc0a6BePINLCwLCIMKHj
18+
PcoPGVsdQXfZK17+qzACwDNbEis/J3H7xcPv7KsCgYBO+SG7k/oV4HbiWPJJlsbf
19+
ciXBMXfpMIeLJq5p1faUxV09RA59f1F9XXS5kCZrjtca8ve+kjWbbm56/mIiWWiF
20+
VIZgxXQJzKIIYErEliIo7R6hdjQEGkAbdGROIqNW/pUHQt6Hn9OJe9M9vd/y9mTG
21+
xB4e4ZqFzZjisl3JqJ+EKQKBgFyJboxDgb9HWj7TWZ32g9FT/hy/iM172PEJZe6K
22+
sNcUVnhrVoVuSlrUrSULPivogEQb1hnIhz5FG7wKRGtQluByUhRwJF/1nbjtDK9E
23+
iLtuYOYgjC8l/a/ujp46Hpf/2hV12v1655RvMQQvYwZbgWtBb0N1biLnyO9N6zbG
24+
uz6ZAoGAXW3mhN9zbN6EgsIlLu+wCdPuC9Vs968gCT612E8Ijul1kiSODG3rbMoG
25+
2FbjqZLahyX8vWVhX/m4xDqO7DXTwi81polfFuxbc/PimOLI4DKq62lWLMBDmUba
26+
X8Bxal3FXLvdcNEplcXadqmxJeXMYnYsfC+MCQhe6im0bGyDOAg=
27+
-----END RSA PRIVATE KEY-----
28+
-----BEGIN CERTIFICATE-----
29+
MIIC/jCCAeYCAQEwDQYJKoZIhvcNAQELBQAwRTELMAkGA1UEBhMCQVUxEzARBgNV
30+
BAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0
31+
ZDAeFw0xODAzMjkyMTMzMTFaFw0yODAyMDUyMTMzMTFaMEUxCzAJBgNVBAYTAkFV
32+
MRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRz
33+
IFB0eSBMdGQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCfUvCGVUNU
34+
reTRbcuBQQ2w9+VM3TPMNr3gdZV1X1yg3TtEJc/kBYnSPfhz3S0Rar+Y8pdxCLvr
35+
lGJeBaevaXwzj8tW85S/3mv5BFtG71x5bB/dueG4wgEaEEhuXJIQHs89nzGgggCb
36+
hZCuDp2lqwptX2U/Pr9YJDbSuko3pp0EvMi7qfnNyzrZY9gLqUkTd1QH+bNWQW5G
37+
DMK9fbDXvGme/xyD4OLLEHy7Cc+KyGBzSOFqLNVpmppC4M5jf9aTM2s57fEaDE+t
38+
pT4E8LKciXYKAW03n+pfoECf1JneslGltmhbvANGLuA4lfOr/I6v5f2QsNS8ISV/
39+
d7+YR+O9k5oVAgMBAAEwDQYJKoZIhvcNAQELBQADggEBACUWEAWfv3EOy8JmUbWA
40+
jEytJIh/N55hfknninjOBPMv1U1BRixJwXVKlwA8o+8JiacriObWeje2rDUOt6zY
41+
U5DnySQbTYJcJZ9jprqU7VXST7D9NvA0ueLclWTZcqIr/josyhK+l1YbezFYBf41
42+
JQ4PVzkNz9Of4e022qONnlEX0MbtFlcyPEK4yWyXLAhidPAV9QcOCy85vob0+3EE
43+
hmRVVzcTv4Pbzgpee0ZORqozSLzZ3N6RvDyYIczqaytcbyvaQ7GuykE7XvIK5hz0
44+
EM8pwsvxSY1z2yNIw38M8ZOYk18LsEGkf/TyT6eQqymMMD9Qy8rOTsOLfY5eQCf7
45+
pKQ=
46+
-----END CERTIFICATE-----

0 commit comments

Comments
 (0)