cloud-sql-python-connector 1.9.2__py2.py3-none-any.whl → 1.11.0__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cloud_sql_python_connector-1.9.2.dist-info → cloud_sql_python_connector-1.11.0.dist-info}/METADATA +75 -13
- cloud_sql_python_connector-1.11.0.dist-info/RECORD +22 -0
- google/cloud/sql/connector/__init__.py +9 -2
- google/cloud/sql/connector/client.py +96 -20
- google/cloud/sql/connector/connection_info.py +105 -0
- google/cloud/sql/connector/connector.py +98 -47
- google/cloud/sql/connector/enums.py +85 -0
- google/cloud/sql/connector/exceptions.py +11 -3
- google/cloud/sql/connector/instance.py +49 -179
- google/cloud/sql/connector/lazy.py +132 -0
- google/cloud/sql/connector/refresh_utils.py +48 -1
- google/cloud/sql/connector/version.py +1 -1
- cloud_sql_python_connector-1.9.2.dist-info/RECORD +0 -19
- {cloud_sql_python_connector-1.9.2.dist-info → cloud_sql_python_connector-1.11.0.dist-info}/LICENSE +0 -0
- {cloud_sql_python_connector-1.9.2.dist-info → cloud_sql_python_connector-1.11.0.dist-info}/WHEEL +0 -0
- {cloud_sql_python_connector-1.9.2.dist-info → cloud_sql_python_connector-1.11.0.dist-info}/top_level.txt +0 -0
{cloud_sql_python_connector-1.9.2.dist-info → cloud_sql_python_connector-1.11.0.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: cloud-sql-python-connector
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.11.0
|
|
4
4
|
Summary: The Cloud SQL Python Connector is a library that can be used alongside a database driver to allow users with sufficient permissions to connect to a Cloud SQL database without having to manually allowlist IPs or manage SSL certificates.
|
|
5
5
|
Home-page: https://github.com/GoogleCloudPlatform/cloud-sql-python-connector
|
|
6
6
|
Author: Google LLC
|
|
@@ -207,9 +207,10 @@ To close the `Connector` object's background resources, call its `close()` metho
|
|
|
207
207
|
connector.close()
|
|
208
208
|
```
|
|
209
209
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
210
|
+
> [!NOTE]
|
|
211
|
+
>
|
|
212
|
+
> For more examples of using SQLAlchemy to manage connection pooling with the connector,
|
|
213
|
+
> please see [Cloud SQL SQLAlchemy Samples](https://cloud.google.com/sql/docs/postgres/connect-connectors#python_1).
|
|
213
214
|
|
|
214
215
|
### Configuring the Connector
|
|
215
216
|
|
|
@@ -218,14 +219,15 @@ defaults for each connection to make, you can initialize a
|
|
|
218
219
|
`Connector` object as follows:
|
|
219
220
|
|
|
220
221
|
```python
|
|
221
|
-
from google.cloud.sql.connector import Connector
|
|
222
|
+
from google.cloud.sql.connector import Connector
|
|
222
223
|
|
|
223
224
|
# Note: all parameters below are optional
|
|
224
225
|
connector = Connector(
|
|
225
226
|
ip_type="public", # can also be "private" or "psc"
|
|
226
227
|
enable_iam_auth=False,
|
|
227
228
|
timeout=30,
|
|
228
|
-
credentials=custom_creds # google.auth.credentials.Credentials
|
|
229
|
+
credentials=custom_creds, # google.auth.credentials.Credentials
|
|
230
|
+
refresh_strategy="lazy", # can be "lazy" or "background"
|
|
229
231
|
)
|
|
230
232
|
```
|
|
231
233
|
|
|
@@ -287,6 +289,21 @@ with Connector() as connector:
|
|
|
287
289
|
print(row)
|
|
288
290
|
```
|
|
289
291
|
|
|
292
|
+
### Configuring a Lazy Refresh (Cloud Run, Cloud Functions etc.)
|
|
293
|
+
|
|
294
|
+
The Connector's `refresh_strategy` argument can be set to `"lazy"` to configure
|
|
295
|
+
the Python Connector to retrieve connection info lazily and as-needed.
|
|
296
|
+
Otherwise, a background refresh cycle runs to retrive the connection info
|
|
297
|
+
periodically. This setting is useful in environments where the CPU may be
|
|
298
|
+
throttled outside of a request context, e.g., Cloud Run, Cloud Functions, etc.
|
|
299
|
+
|
|
300
|
+
To set the refresh strategy, set the `refresh_strategy` keyword argument when
|
|
301
|
+
initializing a `Connector`:
|
|
302
|
+
|
|
303
|
+
```python
|
|
304
|
+
connector = Connector(refresh_strategy="lazy")
|
|
305
|
+
```
|
|
306
|
+
|
|
290
307
|
### Specifying IP Address Type
|
|
291
308
|
|
|
292
309
|
The Cloud SQL Python Connector can be used to connect to Cloud SQL instances
|
|
@@ -309,9 +326,11 @@ conn = connector.connect(
|
|
|
309
326
|
)
|
|
310
327
|
```
|
|
311
328
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
329
|
+
> [!IMPORTANT]
|
|
330
|
+
>
|
|
331
|
+
> If specifying Private IP or Private Service Connect (PSC), your application must be
|
|
332
|
+
> attached to the proper VPC network to connect to your Cloud SQL instance. For most
|
|
333
|
+
> applications this will require the use of a [VPC Connector][vpc-connector].
|
|
315
334
|
|
|
316
335
|
[psc]: https://cloud.google.com/vpc/docs/private-service-connect
|
|
317
336
|
[vpc-connector]: https://cloud.google.com/vpc/docs/configure-serverless-vpc-access#create-connector
|
|
@@ -340,9 +359,20 @@ conn = connector.connect(
|
|
|
340
359
|
)
|
|
341
360
|
```
|
|
342
361
|
|
|
343
|
-
### SQL Server
|
|
362
|
+
### SQL Server (MSSQL)
|
|
363
|
+
|
|
364
|
+
> [!IMPORTANT]
|
|
365
|
+
>
|
|
366
|
+
> If your SQL Server instance is set to [enforce SSL connections](https://cloud.google.com/sql/docs/sqlserver/configure-ssl-instance#enforcing-ssl),
|
|
367
|
+
> you need to download the CA certificate for your instance and include `cafile={path to downloaded certificate}`
|
|
368
|
+
> and `validate_host=False`. This is a workaround for a [known issue](https://issuetracker.google.com/184867147).
|
|
369
|
+
|
|
370
|
+
#### Active Directory Authentication
|
|
344
371
|
|
|
345
|
-
Active Directory authentication for SQL Server instances is currently only supported on Windows.
|
|
372
|
+
Active Directory authentication for SQL Server instances is currently only supported on Windows.
|
|
373
|
+
First, make sure to follow [these steps](https://cloud.google.com/blog/topics/developers-practitioners/creating-sql-server-instance-integrated-active-directory-using-google-cloud-sql)
|
|
374
|
+
to set up a Managed AD domain and join your Cloud SQL instance to the domain.
|
|
375
|
+
[See here for more info on Cloud SQL Active Directory integration](https://cloud.google.com/sql/docs/sqlserver/ad).
|
|
346
376
|
|
|
347
377
|
Once you have followed the steps linked above, you can run the following code to return a connection object:
|
|
348
378
|
|
|
@@ -375,6 +405,14 @@ The Python Connector can be used alongside popular Python web frameworks such
|
|
|
375
405
|
as Flask, FastAPI, etc, to integrate Cloud SQL databases within your
|
|
376
406
|
web applications.
|
|
377
407
|
|
|
408
|
+
> [!NOTE]
|
|
409
|
+
>
|
|
410
|
+
> For serverless environments such as Cloud Functions, Cloud Run, etc, it may be
|
|
411
|
+
> beneficial to initialize the `Connector` with the lazy refresh strategy.
|
|
412
|
+
> i.e. `Connector(refresh_strategy="lazy")`
|
|
413
|
+
>
|
|
414
|
+
> See [Configuring a Lazy Refresh](#configuring-a-lazy-refresh-cloud-run-cloud-functions-etc)
|
|
415
|
+
|
|
378
416
|
#### Flask-SQLAlchemy
|
|
379
417
|
|
|
380
418
|
[Flask-SQLAlchemy](https://flask-sqlalchemy.palletsprojects.com/en/2.x/)
|
|
@@ -555,8 +593,10 @@ An alternative to using the `create_async_connector` function is initializing
|
|
|
555
593
|
a `Connector` as an async context manager, removing the need for explicit
|
|
556
594
|
calls to `connector.close_async()` to cleanup resources.
|
|
557
595
|
|
|
558
|
-
|
|
559
|
-
|
|
596
|
+
> [!NOTE]
|
|
597
|
+
>
|
|
598
|
+
> This alternative requires that the running event loop be
|
|
599
|
+
> passed in as the `loop` argument to `Connector()`.
|
|
560
600
|
|
|
561
601
|
```python
|
|
562
602
|
import asyncio
|
|
@@ -603,6 +643,28 @@ async def main():
|
|
|
603
643
|
await pool.dispose()
|
|
604
644
|
```
|
|
605
645
|
|
|
646
|
+
### Debug Logging
|
|
647
|
+
|
|
648
|
+
The Cloud SQL Python Connector uses the standard [Python logging module][python-logging]
|
|
649
|
+
for debug logging support.
|
|
650
|
+
|
|
651
|
+
Add the below code to your application to enable debug logging with the Cloud SQL
|
|
652
|
+
Python Connector:
|
|
653
|
+
|
|
654
|
+
```python
|
|
655
|
+
import logging
|
|
656
|
+
|
|
657
|
+
logging.basicConfig(format="%(asctime)s [%(levelname)s]: %(message)s")
|
|
658
|
+
logger = logging.getLogger(name="google.cloud.sql.connector")
|
|
659
|
+
logger.setLevel(logging.DEBUG)
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
For more details on configuring logging, please refer to the
|
|
663
|
+
[Python logging docs][configure-logging].
|
|
664
|
+
|
|
665
|
+
[python-logging]: https://docs.python.org/3/library/logging.html
|
|
666
|
+
[configure-logging]: https://docs.python.org/3/howto/logging.html#configuring-logging
|
|
667
|
+
|
|
606
668
|
## Support policy
|
|
607
669
|
|
|
608
670
|
### Major version lifecycle
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
google/cloud/sql/connector/__init__.py,sha256=x8ui7u3gMlMI-LLJkKTtUU9yvzZvQkTJ-yzr2FH4VTU,983
|
|
2
|
+
google/cloud/sql/connector/asyncpg.py,sha256=c4lAlvEgGf_eBq0G8MVNPRAJQQvo6DcwybiXDW36qkU,1838
|
|
3
|
+
google/cloud/sql/connector/client.py,sha256=wGO6ESxWWYT5qZ-Utg_qh-Rtl_4k5tIjoD-ILcP4REQ,11481
|
|
4
|
+
google/cloud/sql/connector/connection_info.py,sha256=3nFuPtGPLUEZRy3Zx9Z2DvTdh6TJqmhOOoE6yKmSGBQ,4308
|
|
5
|
+
google/cloud/sql/connector/connector.py,sha256=NTubTmsT8rPpGYuhxXgKDW3MX7KUi4WSalKCHJkHFY8,21417
|
|
6
|
+
google/cloud/sql/connector/enums.py,sha256=44tfMm8D_t0ls6GfKwiQ6YkslWM21PZR40RhJPWntkc,2882
|
|
7
|
+
google/cloud/sql/connector/exceptions.py,sha256=OVenqBkkn98xFeytHuOXFTWa9R5bCmemBtbSIm8caoM,1665
|
|
8
|
+
google/cloud/sql/connector/instance.py,sha256=EySszNNHMFjAFskf6-G6Upikvb-tYjEwXx9A_OJBl14,10136
|
|
9
|
+
google/cloud/sql/connector/lazy.py,sha256=og4UPiJxeuLgN7tyqrFAnQF3895Yqdf6kkd4HZSzVJA,5093
|
|
10
|
+
google/cloud/sql/connector/pg8000.py,sha256=EZuO7XgCkG_Y79NQlh3Vv8Ff2w85BFJyFVO237hx1GM,1814
|
|
11
|
+
google/cloud/sql/connector/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
+
google/cloud/sql/connector/pymysql.py,sha256=wldUgwDPlPa8Nj5F3JaipuP8hJyvzlgOAJbRnOoaB1U,2105
|
|
13
|
+
google/cloud/sql/connector/pytds.py,sha256=vKJoE7E2P0GrhZojg8bGZkqCMLc8dgKAcXE4ee7vEVc,2452
|
|
14
|
+
google/cloud/sql/connector/rate_limiter.py,sha256=bPTupfM4f5QRSCmCAWMqjQylEB7bUM-50plVBU3W2CA,2993
|
|
15
|
+
google/cloud/sql/connector/refresh_utils.py,sha256=CajL-MOcQBBRUkywowdHB3GNOr0WpQn37DXG7dRpkMk,5069
|
|
16
|
+
google/cloud/sql/connector/utils.py,sha256=FQs7RgoViJLIFgJIynVT-_DwAVWnpyXIKu5WZ4m10Ts,3448
|
|
17
|
+
google/cloud/sql/connector/version.py,sha256=l5iWT7w_8JLh0wHTJOCrcxkaD1xxBwPGJDzeSMQLXTI,598
|
|
18
|
+
cloud_sql_python_connector-1.11.0.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
|
19
|
+
cloud_sql_python_connector-1.11.0.dist-info/METADATA,sha256=4NukfHGYVymtt0NbBrH6bFW3BM544K072H-_f8gx6KM,25925
|
|
20
|
+
cloud_sql_python_connector-1.11.0.dist-info/WHEEL,sha256=bb2Ot9scclHKMOLDEHY6B2sicWOgugjFKaJsT7vwMQo,110
|
|
21
|
+
cloud_sql_python_connector-1.11.0.dist-info/top_level.txt,sha256=_1QvSJIhFAGfxb79D6DhB7SUw2X6T4rwnz_LLrbcD3c,7
|
|
22
|
+
cloud_sql_python_connector-1.11.0.dist-info/RECORD,,
|
|
@@ -16,7 +16,14 @@ limitations under the License.
|
|
|
16
16
|
|
|
17
17
|
from google.cloud.sql.connector.connector import Connector
|
|
18
18
|
from google.cloud.sql.connector.connector import create_async_connector
|
|
19
|
-
from google.cloud.sql.connector.
|
|
19
|
+
from google.cloud.sql.connector.enums import IPTypes
|
|
20
|
+
from google.cloud.sql.connector.enums import RefreshStrategy
|
|
20
21
|
from google.cloud.sql.connector.version import __version__
|
|
21
22
|
|
|
22
|
-
__all__ = [
|
|
23
|
+
__all__ = [
|
|
24
|
+
"__version__",
|
|
25
|
+
"create_async_connector",
|
|
26
|
+
"Connector",
|
|
27
|
+
"IPTypes",
|
|
28
|
+
"RefreshStrategy",
|
|
29
|
+
]
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
from __future__ import annotations
|
|
16
16
|
|
|
17
|
+
import asyncio
|
|
17
18
|
import datetime
|
|
18
19
|
import logging
|
|
19
20
|
from typing import Any, Dict, Optional, Tuple, TYPE_CHECKING
|
|
@@ -21,9 +22,13 @@ from typing import Any, Dict, Optional, Tuple, TYPE_CHECKING
|
|
|
21
22
|
import aiohttp
|
|
22
23
|
from cryptography.hazmat.backends import default_backend
|
|
23
24
|
from cryptography.x509 import load_pem_x509_certificate
|
|
24
|
-
|
|
25
|
+
from google.auth.credentials import TokenState
|
|
26
|
+
from google.auth.transport import requests
|
|
25
27
|
|
|
28
|
+
from google.cloud.sql.connector.connection_info import ConnectionInfo
|
|
29
|
+
from google.cloud.sql.connector.exceptions import AutoIAMAuthNotSupported
|
|
26
30
|
from google.cloud.sql.connector.refresh_utils import _downscope_credentials
|
|
31
|
+
from google.cloud.sql.connector.refresh_utils import retry_50x
|
|
27
32
|
from google.cloud.sql.connector.version import __version__ as version
|
|
28
33
|
|
|
29
34
|
if TYPE_CHECKING:
|
|
@@ -113,9 +118,6 @@ class CloudSQLClient:
|
|
|
113
118
|
addresses and their type and a string representing the
|
|
114
119
|
certificate authority.
|
|
115
120
|
"""
|
|
116
|
-
if not self._credentials.valid:
|
|
117
|
-
request = google.auth.transport.requests.Request()
|
|
118
|
-
self._credentials.refresh(request)
|
|
119
121
|
|
|
120
122
|
headers = {
|
|
121
123
|
"Authorization": f"Bearer {self._credentials.token}",
|
|
@@ -123,9 +125,10 @@ class CloudSQLClient:
|
|
|
123
125
|
|
|
124
126
|
url = f"{self._sqladmin_api_endpoint}/sql/{API_VERSION}/projects/{project}/instances/{instance}/connectSettings"
|
|
125
127
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
128
|
+
resp = await self._client.get(url, headers=headers)
|
|
129
|
+
if resp.status >= 500:
|
|
130
|
+
resp = await retry_50x(self._client.get, url, headers=headers)
|
|
131
|
+
resp.raise_for_status()
|
|
129
132
|
ret_dict = await resp.json()
|
|
130
133
|
|
|
131
134
|
if ret_dict["region"] != region:
|
|
@@ -138,8 +141,10 @@ class CloudSQLClient:
|
|
|
138
141
|
if "ipAddresses" in ret_dict
|
|
139
142
|
else {}
|
|
140
143
|
)
|
|
141
|
-
|
|
142
|
-
|
|
144
|
+
# Remove trailing period from PSC DNS name.
|
|
145
|
+
psc_dns = ret_dict.get("dnsName")
|
|
146
|
+
if psc_dns:
|
|
147
|
+
ip_addresses["PSC"] = psc_dns.rstrip(".")
|
|
143
148
|
|
|
144
149
|
return {
|
|
145
150
|
"ip_addresses": ip_addresses,
|
|
@@ -174,13 +179,6 @@ class CloudSQLClient:
|
|
|
174
179
|
:returns: An ephemeral certificate from the Cloud SQL instance that allows
|
|
175
180
|
authorized connections to the instance.
|
|
176
181
|
"""
|
|
177
|
-
|
|
178
|
-
logger.debug(f"['{instance}']: Requesting ephemeral certificate")
|
|
179
|
-
|
|
180
|
-
if not self._credentials.valid:
|
|
181
|
-
request = google.auth.transport.requests.Request()
|
|
182
|
-
self._credentials.refresh(request)
|
|
183
|
-
|
|
184
182
|
headers = {
|
|
185
183
|
"Authorization": f"Bearer {self._credentials.token}",
|
|
186
184
|
}
|
|
@@ -194,10 +192,10 @@ class CloudSQLClient:
|
|
|
194
192
|
login_creds = _downscope_credentials(self._credentials)
|
|
195
193
|
data["access_token"] = login_creds.token
|
|
196
194
|
|
|
197
|
-
resp = await self._client.post(
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
195
|
+
resp = await self._client.post(url, headers=headers, json=data)
|
|
196
|
+
if resp.status >= 500:
|
|
197
|
+
resp = await retry_50x(self._client.post, url, headers=headers, json=data)
|
|
198
|
+
resp.raise_for_status()
|
|
201
199
|
ret_dict = await resp.json()
|
|
202
200
|
|
|
203
201
|
ephemeral_cert: str = ret_dict["ephemeralCert"]["cert"]
|
|
@@ -220,6 +218,84 @@ class CloudSQLClient:
|
|
|
220
218
|
expiration = token_expiration
|
|
221
219
|
return ephemeral_cert, expiration
|
|
222
220
|
|
|
221
|
+
async def get_connection_info(
|
|
222
|
+
self,
|
|
223
|
+
project: str,
|
|
224
|
+
region: str,
|
|
225
|
+
instance: str,
|
|
226
|
+
keys: asyncio.Future,
|
|
227
|
+
enable_iam_auth: bool,
|
|
228
|
+
) -> ConnectionInfo:
|
|
229
|
+
"""Immediately performs a full refresh operation using the Cloud SQL
|
|
230
|
+
Admin API.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
project (str): The name of the project the Cloud SQL instance is
|
|
234
|
+
located in.
|
|
235
|
+
region (str): The region the Cloud SQL instance is located in.
|
|
236
|
+
instance (str): Name of the Cloud SQL instance.
|
|
237
|
+
keys (asyncio.Future): A future to the client's public-private key
|
|
238
|
+
pair.
|
|
239
|
+
enable_iam_auth (bool): Whether an automatic IAM database
|
|
240
|
+
authentication connection is being requested (Postgres and MySQL).
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
ConnectionInfo: All the information required to connect securely to
|
|
244
|
+
the Cloud SQL instance.
|
|
245
|
+
Raises:
|
|
246
|
+
AutoIAMAuthNotSupported: Database engine does not support automatic
|
|
247
|
+
IAM authentication.
|
|
248
|
+
"""
|
|
249
|
+
priv_key, pub_key = await keys
|
|
250
|
+
# before making Cloud SQL Admin API calls, refresh creds if required
|
|
251
|
+
if not self._credentials.token_state == TokenState.FRESH:
|
|
252
|
+
self._credentials.refresh(requests.Request())
|
|
253
|
+
|
|
254
|
+
metadata_task = asyncio.create_task(
|
|
255
|
+
self._get_metadata(
|
|
256
|
+
project,
|
|
257
|
+
region,
|
|
258
|
+
instance,
|
|
259
|
+
)
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
ephemeral_task = asyncio.create_task(
|
|
263
|
+
self._get_ephemeral(
|
|
264
|
+
project,
|
|
265
|
+
instance,
|
|
266
|
+
pub_key,
|
|
267
|
+
enable_iam_auth,
|
|
268
|
+
)
|
|
269
|
+
)
|
|
270
|
+
try:
|
|
271
|
+
metadata = await metadata_task
|
|
272
|
+
# check if automatic IAM database authn is supported for database engine
|
|
273
|
+
if enable_iam_auth and not metadata["database_version"].startswith(
|
|
274
|
+
("POSTGRES", "MYSQL")
|
|
275
|
+
):
|
|
276
|
+
raise AutoIAMAuthNotSupported(
|
|
277
|
+
f"'{metadata['database_version']}' does not support "
|
|
278
|
+
"automatic IAM authentication. It is only supported with "
|
|
279
|
+
"Cloud SQL Postgres or MySQL instances."
|
|
280
|
+
)
|
|
281
|
+
except Exception:
|
|
282
|
+
# cancel ephemeral cert task if exception occurs before it is awaited
|
|
283
|
+
ephemeral_task.cancel()
|
|
284
|
+
raise
|
|
285
|
+
|
|
286
|
+
ephemeral_cert, expiration = await ephemeral_task
|
|
287
|
+
|
|
288
|
+
return ConnectionInfo(
|
|
289
|
+
ephemeral_cert,
|
|
290
|
+
metadata["server_ca_cert"],
|
|
291
|
+
priv_key,
|
|
292
|
+
metadata["ip_addresses"],
|
|
293
|
+
metadata["database_version"],
|
|
294
|
+
expiration,
|
|
295
|
+
)
|
|
296
|
+
|
|
223
297
|
async def close(self) -> None:
|
|
224
298
|
"""Close CloudSQLClient gracefully."""
|
|
299
|
+
logger.debug("Waiting for Connector's http client to close")
|
|
225
300
|
await self._client.close()
|
|
301
|
+
logger.debug("Closed Connector's http client")
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Copyright 2024 Google LLC
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from dataclasses import dataclass
|
|
18
|
+
import logging
|
|
19
|
+
import ssl
|
|
20
|
+
from tempfile import TemporaryDirectory
|
|
21
|
+
from typing import Any, Dict, Optional, TYPE_CHECKING
|
|
22
|
+
|
|
23
|
+
from google.cloud.sql.connector.exceptions import CloudSQLIPTypeError
|
|
24
|
+
from google.cloud.sql.connector.exceptions import TLSVersionError
|
|
25
|
+
from google.cloud.sql.connector.utils import write_to_file
|
|
26
|
+
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
import datetime
|
|
29
|
+
|
|
30
|
+
from google.cloud.sql.connector.enums import IPTypes
|
|
31
|
+
|
|
32
|
+
logger = logging.getLogger(name=__name__)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class ConnectionInfo:
|
|
37
|
+
"""Contains all necessary information to connect securely to the
|
|
38
|
+
server-side Proxy running on a Cloud SQL instance."""
|
|
39
|
+
|
|
40
|
+
client_cert: str
|
|
41
|
+
server_ca_cert: str
|
|
42
|
+
private_key: bytes
|
|
43
|
+
ip_addrs: Dict[str, Any]
|
|
44
|
+
database_version: str
|
|
45
|
+
expiration: datetime.datetime
|
|
46
|
+
context: Optional[ssl.SSLContext] = None
|
|
47
|
+
|
|
48
|
+
def create_ssl_context(self, enable_iam_auth: bool = False) -> ssl.SSLContext:
|
|
49
|
+
"""Constructs a SSL/TLS context for the given connection info.
|
|
50
|
+
|
|
51
|
+
Cache the SSL context to ensure we don't read from disk repeatedly when
|
|
52
|
+
configuring a secure connection.
|
|
53
|
+
"""
|
|
54
|
+
# if SSL context is cached, use it
|
|
55
|
+
if self.context is not None:
|
|
56
|
+
return self.context
|
|
57
|
+
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
|
|
58
|
+
|
|
59
|
+
# update ssl.PROTOCOL_TLS_CLIENT default
|
|
60
|
+
context.check_hostname = False
|
|
61
|
+
|
|
62
|
+
# TODO: remove if/else when Python 3.10 is min version. PEP 644 has been
|
|
63
|
+
# implemented. The ssl module requires OpenSSL 1.1.1 or newer.
|
|
64
|
+
# verify OpenSSL version supports TLSv1.3
|
|
65
|
+
if ssl.HAS_TLSv1_3:
|
|
66
|
+
# force TLSv1.3 if supported by client
|
|
67
|
+
context.minimum_version = ssl.TLSVersion.TLSv1_3
|
|
68
|
+
# fallback to TLSv1.2 for older versions of OpenSSL
|
|
69
|
+
else:
|
|
70
|
+
if enable_iam_auth:
|
|
71
|
+
raise TLSVersionError(
|
|
72
|
+
f"Your current version of OpenSSL ({ssl.OPENSSL_VERSION}) does not "
|
|
73
|
+
"support TLSv1.3, which is required to use IAM Authentication.\n"
|
|
74
|
+
"Upgrade your OpenSSL version to 1.1.1 for TLSv1.3 support."
|
|
75
|
+
)
|
|
76
|
+
logger.warning(
|
|
77
|
+
"TLSv1.3 is not supported with your version of OpenSSL "
|
|
78
|
+
f"({ssl.OPENSSL_VERSION}), falling back to TLSv1.2\n"
|
|
79
|
+
"Upgrade your OpenSSL version to 1.1.1 for TLSv1.3 support."
|
|
80
|
+
)
|
|
81
|
+
context.minimum_version = ssl.TLSVersion.TLSv1_2
|
|
82
|
+
|
|
83
|
+
# tmpdir and its contents are automatically deleted after the CA cert
|
|
84
|
+
# and ephemeral cert are loaded into the SSLcontext. The values
|
|
85
|
+
# need to be written to files in order to be loaded by the SSLContext
|
|
86
|
+
with TemporaryDirectory() as tmpdir:
|
|
87
|
+
ca_filename, cert_filename, key_filename = write_to_file(
|
|
88
|
+
tmpdir, self.server_ca_cert, self.client_cert, self.private_key
|
|
89
|
+
)
|
|
90
|
+
context.load_cert_chain(cert_filename, keyfile=key_filename)
|
|
91
|
+
context.load_verify_locations(cafile=ca_filename)
|
|
92
|
+
# set class attribute to cache context for subsequent calls
|
|
93
|
+
self.context = context
|
|
94
|
+
return context
|
|
95
|
+
|
|
96
|
+
def get_preferred_ip(self, ip_type: IPTypes) -> str:
|
|
97
|
+
"""Returns the first IP address for the instance, according to the preference
|
|
98
|
+
supplied by ip_type. If no IP addressess with the given preference are found,
|
|
99
|
+
an error is raised."""
|
|
100
|
+
if ip_type.value in self.ip_addrs:
|
|
101
|
+
return self.ip_addrs[ip_type.value]
|
|
102
|
+
raise CloudSQLIPTypeError(
|
|
103
|
+
"Cloud SQL instance does not have any IP addresses matching "
|
|
104
|
+
f"preference: {ip_type.value}"
|
|
105
|
+
)
|