GKEクラスタにおけるノードアクセススコープの最小化について

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

この記事では、GKEクラスタノードのCloud APIアクセススコープ制限について、具体的なセキュリティリスクと実践的な対策方法を解説します。多くの組織で見落とされがちな設定ですが、適切に管理することでクラスタのセキュリティを大幅に向上させることができます。

ポリシーの説明

GKEクラスタで最小権限のサービスアカウントを使用し、権限昇格の可能性を防止します。

リスク

GKEクラスタのノードに対して過度に広範なCloud APIアクセススコープが設定されている場合、以下のような深刻なセキュリティリスクが発生します:

  1. 権限昇格のリスク: ノードが侵害された場合、攻撃者がプロジェクト全体のリソースにアクセスできる可能性があります。特に、コンテナエスケープやKubeletの脆弱性を悪用された場合、ノードのメタデータAPIにアクセスして認証情報を取得される恐れがあります
  2. 横展開攻撃(Lateral Movement): 1つのPodが侵害されると、ノードの権限を使用して他のGCPリソースにアクセスし、攻撃を拡大できます。例えば、Compute Engine APIを使用して新しいインスタンスを作成したり、既存のリソースを改ざんする可能性があります
  3. データ漏洩: 過度な権限により、本来アクセスすべきでないCloud Storage、BigQuery、Cloud SQL、Firestoreなどのデータストアにアクセスできる可能性があります
  4. サービスアカウントキーの悪用: デフォルトのCompute Engineサービスアカウント(PROJECT-NUMBER-compute@developer.gserviceaccount.com)には、プロジェクト編集者(roles/editor)の権限があり、ほぼすべてのリソースの作成・更新・削除が可能です

特に「https://www.googleapis.com/auth/cloud-platform」スコープは、すべてのGoogle Cloud APIへのフルアクセスを許可するため、非常に危険です。このスコープを持つノードは、IAMで制限されていない限り、プロジェクト内のあらゆるリソースを操作できてしまいます。

修復方法

コンソールでの修復手順

Google Cloud コンソールを使用して、GKEクラスタノードのアクセススコープを制限します。

重要: 既存のクラスタではノードプールのアクセススコープを直接変更できません。新しいノードプールを作成し、ワークロードを移行する必要があります。

新しいクラスタを作成する場合

  • Google Cloud Consoleで「Kubernetes Engine」→「クラスタ」に移動
  • 「作成」をクリック
  • 「ノードプール」セクションで「デフォルトプール」をクリックし、「セキュリティ」タブを選択
  • 「アクセススコープ」で「Google Cloud APIへのアクセススコープを設定」を選択
  • 必要最小限のスコープのみを選択:
    • https://www.googleapis.com/auth/logging.write(Cloud Loggingへのログ書き込み用)
    • https://www.googleapis.com/auth/monitoring(Cloud Monitoringへのメトリクス書き込み用)
    • https://www.googleapis.com/auth/devstorage.read_only(Container Registry/Artifact Registryからのイメージ取得用)
    • https://www.googleapis.com/auth/servicecontrol(サービス使用状況の報告用)
    • https://www.googleapis.com/auth/service.management.readonly(サービス設定の読み取り用)
    • https://www.googleapis.com/auth/trace.append(Cloud Traceへのトレース送信用、必要な場合)
  • 「すべてのCloud APIに完全アクセス権を許可」は絶対に選択しない

Workload Identityの有効化(強く推奨)

  • クラスタ作成時に「セキュリティ」セクションで「Workload Identityを有効にする」にチェック
  • これにより、Pod単位で細かい権限制御が可能になります
  • Workload Identityを使用することで、ノードの権限とPodの権限を分離でき、セキュリティが大幅に向上します

既存クラスタの修復

  • 新しいノードプールを作成:
    • クラスタの詳細ページで「ノードプール」タブを選択
    • 「ノードプールを追加」をクリック
    • 「セキュリティ」で適切なアクセススコープを設定
  • ワークロードを新しいノードプールに移行:
  # 現在のノードプールを確認  kubectl get nodes -L cloud.google.com/gke-nodepool 
  # 古いノードプールを排出(新規Podのスケジュールを停止)  kubectl cordon -l cloud.google.com/gke-nodepool=OLD_POOL_NAME 

  # 既存のPodを退避(DaemonSetを除く) 
  kubectl drain -l cloud.google.com/gke-nodepool=OLD_POOL_NAME \\
    --force \\
    --ignore-daemonsets \\
    --delete-emptydir-data \\
    --grace-period=300

  # 移行の完了を確認 
  kubectl get pods --all-namespaces -o wide | grep OLD_POOL_NAME
  • Google Cloud Consoleで古いノードプールを削除

カスタムサービスアカウントの作成と使用

  • 「IAMと管理」→「サービスアカウント」で新しいサービスアカウントを作成
  • 必要最小限のロールのみを付与:
    • roles/logging.logWriter(ログ書き込み)
    • roles/monitoring.metricWriter(メトリクス書き込み)
    • roles/monitoring.viewer(リソースメタデータ読み取り)
    • roles/storage.objectViewer(コンテナイメージ読み取り、特定のバケットに制限推奨)
  • ノードプール作成時にこのサービスアカウントを指定
  • デフォルトのCompute Engineサービスアカウントは使用しない

Terraformでの修復手順

GKEクラスタノードのアクセススコープを制限するTerraformコードと、主要な修正ポイントを説明します。

# ============== カスタムサービスアカウント ==============
resource "google_service_account" "gke_node_sa" {
  account_id   = "gke-node-minimal-sa"
  display_name = "GKE Node Minimal Permissions Service Account"
  description  = "Service account with minimal permissions for GKE nodes"
}

# 必要最小限のロールのみ付与
resource "google_project_iam_member" "gke_node_log_writer" {
  project = var.project_id
  role    = "roles/logging.logWriter"
  member  = "serviceAccount:${google_service_account.gke_node_sa.email}"
}

resource "google_project_iam_member" "gke_node_metric_writer" {
  project = var.project_id
  role    = "roles/monitoring.metricWriter"
  member  = "serviceAccount:${google_service_account.gke_node_sa.email}"
}

resource "google_project_iam_member" "gke_node_monitoring_viewer" {
  project = var.project_id
  role    = "roles/monitoring.viewer"
  member  = "serviceAccount:${google_service_account.gke_node_sa.email}"
}

# Container Registry/Artifact Registry へのアクセス(特定のバケットに制限)
resource "google_storage_bucket_iam_member" "gke_node_registry_reader" {
  bucket = "artifacts.${var.project_id}.appspot.com"  # Container Registry bucket
  role   = "roles/storage.objectViewer"
  member = "serviceAccount:${google_service_account.gke_node_sa.email}"
}

# Artifact Registry を使用する場合
resource "google_artifact_registry_repository_iam_member" "gke_node_ar_reader" {
  project    = var.project_id
  location   = var.artifact_registry_location
  repository = var.artifact_registry_repo_id
  role       = "roles/artifactregistry.reader"
  member     = "serviceAccount:${google_service_account.gke_node_sa.email}"
}

# ============== GKEクラスタ(Workload Identity推奨) ==============
resource "google_container_cluster" "secure_cluster" {
  name     = "secure-gke-cluster"
  location = "asia-northeast1-a"

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

  # Workload Identityの有効化(強く推奨)
  workload_identity_config {
    workload_pool = "${var.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"
  }

  # マスター承認済みネットワーク
  master_authorized_networks_config {
    cidr_blocks {
      cidr_block   = var.authorized_network_cidr
      display_name = "Authorized Network"
    }
  }

  # 追加のセキュリティ機能
  binary_authorization {
    evaluation_mode = "PROJECT_SINGLETON_POLICY_ENFORCE"
  }

  network_policy {
    enabled = true
  }
}

# ============== ノードプール(制限されたスコープ) ==============
resource "google_container_node_pool" "secure_node_pool" {
  name       = "secure-node-pool"
  cluster    = google_container_cluster.secure_cluster.id
  node_count = var.node_count

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

    # カスタムサービスアカウントを使用(重要)
    service_account = google_service_account.gke_node_sa.email

    # 最小限のOAuthスコープのみ指定(重要)
    oauth_scopes = [
      "<https://www.googleapis.com/auth/logging.write>",
      "<https://www.googleapis.com/auth/monitoring>",
      "<https://www.googleapis.com/auth/devstorage.read_only>",  # Container/Artifact Registry用
      "<https://www.googleapis.com/auth/servicecontrol>",
      "<https://www.googleapis.com/auth/service.management.readonly>",
      "<https://www.googleapis.com/auth/trace.append>"  # Cloud Trace用(オプション)
    ]
    # 警告: cloud-platformスコープは絶対に使用しない!
    # これはすべてのGoogle Cloud APIへのフルアクセスを許可してしまいます

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

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

    # セキュリティ強化のメタデータ
    metadata = {
      disable-legacy-endpoints = "true"
      google-compute-enable-virtio-rng = "true"
    }

    labels = {
      environment = "production"
      security-hardened = "true"
    }
  }

  # 自動管理
  management {
    auto_repair  = true
    auto_upgrade = true
  }

  # アップグレード設定
  upgrade_settings {
    max_surge       = 1
    max_unavailable = 0
  }
}

# ============== Workload Identity用のKSA/GSAバインディング例 ==============
# アプリケーション用のGoogle Service Account
resource "google_service_account" "app_sa" {
  account_id   = "app-workload-identity-sa"
  display_name = "Application Workload Identity Service Account"
}

# アプリケーションに必要な権限のみ付与
resource "google_project_iam_member" "app_sa_storage_reader" {
  project = var.project_id
  role    = "roles/storage.objectViewer"
  member  = "serviceAccount:${google_service_account.app_sa.email}"
}

# Kubernetes Service AccountとGoogle Service Accountのバインディング
resource "google_service_account_iam_member" "workload_identity_binding" {
  service_account_id = google_service_account.app_sa.name
  role               = "roles/iam.workloadIdentityUser"
  member             = "serviceAccount:${var.project_id}.svc.id.goog[${var.k8s_namespace}/${var.k8s_service_account}]"
}

# ============== 検証用リソース ==============
# ノードのアクセススコープを確認
resource "null_resource" "verify_node_scopes" {
  provisioner "local-exec" {
    command = <<-EOT
      echo "Verifying node access scopes..."

      # ノードインスタンスのスコープを確認
      INSTANCE_NAME=$(gcloud compute instances list \\\\
        --filter="metadata.cluster-name=${google_container_cluster.secure_cluster.name}" \\\\
        --limit=1 --format="value(name)")

      echo "Checking instance: $INSTANCE_NAME"

      SCOPES=$(gcloud compute instances describe $INSTANCE_NAME \\\\
        --zone=${google_container_cluster.secure_cluster.location} \\\\
        --format="value(serviceAccounts[0].scopes)")

      # cloud-platformスコープが含まれていないことを確認
      if echo "$SCOPES" | grep -q "cloud-platform"; then
        echo "WARNING: Node has overly broad cloud-platform scope!"
        exit 1
      else
        echo "SUCCESS: Node has restricted scopes"
      fi
    EOT
  }

  depends_on = [google_container_node_pool.secure_node_pool]
}

# ============== モニタリングアラート ==============
resource "google_monitoring_alert_policy" "broad_scope_alert" {
  display_name = "GKE Node Broad Scope Alert"
  combiner     = "OR"

  conditions {
    display_name = "Node with cloud-platform scope detected"

    condition_monitoring_query_language {
      query = <<-EOT
        fetch gce_instance
        | filter resource.cluster_name == '${google_container_cluster.secure_cluster.name}'
        | filter metadata.system_labels.service_account_scopes =~ '.*cloud-platform.*'
        | group_by 1m, [value_active_count: count()]
        | every 1m
        | condition value_active_count > 0
      EOT

      duration = "60s"

      trigger {
        count = 1
      }
    }
  }

  notification_channels = [google_monitoring_notification_channel.security_team.id]

  alert_strategy {
    auto_close = "1800s"
  }
}

移行時の注意事項

  1. アプリケーションの動作確認
    • アクセススコープを制限する前に、アプリケーションが使用しているAPIを監査ログで確認
    • Cloud Loggingで以下のクエリを使用: protoPayload.authenticationInfo.principalEmail=~".*@.*iam.gserviceaccount.com$" protoPayload.methodName!="" resource.type="gce_instance"
    • 必要なスコープのみを付与
  2. 段階的な移行
    • 開発環境で先行してテスト
    • 新しいノードプールを作成し、カナリアデプロイメントで少しずつワークロードを移行
    • 各段階でアプリケーションログとメトリクスを監視
  3. Workload Identityへの移行
    • 長期的にはWorkload Identityの使用を強く推奨
    • Pod単位で異なるサービスアカウントを使用でき、より細かい権限制御が可能
    • ノードの権限とPodの権限を完全に分離できる
  4. 緊急時の対応
    • 権限不足でアプリケーションが動作しない場合の一時的な対処法を準備
    • ロールバック手順を文書化

最後に

この記事では、GKEクラスタノードのCloud APIアクセススコープ制限について、リスクと対策を解説しました。

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

参考情報

公式ドキュメント

ベストプラクティス

この記事をシェアする

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

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

料金プランを詳しく見る