From: Jerry Zhao Date: Fri, 23 Apr 2021 15:54:04 +0000 (-0400) Subject: add ssl_check_hostname option in mysqldb X-Git-Tag: rel_1_4_15~12 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=f8b3a6a6e8a51bcb697ffd9aa79ad6ee706e1d24;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git add ssl_check_hostname option in mysqldb Added support for the ``ssl_check_hostname=`` parameter in mysql connection URIs and updated the mysql dialect documentation regarding secure connections. Original pull request courtesy of Jerry Zhao. Fixes: #5397 Closes: #5759 Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/5759 Pull-request-sha: 75f4bdc68d4b5745c518472e8bc2b02cec0f81e6 Change-Id: I964bfa7a4c15e215a3ad6e2b907cb78f5b3e5036 --- diff --git a/doc/build/changelog/unreleased_14/5397.rst b/doc/build/changelog/unreleased_14/5397.rst new file mode 100644 index 0000000000..ca6dcf9cd1 --- /dev/null +++ b/doc/build/changelog/unreleased_14/5397.rst @@ -0,0 +1,7 @@ +.. change:: + :tags: bug, documentation, mysql + :tickets: 5397 + + Added support for the ``ssl_check_hostname=`` parameter in mysql connection + URIs and updated the mysql dialect documentation regarding secure + connections. Original pull request courtesy of Jerry Zhao. diff --git a/lib/sqlalchemy/dialects/mysql/mysqldb.py b/lib/sqlalchemy/dialects/mysql/mysqldb.py index 274f3eea42..e1d11a7853 100644 --- a/lib/sqlalchemy/dialects/mysql/mysqldb.py +++ b/lib/sqlalchemy/dialects/mysql/mysqldb.py @@ -31,6 +31,55 @@ Unicode Please see :ref:`mysql_unicode` for current recommendations on unicode handling. +.. _mysqldb_ssl: + +SSL Connections +---------------- + +The mysqlclient and PyMySQL DBAPIs accept an additional dictionary under the +key "ssl", which may be specified using the +:paramref:`_sa.create_engine.connect_args` dictionary:: + + engine = create_engine( + "mysql+mysqldb://scott:tiger@192.168.0.134/test", + connect_args={ + "ssl": { + "ssl_ca": "/home/gord/client-ssl/ca.pem", + "ssl_cert": "/home/gord/client-ssl/client-cert.pem", + "ssl_key": "/home/gord/client-ssl/client-key.pem" + } + } + ) + +For convenience, the following keys may also be specified inline within the URL +where they will be interpreted into the "ssl" dictionary automatically: +"ssl_ca", "ssl_cert", "ssl_key", "ssl_capath", "ssl_cipher", +"ssl_check_hostname". An example is as follows:: + + connection_uri = ( + "mysql+mysqldb://scott:tiger@192.168.0.134/test" + "?ssl_ca=/home/gord/client-ssl/ca.pem" + "&ssl_cert=/home/gord/client-ssl/client-cert.pem" + "&ssl_key=/home/gord/client-ssl/client-key.pem" + ) + +If the server uses an automatically-generated certificate that is self-signed +or does not match the host name (as seen from the client), it may also be +necessary to indicate ``ssl_check_hostname=false``:: + + connection_uri = ( + "mysql+pymysql://scott:tiger@192.168.0.134/test" + "?ssl_ca=/home/gord/client-ssl/ca.pem" + "&ssl_cert=/home/gord/client-ssl/client-cert.pem" + "&ssl_key=/home/gord/client-ssl/client-key.pem" + "&ssl_check_hostname=false" + ) + + +.. seealso:: + + :ref:`pymysql_ssl` in the PyMySQL dialect + Using MySQLdb with Google Cloud SQL ----------------------------------- @@ -203,11 +252,18 @@ class MySQLDialect_mysqldb(MySQLDialect): # query string. ssl = {} - keys = ["ssl_ca", "ssl_key", "ssl_cert", "ssl_capath", "ssl_cipher"] - for key in keys: + keys = [ + ("ssl_ca", str), + ("ssl_key", str), + ("ssl_cert", str), + ("ssl_capath", str), + ("ssl_cipher", str), + ("ssl_check_hostname", bool), + ] + for key, kw_type in keys: if key in opts: ssl[key[4:]] = opts[key] - util.coerce_kw_type(ssl, key[4:], str) + util.coerce_kw_type(ssl, key[4:], kw_type) del opts[key] if ssl: opts["ssl"] = ssl diff --git a/lib/sqlalchemy/dialects/mysql/pymysql.py b/lib/sqlalchemy/dialects/mysql/pymysql.py index 09b5abffe9..ec8f6ac8e9 100644 --- a/lib/sqlalchemy/dialects/mysql/pymysql.py +++ b/lib/sqlalchemy/dialects/mysql/pymysql.py @@ -19,6 +19,15 @@ Unicode Please see :ref:`mysql_unicode` for current recommendations on unicode handling. +.. _pymysql_ssl: + +SSL Connections +------------------ + +The PyMySQL DBAPI accepts the same SSL arguments as that of MySQLdb, +described at :ref:`mysqldb_ssl`. See that section for examples. + + MySQL-Python Compatibility -------------------------- diff --git a/test/dialect/mysql/test_dialect.py b/test/dialect/mysql/test_dialect.py index 3c569bf058..45d119cf3c 100644 --- a/test/dialect/mysql/test_dialect.py +++ b/test/dialect/mysql/test_dialect.py @@ -124,43 +124,43 @@ class DialectTest(fixtures.TestBase): error = getattr(dbapi, exc_cls_name)(arg0, message) eq_(dialect.is_disconnect(error, None, None), is_disconnect) - def test_ssl_arguments_mysqldb(self): - from sqlalchemy.dialects.mysql import mysqldb - - dialect = mysqldb.dialect() - self._test_ssl_arguments(dialect) - - def test_ssl_arguments_oursql(self): - from sqlalchemy.dialects.mysql import oursql - - dialect = oursql.dialect() - self._test_ssl_arguments(dialect) - - def _test_ssl_arguments(self, dialect): - kwarg = dialect.create_connect_args( - make_url( - "mysql://scott:tiger@localhost:3306/test" - "?ssl_ca=/ca.pem&ssl_cert=/cert.pem&ssl_key=/key.pem" - ) - )[1] - # args that differ among mysqldb and oursql + @testing.combinations( + ("mysqldb"), + ("pymysql"), + ("oursql"), + id_="s", + argnames="driver_name", + ) + def test_ssl_arguments(self, driver_name): + url = ( + "mysql+%s://scott:tiger@localhost:3306/test" + "?ssl_ca=/ca.pem&ssl_cert=/cert.pem&ssl_key=/key.pem" % driver_name + ) + url_obj = make_url(url) + dialect = url_obj.get_dialect()() + + expected = { + "{}".format( + "password" if driver_name == "pymysql" else "passwd" + ): "tiger", + "{}".format( + "database" if driver_name == "pymysql" else "db" + ): "test", + "ssl": {"ca": "/ca.pem", "cert": "/cert.pem", "key": "/key.pem"}, + "host": "localhost", + "user": "scott", + "port": 3306, + } + # add check_hostname check for mysqldb and pymysql + if driver_name in ["mysqldb", "pymysql"]: + url = url + "&ssl_check_hostname=false" + expected["ssl"]["check_hostname"] = False + + kwarg = dialect.create_connect_args(make_url(url))[1] + # args that differ between oursql and others for k in ("use_unicode", "found_rows", "client_flag"): kwarg.pop(k, None) - eq_( - kwarg, - { - "passwd": "tiger", - "db": "test", - "ssl": { - "ca": "/ca.pem", - "cert": "/cert.pem", - "key": "/key.pem", - }, - "host": "localhost", - "user": "scott", - "port": 3306, - }, - ) + eq_(kwarg, expected) @testing.combinations( ("compress", True),