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の準備
- Google Cloud コンソールにログインします
- ナビゲーションメニューから「ロギング」→「ログルーター」を選択します
- 「シンクを作成」をクリックし、DNSログ用のシンクを準備します

ステップ2: DNSポリシーの作成
- ナビゲーションメニューから「ネットワークサービス」→「Cloud DNS」を選択します
- 「DNSサーバーポリシー」タブをクリックします

- 「ポリシーを作成」をクリックします
- 以下の設定を入力します:
- ポリシー名:
dns-logging-policy
- 説明: DNSクエリログを有効化するポリシー
- ログを有効にする: オン
- ポリシー名:
- 「作成」をクリックします
ステップ3: VPCネットワークへのポリシー適用
- 作成したDNSポリシーを選択します
- 「ネットワークを追加」をクリックします
- DNSログを有効化したいVPCネットワークを選択します
- 「追加」をクリックします
ステップ4: ログの確認と設定
- ナビゲーションメニューから「ロギング」→「ログエクスプローラー」を選択します
- クエリビルダーで以下のフィルタを設定します:
resource.type="dns_query"
- DNSクエリログが表示されることを確認します
ステップ5: ログの保存とアラート設定
- 「ログルーター」に移動します
- 「シンクを作成」をクリックします
- 以下の設定を入力します:
- シンク名:
dns-logs-sink
- シンクの宛先: Cloud Storage(長期保存用)またはBigQuery(分析用)
- フィルタ:
resource.type="dns_query" AND ( jsonPayload.rdata_class="IN" OR jsonPayload.query_name=~".*\.suspicious-domain\.com" )
- シンク名:
- 「作成」をクリックします
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機能で簡単に検出及び管理する事が可能です。 運用が非常に楽に出来る製品になっていますので、ぜひ興味がある方はお問い合わせお待ちしております。 最後までお読みいただきありがとうございました。この記事が皆さんの役に立てば幸いです。
参考情報
- Cloud DNS ログ公式ドキュメント
- DNS ポリシーの管理
- Cloud Logging ドキュメント
- BigQuery でのログ分析
- DNS セキュリティベストプラクティス
- VPC Service Controls でのDNS保護