Compute Engine におけるパブリックIPの割当の削除手順

このブログシリーズ 「クラウドセキュリティ 実践集」 では、一般的なセキュリティ課題を取り上げ、「なぜ危険なのか?」 というリスクの解説から、 「どうやって直すのか?」 という具体的な修復手順(コンソール、gcloud CLI、Terraformなど)まで、分かりやすく解説します。
この記事では、Google Cloud Compute EngineインスタンスがパブリックIPアドレス(外部IP)を保持し、インターネットから直接アクセス可能な状態にある場合のセキュリティリスクと、それを解決するための具体的な対策方法を解説します。

ポリシーの説明
Compute EngineインスタンスにパブリックIPアドレスが割り当てられている場合、ネットワークの設定によってはそのインスタンスはインターネットから直接アクセス可能となり、重大なセキュリティリスクを抱えることになります。
パブリックIPのセキュリティ上の問題点:
- インターネットからの直接攻撃: 全世界からアクセス可能な状態
- 管理の複雑化: ファイアウォールルールの維持管理が困難
Google Cloud推奨の代替ソリューション:
- Identity-Aware Proxy (IAP): SSH/RDPアクセスのセキュア化
- Cloud NAT: アウトバウンド通信のセキュア化
- Cloud Load Balancing: Webアプリケーションの公開
- Private Google Access: Google APIへのプライベート接続
上記の手法を利用すれば代替出来るため直接インスタンスに対してパブリックIPアドレスを付与する必要がなくなります。
修復方法
コンソールでの修復手順
Google Cloud コンソールを使用して、VMインスタンスのパブリックIPアドレスを削除し、プライベートアクセスのみに制限します。
既存インスタンスからパブリックIPを削除する手順:
- Google Cloud コンソールにアクセスし、[Compute Engine] > [VMインスタンス] に移動します
- 対象のインスタンス名をクリックして詳細ページを開きます
- 上部の [編集] ボタンをクリックします
- [ネットワーク インターフェース] セクションまでスクロールし、該当するネットワークインターフェースの編集アイコンをクリックします
- [外部IP] のドロップダウンメニューから [なし] を選択します

- [完了] をクリックしてネットワークインターフェースの設定を保存します
- ページ下部の [保存] ボタンをクリックして変更を適用します
組織ポリシーによる制限(推奨):
- [IAMと管理] > [組織のポリシー] に移動します
- [ポリシーを管理] をクリックします
- 検索バーで「compute.vmExternalIpAccess」を検索します

- 該当するポリシーをクリックして編集画面を開きます
- [ポリシーを編集] をクリックします
- [ルールを追加] で [すべて拒否] を選択します
- 必要に応じて、特定のインスタンスを例外として追加します
- [保存] をクリックして変更を適用します
Terraformでの修復手順
VMインスタンスのパブリックIPアドレスを制限するTerraformコードと、主要な修正ポイントを説明します。
# ------------------ ① 基本的なVMインスタンス(パブリックIPなし) ------------------
resource "google_compute_instance" "private_vm" {
name = "private-instance"
machine_type = "e2-medium"
zone = var.zone
boot_disk {
initialize_params {
image = "debian-cloud/debian-11"
size = 20
}
}
network_interface {
network = google_compute_network.custom_network.id
subnetwork = google_compute_subnetwork.custom_subnet.id
# access_config ブロックを省略することで、パブリックIPの割り当てを防止
# access_config {
# // このブロックがあるとパブリックIPが割り当てられる
# }
}
# 追加のセキュリティ設定
metadata = {
enable-oslogin = "TRUE"
block-project-ssh-keys = "TRUE"
serial-port-enable = "FALSE"
}
# Shielded VMの有効化(推奨)
shielded_instance_config {
enable_secure_boot = true
enable_vtpm = true
enable_integrity_monitoring = true
}
# サービスアカウント(最小権限の原則)
service_account {
email = google_service_account.vm_sa.email
scopes = ["cloud-platform"]
}
tags = ["no-public-ip", "private-vm"]
}
# ------------------ ② ネットワーク設定 ------------------
resource "google_compute_network" "custom_network" {
name = "secure-network"
auto_create_subnetworks = false
description = "Custom network without default internet gateway"
}
resource "google_compute_subnetwork" "custom_subnet" {
name = "private-subnet"
ip_cidr_range = "10.0.0.0/24"
region = var.region
network = google_compute_network.custom_network.id
private_ip_google_access = true # Google APIへのプライベートアクセスを有効化
}
# ------------------ ③ Cloud NAT(アウトバウンド接続用) ------------------
resource "google_compute_router" "nat_router" {
name = "nat-router"
network = google_compute_network.custom_network.id
region = var.region
}
resource "google_compute_router_nat" "nat_gateway" {
name = "secure-nat-gateway"
router = google_compute_router.nat_router.name
region = google_compute_router.nat_router.region
nat_ip_allocate_option = "AUTO_ONLY"
source_subnetwork_ip_ranges_to_nat = "ALL_SUBNETWORKS_ALL_IP_RANGES"
log_config {
enable = true
filter = "ERRORS_ONLY"
}
# タイムアウト設定(推奨)
tcp_established_idle_timeout_sec = 1200 # 20分
tcp_transitory_idle_timeout_sec = 30 # 30秒
udp_idle_timeout_sec = 30 # 30秒
icmp_idle_timeout_sec = 30 # 30秒
}
# ------------------ ④ ファイアウォールルール ------------------
resource "google_compute_firewall" "deny_all_ingress" {
name = "deny-all-ingress"
network = google_compute_network.custom_network.name
# すべてのインバウンドトラフィックを拒否(デフォルト)
priority = 1000
deny {
protocol = "all"
}
source_ranges = ["0.0.0.0/0"]
direction = "INGRESS"
}
# IAP経由のSSHアクセスを許可
resource "google_compute_firewall" "allow_iap_ssh" {
name = "allow-iap-ssh"
network = google_compute_network.custom_network.name
allow {
protocol = "tcp"
ports = ["22"]
}
# IAPのIPレンジからのみ許可
source_ranges = ["35.235.240.0/20"]
target_tags = ["allow-iap-ssh"]
priority = 500
# ログ収集を有効化
log_config {
metadata = "INCLUDE_ALL_METADATA"
}
}
# 内部通信の許可(同一VPC内)
resource "google_compute_firewall" "allow_internal" {
name = "allow-internal"
network = google_compute_network.custom_network.name
allow {
protocol = "tcp"
ports = ["0-65535"]
}
allow {
protocol = "udp"
ports = ["0-65535"]
}
allow {
protocol = "icmp"
}
source_ranges = [google_compute_subnetwork.custom_subnet.ip_cidr_range]
priority = 1000
}
# ------------------ ⑤ 組織ポリシー(外部IPアクセス制限) ------------------
resource "google_org_policy_policy" "vm_external_ip_restriction" {
name = "organizations/${var.organization_id}/policies/compute.vmExternalIpAccess"
parent = "organizations/${var.organization_id}"
spec {
rules {
deny_all = "TRUE"
}
}
# 依存関係を明示(組織が作成された後に適用)
depends_on = [google_organization.org]
}
# 特定のプロジェクトで例外を許可する場合
resource "google_org_policy_policy" "project_vm_external_ip_exception" {
count = var.allow_exceptions ? 1 : 0
name = "projects/${var.project_id}/policies/compute.vmExternalIpAccess"
parent = "projects/${var.project_id}"
spec {
inherit_from_parent = false
rules {
values {
allowed_values = var.exception_instance_list
# 例: ["projects/my-project/zones/us-central1-a/instances/bastion-host"]
}
}
# 例外を許可する場合は、明確な理由をコメントで記載
# 例: # Bastion host for emergency access - approved by security team on 2025-01-15
}
}
# ------------------ ⑥ 変数定義 ------------------
variable "region" {
description = "The region for resources"
type = string
default = "us-central1"
}
variable "zone" {
description = "The zone for compute resources"
type = string
default = "us-central1-a"
}
variable "organization_id" {
description = "The organization ID"
type = string
}
variable "project_id" {
description = "The project ID"
type = string
}
variable "allow_exceptions" {
description = "Whether to allow exceptions to the external IP policy"
type = bool
default = false
}
variable "exception_instance_list" {
description = "List of instances allowed to have external IPs"
type = list(string)
default = []
}
gcloud CLIでの修復手順
# 0. IAP設定の確認(パブリックIP削除前に必須)
# IAPファイアウォールルールの作成
gcloud compute firewall-rules create allow-iap-ssh \
--direction=INGRESS \
--action=allow \
--rules=tcp:22 \
--source-ranges=35.235.240.0/20 \
--project=PROJECT_ID
# IAP経由でのSSH接続テスト
gcloud compute ssh INSTANCE_NAME \
--zone=ZONE \
--tunnel-through-iap \
--project=PROJECT_ID
# 1. パブリックIPを持つインスタンスの確認
gcloud compute instances list \
--filter="networkInterfaces[].accessConfigs[].natIP:*" \
--format="table(name,zone,networkInterfaces[].accessConfigs[0].natIP,status)" \
--project=PROJECT_ID
# プロジェクト全体のパブリックIP使用状況をサマリー表示
echo "Total instances with public IPs:"
gcloud compute instances list \
--format="value(networkInterfaces[].accessConfigs[].natIP)" \
--project=PROJECT_ID | grep -v '^$' | wc -l
# 2. 既存インスタンスからパブリックIPを削除
# 注意: インスタンスへの接続が失われる可能性があります
# IAP経由でのアクセスを事前に設定してください
gcloud compute instances delete-access-config INSTANCE_NAME \
--access-config-name="External NAT" \
--zone=ZONE \
--project=PROJECT_ID
# 複数インスタンスを一括で処理するスクリプト(エラーハンドリング付き)
for instance in $(gcloud compute instances list \
--filter="networkInterfaces[].accessConfigs[].natIP:*" \
--format="value(name,zone)" \
--project=PROJECT_ID); do
name=$(echo $instance | cut -d' ' -f1)
zone=$(echo $instance | cut -d' ' -f2)
echo "Removing external IP from $name in $zone"
if gcloud compute instances delete-access-config $name \
--access-config-name="External NAT" \
--zone=$zone \
--project=PROJECT_ID; then
echo "Successfully removed external IP from $name"
else
echo "Failed to remove external IP from $name" >&2
fi
done
# 3. Cloud NATの設定(アウトバウンド通信用)
# ルーターの作成
gcloud compute routers create nat-router \
--network=VPC_NAME \
--region=REGION \
--project=PROJECT_ID
# NATゲートウェイの作成
gcloud compute routers nats create nat-gateway \
--router=nat-router \
--region=REGION \
--nat-all-subnet-ip-ranges \
--auto-allocate-nat-external-ips \
--project=PROJECT_ID
# 4. IAPを使用したSSHアクセス(パブリックIP不要)
gcloud compute ssh INSTANCE_NAME \
--zone=ZONE \
--tunnel-through-iap \
--project=PROJECT_ID
# 5. 組織ポリシーで外部IPを制限
# policy.yamlを作成してから実行
cat > policy.yaml << 'EOF'
constraint: compute.vmExternalIpAccess
spec:
rules:
- denyAll: true
EOF
gcloud resource-manager org-policies set-policy policy.yaml \
--organization=ORGANIZATION_ID
policy.yaml
の内容:
constraint: compute.vmExternalIpAccess
spec:
rules:
- denyAll: true
# 例外を許可する場合の例
# rules:
# - values:
# allowedValues:
# - "projects/PROJECT_ID/zones/ZONE/instances/INSTANCE_NAME"
修復の確認方法
1. パブリックIPの使用状況確認
# プロジェクト内のパブリックIP使用状況を確認
gcloud compute instances list \
--filter="networkInterfaces[].accessConfigs[].natIP:*" \
--format="table(name,zone,status,networkInterfaces[].accessConfigs[0].natIP)" \
--project=PROJECT_ID
# 期待される結果: リストが空または承認された例外のみ
# Cloud Asset Inventoryで組織全体を確認
gcloud asset search-all-resources \
--asset-types='compute.googleapis.com/Instance' \
--query='networkInterfaces.accessConfigs.natIP:*' \
--format="table(name,location)" \
--scope=organizations/ORGANIZATION_ID
# 検証スクリプト(パブリックIPを持つインスタンスを検出)
#!/bin/bash
echo "=== Public IP Audit Report ==="
echo "Generated at: $(date)"
echo ""
# プロジェクト毎の集計
for project in $(gcloud projects list --format="value(projectId)"); do
count=$(gcloud compute instances list --project=$project \
--filter="networkInterfaces[].accessConfigs[].natIP:*" \
--format="value(name)" 2>/dev/null | wc -l)
if [ $count -gt 0 ]; then
echo "Project: $project - Found $count instances with public IPs"
gcloud compute instances list --project=$project \
--filter="networkInterfaces[].accessConfigs[].natIP:*" \
--format="table(name,zone,networkInterfaces[].accessConfigs[0].natIP)"
echo ""
fi
done
2. 組織ポリシーの適用状態確認
# 組織ポリシーの状態を確認
gcloud resource-manager org-policies describe \
compute.vmExternalIpAccess \
--organization=ORGANIZATION_ID
3. Security Command Centerでの監視
Security Command Centerを使用して、パブリックIPを持つインスタンスを継続的に監視します。
# Security Command Centerでの検出ルール確認
gcloud scc findings list ORGANIZATION_ID \
--source="organizations/ORGANIZATION_ID/sources/-" \
--filter="category=\"PUBLIC_IP_ADDRESS\"" \
--format="table(name,resourceName,category,state)"
ベストプラクティス
1. ゼロトラストアーキテクチャの実装
- すべてのVMインスタンスはデフォルトでプライベートIPのみを使用
- パブリックIPはビジネス上必須の場合のみ例外承認
- Bastion Hostパターンの回避とIAPの使用
2. 代替ソリューションの活用
ユースケース | 推奨ソリューション | メリット |
---|---|---|
SSH/RDPアクセス | Identity-Aware Proxy (IAP) | パブリックIP不要、IAM統合 |
Webアプリ公開 | Cloud Load Balancing | DDoS保護、SSL終端 |
アウトバウンド通信 | Cloud NAT | 自動スケーリング、高可用性 |
Google API | Private Google Access | プライベート接続 |
3. セキュリティグループの実装
- 同一セキュリティ要件のVMをグループ化
- ネットワークタグを使用したファイアウォールルール適用
- サブネット単位でのネットワーク分離
4. 監査とモニタリング
- パブリックIP付与イベントのアラート設定
- 月次でのパブリックIP使用状況レビュー
- Cloud Asset Inventoryによる継続的な監視
トラブルシューティング
よくある問題と解決方法
💡 ヒント: パブリックIP削除前に、必ずIAPやCloud NATの設定を完了してください。
- パブリックIP削除後にアクセスできない
- 原因: IAPまたは代替アクセス方法が未設定
- 解決:
# IAP経由でSSHアクセス gcloud compute ssh INSTANCE_NAME \ --zone=ZONE \ --tunnel-through-iap \ --project=PROJECT_ID
- アウトバウンド通信ができない
- 原因: Cloud NATが未設定
- 解決: Cloud NATの設定を実施(上記手順参照)
- 組織ポリシーが適用されない
- 原因: 組織IDまたは権限の問題
- 解決:
# 必要な権限を確認 gcloud organizations get-iam-policy ORGANIZATION_ID \ --filter="bindings.members:user:YOUR_EMAIL" \ --format="table(bindings.role)" # orgpolicy.policyAdminロールが必要 gcloud organizations add-iam-policy-binding ORGANIZATION_ID \ --member="user:YOUR_EMAIL" \ --role="roles/orgpolicy.policyAdmin"
- 既存のアプリケーションが動作しない
- 原因: アプリケーションがパブリックIPを前提としている
- 解決:
- アプリケーションコードでメタデータサービスを使用
- プライベートIPで動作するようアプリケーションを修正
まとめ
この記事では、Compute EngineインスタンスのパブリックIPアドレスに関するセキュリティリスクとその対策方法を解説しました。
重要なポイント:
- パブリックIPの削除: インターネットからの直接攻撃を防止
- 代替ソリューションの活用: IAP、Cloud NAT、Private Google Access
- 組織ポリシーの適用: 組織全体での統一的なセキュリティ強化
- 継続的な監視: Security Command CenterやCloud Asset Inventoryによる定期チェック
パブリックIPアドレスの使用は、セキュリティインシデントの原因となる最も一般的な設定ミスの一つです。ゼロトラストアーキテクチャを採用し、プライベートネットワークを基本とすることで、セキュリティリスクを大幅に削減できます。
この問題の検出は弊社が提供するSecurifyのCSPM機能で簡単に検出及び管理する事が可能です。 運用が非常に楽に出来る製品になっていますので、ぜひ興味がある方はお問い合わせお待ちしております。 最後までお読みいただきありがとうございました。この記事が皆さんの役に立てば幸いです。
参考情報
Google Cloud公式ドキュメント
- VPCネットワークの概要
- Identity-Aware Proxy (IAP) の概要
- Cloud NAT の概要
- 組織のポリシーの制約
- Private Google Access
- セキュリティベストプラクティス
- Compute Engineのセキュリティ強化
セキュリティ基準とフレームワーク
- CWE-200: Exposure of Sensitive Information
- CWE-213: Exposure of Sensitive Information Due to Incompatible Policies
- CWE-359: Exposure of Private Personal Information
- CWE-400: Uncontrolled Resource Consumption
- MITRE ATT&CK T1190 – Exploit Public-Facing Application
- MITRE ATT&CK T1498 – Network Denial of Service
- MITRE ATT&CK T1110 – Brute Force