Cloud SQLで組み込みの認証を利用せず、IAMベースの認証でDBに接続できるかやってみました。
IAMベースの認証にすると、パスワードが不要になり、SSL/TLSで接続できるようになります。
今回は下記の条件で試しました。
- プリンシパルではなく、サービスアカウントを利用したIAM認証
- Cloud SQL
- PostgreSQL v18
- SSL接続のみを許可する(信頼できるクライアント証明書を必須にするが本来はいい)
- パブリック IP接続
- IAM認証を有効にする
- Cloud Run
手順としては、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_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"
}
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}"
}
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をデプロイして、接続できていれば完了です。