Google Cloud VPCネットワークのDNSログ収集について

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

この記事では、Google Cloud VPCネットワークでDNSログ収集を有効化する設定手順について、セキュリティリスクの詳細な解説と、2025年最新のベストプラクティスに基づいた対策を提供します。

ポリシーの説明

Google Cloud DNSログは、VPCネットワーク内のリソースが実行するDNSクエリを記録する重要なセキュリティ機能です。

DNSログはネットワークセキュリティの重要な情報源であり、マルウェア通信の検出、内部脅威の特定、DNSトンネリングの検出などに活用できます。BigQueryと連携することで、大量のDNSログを効率的に分析し、異常なパターンを早期に検出できます。

デフォルトでは無効になっているため、特に本番環境では有効化にし、適切に監視できるようにしましょう。

修復方法

コンソールでの修復手順

Google Cloud コンソールを使用して、VPCネットワークでDNSログ収集を有効化します。

ステップ1: Cloud Loggingの準備

  1. Google Cloud コンソールにログインします
  2. ナビゲーションメニューから「ロギング」→「ログルーター」を選択します
  3. 「シンクを作成」をクリックし、DNSログ用のシンクを準備します

ステップ2: DNSポリシーの作成

  1. ナビゲーションメニューから「ネットワークサービス」→「Cloud DNS」を選択します
  2. 「DNSサーバーポリシー」タブをクリックします
  1. 「ポリシーを作成」をクリックします
  2. 以下の設定を入力します:
    • ポリシー名: dns-logging-policy
    • 説明: DNSクエリログを有効化するポリシー
    • ログを有効にする: オン
  3. 「作成」をクリックします

ステップ3: VPCネットワークへのポリシー適用

  1. 作成したDNSポリシーを選択します
  2. 「ネットワークを追加」をクリックします
  3. DNSログを有効化したいVPCネットワークを選択します
  4. 「追加」をクリックします

ステップ4: ログの確認と設定

  1. ナビゲーションメニューから「ロギング」→「ログエクスプローラー」を選択します
  2. クエリビルダーで以下のフィルタを設定します:
    resource.type="dns_query"

     

  3. DNSクエリログが表示されることを確認します

ステップ5: ログの保存とアラート設定

  1. 「ログルーター」に移動します
  2. 「シンクを作成」をクリックします
  3. 以下の設定を入力します:
    • シンク名: dns-logs-sink
    • シンクの宛先: Cloud Storage(長期保存用)またはBigQuery(分析用)
    • フィルタ:
    resource.type="dns_query"
    AND (
      jsonPayload.rdata_class="IN"
      OR jsonPayload.query_name=~".*\.suspicious-domain\.com"
    )
    

     

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

Terraformでの修復手順

VPCネットワークでDNSログ収集を有効化するTerraformコードと、主要な修正ポイントを説明します。

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

provider "google-beta" {
  project = var.project_id
  region  = var.region
}

# 前提条件の確認
data "google_project" "current" {}

# APIの有効化確認
resource "google_project_service" "required_apis" {
  for_each = toset([
    "dns.googleapis.com",
    "logging.googleapis.com",
    "monitoring.googleapis.com",
    "bigquery.googleapis.com"
  ])

  service = each.value
  disable_on_destroy = false
}

# DNSポリシーの作成(ログ有効化)
resource "google_dns_policy" "dns_logging_policy" {
  name                      = "${var.environment}-dns-logging-policy"
  description              = "DNS logging policy for ${var.environment} environment - 2025 security standards"
  enable_logging           = true  # DNSログを有効化

  # セキュリティ強化設定(2025年推奨)
  enable_inbound_forwarding = false  # インバウンド転送を無効化

  # 代替ネームサーバー設定(セキュアDNS)
  alternative_name_server_config {
    # Cloudflare DNS (マルウェアブロッキング付き)
    target_name_servers {
      ipv4_address    = "1.1.1.2"  # マルウェアブロッキング
      forwarding_path = "private"
    }
    target_name_servers {
      ipv4_address    = "1.0.0.2"  # マルウェアブロッキング
      forwarding_path = "private"
    }
    # Google Public DNS (バックアップ)
    target_name_servers {
      ipv4_address    = "8.8.8.8"
      forwarding_path = "private"
    }
    target_name_servers {
      ipv4_address    = "8.8.4.4"
      forwarding_path = "private"
    }
  }

  depends_on = [google_project_service.required_apis]
}

# 既存のVPCネットワーク(またはカスタムVPC)
resource "google_compute_network" "vpc_network" {
  name                    = "${var.environment}-vpc"
  auto_create_subnetworks = false
  description            = "VPC network with DNS logging enabled"
}

# VPCネットワークへのDNSポリシー適用
resource "google_dns_policy" "vpc_dns_policy_attachment" {
  name                      = "${var.environment}-vpc-dns-policy"
  enable_logging           = true
  enable_inbound_forwarding = false

  networks {
    network_url = google_compute_network.vpc_network.id
  }

  depends_on = [google_compute_network.vpc_network]
}

# Cloud Storageバケット(DNSログ保存用)
resource "google_storage_bucket" "dns_logs_bucket" {
  name                        = "${var.project_id}-dns-logs-${var.environment}"
  location                    = var.region
  force_destroy              = false
  uniform_bucket_level_access = true

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

  lifecycle_rule {
    condition {
      age = 30  # 30日後にNearclineストレージクラスへ移行
    }
    action {
      type = "SetStorageClass"
      storage_class = "NEARLINE"
    }
  }

  versioning {
    enabled = true
  }
}

# BigQueryデータセット(DNSログ分析用)
resource "google_bigquery_dataset" "dns_logs_dataset" {
  dataset_id                  = "${var.environment}_dns_logs"
  friendly_name              = "DNS Logs Dataset"
  description                = "Dataset for storing and analyzing DNS query logs"
  location                   = var.region
  default_table_expiration_ms = 7776000000  # 90日

  access {
    role          = "OWNER"
    user_by_email = var.admin_email
  }

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

# ログシンク(DNS logs to Cloud Storage)
resource "google_logging_project_sink" "dns_logs_storage_sink" {
  name        = "${var.environment}-dns-logs-storage-sink"
  destination = "storage.googleapis.com/${google_storage_bucket.dns_logs_bucket.name}"

  filter = <<-EOT
    resource.type="dns_query"
    AND NOT (
      jsonPayload.query_name=~".*\.googleapis\.com\.$"
      OR jsonPayload.query_name=~".*\.google\.com\.$"
      OR jsonPayload.query_name=~".*\.gstatic\.com\.$"
      OR jsonPayload.query_name=~".*\.googleusercontent\.com\.$"
      OR jsonPayload.query_name=~"metadata\.google\.internal\.$"
    )
    AND NOT (
      jsonPayload.source_ip="169.254.169.254"  # メタデータサーバー
    )
  EOT

  unique_writer_identity = true
}

# ログシンク(DNS logs to BigQuery)
resource "google_logging_project_sink" "dns_logs_bigquery_sink" {
  name        = "${var.environment}-dns-logs-bigquery-sink"
  destination = "bigquery.googleapis.com/projects/${var.project_id}/datasets/${google_bigquery_dataset.dns_logs_dataset.dataset_id}"

  filter = <<-EOT
    resource.type="dns_query"
  EOT

  unique_writer_identity = true

  bigquery_options {
    use_partitioned_tables = true
  }
}

# IAMバインディング(ログシンクのサービスアカウント用)
resource "google_storage_bucket_iam_member" "dns_logs_writer" {
  bucket = google_storage_bucket.dns_logs_bucket.name
  role   = "roles/storage.objectCreator"
  member = google_logging_project_sink.dns_logs_storage_sink.writer_identity
}

resource "google_bigquery_dataset_iam_member" "dns_logs_editor" {
  dataset_id = google_bigquery_dataset.dns_logs_dataset.dataset_id
  role       = "roles/bigquery.dataEditor"
  member     = google_logging_project_sink.dns_logs_bigquery_sink.writer_identity
}

# アラートポリシー(疑わしいDNSクエリ検出)
resource "google_monitoring_alert_policy" "suspicious_dns_queries" {
  display_name = "${var.environment} Suspicious DNS Queries"
  combiner     = "OR"

  conditions {
    display_name = "High volume of DNS queries to unknown domains"

    condition_logs_based {
      filter = <<-EOT
        resource.type="dns_query"
        AND NOT (
          jsonPayload.query_name=~".*\.(googleapis|google|gstatic)\.com\.$"
        )
        AND jsonPayload.response_code=3  # NXDOMAIN
      EOT

      # 5分間で100回以上のNXDOMAINレスポンス(DGA検出)
      aggregations {
        alignment_period     = "300s"
        per_series_aligner   = "ALIGN_RATE"
        cross_series_reducer = "REDUCE_COUNT"
        group_by_fields = ["jsonPayload.source_ip"]
      }

      comparison = "COMPARISON_GT"
      threshold_value = 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 Email"
  type         = "email"

  labels = {
    email_address = var.security_team_email
  }
}

# 追加のセキュリティアラート
resource "google_monitoring_alert_policy" "dns_tunneling_detection" {
  display_name = "${var.environment} DNS Tunneling Detection"
  combiner     = "OR"

  conditions {
    display_name = "Large DNS queries (potential tunneling)"

    condition_logs_based {
      filter = <<-EOT
        resource.type="dns_query"
        AND jsonPayload.query_name=~".*\.(duckdns|no-ip|dynu|afraid)\..*"
        OR (
          LENGTH(jsonPayload.query_name) > 100
          AND jsonPayload.query_type="TXT"
        )
      EOT
    }
  }

  conditions {
    display_name = "Suspicious TLD usage"

    condition_logs_based {
      filter = <<-EOT
        resource.type="dns_query"
        AND jsonPayload.query_name=~".*\.(tk|ml|ga|cf|click|download|top)\.$"
      EOT
    }
  }

  notification_channels = [google_monitoring_notification_channel.security_team.id]

  documentation {
    content = <<-EOT
    ## DNS Tunneling Alert

    Potential DNS tunneling or suspicious domain access detected.

    **Immediate Actions:**
    1. Identify source IP: Check alert details
    2. Review DNS query patterns
    3. Block suspicious domains if confirmed
    4. Investigate affected instances

    **Known DNS Tunneling Tools:**
    - dnscat2
    - iodine
    - dns2tcp
    EOT
    mime_type = "text/markdown"
  }
}

# BigQueryビュー(分析用)
resource "google_bigquery_table" "dns_analysis_view" {
  dataset_id = google_bigquery_dataset.dns_logs_dataset.dataset_id
  table_id   = "dns_analysis_view"

  view {
    query = <<-EOT
      SELECT
        TIMESTAMP(jsonPayload.query_time) as query_time,
        jsonPayload.source_ip as source_ip,
        jsonPayload.query_name as domain,
        jsonPayload.query_type as query_type,
        jsonPayload.response_code as response_code,
        CASE
          WHEN jsonPayload.response_code = 0 THEN 'NOERROR'
          WHEN jsonPayload.response_code = 3 THEN 'NXDOMAIN'
          ELSE CAST(jsonPayload.response_code AS STRING)
        END as response_code_name,
        resource.labels.network_id as network,
        COUNT(*) OVER (
          PARTITION BY jsonPayload.source_ip, jsonPayload.query_name
          ORDER BY TIMESTAMP(jsonPayload.query_time)
          RANGE BETWEEN INTERVAL 1 HOUR PRECEDING AND CURRENT ROW
        ) as queries_per_hour
      FROM `${var.project_id}.${var.environment}_dns_logs.dns_query_*`
      WHERE _TABLE_SUFFIX = FORMAT_DATE('%Y%m%d', CURRENT_DATE())
    EOT

    use_legacy_sql = false
  }
}

# 変数定義
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 "admin_email" {
  description = "Admin email for BigQuery dataset access"
  type        = string
}

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

# 出力値
output "dns_policy_id" {
  value       = google_dns_policy.dns_logging_policy.id
  description = "DNS policy ID with logging enabled"
}

output "logs_bucket" {
  value       = google_storage_bucket.dns_logs_bucket.url
  description = "Cloud Storage bucket for DNS logs"
}

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

最後に

この記事では、Google Cloud VPCネットワークでDNSログ収集を有効化する設定手順について解説しました。

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

参考情報

 

この記事をシェアする

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

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

料金プランを詳しく見る