在 GCP 上使用 Workload Identity 设置 PostgreSQL 服务器

2026-06-30#GCP#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_USERCLOUD_IAM_GROUPCLOUD_IAM_SERVICE_ACCOUNT。这些用户必须有角色 roles/cloudsql.instanceUser (Database Auth Layer) 和 roles/cloudsql.client ( Network / API Layer ) 才可以登录数据库。对于 CLOUD_IAM_USERCLOUD_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>";