Google Cloud VPCフローログの有効化について

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

この記事では、Google Cloud VPCフローログを有効化してネットワークトラフィックを監視する設定手順について、リスクと対策を解説します。

ポリシーの説明

VPCフローログは、VPCネットワーク内のネットワークインターフェースとの間で送受信されるIPトラフィックに関する情報を記録する機能です。この機能により、ネットワークトラフィックの可視性が向上し、セキュリティ分析、ネットワーク監視、コンプライアンス監査、パフォーマンストラブルシューティングが可能になります。デフォルトでは無効になっているため、明示的に有効化する必要があります。

コスト面を考慮に入れつつも本番環境で動作させる際には有効化するようにしましょう。

修復方法

コンソールでの修復手順

Google Cloud コンソールを使用して、VPCフローログを有効化します。

ステップ1: VPCネットワークの確認

  1. Google Cloud コンソールにログインします
  2. ナビゲーションメニューから「VPCネットワーク」を選択します
  1. フローログを有効化したいVPCネットワークをクリックします

ステップ2: サブネットレベルでのフローログ有効化

  1. VPCネットワークの詳細画面で「サブネット」タブをクリックします
  2. フローログを有効化したいサブネットを選択します
  3. 「編集」ボタンをクリックします
  1. 「フローログ」セクションで以下を設定します:
    • フローログ: オン
    • 集約間隔: 5秒(推奨)
    • サンプルレート: 1.0(100%、セキュリティ監視用)
  2. 「保存」をクリックします

ステップ3: ログの保存先設定

  1. ナビゲーションメニューから「ロギング」→「ログルーター」を選択します
  2. 「シンクを作成」をクリックします
  1. 以下の設定を入力します:
    • シンク名: vpc-flow-logs-sink
    • シンクの宛先: Cloud Storage(長期保存)またはBigQuery(分析用)を選択
  2. フィルタに以下を入力:
    resource.type="gce_subnetwork"
    logName="projects/PROJECT_ID/logs/compute.googleapis.com%2Fvpc_flows"

     

  3. 「作成」をクリックします

必要に応じてアラートを設定するなどして監視するようにしましょう。

Terraformでの修復手順

VPCフローログを有効化し、包括的な監視体制を構築するTerraformコードと、主要な修正ポイントを説明します。

# プロバイダー設定
provider "google" {
  project = var.project_id
  region  = var.region
}

# VPCネットワーク
resource "google_compute_network" "vpc_network" {
  name                    = "${var.environment}-vpc"
  auto_create_subnetworks = false
  description            = "VPC network with flow logs enabled"
}

# サブネット(フローログ有効)
resource "google_compute_subnetwork" "subnet_with_flow_logs" {
  name          = "${var.environment}-subnet-${var.region}"
  ip_cidr_range = var.subnet_cidr
  region        = var.region
  network       = google_compute_network.vpc_network.id

  # プライベートGoogleアクセス
  private_ip_google_access = true

  # フローログの設定(重要)
  log_config {
    aggregation_interval = "INTERVAL_5_SEC"      # 集約間隔(5秒)
    flow_sampling       = 1.0                    # サンプリングレート(100%)
    metadata            = "INCLUDE_ALL_METADATA" # すべてのメタデータを含める

    # フローログに含まれる標準フィールド:
    # - connection.src_ip: 送信元IPアドレス
    # - connection.dest_ip: 宛先IPアドレス
    # - connection.src_port: 送信元ポート
    # - connection.dest_port: 宛先ポート
    # - connection.protocol: プロトコル番号
    # - bytes_sent: 送信バイト数
    # - packets_sent: 送信パケット数
    # - start_time/end_time: フロー開始/終了時刻
  }

  # セカンダリIP範囲(GKE等で使用)
  secondary_ip_range {
    range_name    = "pods"
    ip_cidr_range = var.pods_cidr
  }

  secondary_ip_range {
    range_name    = "services"
    ip_cidr_range = var.services_cidr
  }
}

# Cloud Storageバケット(フローログ長期保存用)
resource "google_storage_bucket" "flow_logs_bucket" {
  name                        = "${var.project_id}-vpc-flow-logs-${var.environment}"
  location                    = var.region
  force_destroy              = false
  uniform_bucket_level_access = true

  # ライフサイクルルール
  lifecycle_rule {
    condition {
      age = 90  # 90日後にColdlineへ移行
    }
    action {
      type          = "SetStorageClass"
      storage_class = "COLDLINE"
    }
  }

  lifecycle_rule {
    condition {
      age = 365  # 1年後に削除
    }
    action {
      type = "Delete"
    }
  }

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

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

# KMS暗号化キー
resource "google_kms_key_ring" "flow_logs_keyring" {
  name     = "${var.environment}-flow-logs-keyring"
  location = var.region
}

resource "google_kms_crypto_key" "flow_logs_key" {
  name            = "${var.environment}-flow-logs-key"
  key_ring        = google_kms_key_ring.flow_logs_keyring.id
  rotation_period = "7776000s"  # 90日

  lifecycle {
    prevent_destroy = true
  }
}

# BigQueryデータセット(フローログ分析用)
resource "google_bigquery_dataset" "flow_logs_dataset" {
  dataset_id                  = "${var.environment}_vpc_flow_logs"
  friendly_name              = "VPC Flow Logs Dataset"
  description                = "Dataset for analyzing VPC flow logs"
  location                   = var.region
  default_table_expiration_ms = 2592000000  # 30日

  # アクセス制御
  access {
    role          = "OWNER"
    user_by_email = var.admin_email
  }

  access {
    role          = "READER"
    group_by_email = var.security_team_email
  }

  # 暗号化
  default_encryption_configuration {
    kms_key_name = google_kms_crypto_key.flow_logs_key.id
  }
}

# ログシンク(フローログ → Cloud Storage)
resource "google_logging_project_sink" "flow_logs_storage_sink" {
  name        = "${var.environment}-flow-logs-storage-sink"
  destination = "storage.googleapis.com/${google_storage_bucket.flow_logs_bucket.name}"

  filter = <<-EOT
    resource.type="gce_subnetwork"
    logName="projects/${var.project_id}/logs/compute.googleapis.com%2Fvpc_flows"
  EOT

  unique_writer_identity = true
}

# ログシンク(フローログ → BigQuery)
resource "google_logging_project_sink" "flow_logs_bigquery_sink" {
  name        = "${var.environment}-flow-logs-bigquery-sink"
  destination = "bigquery.googleapis.com/projects/${var.project_id}/datasets/${google_bigquery_dataset.flow_logs_dataset.dataset_id}"

  filter = <<-EOT
    resource.type="gce_subnetwork"
    logName="projects/${var.project_id}/logs/compute.googleapis.com%2Fvpc_flows"
  EOT

  unique_writer_identity = true

  bigquery_options {
    use_partitioned_tables = true
  }
}

# IAMバインディング
resource "google_storage_bucket_iam_member" "flow_logs_writer" {
  bucket = google_storage_bucket.flow_logs_bucket.name
  role   = "roles/storage.objectCreator"
  member = google_logging_project_sink.flow_logs_storage_sink.writer_identity
}

resource "google_bigquery_dataset_iam_member" "flow_logs_editor" {
  dataset_id = google_bigquery_dataset.flow_logs_dataset.dataset_id
  role       = "roles/bigquery.dataEditor"
  member     = google_logging_project_sink.flow_logs_bigquery_sink.writer_identity
}

# BigQueryビュー(セキュリティ分析用)
resource "google_bigquery_table" "security_analysis_view" {
  dataset_id = google_bigquery_dataset.flow_logs_dataset.dataset_id
  table_id   = "security_analysis_view"

  view {
    query = <<-EOT
      WITH flow_summary AS (
        SELECT
          jsonPayload.connection.src_ip as src_ip,
          jsonPayload.connection.dest_ip as dest_ip,
          jsonPayload.connection.src_port as src_port,
          jsonPayload.connection.dest_port as dest_port,
          jsonPayload.connection.protocol as protocol,
          jsonPayload.bytes_sent as bytes_sent,
          jsonPayload.packets_sent as packets_sent,
          jsonPayload.start_time as start_time,
          jsonPayload.end_time as end_time,
          resource.labels.subnetwork_name as subnet,
          CASE
            WHEN jsonPayload.connection.dest_port IN (22, 3389, 445, 135, 139) THEN 'HIGH_RISK_PORT'
            WHEN jsonPayload.bytes_sent > 1073741824 THEN 'LARGE_TRANSFER'  -- 1GB
            WHEN jsonPayload.connection.protocol = 6 AND jsonPayload.packets_sent > 10000 THEN 'POSSIBLE_SCAN'
            ELSE 'NORMAL'
          END as risk_category
        FROM `${var.project_id}.${var.environment}_vpc_flow_logs.compute_googleapis_com_vpc_flows_*`
        WHERE _TABLE_SUFFIX = FORMAT_DATE('%Y%m%d', CURRENT_DATE())
      )
      SELECT
        *,
        COUNT(*) OVER (
          PARTITION BY src_ip, dest_port
          ORDER BY TIMESTAMP(start_time)
          RANGE BETWEEN INTERVAL 1 HOUR PRECEDING AND CURRENT ROW
        ) as connections_per_hour
      FROM flow_summary
      WHERE risk_category != 'NORMAL'
      ORDER BY start_time DESC
    EOT

    use_legacy_sql = false
  }
}

# アラートポリシー(異常トラフィック検出)
resource "google_monitoring_alert_policy" "abnormal_traffic" {
  display_name = "${var.environment} Abnormal VPC Traffic"
  combiner     = "OR"

  conditions {
    display_name = "High volume data transfer detected"

    condition_logs_based {
      filter = <<-EOT
        resource.type="gce_subnetwork"
        logName="projects/${var.project_id}/logs/compute.googleapis.com%2Fvpc_flows"
        jsonPayload.bytes_sent > 10737418240  -- 10GB
      EOT

      aggregations {
        alignment_period     = "300s"
        per_series_aligner   = "ALIGN_RATE"
        cross_series_reducer = "REDUCE_SUM"
        group_by_fields      = ["jsonPayload.connection.src_ip"]
      }

      comparison      = "COMPARISON_GT"
      threshold_value = 1
      duration        = "60s"
    }
  }

  conditions {
    display_name = "Port scanning activity detected"

    condition_logs_based {
      filter = <<-EOT
        resource.type="gce_subnetwork"
        logName="projects/${var.project_id}/logs/compute.googleapis.com%2Fvpc_flows"
        jsonPayload.connection.dest_port > 0
      EOT

      aggregations {
        alignment_period     = "60s"
        per_series_aligner   = "ALIGN_COUNT_TRUE"
        cross_series_reducer = "REDUCE_COUNT_TRUE"
        group_by_fields      = ["jsonPayload.connection.src_ip"]
      }

      comparison      = "COMPARISON_GT"
      threshold_value = 100  # 1分間に100以上の異なるポートへの接続
      duration        = "60s"
    }
  }

  notification_channels = [google_monitoring_notification_channel.security_team.id]

  alert_strategy {
    auto_close = "1800s"
  }
}

# 通知チャンネル
resource "google_monitoring_notification_channel" "security_team" {
  display_name = "Security Team"
  type         = "email"

  labels = {
    email_address = var.security_team_email
  }
}

# Pub/Subトピック(リアルタイム処理用)
resource "google_pubsub_topic" "flow_logs_topic" {
  name = "${var.environment}-flow-logs-topic"

  message_retention_duration = "86400s"  # 1日
}

# ログシンク(フローログ → Pub/Sub)
resource "google_logging_project_sink" "flow_logs_pubsub_sink" {
  name        = "${var.environment}-flow-logs-pubsub-sink"
  destination = "pubsub.googleapis.com/${google_pubsub_topic.flow_logs_topic.id}"

  filter = <<-EOT
    resource.type="gce_subnetwork"
    logName="projects/${var.project_id}/logs/compute.googleapis.com%2Fvpc_flows"
    (jsonPayload.connection.dest_port=22 OR
     jsonPayload.connection.dest_port=3389 OR
     jsonPayload.bytes_sent > 1073741824)
  EOT

  unique_writer_identity = true
}

# 変数定義
variable "project_id" {
  description = "GCP Project ID"
  type        = string
}

variable "environment" {
  description = "Environment name (prod, staging, dev)"
  type        = string
}

variable "region" {
  description = "GCP region"
  type        = string
  default     = "asia-northeast1"
}

variable "subnet_cidr" {
  description = "CIDR range for the subnet"
  type        = string
  default     = "10.0.0.0/24"
}

variable "pods_cidr" {
  description = "CIDR range for GKE pods"
  type        = string
  default     = "10.1.0.0/16"
}

variable "services_cidr" {
  description = "CIDR range for GKE services"
  type        = string
  default     = "10.2.0.0/16"
}

variable "admin_email" {
  description = "Admin email for dataset access"
  type        = string
}

variable "security_team_email" {
  description = "Security team email for alerts"
  type        = string
}

# 出力値
output "flow_logs_bucket" {
  value       = google_storage_bucket.flow_logs_bucket.url
  description = "Cloud Storage bucket for flow logs"
}

output "bigquery_dataset" {
  value       = google_bigquery_dataset.flow_logs_dataset.id
  description = "BigQuery dataset for flow log analysis"
}

output "pubsub_topic" {
  value       = google_pubsub_topic.flow_logs_topic.id
  description = "Pub/Sub topic for real-time flow log processing"
}

 

最後に

この記事では、Google Cloud VPCフローログを有効化してネットワークトラフィックを監視する設定手順について、リスクと対策を解説しました。

VPCフローログを有効化することで、ネットワークレベルでの包括的な可視性を確保し、セキュリティ脅威の早期検出、コンプライアンス要件の充足、効率的なトラブルシューティングが可能になります。

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

参考情報

公式ドキュメント

この記事をシェアする

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

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

料金プランを詳しく見る