Cloud SQLで組み込みの認証を利用せず、IAMベースの認証でDBに接続できるかやってみました。
IAMベースの認証にすると、パスワードが不要になり、SSL/TLSで接続できるようになります。
今回は下記の条件で試しました。
- プリンシパルではなく、サービスアカウントを利用したIAM認証
- Cloud SQL
- PostgreSQL v18
- SSL接続のみを許可する(信頼できるクライアント証明書を必須にするが本来はいい)
- パブリック IP接続
- IAM認証を有効にする
- Cloud Run
- Nodejsアプリケーション
手順としては、DBインスタンスを用意して、接続できるサービスアカウントを用意し、アプリケーションをIAM認証で接続する様に変更する感じです。
Cloud SQLのインスタンスを起動する
ssl_modeをENCRYPTED_ONLYにするのとcloudsql.iam_authenticationフラグを有効にして、インスタンスを起動する。
「SSL 接続のみを許可する」になっているのと、データベースのフラグとパラメータのcloudsql.iam_authenticationがonになっているのを確認する。
resource "google_sql_database_instance" "app_db" { project = var.project_id name = "app-db" database_version = "POSTGRES_18" region = var.region settings { tier = "db-f1-micro" backup_configuration { enabled = true } // サービスアカウントで接続できるようにフラグを有効にする database_flags { name = "cloudsql.iam_authentication" value = "on" } ip_configuration { // SSL/TLSで接続するが、クライアント証明書は検証しない ssl_mode = "ENCRYPTED_ONLY" } deletion_protection_enabled = true } deletion_protection = true } resource "google_sql_database" "database" { project = var.project_id name = "app-db" instance = google_sql_database_instance.app_db.name }
Cloud SQLの接続の為にサービスアカウントを作成する
DBに接続するサービスアカウントを用意する。
今回はCloud RunからCloud SQLに接続するので、Cloud Runで指定するサービスアカウントに対して、ロールとDBユーザーを用意する。
// サービスアカウント resource "google_service_account" "app" { project = var.project_id account_id = "app-server" } // DB接続に必要なロールを指定する // 必要に応じてそのほかのロールを追加する resource "google_project_iam_member" "client" { project = var.project_id role = "roles/cloudsql.client" member = "serviceAccount:${google_service_account.app.email}" } resource "google_project_iam_member" "instance_user" { project = var.project_id role = "roles/cloudsql.instanceUser" member = "serviceAccount:${google_service_account.app.email}" } // DBユーザーをサービスアカウントで追加する resource "google_sql_user" "app_sql_user" { project = var.project_id name = trimsuffix(google_service_account.app.email, ".gserviceaccount.com") instance = google_sql_database.database.name type = "CLOUD_IAM_SERVICE_ACCOUNT" }
サービスアカウントで接続できるかを確認する
サービスアカウントの権限借用を使用するプリンシパルにロールのroles/iam.serviceAccountTokenCreatorを割り当てる。
そいうしないと、権限借用する際のアクセストークンを取得できない。
gcloud auth application-default login \ --project [PROJECT_NAME] \ --impersonate-service-account [SERVICE_ACCOUNT_EMAIL]
次はプロキシーを起動する。
cloud-sql-proxy --auto-iam-authn \ --address 127.0.0.1 \ --port 5432 \ [CONNECTION STRING]
プロキシーを起動したらサービスアカウントで接続してみる。
接続できれば問題ないが、権限が何も割り当てられていないので、テーブルのデータをSELECTしたりすることができない。
なので、必要な権限を別途GRANTで割り当てる。
psql -h 127.0.0.1 -p 5432 -U [サービスアカウント(.iamまで)] app-db
アプリケーションのDBの接続方法を変える
言語コネクタ@google-cloud/cloud-sql-connectorが提供されているので、それを利用する。
TypeORMでの接続方法は@google-cloud/cloud-sql-connectorのexamplesにある。
prismaやsequelizeのもあるので、それぞれ参考にするといいかもしれない。
import "reflect-metadata"; import { PostgresConnectionOptions } from "typeorm/driver/postgres/PostgresConnectionOptions"; import { DataSource } from "typeorm"; import { Connector, AuthTypes, IpAddressTypes, } from "@google-cloud/cloud-sql-connector"; export const AppDataSource = new DataSource({ type: "postgres" }); export const connect = async () => { const connector = new Connector(); const clientOpts = await connector.getOptions({ instanceConnectionName: process.env.INSTANCE_CONNECTION_NAME, ipType: IpAddressTypes.PUBLIC, authType: AuthTypes.IAM, }); const datasourceOptions: PostgresConnectionOptions = { type: "postgres", database: process.env.DB_NAME, entities: [], synchronize: false, logging: true, migrations: [], username: process.env.IAM_DB_USERNAME, extra: clientOpts, }; const ds = await AppDataSource.setOptions(datasourceOptions).initialize(); return { async close() { await ds.destroy(); connector.close(); }, }; };
コードの変更が完了したら、Cloud Runで環境変数を指定する。
ユーザー名、接続名、データベース名などはSecretManagerで管理して、環境変数はSecretManagerから取得するようにすると良い。
Cloud Runをデプロイして、接続できていれば完了です。