はじめに

クラウド環境でのデータベース管理において、セキュリティは日々進化する脅威への対応が求められる重要課題です。特に、以下の課題が顕在化しています:

  • 従来の認証情報(パスワード)の漏洩リスク
  • 認証情報のローテーション管理の煩雑さ
  • アプリケーション展開時の認証情報受け渡しの安全性確保

これらの課題に対し、IAM認証を活用することで、より強固でかつ運用負荷の少ないセキュリティ体制を実現できます。IAM認証のメリットは以下の通りです:

  • 一時的な認証トークンの利用による漏洩リスクの低減
  • クラウドプロバイダーの統合認証基盤との連携による管理の一元化
  • きめ細かなアクセス制御とアクセスログの監査対応

本ガイドでは、Cloud FunctionsからCloudSQL(PostgreSQL)へIAM認証で接続する実践的な手順を解説します。この設定により、セキュアかつスケーラブルなデータベースアクセス環境を構築できます。

Cloud Function から CloudSQL(PostgreSQL) へ IAM 認証で接続するために必要な手順

  1. Cloud Function 用 Service Account に必要な権限を付与
  2. PGUSER の作成(IAM認証用)
  3. データベースへの権限付与
  4. Cloud Function の実装
  5. Service Account での Cloud Function のデプロイ

具体的な設定手順

1. Cloud Function 用 Service Account に必要な権限を付与

以下の権限が必要:

  • Cloud SQL インスタンス ユーザー
  • Cloud SQL クライアント

今回はコンソールから設定したが、以下のコマンドで設定も可能:

gcloud projects add-iam-policy-binding example-project \
    --member="serviceAccount:example-function@example-project.iam.gserviceaccount.com" \
    --role="roles/cloudsql.instanceUser"

gcloud projects add-iam-policy-binding example-project \
    --member="serviceAccount:example-function@example-project.iam.gserviceaccount.com" \
    --role="roles/cloudsql.client"

2. PGUSER の作成

  • IAM認証用のユーザーを作成する。

    gcloud sql users create example-function@example-project.iam \
        --instance=example-project-fdc \
        --type=cloud_iam_service_account
    • PostgreSQLの場合、以下の点に注意が必要:

      • ユーザー名から .gserviceaccount.com を除いた形で作成
      • タイプは cloud_iam_service_account を指定
  • MySQL の場合は @project-id.iam.gserviceaccount.com を除いた形で作成する模様

3. データベース権限の付与

  • 作成したユーザーに必要な権限を付与:

    grant ALL on ALL TABLES IN SCHEMA "public" to "example-function@example-project.iam";
  • 別途 cloud-sql-proxy を使用して、CloudSQLへポートフォワードしてCLIや、手持ちのクライアントソフト(DBeaverなど)から接続できる

    # ポートフォワードを実行
    cloud-sql-proxy $(instance_name)
    # CLIから接続する場合
    gcloud sql connect $(instance) --user=$(psql_user) --database=$(database)

4. Cloud Function の実装

  • 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 connectWithConnector() (*sql.DB, error) {
        mustGetenv := func(k string) string {
            v := os.Getenv(k)
            if v == "" {
                log.Fatalf("Fatal Error in connect_connector.go: %s environment variable not set.\n", 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 passwords and other secrets safe.
        var (
            dbUser                 = mustGetenv("DB_USER")                  // e.g. 'my-db-user'
            dbName                 = mustGetenv("DB_NAME")                  // e.g. 'my-database'
            dbSslMode              = mustGetenv("DB_SSL_MODE")              // e.g. 'disable'
            dbInstanceName         = mustGetenv("DB_INSTANCE")              // e.g. 'instance'
            project                = mustGetenv("PROJECT")                  // e.g. 'project'
            region                 = mustGetenv("REGION")                   // e.g. 'region'
            usePrivate             = os.Getenv("PRIVATE_IP")
        )
    
        dsn := fmt.Sprintf("user=%s dbname=%s sslmode=%s", dbUser, dbName, dbSslMode)
        config, err := pgx.ParseConfig(dsn)
        if err != nil {
            return nil, err
        }
        var opts []cloudsqlconn.Option
        if usePrivate != "" {
            opts = append(opts, cloudsqlconn.WithDefaultDialOptions(cloudsqlconn.WithPrivateIP()))
        }
        d, err := cloudsqlconn.NewDialer(context.Background(), opts...)
        if err != nil {
            return nil, err
        }
        // Use the Cloud SQL connector to handle connecting to the instance.
        // This approach does *NOT* require the Cloud SQL proxy.
        config.DialFunc = func(ctx context.Context, network, instance string) (net.Conn, error) {
            // return d.Dial(ctx, instanceConnectionName)
            return d.Dial(ctx, fmt.Sprintf("%s:%s:%s", project, region, dbInstanceName))
        }
        dbURI := stdlib.RegisterConnConfig(config)
        dbPool, err := sql.Open("pgx", dbURI)
        if err != nil {
            return nil, fmt.Errorf("sql.Open: %w", err)
        }
        if err := dbPool.Ping(); err != nil {
            return nil, fmt.Errorf("dbPool.Ping: %w", err)
        }
    
        return dbPool, nil
    }

5. Cloud Function のデプロイ

以下の点に注意してデプロイを行う:

  • Gen2 ランタイムを使用
  • Service Account を指定
    • --run-service-account または --service-account で指定
      • --run-service-account は Gen2 用
      • 確認した環境ではどちらでも動作した
        • 最終的には --run-service-account で指定した
  • 必要なメモリやランタイムを設定
    gcloud functions deploy example-function-name \
        --gen2 \
        --trigger-http \
        --runtime=go122 \
        --memory=256MB \
        --allow-unauthenticated \
        --source=/path/to/source \
        --env-vars-file=./path/to/env.yaml \
        --run-service-account=example-function@example-project.iam.gserviceaccount.com \
        --entry-point=ExampleFunctionName \
        --region=your-region

まとめ

PGUSERの作成時に cloud_iam_service_account を指定する必要があるが、これをしていなかったので、原因の切り分けに時間がかかった。 IAM認証を使用した CloudSQL への接続設定は、セキュリティを強化しつつ、管理のしやすい方式を提供する。適切な権限設定と手順の遵守で、安全なデータベース接続環境を構築できる。