★★ 中級

09. Cloud KMS / Secret Manager

GCP の暗号化キー管理は Cloud KMS、シークレット(パスワード/API キー)は Secret Manager。AWS の KMS + Secrets Manager に対応します。KMS には「KeyRing」という独自の階層あり。

Cloud KMS の階層

Project
   └── Location(リージョン or global)
          └── KeyRing                    ← キーをグループ化する箱
                 └── CryptoKey            ← 個別のキー
                        └── CryptoKeyVersion  ← キーのバージョン

AWS KMS には KeyRing がなく、Key が直接プロジェクト直下。GCP は KeyRing で論理グループを作る のが特徴。

KeyRing と CryptoKey は削除不可 terraform destroy しても API レベルで削除できません。CryptoKey のバージョンは「破棄スケジュール」になるだけ。実質的に永続的なリソースなので、命名と作成は慎重に。

KeyRing と CryptoKey

resource "google_kms_key_ring" "main" {
  name     = "kr-myapp-prd"
  location = "asia-northeast1"   # global / リージョン / マルチリージョン
}

resource "google_kms_crypto_key" "app" {
  name     = "app-encryption"
  key_ring = google_kms_key_ring.main.id
  purpose  = "ENCRYPT_DECRYPT"

  rotation_period = "7776000s"   # 90 日でローテーション

  version_template {
    algorithm        = "GOOGLE_SYMMETRIC_ENCRYPTION"
    protection_level = "SOFTWARE"   # or HSM(ハードウェア暗号化、料金高)
  }

  lifecycle {
    prevent_destroy = true   # 誤削除防止
  }
}

自動ローテーション

rotation_period を設定すれば、自動で新しい CryptoKeyVersion が生成され、それ以降の暗号化は新版で行われます(既存暗号化データの復号は古い版で可能)。

期間秒数用途
30 日"2592000s"機密性が極めて高い
90 日"7776000s"標準
1 年"31536000s"緩め

他リソースの暗号化

# Cloud Storage のバケット暗号化
resource "google_kms_crypto_key_iam_member" "gcs_use_key" {
  crypto_key_id = google_kms_crypto_key.app.id
  role          = "roles/cloudkms.cryptoKeyEncrypterDecrypter"
  # GCS の service account(Project ごとに固定)
  member = "serviceAccount:service-${data.google_project.current.number}@gs-project-accounts.iam.gserviceaccount.com"
}

resource "google_storage_bucket" "data" {
  # ...
  encryption {
    default_kms_key_name = google_kms_crypto_key.app.id
  }

  depends_on = [google_kms_crypto_key_iam_member.gcs_use_key]
}

# Compute Engine のディスク暗号化
resource "google_compute_disk" "data" {
  name = "disk-data-001"
  zone = "asia-northeast1-a"
  size = 100

  disk_encryption_key {
    kms_key_self_link = google_kms_crypto_key.app.id
  }
}

data "google_project" "current" {}

Secret Manager

resource "google_secret_manager_secret" "db_password" {
  secret_id = "db-password"

  replication {
    user_managed {
      replicas {
        location = "asia-northeast1"
      }
      replicas {
        location = "asia-southeast1"   # DR 用
      }
    }
  }

  # 自前 CMEK で暗号化
  rotation {
    next_rotation_time = "2026-12-31T00:00:00Z"
    rotation_period    = "7776000s"   # 90 日
  }

  labels = local.common_labels
}

resource "random_password" "db" {
  length  = 32
  special = true
}

resource "google_secret_manager_secret_version" "db_password" {
  secret      = google_secret_manager_secret.db_password.id
  secret_data = random_password.db.result
}

# Cloud Run の SA に Secret Accessor 権限
resource "google_secret_manager_secret_iam_member" "app_access" {
  secret_id = google_secret_manager_secret.db_password.id
  role      = "roles/secretmanager.secretAccessor"
  member    = "serviceAccount:${google_service_account.app.email}"
}

アプリから取得

Cloud Run の環境変数として注入(推奨)

resource "google_cloud_run_v2_service" "api" {
  # ...
  template {
    containers {
      env {
        name = "DB_PASSWORD"
        value_source {
          secret_key_ref {
            secret  = google_secret_manager_secret.db_password.secret_id
            version = "latest"
          }
        }
      }
    }
  }
}

これなら Terraform は値を直接読まないので、state に平文で残りません。Cloud Run がランタイムで Secret Manager から取得。

CLI から取得(運用確認用)

gcloud secrets versions access latest --secret="db-password"

Terraform で値を読む(非推奨)

data "google_secret_manager_secret_version" "db" {
  secret = "db-password"
}
# 使い方: data.google_secret_manager_secret_version.db.secret_data
# ⚠ state に平文で書かれる
推奨パターン ① Secret Manager で値を保管 → ② SA にロール付与 → ③ Cloud Run / Cloud Functions の value_source.secret_key_ref で参照。Terraform は ARN・名前だけ扱う。