Cloud KMSを使用したGKEクラスタのetcdのデータ暗号化について

このブログシリーズ 「クラウドセキュリティ 実践集」 では、一般的なセキュリティ課題を取り上げ、「なぜ危険なのか?」 というリスクの解説から、 「どうやって直すのか?」 という具体的な修復手順(コンソール、Google Cloud CLI、Terraformなど)まで、分かりやすく解説します。

この記事では、Cloud KMSを使用したGKEクラスタのetcdのデータ暗号化について、具体的なセキュリティリスクと実践的な対策方法を解説します。etcdに保存される機密データをCloud KMSで暗号化する機能で、Kubernetesの機密情報を強力に保護し、コンプライアンス要件を満たすことができます。

ポリシーの説明

GKEクラスタでApplication-layer Secrets Encryption(ALSE)を有効化し、etcd内の機密データをCloud KMSを使用して暗号化します。

リスク

GKEクラスタでApplication-layer Secrets Encryption(ALSE)が無効化されている場合、以下のような深刻なセキュリティリスクが発生します:

  1. 機密データの露出: etcdに保存されるKubernetes Secretsが暗号化されずに保存され、データベースレベルでの侵害時に全ての機密情報が露出する可能性があります(注:ALSEはSecretsとTokenRequestのみを暗号化し、ConfigMapは暗号化対象外です)
  2. 内部脅威への脆弱性: etcdのバックアップやスナップショットへのアクセス権を持つ内部関係者が、機密データを読み取れる可能性があります
  3. ディスク盗難のリスク: 物理的なディスクが盗難された場合、etcdデータベースから機密情報を抽出される可能性があります

etcdには以下のような重要なデータが保存されています:

  • アプリケーションのパスワード、APIキー(Secrets)
  • サービスアカウントトークン
  • TLS証明書
  • データベース接続情報

修復方法

コンソールでの修復手順

Google Cloud コンソールを使用して、GKEクラスタでKMS暗号化を有効化します。

重要: 既存のクラスタでは、Application-layer Secrets Encryption(ALSE)を後から有効化できません。ALSEを有効化するには新しいクラスタを作成する必要があります。ただし、GKE Autopilotクラスタでは、デフォルトでALSEが有効化されています。

  1. Cloud KMS暗号化キーの準備
    • 「セキュリティ」→「鍵管理」に移動
    • 「キーリングを作成」をクリック
    • キーリング名(例:gke-encryption-keyring)とリージョンを指定
    • 「鍵を作成」で暗号化キーを作成(例:gke-etcd-encryption-key)
    • 鍵の目的は「対称暗号化/復号化」を選択
    • ローテーション期間を設定(推奨:90日)
  2. 新しいGKEクラスタの作成
    • 「Kubernetes Engine」→「クラスタ」→「作成」に移動
    • 「Standard」モードを選択
    • 基本設定を入力後、「セキュリティ」セクションに移動
    • 「Application-layer Secrets Encryption」セクションで「有効にする」にチェック
    • 作成したCloud KMS暗号化キーを選択
    • クラスタの作成には5~10分程度かかります
  3. 暗号化の確認
    • クラスタの詳細ページで「Application-layer Secrets Encryption」が有効になっていることを確認
    • 使用されているKMS鍵が表示されていることを確認

Terraformでの修復手順

GKEクラスタでALSEを有効化するTerraformコードと、主要な修正ポイントを説明します。

# ============== Cloud KMS設定 ==============
# KMSキーリングの作成
resource "google_kms_key_ring" "gke_keyring" {
  name     = "gke-encryption-keyring"
  location = var.region
}

# KMS暗号化キーの作成
resource "google_kms_crypto_key" "gke_etcd_key" {
  name            = "gke-etcd-encryption-key"
  key_ring        = google_kms_key_ring.gke_keyring.id
  rotation_period = "7776000s" # 90日でローテーション
  purpose         = "ENCRYPT_DECRYPT"

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

  version_template {
    algorithm = "GOOGLE_SYMMETRIC_ENCRYPTION"
  }
}

# GKEサービスアカウント用のIAMバインディング
data "google_project" "project" {}

resource "google_kms_crypto_key_iam_member" "gke_sa_encrypt_decrypt" {
  crypto_key_id = google_kms_crypto_key.gke_etcd_key.id
  role          = "roles/cloudkms.cryptoKeyEncrypterDecrypter"

  # GKEサービスアカウント(プロジェクト番号を使用)
  # 注:Autopilotクラスタの場合は異なるサービスアカウントが使用されます
  member = "serviceAccount:service-${data.google_project.project.number}@container-engine-robot.iam.gserviceaccount.com"
}

# ============== GKEクラスタ(KMS暗号化有効) ==============
resource "google_container_cluster" "encrypted_cluster" {
  name     = "gke-encrypted-cluster"
  location = var.zone

  # 最小ノード数(後でノードプールで管理)
  initial_node_count       = 1
  remove_default_node_pool = true

  # 最小バージョン要件(ALSEサポート)
  min_master_version = "1.11.0-gke.0"

  # ============== Application-layer Secrets Encryption(重要) ==============
  database_encryption {
    state    = "ENCRYPTED"
    key_name = google_kms_crypto_key.gke_etcd_key.id
  }

  # ============== その他のセキュリティ設定 ==============
  # Workload Identity
  workload_identity_config {
    workload_pool = "${data.google_project.project.project_id}.svc.id.goog"
  }

  # プライベートクラスタ
  private_cluster_config {
    enable_private_nodes    = true
    enable_private_endpoint = false
    master_ipv4_cidr_block  = "172.16.0.0/28"
  }

  # IPアクセス制限
  master_authorized_networks_config {
    cidr_blocks {
      cidr_block   = var.authorized_network_cidr
      display_name = "Authorized Network"
    }
  }

  # Binary Authorization
  binary_authorization {
    evaluation_mode = "PROJECT_SINGLETON_POLICY_ENFORCE"
  }

  # ネットワークポリシー
  network_policy {
    enabled = true
  }

  # ログの設定
  logging_config {
    enable_components = ["SYSTEM_COMPONENTS", "WORKLOADS"]
  }

  # 監査ログ
  cluster_telemetry {
    type = "ENABLED"
  }

  # メンテナンスウィンドウの設定(推奨)
  maintenance_policy {
    recurring_window {
      start_time = "2024-01-01T00:00:00Z"
      end_time   = "2024-01-01T04:00:00Z"
      recurrence = "FREQ=WEEKLY;BYDAY=SA"
    }
  }

  depends_on = [
    google_kms_crypto_key_iam_member.gke_sa_encrypt_decrypt
  ]
}

# ============== セキュアなノードプール ==============
resource "google_container_node_pool" "encrypted_pool" {
  name       = "encrypted-node-pool"
  cluster    = google_container_cluster.encrypted_cluster.id
  node_count = var.node_count

  node_config {
    preemptible  = false
    machine_type = "n2-standard-4"

    # カスタムサービスアカウント(最小権限)
    service_account = google_service_account.gke_node_sa.email
    oauth_scopes = [
      "<https://www.googleapis.com/auth/logging.write>",
      "<https://www.googleapis.com/auth/monitoring>",
    ]

    # Shielded VM
    shielded_instance_config {
      enable_secure_boot          = true
      enable_integrity_monitoring = true
    }

    # Workload Identityメタデータ
    workload_metadata_config {
      mode = "GKE_METADATA"
    }

    # セキュリティメタデータ
    metadata = {
      disable-legacy-endpoints = "true"
    }
  }

  management {
    auto_repair  = true
    auto_upgrade = true
  }
}

# ============== 暗号化検証用リソース ==============
resource "null_resource" "verify_encryption" {
  provisioner "local-exec" {
    command = <<-EOT
      echo "Verifying ALSE is enabled..."

      # クラスタの暗号化状態を確認
      ENCRYPTION_STATE=$(gcloud container clusters describe ${google_container_cluster.encrypted_cluster.name} \
        --zone=${google_container_cluster.encrypted_cluster.location} \
        --format="value(databaseEncryption.state)")

      if [ "$ENCRYPTION_STATE" = "ENCRYPTED" ]; then
        echo "SUCCESS: Application-layer Secrets Encryption is enabled"

        # 使用されているKMSキーを確認
        KMS_KEY=$(gcloud container clusters describe ${google_container_cluster.encrypted_cluster.name} \
          --zone=${google_container_cluster.encrypted_cluster.location} \
          --format="value(databaseEncryption.keyName)")

        echo "Using KMS key: $KMS_KEY"

        # KMSキーのバージョンも確認
        KEY_VERSION=$(gcloud kms keys describe gke-etcd-encryption-key \
          --location=us-central1 \
          --keyring=gke-encryption-keyring \
          --format="value(primary.name)")

        echo "Active key version: $KEY_VERSION"
      else
        echo "ERROR: ALSE is not enabled!"
        exit 1
      fi
    EOT
  }

  depends_on = [google_container_cluster.encrypted_cluster]
}

# ============== 暗号化キー使用状況のモニタリング ==============
resource "google_monitoring_alert_policy" "kms_key_usage" {
  display_name = "GKE KMS Key Usage Alert"
  combiner     = "OR"

  conditions {
    display_name = "High KMS key operations"

    condition_threshold {
      filter          = <<-EOT
        resource.type = "cloudkms.googleapis.com/CryptoKey"
        resource.labels.key_ring_id = "${google_kms_key_ring.gke_keyring.name}"
        resource.labels.crypto_key_id = "${google_kms_crypto_key.gke_etcd_key.name}"
        metric.type = "cloudkms.googleapis.com/cryptokey/request_count"
      EOT

      duration        = "300s"
      comparison      = "COMPARISON_GT"
      threshold_value = 10000  # 5分間で10,000リクエスト以上

      aggregations {
        alignment_period   = "60s"
        per_series_aligner = "ALIGN_RATE"
      }
    }
  }

  notification_channels = [google_monitoring_notification_channel.security_team.id]
}

# ============== 追加セキュリティ: キーローテーション監視 ==============
resource "google_logging_metric" "kms_key_rotation" {
  name        = "gke-kms-key-rotation"
  description = "Tracks KMS key rotation events for GKE"
  filter      = <<-EOT
    resource.type="cloudkms.googleapis.com/CryptoKey"
    protoPayload.methodName="RotateCryptoKey"
    resource.labels.key_ring_id="${google_kms_key_ring.gke_keyring.name}"
  EOT

  metric_descriptor {
    metric_kind = "DELTA"
    value_type  = "INT64"
  }
}

# ============== 変数定義 ==============
variable "region" {
  description = "GCP region"
  type        = string
  default     = "us-central1"
}

variable "zone" {
  description = "GCP zone"
  type        = string
  default     = "us-central1-a"
}

variable "authorized_network_cidr" {
  description = "CIDR block for authorized networks"
  type        = string
  default     = "10.0.0.0/8"
}

variable "node_count" {
  description = "Number of nodes in the node pool"
  type        = number
  default     = 3
}

# ============== サンプル: 暗号化されたSecretの作成 ==============
resource "kubernetes_secret" "encrypted_app_secret" {
  metadata {
    name      = "app-secrets"
    namespace = "default"

    annotations = {
      "description" = "This secret is encrypted at rest using Cloud KMS"
    }
  }

  data = {
    database_password = base64encode(var.database_password)
    api_key          = base64encode(var.api_key)
  }

  # 注:このSecretはALSEによって自動的にCloud KMSで暗号化されてetcdに保存されます

  type = "Opaque"

  depends_on = [google_container_node_pool.encrypted_pool]
}

 

最後に

この記事では、GKEクラスタのApplication-layer Secrets Encryption(ALSE)について、リスクと対策を解説しました。ALSEの有効化により、etcd内のSecretsをCloud KMSで暗号化し、機密データの保護を大幅に強化できます。

この問題の検出は弊社が提供するSecurifyのCSPM機能で簡単に検出及び管理する事が可能です。 運用が非常に楽に出来る製品になっていますので、ぜひ興味がある方はお問い合わせお待ちしております。 最後までお読みいただきありがとうございました。この記事が皆さんの役に立てば幸いです。

参考情報

公式ドキュメント

ベストプラクティス

この記事をシェアする

クラウドセキュリティ対策実践集一覧へ戻る

貴社の利用状況に合わせた見積もりを作成します。

料金プランを詳しく見る