在 GCP 上使用 Workload Identity 设置 PostgreSQL 服务器
在现代的云基础设施中,安全性和身份验证是至关重要的。Google Cloud Platform (GCP) 提供了 Workload Identity 认证机制,使得在云环境中运行的应用程序可以安全地访问 Cloud SQL 数据库,而无需在代码中硬编码数据库凭据。本文将介绍如何在 GCP 上使用 Workload Identity 认证设置 PostgreSQL 服务器,包括实例创建、用户管理和数据库连接示例。本文介绍使用 Pulumi 来创建 GCP Cloud SQL PostgreSQL 实例,并使用 Workload Identity 进行身份验证。
在GCP上创建数据库实例、数据库和用户 🔗
创建 PostgreSQL 实例 🔗
使用 gcp.sql.DatabaseInstance 创建数据库实例,并开启选项 cloudsql.iam_authentication=on。
创建用户 🔗
数据库用户即 gcp.sql.User 的实例,它有 3 个类型(type): CLOUD_IAM_USER、CLOUD_IAM_GROUP 和 CLOUD_IAM_SERVICE_ACCOUNT。这些用户必须有角色 roles/cloudsql.instanceUser (Database Auth Layer) 和 roles/cloudsql.client ( Network / API Layer ) 才可以登录数据库。对于 CLOUD_IAM_USER 和 CLOUD_IAM_GROUP,用户名为邮箱地址;对于 CLOUD_IAM_SERVICE_ACCOUNT,用户名不包括 .gserviceaccount.com 后缀。即 [email protected]
在创建用户时,还可以传入数据库的角色,比如管理员 cloudsqlsuperuser。注意,老版本的 pulumi/gcp (比如 8.x )不支持传入 databaseRoles。那么可使用命令行赋予角色(在 Pulumi 中也可使用 Pulumi Command Provider 实现):
gcloud sql users assign-roles "${email}" --instance="${databaseInstanceName}" --project="${projectId}" --database-roles="cloudsqlsuperuser" --type="${type}" --quiet
删除角色可使用:
gcloud sql users assign-roles "${email}" --instance="${databaseInstanceName}" --project="${projectId}" --database-roles="cloudsqlsuperuser" --revoke-existing-roles --type="${type}" --quiet
注意:创建数据库实例需要一定的时间。因此,需要在数据库实例完全创建后,再创建 IAM 用户。
创建数据库 🔗
创建数据库 gcp.sql.Database 实例即可。
在应用中使用 Workload Identity 认证连接数据库 🔗
Typescript 代码实例如下:
import pg from "pg";
import { Connector, AuthTypes, IpAddressTypes } from "@google-cloud/cloud-sql-connector";
const createPool = async () => {
const { Pool } = pg;
let poolOptions: pg.PoolConfig = {};
if (process.env.DATABASE_INSTANCE_CONNECTION_NAME) {
console.log("🔄 Initializing database connection via GCP Workload Identity (IAM)...");
const connector = new Connector();
const clientOpts = await connector.getOptions({
instanceConnectionName: process.env.DATABASE_INSTANCE_CONNECTION_NAME,
authType: AuthTypes.IAM,
ipType: IpAddressTypes.PRIVATE,
});
poolOptions = {
...clientOpts,
user: process.env.DATABASE_USER!,
database: process.env.DATABASE_NAME!,
max: 2
};
}
else if (process.env.DATABASE_URL) {
console.log("🔌 Initializing database connection via standard DATABASE_URL...");
poolOptions = { connectionString: process.env.DATABASE_URL };
}
else {
throw new Error(
"Database configuration missing. You must set either INSTANCE_CONNECTION_NAME (for Workload Identity) or DATABASE_URL.",
);
}
const pool = new Pool(poolOptions);
pool.on("error", (err, _) => {
console.error("⚠️ Unexpected background error encountered on an idle pool connection:", err.message);
});
return pool;
}
const pool = await createPool();
其中:
DATABASE_INSTANCE_CONNECTION_NAME: <gcp_project>:<region>:<instanceName>
DATABASE_USER: [email protected]
DATABASE_NAME: <databaseName>
数据库用户权限管理 🔗
如果不指定数据库角色,那么数据库用户可以登录数据库,比如执行 SELECT NOW() as current_time,但它并没有更多的权限。但这属于 PostgreSQL 数据库内部的权限管理了。
比如可以用管理员登录后,使用下面的语句创建 schema,并赋予其他用户所有权限:
-- Create the schema under the postgres admin user
CREATE SCHEMA IF NOT EXISTS "<schemaName>";
-- Allow the IAM service account to see and create objects inside this schema
GRANT USAGE, CREATE ON SCHEMA "<schemaName>" TO "<userName>";
-- Grant full data rights (Read, Write, Delete) on any existing tables or views
-- (Note: 'ALL TABLES' natively covers both tables and views in PostgreSQL)
GRANT SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER ON ALL TABLES IN SCHEMA "<schemaName>" TO "<userName>";
-- Grant full control over existing sequences (required for auto-incrementing IDs)
GRANT USAGE, SELECT, UPDATE ON ALL SEQUENCES IN SCHEMA "<schemaName>" TO "<userName>";
-- Automatically grant full data rights on all future tables and views
ALTER DEFAULT PRIVILEGES IN SCHEMA "<schemaName>" GRANT SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER ON TABLES TO "<userName>";
-- Automatically grant access to all future sequences
ALTER DEFAULT PRIVILEGES IN SCHEMA "<schemaName>" GRANT USAGE, SELECT, UPDATE ON SEQUENCES TO "<userName>";