【実務・中級編】 VPC設計におけるゼロトラストネットワークの境界定義とサブネット分離 – インフラ・ネットワーク & クラウドセキュリティ防御ガイド

お疲れ。インフラの構築やWebアプリの実装で日々泥臭くコードと向き合っている君たちなら、教科書通りの「VPCはパブリックとプライベートに分けましょう」という説明が、いかに現場の実戦においてフワッとした気休めに過ぎないか、痛感している頃だろう。

数々のインシデント現場を踏んできた私から言わせてもらえば、甘いサブネット設計や「動けばいいや」で作られたNATの穴は、攻撃者にとって格好の踏み台(ホップポイント)でしかない。今日は、クラウドの境界防衛におけるリアルな脅威と、それを完全に封じ込めるための要塞化手法を徹底的に叩き込む。

—

1. 境界防衛の幻想と、攻撃者が狙う「最初の一歩」

多くの開発現場では、「プライベートサブネットに置いたサーバーは、インターネットから直接アクセスできないから安全だ」という神話がまかり通っている。しかし、それは大きな誤りだ。

攻撃者は、パブリックサブネットに配置された不十分な設定のロードバランサーや、古いミドルウェアを抱えた踏み台サーバー(コンテナ)の脆弱性を突いて侵入する。そこでRCE(リモートコード実行)を達成した瞬間、彼らはそのプライベート空間へと横展開(ラテラルムーブメント)を開始する。

攻撃シナリオ:SSRFと踏み台からの内部スキャン

例えば、パブリック側に公開されたWebアプリにSSRF(サーバー側リクエスト偽造)の脆弱性があったとする。攻撃者は以下のようなリクエストを送り、プライベートサブネットに隠れているはずのメタデータサービスや、内部管理用のデータベース(10.0.0.0/16 などのプライベートIP)へアクセスを試みる。

// 攻撃者がアプリの脆弱性を悪用して内部APIを叩くPoC(イメージ)
const maliciousPayload = {
  // クラウドのメタデータサービスや内部の管理画面をターゲットにする
  targetUrl: "http://169.254.169.254/latest/meta-data/iam/security-credentials/"
};

fetch('/api/proxy', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(maliciousPayload)
})
.then(res => res.text())
.then(data => console.log("盗み出したクレデンシャル:", data));

もし、このWebアプリが属するサブネットから、必要のない内部リソースへ自由に通信できるネットワークACLやセキュリティグループの設計になっていれば、ゲームオーバーだ。クラウドのIAMロールの権限まで奪われ、インフラ全体が乗っ取られる。

—

2. ゼロトラストに基づくVPCアーキテクチャの鉄則

このリスクを根絶するためには、ネットワークの「境界」を信頼するのではなく、「すべての通信を検証し、デフォルトで拒否する(Default Deny)」 ゼロトラストの原則をVPC設計に持ち込む必要がある。

実務で必ず守るべき3つの防衛線は以下の通りだ:

1. パブリックサブネットの最小化: インターネットからのトラフィックを受け入れるのは、ロードバランサー(ALB/NLB)と、厳重に管理された踏み台(あるいはAWS Systems Manager Session Managerを利用して踏み台自体を廃止する)のみに絞る。
2. プライベートサブネットの完全孤立: アプリケーションサーバーやデータベースが存在するプライベートサブネットは、インバウンドのルーティングを完全に遮断する。
3. アウトバウンド通信の厳格な制御(Egress Filtering): パッチ適用や外部API連携のために必要なアウトバウンド通信も、NATゲートウェイの背後で宛先IPやポート、プロキシを経由して厳しく制限する。

—

3. 【実務設定】Terraformによる堅牢なVPC・サブネット分離コード

口で言うだけなら誰でもできる。ここでは、パブリック・プライベートを厳格に分離し、不要な通信をシャットアウトするTerraformの設定サンプルを示す。インフラのコード化(IaC)において、このレベルの分離は最低限のラインだ。

# ==========================================
# 堅牢なVPCおよびサブネット分離のTerraform設定
# ==========================================

# 1. VPCの定義
resource "aws_vpc" "secure_vpc" {
  cidr_block           = "10.100.0.0/16"
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = {
    Name        = "secure-production-vpc"
    Environment = "production"
  }
}

# 2. パブリックサブネット(ALB用:インターネットからの入り口)
resource "aws_subnet" "public_az1" {
  vpc_id            = aws_vpc.secure_vpc.id
  cidr_block        = "10.100.1.0/24"
  availability_zone = "ap-northeast-1a"

  map_public_ip_on_launch = false # 自動パブリックIP付与は原則オフ
  tags = {
    Name = "subnet-public-ap-northeast-1a"
  }
}

# 3. プライベートサブネット(アプリ・DB用:インターネットから完全隔離)
resource "aws_subnet" "private_az1" {
  vpc_id            = aws_vpc.secure_vpc.id
  cidr_block        = "10.100.10.0/24"
  availability_zone = "ap-northeast-1a"

  tags = {
    Name = "subnet-private-ap-northeast-1a"
  }
}

# 4. パブリック用ルートテーブル(インターネットゲートウェイへ接続)
resource "aws_route_table" "public" {
  vpc_id = aws_vpc.secure_vpc.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.igw.id
  }

  tags = { Name = "rt-public" }
}

# 5. プライベート用ルートテーブル(インターネットへの直接ルートは持たせない)
# ※外部API通信が必要な場合は、ここにNATゲートウェイへのルートを追加するが、
#   不要な環境であればルート自体を定義しない(完全閉域)。
resource "aws_route_table" "private" {
  vpc_id = aws_vpc.secure_vpc.id

  tags = { Name = "rt-private-isolated" }
}

# サブネットとルートテーブルの関連付け
resource "aws_route_table_association" "public_assoc" {
  subnet_id      = aws_subnet.public_az1.id
  route_table_id = aws_route_table.public.id
}

resource "aws_route_table_association" "private_assoc" {
  subnet_id      = aws_subnet.private_az1.id
  route_table_id = aws_route_table.private.id
}

# インターネットゲートウェイ
resource "aws_internet_gateway" "igw" {
  vpc_id = aws_vpc.secure_vpc.id
  tags   = { Name = "secure-igw" }
}

—

4. アプリケーション層での多層防御(PHPによるアウトバウンド・SSRF対策)

ネットワーク層でサブネットを分離しただけでは不十分だ。万が一、アプリケーション層に脆弱性が混入した場合を想定し、アプリ側からも外部への不正なリクエストをブロックする実装(多層防御)を組み込んでおく必要がある。

以下は、PHPで外部APIを叩く際、SSRFを防ぐためにURLのスキームやプライベートIPへのリクエストを厳格にバリデーションする実装サンプルだ。

<?php
/**
 * セキュアなHTTPリクエスト実行関数(SSRF・内部スキャン防止)
 * 
 * @param string $url 接続先のURL
 * @return stringレスポンスボディ
 * @throws Exception 不正なリクエストと判断した場合
 */
function secureHttpCall(string $url): string {
    // 1. URLの構文解析
    $parsedUrl = parse_url($url);
    if ($parsedUrl === false || !isset($parsedUrl['host'], $parsedUrl['scheme'])) {
        throw new \InvalidArgumentException("無効なURL形式です。");
    }

    // 2. スキームのホワイトリスト検証(http / https のみ許可)
    if (!in_array(strtolower($parsedUrl['scheme']), ['http', 'https'], true)) {
        throw new \SecurityException("許可されていないプロトコルです: " . $parsedUrl['scheme']);
    }

    $host = $parsedUrl['host'];
    
    // 3. ホスト名からIPアドレスを解決し、プライベートIPやローカルループバックへのアクセスを阻止
    $ip = gethostbyname($host);
    if ($ip === $host && !filter_var($host, FILTER_VALIDATE_IP)) {
        throw new \SecurityException("ホスト名の解決に失敗しました。");
    }

    // プライベートIPアドレス(RFC 1918等)およびローカルメタデータサービスのIPかチェック
    if (
        filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false ||
        $ip === '169.254.169.254' // クラウドのメタデータIP
    ) {
        // ログに不正アクセスの兆候として記録
        error_log("【SECURITY ALERT】内部IP・メタデータへの不正なリクエスト試行をブロック: " . $ip);
        throw new \SecurityException("セキュリティポリシーにより、この宛先への通信は禁止されています。");
    }

    // 4. cURLを使った安全なリクエスト実行(タイムアウトやリダイレクト制限を設定)
    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_TIMEOUT, 5);          // タイムアウトを5秒に制限
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false); // リダイレクト追跡を禁止(SSRFバイパス対策)
    
    $response = curl_exec($ch);
    
    if (curl_errno($ch)) {
        $error = curl_error($ch);
        curl_close($ch);
        throw new \RuntimeException("通信エラー: " . $error);
    }
    
    curl_close($ch);
    return $response;
}

このようなガードをアプリケーションコードの根底に仕込んでおけば、万が一ネットワーク境界が破られたとしても、攻撃者はそこから先の重要データや内部APIへ到達することができなくなる。

—

チーフエンジニアからの総括

セキュリティは「点」ではなく「面」で守るものだ。「サブネットを分けたから大丈夫」「WAFを入れたから安心」という油断こそが、一番の脆弱性である。

今回解説したVPCの厳格な分離設計と、アプリケーション層での多層防御を組み合わせることで初めて、本番環境としての最低限の要塞化が完了する。自分の担当しているシステムのネットワーク図とコードを、今一度この視点で見直してほしい。不審な通信の匂いがする箇所があれば、すぐに手を入れよう。それがプロのエンジニアの仕事だ。

コメント

タイトルとURLをコピーしました