]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
add ssl_check_hostname option in mysqldb
authorJerry Zhao <jerryzhao@fortinet.com>
Fri, 23 Apr 2021 15:54:04 +0000 (11:54 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Fri, 7 May 2021 17:38:01 +0000 (13:38 -0400)
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

doc/build/changelog/unreleased_14/5397.rst [new file with mode: 0644]
lib/sqlalchemy/dialects/mysql/mysqldb.py
lib/sqlalchemy/dialects/mysql/pymysql.py
test/dialect/mysql/test_dialect.py

diff --git a/doc/build/changelog/unreleased_14/5397.rst b/doc/build/changelog/unreleased_14/5397.rst
new file mode 100644 (file)
index 0000000..ca6dcf9
--- /dev/null
@@ -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.
index 274f3eea427e8baa71995f26d57c35c805db6421..e1d11a7853b8bc6b203172d8247a4b837c6d98dc 100644 (file)
@@ -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
index 09b5abffe9cd03189bc51734f9063db07bde65d0..ec8f6ac8e9a834c933192aa8031200db4cc3b3b1 100644 (file)
@@ -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
 --------------------------
 
index 3c569bf058e705cb21329f4eb41e4b92977021e9..45d119cf3c6c95b3ba6d4c183010f177f23167e3 100644 (file)
@@ -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),