Skip to content

Commit a697e2d

Browse files
authored
Pass SSL properties from Glue Connection to MySQL (#664)
Co-authored-by: kukushking <>
1 parent d28408a commit a697e2d

File tree

4 files changed

+66
-3
lines changed

4 files changed

+66
-3
lines changed

awswrangler/_databases.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Databases Utilities."""
22

33
import logging
4+
import ssl
45
from typing import Any, Dict, Generator, Iterator, List, NamedTuple, Optional, Tuple, Union, cast
56

67
import boto3
@@ -22,6 +23,7 @@ class ConnectionAttributes(NamedTuple):
2223
host: str
2324
port: int
2425
database: str
26+
ssl_context: Optional[ssl.SSLContext]
2527

2628

2729
def _get_dbname(cluster_id: str, boto3_session: Optional[boto3.Session] = None) -> str:
@@ -41,13 +43,28 @@ def _get_connection_attributes_from_catalog(
4143
else:
4244
database_sep = "/"
4345
port, database = details["JDBC_CONNECTION_URL"].split(":")[3].split(database_sep)
46+
ssl_context: Optional[ssl.SSLContext] = None
47+
if details.get("JDBC_ENFORCE_SSL") == "true":
48+
ssl_cert_path: Optional[str] = details.get("CUSTOM_JDBC_CERT")
49+
ssl_cadata: Optional[str] = None
50+
if ssl_cert_path:
51+
bucket_name, key_path = _utils.parse_path(ssl_cert_path)
52+
client_s3: boto3.client = _utils.client(service_name="s3", session=boto3_session)
53+
try:
54+
ssl_cadata = client_s3.get_object(Bucket=bucket_name, Key=key_path)["Body"].read().decode("utf-8")
55+
except client_s3.exception.NoSuchKey:
56+
raise exceptions.NoFilesFound( # pylint: disable=raise-missing-from
57+
f"No CA certificate found at {ssl_cert_path}."
58+
)
59+
ssl_context = ssl.create_default_context(cadata=ssl_cadata)
4460
return ConnectionAttributes(
4561
kind=details["JDBC_CONNECTION_URL"].split(":")[1].lower(),
4662
user=details["USERNAME"],
4763
password=details["PASSWORD"],
4864
host=details["JDBC_CONNECTION_URL"].split(":")[2].replace("/", ""),
4965
port=int(port),
5066
database=dbname if dbname is not None else database,
67+
ssl_context=ssl_context,
5168
)
5269

5370

@@ -71,6 +88,7 @@ def _get_connection_attributes_from_secrets_manager(
7188
host=secret_value["host"],
7289
port=secret_value["port"],
7390
database=_dbname,
91+
ssl_context=None,
7492
)
7593

7694

awswrangler/mysql.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,15 @@ def connect(
7878
write_timeout: Optional[int] = None,
7979
connect_timeout: int = 10,
8080
) -> pymysql.connections.Connection:
81-
"""Return a pymysql connection from a Glue Catalog Connection.
81+
"""Return a pymysql connection from a Glue Catalog Connection or Secrets Manager.
8282
8383
https://pymysql.readthedocs.io
8484
85+
Note
86+
----
87+
It is only possible to configure SSL using Glue Catalog Connection. More at:
88+
https://docs.aws.amazon.com/glue/latest/dg/connection-defining.html
89+
8590
Parameters
8691
----------
8792
connection : str
@@ -136,6 +141,7 @@ def connect(
136141
password=attrs.password,
137142
port=attrs.port,
138143
host=attrs.host,
144+
ssl=attrs.ssl_context,
139145
read_timeout=read_timeout,
140146
write_timeout=write_timeout,
141147
connect_timeout=connect_timeout,

cloudformation/databases.yaml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,32 @@ Resources:
384384
PASSWORD:
385385
Ref: DatabasesPassword
386386
Name: aws-data-wrangler-mysql
387+
MysqlGlueConnectionSSL:
388+
Type: AWS::Glue::Connection
389+
Properties:
390+
CatalogId:
391+
Ref: AWS::AccountId
392+
ConnectionInput:
393+
Description: Connect to Aurora (MySQL) SSL enabled.
394+
ConnectionType: JDBC
395+
PhysicalConnectionRequirements:
396+
AvailabilityZone:
397+
Fn::Select:
398+
- 0
399+
- Fn::GetAZs: ''
400+
SecurityGroupIdList:
401+
- Ref: DatabaseSecurityGroup
402+
SubnetId:
403+
Fn::ImportValue: aws-data-wrangler-base-PrivateSubnet
404+
ConnectionProperties:
405+
JDBC_CONNECTION_URL:
406+
Fn::Sub: jdbc:mysql://${AuroraInstanceMysql.Endpoint.Address}:${AuroraInstanceMysql.Endpoint.Port}/test
407+
JDBC_ENFORCE_SSL: true
408+
CUSTOM_JDBC_CERT: s3://rds-downloads/rds-combined-ca-bundle.pem
409+
USERNAME: test
410+
PASSWORD:
411+
Ref: DatabasesPassword
412+
Name: aws-data-wrangler-mysql-ssl
387413
SqlServerGlueConnection:
388414
Type: AWS::Glue::Connection
389415
Properties:

tests/test_mysql.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,16 @@ def mysql_con():
2020
con.close()
2121

2222

23-
def test_connection():
24-
wr.mysql.connect("aws-data-wrangler-mysql", connect_timeout=10).close()
23+
@pytest.fixture(scope="function")
24+
def mysql_con_ssl():
25+
con = wr.mysql.connect("aws-data-wrangler-mysql-ssl")
26+
yield con
27+
con.close()
28+
29+
30+
@pytest.mark.parametrize("connection", ["aws-data-wrangler-mysql", "aws-data-wrangler-mysql-ssl"])
31+
def test_connection(connection):
32+
wr.mysql.connect(connection, connect_timeout=10).close()
2533

2634

2735
def test_read_sql_query_simple(databases_parameters):
@@ -42,6 +50,11 @@ def test_to_sql_simple(mysql_table, mysql_con):
4250
wr.mysql.to_sql(df, mysql_con, mysql_table, "test", "overwrite", True)
4351

4452

53+
def test_to_sql_simple_ssl(mysql_table, mysql_con_ssl):
54+
df = pd.DataFrame({"c0": [1, 2, 3], "c1": ["foo", "boo", "bar"]})
55+
wr.mysql.to_sql(df, mysql_con_ssl, mysql_table, "test", "overwrite", True)
56+
57+
4558
def test_sql_types(mysql_table, mysql_con):
4659
table = mysql_table
4760
df = get_df()

0 commit comments

Comments
 (0)