GKEでのCloud Logging機能の有効化について

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

この記事では、GKEクラスタでCloud Loggingが無効化されている場合の深刻なセキュリティリスクと、適切な設定方法について解説します。

ポリシーの説明

GKE(Google Kubernetes Engine)のCloud Logging機能は、Kubernetesクラスタ内で発生するすべてのアクティビティを記録・監視するための重要な機能です。この機能により、以下のログが自動的にCloud Loggingに送信され、一元的に管理・分析できます:

  • コントロールプレーンログ:APIサーバー、スケジューラー、コントローラーマネージャーの動作記録
  • ワークロードログ:Pod内で動作するアプリケーションのログ出力
  • システムコンポーネントログ:kubelet、kube-proxy、dockerなどのシステムレベルログ
  • 監査ログ:すべてのKubernetes APIコールの記録(誰が、いつ、何を実行したか)

一定のコストはもちろん掛かってしまうもののデバッグなどを考慮すると運用上必要となるので、出来る限り有効化しておきましょう。

修復方法

コンソールでの修復手順

Google Cloud コンソールを使用して、既存のGKEクラスタでCloud Loggingを有効化します。

既存クラスタでのCloud Logging有効化手順

  1. Google Cloud コンソールで「Kubernetes Engine」→「クラスタ」に移動
  1. Cloud Loggingを有効化したいクラスタ名をクリック
  1. 「詳細」タブで現在のロギング設定を確認
  2. 上部の「編集」ボタンをクリックし、「機能」セクションまでスクロール
  3. 「ロギング」の設定で以下を選択:
    • 「システムとワークロードのロギング」を選択(推奨)
    • 「SYSTEM_COMPONENTS」: 有効(必須)
    • 「WORKLOADS」: 有効(推奨)
    • 「API_SERVER」: 有効(監査ログ用)
  1. 「保存」をクリックして変更を適用

ログの保持期間とストレージ設定

  1. Cloud Loggingコンソールで「ログルーター」に移動
  2. 「_Default」シンクを編集
  1. 保持期間を設定(デフォルト30日から延長可能)
    • コンプライアンス要件に応じて90日、180日、365日などに設定
  2. 長期保存が必要な場合はCloud Storageへのエクスポートを設定してください。
    • 新しいシンクを作成
    • フィルタで必要なログを指定
    • 宛先にCloud Storageバケットを指定

監査ログの詳細設定

  1. 「IAMと管理」→「監査ログ」に移動
  2. 「Kubernetes Engine API」を検索して選択
  3. 以下のログタイプを有効化:
    • 管理アクティビティログ(デフォルトで有効)
    • データアクセスログ(読み取り)
    • データアクセスログ(書き込み)
  4. 「保存」をクリック

Terraformでの修復手順

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

# -------------------- ① プロジェクト設定 --------------------
data "google_project" "project" {
  project_id = var.project_id
}

# 監査ログの有効化
resource "google_project_iam_audit_config" "gke_audit" {
  project = var.project_id
  service = "container.googleapis.com"

  audit_log_config {
    log_type = "ADMIN_READ"
  }

  audit_log_config {
    log_type = "DATA_READ"
  }

  audit_log_config {
    log_type = "DATA_WRITE"
  }
}

# -------------------- ② Cloud Loggingバケット設定 --------------------
resource "google_logging_project_bucket_config" "gke_logs" {
  project        = var.project_id
  location       = var.region
  bucket_id      = "gke-cluster-logs-${var.environment}"

  # 保持期間の設定(日数)
  retention_days = var.log_retention_days  # 例: 90, 180, 365

  # ログのロック設定(削除防止)
  locked = var.environment == "production" ? true : false
}

# -------------------- ③ Cloud Logging対応GKEクラスタ --------------------
resource "google_container_cluster" "primary" {
  name     = "gke-cluster-${var.environment}"
  location = var.region
  project  = var.project_id

  # 初期設定
  initial_node_count       = 1
  remove_default_node_pool = true

  # ネットワーク設定
  network    = google_compute_network.gke_network.name
  subnetwork = google_compute_subnetwork.gke_subnet.name

  # Cloud Loggingの設定(重要)
  logging_config {
    enable_components = [
      "SYSTEM_COMPONENTS",    # システムコンポーネントのログ
      "WORKLOADS",           # ワークロードのログ
      "APISERVER",           # APIサーバーのログ(監査ログ含む)
      "CONTROLLER_MANAGER",   # コントローラーマネージャーのログ
      "SCHEDULER"            # スケジューラーのログ
    ]
  }

  # モニタリング設定(ログと連携)
  monitoring_config {
    enable_components = [
      "SYSTEM_COMPONENTS",
      "APISERVER",
      "CONTROLLER_MANAGER",
      "SCHEDULER"
    ]

    # Managed Prometheusの有効化
    managed_prometheus {
      enabled = true
    }
  }

  # クラスタ監査ログの設定
  cluster_telemetry {
    type = "ENABLED"
  }

  # マスター認証設定
  master_auth {
    client_certificate_config {
      issue_client_certificate = false
    }
  }

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

  # アドオン設定
  addons_config {
    horizontal_pod_autoscaling {
      disabled = false
    }
    http_load_balancing {
      disabled = false
    }
    network_policy_config {
      disabled = false
    }
  }

  # Workload Identity
  workload_identity_config {
    workload_pool = "${var.project_id}.svc.id.goog"
  }
}

# -------------------- ④ ログシンク設定 --------------------
# 長期保存用のCloud Storageバケット
resource "google_storage_bucket" "log_archive" {
  name          = "${var.project_id}-gke-logs-archive-${var.environment}"
  location      = var.region
  storage_class = "NEARLINE"

  # ライフサイクル管理
  lifecycle_rule {
    condition {
      age = 365  # 1年後にCOLDLINEに移行
    }
    action {
      type          = "SetStorageClass"
      storage_class = "COLDLINE"
    }
  }

  lifecycle_rule {
    condition {
      age = 2555  # 7年後に削除(コンプライアンス要件に応じて調整)
    }
    action {
      type = "Delete"
    }
  }

  # バージョニング
  versioning {
    enabled = true
  }

  # 暗号化
  encryption {
    default_kms_key_name = google_kms_crypto_key.log_encryption.id
  }

  # アクセス制御
  uniform_bucket_level_access = true
}

# ログシンク(重要なログを長期保存)
resource "google_logging_project_sink" "gke_audit_sink" {
  name        = "gke-audit-logs-sink-${var.environment}"
  project     = var.project_id
  destination = "storage.googleapis.com/${google_storage_bucket.log_archive.name}"

  # 監査ログのフィルタ
  filter = <<EOF
    resource.type="k8s_cluster"
    AND resource.labels.cluster_name="${google_container_cluster.primary.name}"
    AND (
      protoPayload.methodName:"io.k8s.core"
      OR protoPayload.methodName:"io.k8s.apps"
      OR protoPayload.methodName:"io.k8s.autoscaling"
      OR protoPayload.methodName:"io.k8s.batch"
      OR protoPayload.methodName:"io.k8s.rbac"
    )
    AND NOT protoPayload.authenticationInfo.principalEmail:"system:serviceaccount"
  EOF

  # 除外フィルタ(ノイズの多いログを除外)
  exclusions {
    name   = "exclude_get_requests"
    filter = 'protoPayload.methodName=~".*\\.get$"'
  }

  exclusions {
    name   = "exclude_health_checks"
    filter = 'protoPayload.userAgent:"GoogleHC" OR protoPayload.userAgent:"kube-probe"'
  }
}

# シンクのサービスアカウントに権限付与
resource "google_storage_bucket_iam_member" "log_sink_writer" {
  bucket = google_storage_bucket.log_archive.name
  role   = "roles/storage.objectCreator"
  member = google_logging_project_sink.gke_audit_sink.writer_identity
}

# -------------------- ⑤ ログベースメトリクス --------------------
# 不正なアクセス試行を検知するメトリクス
resource "google_logging_metric" "unauthorized_access" {
  name    = "gke-unauthorized-access-${var.environment}"
  project = var.project_id

  filter = <<EOF
    resource.type="k8s_cluster"
    AND protoPayload.status.code=403
    AND resource.labels.cluster_name="${google_container_cluster.primary.name}"
  EOF

  metric_descriptor {
    metric_kind = "DELTA"
    value_type  = "INT64"
    unit        = "1"

    labels {
      key         = "user"
      value_type  = "STRING"
      description = "User attempting unauthorized access"
    }
  }

  label_extractors = {
    "user" = "EXTRACT(protoPayload.authenticationInfo.principalEmail)"
  }
}

# -------------------- ⑥ アラート設定 --------------------
# 不正アクセスアラート
resource "google_monitoring_alert_policy" "unauthorized_access_alert" {
  display_name = "GKE Unauthorized Access Alert - ${var.environment}"
  project      = var.project_id
  combiner     = "OR"

  conditions {
    display_name = "Unauthorized API calls"

    condition_threshold {
      filter = <<EOF
        metric.type="logging.googleapis.com/user/gke-unauthorized-access-${var.environment}"
        AND resource.type="k8s_cluster"
      EOF

      duration   = "60s"
      comparison = "COMPARISON_GT"

      threshold_value = 5  # 5回以上の不正アクセスでアラート

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

  notification_channels = var.notification_channels

  alert_strategy {
    auto_close = "1800s"  # 30分後に自動クローズ
  }
}

# ログが無効化された場合のアラート
resource "google_monitoring_alert_policy" "logging_disabled_alert" {
  display_name = "GKE Logging Disabled Alert - ${var.environment}"
  project      = var.project_id

  conditions {
    display_name = "Cloud Logging disabled"

    condition_absent {
      filter = <<EOF
        resource.type="k8s_cluster"
        AND resource.labels.cluster_name="${google_container_cluster.primary.name}"
        AND metric.type="kubernetes.io/anthos/logging_request_count"
      EOF

      duration = "300s"

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

  notification_channels = var.notification_channels
}

# -------------------- ⑦ KMS暗号化キー --------------------
resource "google_kms_key_ring" "log_keyring" {
  name     = "gke-log-keyring-${var.environment}"
  location = var.region
  project  = var.project_id
}

resource "google_kms_crypto_key" "log_encryption" {
  name            = "gke-log-encryption-key"
  key_ring        = google_kms_key_ring.log_keyring.id
  rotation_period = "7776000s"  # 90日

  lifecycle {
    prevent_destroy = true
  }
}

# -------------------- ⑧ ダッシュボード設定 --------------------
resource "google_monitoring_dashboard" "gke_logging" {
  dashboard_json = jsonencode({
    displayName = "GKE Logging Dashboard - ${var.environment}"
    mosaicLayout = {
      columns = 12
      tiles = [
        {
          width  = 6
          height = 4
          widget = {
            title = "Log Volume by Component"
            xyChart = {
              dataSets = [{
                timeSeriesQuery = {
                  timeSeriesFilter = {
                    filter = "resource.type=\"k8s_cluster\" resource.labels.cluster_name=\"${google_container_cluster.primary.name}\""
                    aggregation = {
                      alignmentPeriod  = "60s"
                      perSeriesAligner = "ALIGN_RATE"
                    }
                  }
                }
              }]
            }
          }
        },
        {
          width  = 6
          height = 4
          widget = {
            title = "API Server Audit Events"
            xyChart = {
              dataSets = [{
                timeSeriesQuery = {
                  timeSeriesFilter = {
                    filter = "resource.type=\"k8s_cluster\" metric.type=\"kubernetes.io/anthos/audit_event_count\""
                    aggregation = {
                      alignmentPeriod  = "60s"
                      perSeriesAligner = "ALIGN_RATE"
                    }
                  }
                }
              }]
            }
          }
        }
      ]
    }
  })
}

# -------------------- ⑨ アプリケーションログ設定 --------------------
# Fluent Bitを使用したカスタムログ収集
resource "helm_release" "fluent_bit" {
  count      = var.enable_custom_log_collection ? 1 : 0
  name       = "fluent-bit"
  repository = "<https://fluent.github.io/helm-charts>"
  chart      = "fluent-bit"
  namespace  = "kube-system"
  version    = "0.25.0"

  values = [
    yamlencode({
      config = {
        outputs = |
          [OUTPUT]
              Name stackdriver
              Match *
              google_service_credentials /var/secrets/google/key.json
              resource k8s_container
              k8s_cluster_name ${google_container_cluster.primary.name}
              k8s_cluster_location ${var.region}
              tag_prefix kube.var.log.containers.

        filters = |
          [FILTER]
              Name parser
              Match *
              Key_Name log
              Parser json
              Reserve_Data true

          [FILTER]
              Name grep
              Match *
              Exclude log health check|liveness probe|readiness probe
      }

      # リソース制限
      resources = {
        limits = {
          cpu    = "200m"
          memory = "256Mi"
        }
        requests = {
          cpu    = "100m"
          memory = "128Mi"
        }
      }
    })
  ]
}

# -------------------- ⑩ 変数定義例 --------------------
variable "log_retention_days" {
  description = "Number of days to retain logs"
  type        = number
  default     = 90

  validation {
    condition     = var.log_retention_days >= 30 && var.log_retention_days <= 3650
    error_message = "Log retention must be between 30 and 3650 days."
  }
}

variable "environment" {
  description = "Environment name"
  type        = string

  validation {
    condition     = contains(["development", "staging", "production"], var.environment)
    error_message = "Environment must be development, staging, or production."
  }
}

variable "notification_channels" {
  description = "List of notification channel IDs for alerts"
  type        = list(string)
}

 

Terraform実装時の重要なポイント:

  1. 包括的なログ収集: SYSTEM_COMPONENTS、WORKLOADS、APIサーバーなどすべての重要コンポーネントのログを有効化
  2. 監査ログの設定: データアクセスログを含むすべての監査ログタイプを有効化
  3. 長期保存: コンプライアンス要件に応じた保持期間の設定とアーカイブストレージの活用
  4. セキュリティ: ログの暗号化とアクセス制御の実装
  5. モニタリング: ログベースメトリクスとアラートによる異常検知
  6. コスト最適化: ライフサイクルポリシーによる古いログの自動アーカイブ

まとめ

この記事では、GKEクラスタでCloud Loggingが無効化されている場合の深刻なリスクと対策について解説しました。

Cloud Loggingを適切に設定することで、セキュリティインシデントの早期発見、コンプライアンス要件の充足、効率的なトラブルシューティングが可能になります。特に、監査ログは攻撃の痕跡を追跡する唯一の手段であり、事後対応の成否を左右します。

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

参考情報

この記事をシェアする

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

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

料金プランを詳しく見る