Skip to content

Move strr-api deployed DB access to Cloud SQL IAM auth - #1763

Open
Jacky-Pham wants to merge 3 commits into
mainfrom
Jacky/alembic-service-account-ownership
Open

Move strr-api deployed DB access to Cloud SQL IAM auth#1763
Jacky-Pham wants to merge 3 commits into
mainfrom
Jacky/alembic-service-account-ownership

Conversation

@Jacky-Pham

@Jacky-Pham Jacky-Pham commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

Summary

This moves deployed strr-api database access from password/socket DB auth to Cloud SQL IAM database authentication through the Cloud SQL Auth Proxy.

Local Docker and pytest still use the existing password-based Postgres config. Normal local dev is unaffected as long as local env stays on POD_NAMESPACE=local or leaves DEPLOYMENT_ENV unset.

Changes

  • Removed cloud-sql-python-connector[pg8000] and kept the normal psycopg2 SQLAlchemy path.
  • Updated deployed GCP strr-api config to build a passwordless postgresql+psycopg2:// URI for the proxy endpoint.
  • Runtime API uses DATABASE_USERNAME; migration mode uses DATABASE_MIGRATION_USERNAME when DEPLOYMENT_ENV=migration.
  • Deployed GCP env mapping now sets DATABASE_HOST=127.0.0.1 and DATABASE_PORT=5432 for the proxy sidecar.
  • Local/test DB config still uses the existing password-based path.
  • Updated the IAM config tests and the vault mapping guard for the proxy approach.
  • Kept Alembic ownership aligned with latest main through DATABASE_OWNER_ROLE.

Deployment Handoff

I do not have access to update these 1Password values directly, so this still needs to be handled by someone with access.

Needed STRR GCP env mapping changes:

Remove deployed use of:

  • DATABASE_PASSWORD
  • DATABASE_UNIX_SOCKET
  • CLOUDSQL_INSTANCE_CONNECTION_NAME
  • CLOUDSQL_IP_TYPE

Add/keep:

  • DATABASE_NAME=strr-db
  • DATABASE_HOST=127.0.0.1
  • DATABASE_PORT=5432
  • DATABASE_USERNAME
  • DATABASE_MIGRATION_USERNAME
  • DATABASE_OWNER_ROLE=strr

For UAT, use the bcrbk9-test identity and Cloud SQL instance because UAT deploys into the test project.

The Cloud Run deployment layer also needs to run Cloud SQL Auth Proxy v2 with automatic IAM database authentication, listening on 127.0.0.1:5432, using the existing cloudsql-instances value for the instance connection name:

cloud-sql-proxy --auto-iam-authn --port=5432 <INSTANCE_CONNECTION_NAME>

@bcregistry-sre

Copy link
Copy Markdown
Collaborator

Temporary Url for review: https://strr-hosts-dev--pr-1763-ciu8zc70.web.app

@Jacky-Pham
Jacky-Pham force-pushed the Jacky/alembic-service-account-ownership branch 2 times, most recently from bc568e9 to 1834cc0 Compare July 8, 2026 22:10
@Jacky-Pham
Jacky-Pham force-pushed the Jacky/alembic-service-account-ownership branch from 1834cc0 to 16ef0a0 Compare July 8, 2026 22:20
Comment thread strr-api/src/strr_api/config.py Outdated
return f"postgresql+pg8000://{db_user}:{db_password}@/{db_name}?unix_sock={db_unix_socket}/.s.PGSQL.5432"
return f"postgresql+psycopg2://{db_user}:{db_password}@/{db_name}?host={db_unix_socket}"

if driver == "pg8000":

@jimmypalelil jimmypalelil Jul 10, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are switching to pg8000 from psycopg2. lets use auth proxy from gcp for the iam auth.

ref: https://docs.cloud.google.com/sql/docs/postgres/sql-proxy

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had thought about this but figured it would be a more complex change and more resistance. I can switch to proxy

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

switched to proxy please review

assert config_module.Production.SQLALCHEMY_ENGINE_OPTIONS == {}


def test_deployed_config_requires_cloudsql_proxy_iam_env(monkeypatch):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this test case is failing for me in local. do i have configure the .env in a certain way?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image.png

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry you shouldn’t need to configure your .env for this test. I reproduced the failure with a normal local .env and the config reload was loading those values after the test cleared the environment, so the RuntimeError was not raised. I updated the test helper to disable dotenv loading

@sonarqubecloud

Copy link
Copy Markdown

Comment on lines +54 to +120
GCP_DEPLOYMENT_ENVS = {"development", "test", "uat", "sandbox", "production", "migration"}
PROXY_REQUIRED_ENVS = ("DATABASE_NAME",)


def _deployment_env() -> str:
return os.getenv("DEPLOYMENT_ENV", os.getenv("POD_NAMESPACE", "local"))


def _is_deployed_gcp() -> bool:
return bool(os.getenv("K_SERVICE")) or _deployment_env() in GCP_DEPLOYMENT_ENVS


def _use_proxy_iam() -> bool:
return _is_deployed_gcp()


def _cloudsql_user_env() -> str:
return "DATABASE_MIGRATION_USERNAME" if _deployment_env() == "migration" else "DATABASE_USERNAME"


def _require_proxy_env(user_env: str):
required = (*PROXY_REQUIRED_ENVS, user_env)
missing = [env_name for env_name in required if not os.getenv(env_name)]
if missing:
raise RuntimeError(f"Missing Cloud SQL IAM proxy environment variables: {', '.join(missing)}")


def _proxy_database_uri(user_env: str) -> str:
db_user = os.environ[user_env]
db_name = os.environ["DATABASE_NAME"]
db_host = os.getenv("DATABASE_HOST", "127.0.0.1")
db_port = int(os.getenv("DATABASE_PORT", "5432"))

if db_unix_socket := os.getenv("DATABASE_UNIX_SOCKET", None):
return str(
URL.create(
"postgresql+psycopg2",
username=db_user,
database=db_name,
query={"host": db_unix_socket},
)
)

return str(URL.create("postgresql+psycopg2", username=db_user, host=db_host, port=db_port, database=db_name))


def _local_database_uri() -> str:
db_user = os.getenv("DATABASE_USERNAME", "")
db_password = os.getenv("DATABASE_PASSWORD", "")
db_name = os.getenv("DATABASE_NAME", "")
db_host = os.getenv("DATABASE_HOST", "")
db_port = int(os.getenv("DATABASE_PORT", "5432"))

if db_unix_socket := os.getenv("DATABASE_UNIX_SOCKET", None):
return f"postgresql+psycopg2://{db_user}:{db_password}@/{db_name}?host={db_unix_socket}"

return f"postgresql://{db_user}:{db_password}@{db_host}:{db_port}/{db_name}"


def _database_settings() -> tuple[str, dict]:
if _use_proxy_iam():
user_env = _cloudsql_user_env()
_require_proxy_env(user_env)
return _proxy_database_uri(user_env), {}

return _local_database_uri(), {}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changes you made in this PR should work here as well right? we could remove all these lines and simplify

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants