使用 IAM 資料庫驗證功能登入

本頁面說明使用者和服務帳戶如何使用 Cloud SQL IAM 資料庫驗證機制,登入 Cloud SQL 資料庫。詳情請參閱「IAM 驗證」。

事前準備

使用自動 IAM 資料庫驗證機制登入

您可以設定 Cloud SQL 連接器,代表使用者或應用程式自動處理 Cloud SQL 執行個體的驗證作業。連接器包括 Cloud SQL Auth Proxy、Go 連接器、Java 連接器和 Python 連接器,全都支援自動 IAM 資料庫驗證。使用 Cloud SQL 連接器搭配自動 IAM 資料庫驗證時,用於啟動連接器的 IAM 帳戶必須與向資料庫驗證的帳戶相同。

如要使用自動 IAM 資料庫驗證機制登入,請按照下列步驟操作:

Cloud SQL Auth Proxy

  1. 向 Cloud de Confiance by S3NS驗證。

    使用者

    使用應用程式預設憑證 (ADC) 向 Cloud de Confiance by S3NS 進行驗證。

    使用 gcloud auth application-default login 指令。詳情請參閱「設定應用程式預設憑證」。

    服務帳戶

    如要使用服務帳戶透過 ADC 向 Cloud de Confiance by S3NS 進行驗證,您可以使用服務帳戶模擬功能或服務帳戶金鑰。如要使用服務帳戶模擬功能,請替換 SERVICE_ACCOUNT_EMAIL_ADDRESS,然後執行下列指令:

    gcloud auth application-default login --impersonate-service-account SERVICE_ACCOUNT_EMAIL_ADDRESS

    詳情請參閱「設定應用程式預設憑證」。

  2. 使用 --auto-iam-authn 旗標啟動 Cloud SQL Auth Proxy。

    更改下列內容:

    • INSTANCE_CONNECTION_NAME:用於識別 Cloud SQL 執行個體的連線字串。如果您使用的通訊埠不是預設的 PostgreSQL 通訊埠,請指定通訊埠編號。如要進一步瞭解如何尋找及建構這個字串,請參閱「驗證 Cloud SQL Auth Proxy 的選項」。
    ./cloud-sql-proxy --auto-iam-authn INSTANCE_CONNECTION_NAME

    如要進一步瞭解如何啟動 Proxy,請參閱「啟動 Cloud SQL Auth Proxy」。

  3. 準備好使用 Cloud SQL Auth Proxy 連線至執行個體時,請使用 psql 用戶端登入。

    更改下列內容:

    • HOSTNAME:Cloud SQL Auth Proxy 使用的 IP 位址。根據預設,Cloud SQL Auth Proxy 會使用 127.0.0.1 的 localhost 位址,但您可以在啟動 Cloud SQL Auth Proxy 時指派其他 IP 位址。
    • USERNAME:如果是 IAM,使用者名稱是使用者的完整電子郵件地址。如果是服務帳戶,則為服務帳戶的電子郵件地址,不含 .s3ns-system.iam.gserviceaccount.com 網域字尾。
    • PORT_NUMBER:選用。如果您在執行個體連線字串中指定了其他通訊埠,請指定該通訊埠編號。
    • DATABASE_NAME:要連線的資料庫名稱。

    執行下列指令:

    psql -h HOSTNAME \
     -U USERNAME \
     --port PORT_NUMBER \
     --dbname=DATABASE_NAME
     

    如要進一步瞭解如何連線至 Cloud SQL Auth Proxy,請參閱「使用 psql 用戶端連線」。

Go

import (
	"context"
	"database/sql"
	"fmt"
	"log"
	"net"
	"os"

	"cloud.google.com/go/cloudsqlconn"
	"github.com/jackc/pgx/v5"
	"github.com/jackc/pgx/v5/stdlib"
)

func connectWithConnectorIAMAuthN() (*sql.DB, error) {
	mustGetenv := func(k string) string {
		v := os.Getenv(k)
		if v == "" {
			log.Fatalf("Warning: %s environment variable not set.", k)
		}
		return v
	}
	// Note: Saving credentials in environment variables is convenient, but not
	// secure - consider a more secure solution such as
	// Cloud Secret Manager (https://cloud.google.com/secret-manager) to help
	// keep secrets safe.
	var (
		dbUser                 = mustGetenv("DB_IAM_USER")              // e.g. 'service-account-name@project-id.iam'
		dbName                 = mustGetenv("DB_NAME")                  // e.g. 'my-database'
		instanceConnectionName = mustGetenv("INSTANCE_CONNECTION_NAME") // e.g. 'project:region:instance'
		usePrivate             = os.Getenv("PRIVATE_IP")
	)

	// WithLazyRefresh() Option is used to perform refresh
	// when needed, rather than on a scheduled interval.
	// This is recommended for serverless environments to
	// avoid background refreshes from throttling CPU.
	d, err := cloudsqlconn.NewDialer(
		context.Background(),
		cloudsqlconn.WithIAMAuthN(),
		cloudsqlconn.WithLazyRefresh(),
	)
	if err != nil {
		return nil, fmt.Errorf("cloudsqlconn.NewDialer: %w", err)
	}
	var opts []cloudsqlconn.DialOption
	if usePrivate != "" {
		opts = append(opts, cloudsqlconn.WithPrivateIP())
	}

	dsn := fmt.Sprintf("user=%s database=%s", dbUser, dbName)
	config, err := pgx.ParseConfig(dsn)
	if err != nil {
		return nil, err
	}

	config.DialFunc = func(ctx context.Context, network, instance string) (net.Conn, error) {
		return d.Dial(ctx, instanceConnectionName, opts...)
	}
	dbURI := stdlib.RegisterConnConfig(config)
	dbPool, err := sql.Open("pgx", dbURI)
	if err != nil {
		return nil, fmt.Errorf("sql.Open: %w", err)
	}
	return dbPool, nil
}

Java JDBC

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import javax.sql.DataSource;

public class ConnectorIamAuthnConnectionPoolFactory extends ConnectionPoolFactory {

  // Note: Saving credentials in environment variables is convenient, but not
  // secure - consider a more secure solution such as
  // Cloud Secret Manager (https://cloud.google.com/secret-manager) to help
  // keep secrets safe.
  private static final String INSTANCE_CONNECTION_NAME =
      System.getenv("INSTANCE_CONNECTION_NAME");
  private static final String DB_IAM_USER = System.getenv("DB_IAM_USER");
  private static final String DB_NAME = System.getenv("DB_NAME");

  public static DataSource createConnectionPool() {
    // The configuration object specifies behaviors for the connection pool.
    HikariConfig config = new HikariConfig();

    // The following URL is equivalent to setting the config options below:
    // jdbc:postgresql:///<DB_NAME>?cloudSqlInstance=<INSTANCE_CONNECTION_NAME>&
    // socketFactory=com.google.cloud.sql.postgres.SocketFactory&user=<DB_IAM_USER>&
    // password=password
    // See the link below for more info on building a JDBC URL for the Cloud SQL JDBC Socket Factory
    // https://github.com/GoogleCloudPlatform/cloud-sql-jdbc-socket-factory#creating-the-jdbc-url

    // Configure which instance and what database to connect with.
    config.setJdbcUrl(String.format("jdbc:postgresql:///%s", DB_NAME));

    config.addDataSourceProperty("socketFactory", "com.google.cloud.sql.postgres.SocketFactory");
    config.addDataSourceProperty("cloudSqlInstance", INSTANCE_CONNECTION_NAME);

    // If connecting using automatic database authentication, follow the instructions for
    // connecting using the connector, but set the DB_IAM_USER value to an IAM user or
    // service account that has been given access to the database.
    // See https://cloud.google.com/sql/docs/postgres/iam-logins for more details.
    config.addDataSourceProperty("enableIamAuth", "true");
    config.addDataSourceProperty("user", DB_IAM_USER);
    // Password must be set to a nonempty value to bypass driver validation errors.
    config.addDataSourceProperty("password", "password");
    // Explicitly set sslmode to disable to prevent driver from hanging.
    // The Java Connector will handle SSL so it is unneccesary to enable it at the driver level.
    config.addDataSourceProperty("sslmode", "disable");

    // cloudSqlRefreshStrategy set to "lazy" is used to perform a
    // refresh when needed, rather than on a scheduled interval.
    // This is recommended for serverless environments to
    // avoid background refreshes from throttling CPU.
    config.addDataSourceProperty("cloudSqlRefreshStrategy", "lazy");

    // ... Specify additional connection properties here.
    // ...

    // Initialize the connection pool using the configuration object.
    return new HikariDataSource(config);
  }
}

Java R2DBC

private static final String CONNECTION_NAME = System.getenv("POSTGRES_CONNECTION_NAME");
private static final String DB_NAME = System.getenv("POSTGRES_DB");
private static final String DB_USER = System.getenv("POSTGRES_IAM_USER");
private static final String IP_TYPE =
    System.getenv("IP_TYPE") == null ? "PUBLIC" : System.getenv("IP_TYPE");
  System.out.println("Using IAM user: " + DB_USER);
  // Set up ConnectionFactoryOptions
  ConnectionFactoryOptions options =
      ConnectionFactoryOptions.builder()
          .option(DRIVER, "gcp")
          .option(PROTOCOL, "postgresql")
          // Password must be set to a nonempty value to bypass driver validation errors
          .option(USER, DB_USER)
          .option(DATABASE, DB_NAME)
          .option(HOST, CONNECTION_NAME)
          .option(IP_TYPES, IP_TYPE)
          .option(ENABLE_IAM_AUTH, true)
          .build();

  // Initialize connection pool
  ConnectionFactory connectionFactory = ConnectionFactories.get(options);
  ConnectionPoolConfiguration configuration =
      ConnectionPoolConfiguration.builder(connectionFactory).build();

  this.connectionPool = new ConnectionPool(configuration);

Node.js

const Knex = require('knex');
const {Connector} = require('@google-cloud/cloud-sql-connector');

// In case the PRIVATE_IP environment variable is defined then we set
// the ipType=PRIVATE for the new connector instance, otherwise defaults
// to public ip type.
const getIpType = () =>
  process.env.PRIVATE_IP === '1' || process.env.PRIVATE_IP === 'true'
    ? 'PRIVATE'
    : 'PUBLIC';

// connectWithConnectorAutoIAMAuthn initializes connection pool for a Cloud SQL instance
// of Postgres using the Cloud SQL Node.js Connector.
const connectWithConnectorAutoIAMAuthn = async config => {
  // Note: Saving credentials in environment variables is convenient, but not
  // secure - consider a more secure solution such as
  // Cloud Secret Manager (https://cloud.google.com/secret-manager) to help
  // keep secrets safe.
  const connector = new Connector();
  const clientOpts = await connector.getOptions({
    instanceConnectionName: process.env.INSTANCE_CONNECTION_NAME,
    ipType: getIpType(),
    authType: 'IAM',
  });
  const dbConfig = {
    client: 'pg',
    connection: {
      ...clientOpts,
      user: process.env.IAM_DB_USER, // e.g. 'sa-name@project-id.iam'
      database: process.env.DB_NAME, // e.g. 'my-database'
    },
    // ... Specify additional properties here.
    ...config,
  };
  // Establish a connection to the database.
  return Knex(dbConfig);
};

Python

import os

from google.cloud.sql.connector import Connector, IPTypes
import pg8000

import sqlalchemy


def connect_with_connector_auto_iam_authn() -> sqlalchemy.engine.base.Engine:
    """
    Initializes a connection pool for a Cloud SQL instance of Postgres.

    Uses the Cloud SQL Python Connector with Automatic IAM Database Authentication.
    """
    # Note: Saving credentials in environment variables is convenient, but not
    # secure - consider a more secure solution such as
    # Cloud Secret Manager (https://cloud.google.com/secret-manager) to help
    # keep secrets safe.
    instance_connection_name = os.environ[
        "INSTANCE_CONNECTION_NAME"
    ]  # e.g. 'project:region:instance'
    db_iam_user = os.environ["DB_IAM_USER"]  # e.g. 'sa-name@project-id.iam'
    db_name = os.environ["DB_NAME"]  # e.g. 'my-database'

    ip_type = IPTypes.PRIVATE if os.environ.get("PRIVATE_IP") else IPTypes.PUBLIC

    # initialize Cloud SQL Python Connector object
    connector = Connector(refresh_strategy="LAZY")

    def getconn() -> pg8000.dbapi.Connection:
        conn: pg8000.dbapi.Connection = connector.connect(
            instance_connection_name,
            "pg8000",
            user=db_iam_user,
            db=db_name,
            enable_iam_auth=True,
            ip_type=ip_type,
        )
        return conn

    # The Cloud SQL Python Connector can be used with SQLAlchemy
    # using the 'creator' argument to 'create_engine'
    pool = sqlalchemy.create_engine(
        "postgresql+pg8000://",
        creator=getconn,
        # ...
    )
    return pool

使用手動 IAM 資料庫驗證機制登入

使用者或應用程式可以手動向 Cloud de Confiance by S3NS 要求存取權杖,並將權杖提供給資料庫,藉此使用 IAM 向資料庫進行驗證。您可以使用 gcloud CLI,明確要求具有 Cloud SQL Admin API 範圍的 OAuth 2.0 權杖,用於登入資料庫。以資料庫使用者身分登入時,如果採用手動 IAM 資料庫驗證機制,請使用電子郵件地址做為使用者名稱,並以存取權杖做為密碼。您可以直接連線至資料庫,或使用 Cloud SQL 連接器。

在這個程序中,您會向 Cloud de Confiance by S3NS進行驗證、要求存取權杖,然後將權杖做為 IAM 資料庫使用者的密碼傳入,連線至資料庫。請按照下列步驟操作,在不使用 Cloud SQL Auth Proxy 的情況下連線。

如要執行這些步驟,您必須:

  • 如果您要連線至具有公開 IP 的執行個體,請授權外部存取該執行個體。詳情請參閱「授權電腦的 IP 位址存取公開 IP」。
  • 如要連線至具有私人 IP 的執行個體,請在虛擬私有雲 (VPC) 網路中執行指令。
  • 使用 gcloud sql generate-login-token 指令產生驗證權杖。
  • 如要使用手動 IAM 資料庫驗證機制登入,請按照下列步驟操作:

    gcloud

    1. 向 Cloud de Confiance by S3NS驗證。

      使用者

      使用 gcloud auth login 向 IAM 驗證身分。詳情請參閱「使用 gcloud CLI 進行驗證」。

      服務帳戶

      使用 gcloud auth activate-service-account 向 IAM 驗證。詳情請參閱「使用 gcloud CLI 進行驗證」。

    2. 要求存取權杖,並使用用戶端登入。

      更改下列內容:

      • HOSTNAME:執行個體的 IP 位址,可以是公開 IP 位址或私人 IP 位址。
      • USERNAME:如果是 IAM,使用者名稱是使用者的完整電子郵件地址。如果是服務帳戶,則為服務帳戶的電子郵件地址,不含 .s3ns-system.iam.gserviceaccount.com 網域字尾。
      • DATABASE_NAME::要連線的資料庫名稱。

       PGPASSWORD=`gcloud sql generate-login-token` \
       psql "sslmode=require \
       hostaddr=HOSTNAME \
       user=USERNAME \
       dbname=DATABASE_NAME" \
       --no-password
       

      如果 Cloud SQL 執行個體上的 ssl_mode 設為 TRUSTED_CLIENT_CERTIFICATE_REQUIRED,建議您使用自動 IAM 資料庫驗證機制登入,強制驗證用戶端身分。

    後續步驟