Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
e2408a1
feat: add support for connecting to cloudsql via cloud auth proxy
moabu Dec 1, 2025
99fa5aa
Merge branch 'main' into cn-add-cloud-auth-proxy
moabu Dec 1, 2025
edf7edf
fix: added context manager support
moabu Dec 1, 2025
f541b16
fix: refactor for cleanup
moabu Dec 1, 2025
e9c2187
fix: the getconn closure to fetch the password fresh on each connecti…
moabu Dec 1, 2025
f75e2b3
chore: update dockerfile jans source version
moabu Dec 1, 2025
a7dba0f
chore: install cloud sql python
moabu Dec 1, 2025
5343272
docs: fix get manager example
moabu Dec 2, 2025
16ba36d
chore: update jans source version
moabu Dec 2, 2025
6a7305c
style: cosmetic touch
moabu Dec 2, 2025
f86e7bc
Merge branch 'main' into cn-add-cloud-auth-proxy
moabu Dec 2, 2025
7ef8dc8
fix: add defensive check for cloudsql driver
moabu Dec 2, 2025
717fb68
fix: improve error logging when closing database connections
moabu Dec 2, 2025
04be023
fix(docker-jans-auth-server): implemented Cloud SQL JDBC Socket Facto…
moabu Dec 2, 2025
be95dc6
fix(docker): implemented Cloud SQL JDBC Socket Factory support for Ja…
moabu Dec 3, 2025
01995d3
chore(docker): update source version
moabu Dec 3, 2025
9489a96
fix(docker): build with jar deps
moabu Dec 4, 2025
4506c30
chore(docker): update jans source version
moabu Dec 4, 2025
cff424d
chore(docker): fix maven build
moabu Dec 4, 2025
df2f7d1
ci: add clean up
moabu Dec 4, 2025
b68323d
ci: add clean up
moabu Dec 4, 2025
f05d18a
ci: clean up cloud sql jar build
moabu Dec 8, 2025
0446c6a
ci: apply suggestions from code review
moabu Dec 9, 2025
dbf2a8a
ci: add timeouts
moabu Dec 9, 2025
3356549
Merge branch 'main' into cn-add-cloud-auth-proxy
moabu Dec 9, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
290 changes: 290 additions & 0 deletions jans-pycloudlib/docs/api/persistence/cloudsql_connector.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,290 @@
# Cloud SQL Python Connector

This module provides support for connecting to Google Cloud SQL instances (PostgreSQL and MySQL) from Cloud Run services using the Cloud SQL Python Connector with Private IP.

## Overview

The Cloud SQL Python Connector provides a secure and efficient way to connect to Cloud SQL instances without managing IP allowlists, SSL certificates, or network configurations manually. The connector handles secure tunnel establishment and automatic SSL/TLS encryption.

**Note**: This implementation uses standard SQL username/password authentication. For IAM database authentication, additional configuration would be required (setting `enable_iam_auth=True` in the connector).

## Supported Databases

| Database | Driver | SQLAlchemy URL |
|----------|--------|----------------|
| PostgreSQL | pg8000 | `postgresql+pg8000://` |
| MySQL | pymysql | `mysql+pymysql://` |

## Installation

Install the optional Cloud SQL dependencies:

```bash
pip install 'jans-pycloudlib[cloudsql]'
```

This installs:
- `cloud-sql-python-connector[pg8000]>=1.0.0`
- `pg8000>=1.30.0`

**Note**: For MySQL connections, `pymysql` is already included as a base dependency.

## Configuration

### Environment Variables

| Variable | Description | Required | Default |
|----------|-------------|----------|---------|
| `CN_SQL_CLOUDSQL_CONNECTOR_ENABLED` | Enable Cloud SQL Python Connector | Yes | `false` |
| `CN_SQL_CLOUDSQL_INSTANCE_CONNECTION_NAME` | Cloud SQL instance connection name (format: `project:region:instance`) | Yes (when enabled) | - |
| `CN_SQL_DB_USER` | Database username | Yes | `jans` |
| `CN_SQL_DB_NAME` | Database name | Yes | `jans` |
| `CN_SQL_PASSWORD_FILE` | Path to file containing database password | Yes | `/etc/jans/conf/sql_password` |
| `CN_SQL_DB_DIALECT` | Database dialect (`pgsql`, `postgresql`, or `mysql`) | Yes | `mysql` |

### Example Configuration - PostgreSQL

```bash
export CN_SQL_CLOUDSQL_CONNECTOR_ENABLED=true
export CN_SQL_CLOUDSQL_INSTANCE_CONNECTION_NAME=my-project:us-central1:my-postgres-instance
export CN_SQL_DB_USER=jans
export CN_SQL_DB_NAME=jans
export CN_SQL_DB_DIALECT=pgsql
```

### Example Configuration - MySQL

```bash
export CN_SQL_CLOUDSQL_CONNECTOR_ENABLED=true
export CN_SQL_CLOUDSQL_INSTANCE_CONNECTION_NAME=my-project:us-central1:my-mysql-instance
export CN_SQL_DB_USER=jans
export CN_SQL_DB_NAME=jans
export CN_SQL_DB_DIALECT=mysql
```

## Implementation Details

The implementation uses:

- **`creator` argument**: SQLAlchemy's `create_engine` is called with a `creator` function that returns connections from the Cloud SQL Connector.
- **LAZY refresh strategy**: The connector is initialized with `refresh_strategy='LAZY'` to defer token refresh until needed.
- **Private IP**: Connections use `IPTypes.PRIVATE` for connecting to Cloud SQL instances via Private IP.

### Architecture

The Cloud SQL Connector functionality is implemented using a shared mixin pattern:

- **`CloudSqlConnectorMixin`**: Base mixin class that provides:
- Connector lifecycle management (`_ensure_connector`, `close`)
- Instance name validation (`_get_instance_connection_name`)
- Connection creator factory (`get_cloudsql_connection_creator`)
- `cloudsql_connector_enabled` property

- **`PostgresqlAdapter`**: Inherits from `CloudSqlConnectorMixin`, sets `cloudsql_driver = "pg8000"`
- **`MysqlAdapter`**: Inherits from `CloudSqlConnectorMixin`, sets `cloudsql_driver = "pymysql"`

This design eliminates code duplication while allowing each adapter to specify its driver.

### Code Example - PostgreSQL (Context Manager)

```python
from jans.pycloudlib.persistence.sql import SqlClient
from jans.pycloudlib.manager import Manager

import os
os.environ["CN_SQL_CLOUDSQL_CONNECTOR_ENABLED"] = "true"
os.environ["CN_SQL_CLOUDSQL_INSTANCE_CONNECTION_NAME"] = "project:region:instance"
os.environ["CN_SQL_DB_DIALECT"] = "pgsql"
os.environ["CN_SQL_DB_USER"] = "jans"
os.environ["CN_SQL_DB_NAME"] = "jans"

manager = Manager()

with SqlClient(manager) as client:
if client.connected():
print("Successfully connected to Cloud SQL PostgreSQL via Private IP")
```

### Code Example - MySQL (Context Manager)

```python
from jans.pycloudlib.persistence.sql import SqlClient
from jans.pycloudlib.manager import Manager

import os
os.environ["CN_SQL_CLOUDSQL_CONNECTOR_ENABLED"] = "true"
os.environ["CN_SQL_CLOUDSQL_INSTANCE_CONNECTION_NAME"] = "project:region:instance"
os.environ["CN_SQL_DB_DIALECT"] = "mysql"
os.environ["CN_SQL_DB_USER"] = "jans"
os.environ["CN_SQL_DB_NAME"] = "jans"

manager = Manager()

with SqlClient(manager) as client:
if client.connected():
print("Successfully connected to Cloud SQL MySQL via Private IP")
```

### Resource Cleanup

The `SqlClient` class implements proper resource cleanup for both the SQLAlchemy engine and the Cloud SQL Connector:

**Using Context Manager (Recommended)**:
```python
with SqlClient(manager) as client:
# Use the client
pass
# Resources automatically cleaned up
```

**Manual Cleanup**:
```python
client = SqlClient(manager)
try:
# Use the client
pass
finally:
client.close()
```

**Automatic Cleanup**: An `atexit` handler is registered to clean up any remaining `SqlClient` instances when the Python interpreter shuts down.

---

## MANDATORY CHECKLIST: IAM and VPC Requirements

Before deploying your Cloud Run service with Cloud SQL Python Connector, ensure all the following requirements are met:

### IAM Requirements

- [ ] **Cloud SQL Client Role**: The Cloud Run service account must have the `roles/cloudsql.client` IAM role on the Cloud SQL instance.

```bash
gcloud projects add-iam-policy-binding PROJECT_ID \
--member="serviceAccount:SERVICE_ACCOUNT_EMAIL" \
--role="roles/cloudsql.client"
```

- [ ] **Cloud SQL Instance User Role** (Optional but recommended): For IAM database authentication, grant `roles/cloudsql.instanceUser`.

```bash
gcloud projects add-iam-policy-binding PROJECT_ID \
--member="serviceAccount:SERVICE_ACCOUNT_EMAIL" \
--role="roles/cloudsql.instanceUser"
```

- [ ] **Service Account Configured**: The Cloud Run service is configured to use a service account with the above roles (not the default compute service account in production).

### VPC Connector Requirements

- [ ] **VPC Network Created**: A VPC network exists that can reach the Cloud SQL instance's Private IP.

- [ ] **Serverless VPC Access Connector Created**: A VPC Access connector is created in the same region as your Cloud Run service.

```bash
gcloud compute networks vpc-access connectors create CONNECTOR_NAME \
--region=REGION \
--network=VPC_NETWORK \
--range=IP_RANGE # e.g., 10.8.0.0/28
```

- [ ] **Cloud Run Service Configured with VPC Connector**: The Cloud Run service is deployed with the VPC connector.

```bash
gcloud run deploy SERVICE_NAME \
--image=IMAGE_URL \
--vpc-connector=CONNECTOR_NAME \
--vpc-egress=private-ranges-only
```

### Cloud SQL Instance Requirements

- [ ] **Private IP Enabled**: The Cloud SQL instance has Private IP enabled.

```bash
gcloud sql instances patch INSTANCE_NAME \
--network=VPC_NETWORK \
--no-assign-ip # Optional: disable public IP
```

- [ ] **Private Services Access Configured**: Private services access is configured for the VPC network.

```bash
gcloud compute addresses create google-managed-services-NETWORK \
--global \
--purpose=VPC_PEERING \
--prefix-length=16 \
--network=VPC_NETWORK

gcloud services vpc-peerings connect \
--service=servicenetworking.googleapis.com \
--ranges=google-managed-services-NETWORK \
--network=VPC_NETWORK
```

- [ ] **Database User Created**: A database user exists with the credentials specified in environment variables.

### Network Firewall Rules

- [ ] **Egress Allowed**: The VPC network allows egress traffic to the Cloud SQL instance's Private IP on the appropriate port:
- PostgreSQL: Port 5432
- MySQL: Port 3306

- [ ] **No Conflicting Firewall Rules**: No firewall rules block traffic from the VPC connector's IP range to the Cloud SQL instance.

### Verification Steps

1. **Test Connectivity**: Deploy a test Cloud Run service and verify it can connect to Cloud SQL.

2. **Check Logs**: Review Cloud Run logs for connection errors or IAM permission issues.

3. **Verify IAM Bindings**:
```bash
gcloud projects get-iam-policy PROJECT_ID \
--flatten="bindings[].members" \
--filter="bindings.role:roles/cloudsql.client"
```

4. **Verify VPC Connector Status**:
```bash
gcloud compute networks vpc-access connectors describe CONNECTOR_NAME \
--region=REGION
```

---

## Backward Compatibility

The Cloud SQL Connector is **opt-in** and disabled by default. Existing deployments using standard connections (psycopg2 for PostgreSQL, pymysql for MySQL) will continue to work without any changes.

To migrate to Cloud SQL Connector:

1. Install the optional dependencies: `pip install 'jans-pycloudlib[cloudsql]'`
2. Complete the IAM and VPC checklist above
3. Set `CN_SQL_CLOUDSQL_CONNECTOR_ENABLED=true`
4. Set `CN_SQL_CLOUDSQL_INSTANCE_CONNECTION_NAME` to your instance connection name
5. Deploy your updated Cloud Run service

## Troubleshooting

### Common Issues

1. **"Cloud SQL Python Connector is not installed"**
- Install with: `pip install 'jans-pycloudlib[cloudsql]'`

2. **"Permission denied" or IAM errors**
- Verify the service account has `roles/cloudsql.client` role
- Check that the correct service account is attached to Cloud Run

3. **Connection timeout**
- Verify the VPC connector is properly configured
- Check that the Cloud SQL instance has Private IP enabled
- Ensure firewall rules allow egress to the instance

4. **"Could not connect to Cloud SQL instance"**
- Verify `CN_SQL_CLOUDSQL_INSTANCE_CONNECTION_NAME` format: `project:region:instance`
- Check that the instance exists and is running

5. **MySQL strict mode errors**
- The Cloud SQL Connector still applies MySQL strict mode settings
- Check your data for compatibility with strict mode
Loading