【実務・中級編】 セキュリティグループとネットワークACLのNATゲートウェイ周辺での適用順序 – クラウド&コンテナネットワーク実践ガイド

はじめに:なぜパケットの「通り道」で頭を抱えるのか

こんにちは。クラウドのインフラを触っていると、一度は必ずこんな壁にぶつかりますよね。

「プライベートサブネットから外部のWeb APIへ fetch や curl でリクエストを投げたい。NATゲートウェイも置いた、ルートテーブルのルーティングも正しい。それなのに、なぜかタイムアウトする……」

夜中にアラートが鳴り響き、VPCフローログを睨みつけながら「セキュリティグループ(SG)だっけ? いや、ネットワークACL(NACL)のルールを書き忘れたか?」と冷や汗をかいた経験を持つシニアエンジニアは私だけではないはずです。

パケットは私たちが思っている以上に、シビアに、そして冷徹にAWSやGCP(ここでは代表してAWSのVPCをベースに語りますが、本質はどのクラウドでも同じです)の境界防衛ラインを通過します。特にプライベートサブネットとNATゲートウェイ、そしてインターネットゲートウェイ(IGW)が絡むエリアでは、「ステートフルなSG」と「ステートレスなNACL」がどのような順序でパケットを評価するのかを完全に理解していないと、頭の中がバグの迷宮と化します。

今回は、数々の修羅場をくぐり抜けてきたネットワークエンジニアの視点から、このパケットの評価順序の真実を徹底的に解き明かします。実務で即座に使えるデバッグ手法やコード例も交えて解説していきますので、ぜひ最後までついてきてください。

—

1. 基礎固め:ステートフル(SG)とステートレス(NACL)の決定的な違い

パケットの挙動を追う前に、まずはこの2つの防衛システムの「性格」を完全に把握しておきましょう。ここを混同していると、トラブルシューティングの方向が180度間違ってしまいます。

セキュリティグループ(SG):空気を読む「ステートフル」な番人

SGは、インスタンスやENI(Elastic Network Interface)のレベルでアタッチされる仮想ファイアウォールです。

  • 特徴: ステートフル(Stateful)。
  • 挙動: インバウンド(受信)で許可された通信に対するレスポンス(アウトバウンド)は、宛先ポートやIPアドレスに関わらず自動的に許可されます。逆に、アウトバウンド(送信)で許可した通信に対する戻りのインバウンドも自動的に許可されます。
  • 実務的Tips: 「外に出ていく通信のルールさえ書けば、戻りは勝手に通る」と覚えておけば間違いありません。ただし、エフェメラルポート(一時ポート)の概念を意識し忘れると痛い目をみます。

ネットワークACL(NACL):マニュアルを厳格に守る「ステートレス」な番人

NACLは、サブネットの境界に位置するステートレスなファイアウォールです。VPCの「外周警備」を担当します。

  • 特徴: ステートレス(Stateless)。
  • 挙動: インバウンドとアウトバウンドのルールが完全に独立しています。つまり、外に出ていくパケットをアウトバウンドルールで許可しても、帰ってきたパケットをインバウンドルールで明示的に許可しなければ、冷酷にドロップされます。
  • 実務的Tips: ここで最もハマるのが「エフェメラルポート(高位ポート)」の開放漏れです。クライアントが外部と通信する際、OSはランダムな高位ポート(通常 1024 から 65535)を送信元ポートとして使います。ここを塞いでいると、往路は通っても復路が一切返ってきません。

—

2. パケットの旅:プライベートサブネットからNATゲートウェイを経由する全シーケンス

それでは、プライベートサブネットにあるアプリケーションサーバー(例えばPythonやNode.jsで書かれたWeb APIクライアント)が、外部の決済API等へリクエストを投げ、レスポンスを受け取るまでの正確なパケットの往復ルートを追ってみましょう。

往路(Outbound):アプリから外の世界へ

1. プライベートサブネットのインスタンス(送信元)

  • アプリが外部APIへHTTPリクエストを送信。
  • パケットがインスタンスにアタッチされた インスタンスのSG(アウトバウンド) を通過。ここで通信が許可されているかチェックされます。

2. プライベートサブネットの境界

  • パケットがサブネットを出る際、プライベートサブネットのNACL(アウトバウンド)を通過。

3. NATゲートウェイの存在(パケット変態ポイント)

  • パケットの宛先IPが外部のものになっていますが、ルートテーブルに従ってNATゲートウェイへルーティングされます。
  • NATゲートウェイは、プライベートIPを自身のElastic IP(EIP)にSNAT(Source Network Address Translation)します。この時、送信元ポートも書き換わります。

4. パブリックサブネットとIGW

  • NATゲートウェイを通過したパケットはインターネットゲートウェイ(IGW)を通り、ワールドワイドなインターネットへ飛び出します。

復路(Inbound):外の世界からアプリへ

1. IGWからNATゲートウェイへ

  • 外部APIからのレスポンスパケットがIGWに到着し、NATゲートウェイでDNAT(Destination NAT)され、元のプライベートIPアドレスに復元されます。

2. プライベートサブネットへの帰還

  • パケットがプライベートサブネットに戻ってきます。ここで最初に直面するのが プライベートサブネットのNACL(インバウンド) です。
  • *重要:* NACLはステートレスなので、外部からの戻りパケットを受け入れるための「インバウンドルール(例: ポート 1024-65535 の許可)」が明示的に書かれていなければなりません。

3. インスタンスへの到着

  • 最後に インスタンスのSG(インバウンド) を通過します。しかし、これはステートフルなSGなので、往路で外へ通信を叩いていれば、戻りのパケットはSGのインバウンド設定を気にせず自動的に通過します。

—

3. 適用順序のまとめ:頭に叩き込むべきマトリクス

トラブルシューティングの際に思い出すべき、通信の方向ごとの評価順序は以下の通りです。

| 通信の向き | 最初にヒットするもの | 最後にヒットするもの | 備考 |
| :— | :— | :— | :— |
| 往路(送信) | 1. インスタンスSG (Outbound)
2. サブネットNACL (Outbound) | NATゲートウェイ経由でIGWへ | SGはステートフルなので戻りを覚えている。 |
| 復路(受信) | 1. サブネットNACL (Inbound)
2. インスタンスSG (Inbound) | アプリケーションへ到達 | NACLのインバウンド評価が最難関。ここでエフェメラルポートを開けないと死ぬ。 |

—

4. 実務で即効性のある設定例とコード

理論が分かったところで、現場でそのまま使える具体的な設定とコードを見てみましょう。ここでは、Python(requests または urllib)を用いた外部API呼び出しを想定します。

PythonによるAPIリクエストコード例

プライベートサブネット内のECSタスクやEC2上で稼働するアプリケーションのイメージです。

import logging
import requests
from requests.exceptions import RequestException

# ログの設定
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

def call_external_payment_api(payload: dict) -> dict:
    """
    プライベートサブネットからNATゲートウェイ経由で外部決済APIを叩く関数。
    タイムアウトと例外処理を厳格に実装し、インフラ側のトラブルを早期検知できるようにする。
    """
    api_url = "https://api.example-payment.com/v1/charge"
    
    # 接続・読み込みタイムアウトを必ず設定する(デフォルトなしは厳禁)
    timeout_seconds = (3.0, 10.0)  # (接続タイムアウト, 読み込みタイムアウト)

    try:
        logger.info("外部APIへのリクエストを開始します...")
        response = requests.post(api_url, json=payload, timeout=timeout_seconds)
        
        # HTTPステータスコードが4xx/5xxの場合に例外を発生させる
        response.raise_for_status()
        
        logger.info(f"APIリクエスト成功: Status {response.status_code}")
        return response.json()

    except requests.exceptions.Timeout as te:
        logger.error(f"タイムアウトエラーが発生しました。NACLやNATGWのルーティングを確認してください: {te}")
        raise
    except requests.exceptions.RequestException as re:
        logger.error(f"通信エラーが発生しました: {re}")
        raise

if __name__ == "__main__":
    test_payload = {"amount": 1000, "currency": "JPY"}
    # call_external_payment_api(test_payload)

NACL(ネットワークACL)の推奨設定例(Terraform風)

プライベートサブネットに適用するNACLの定義です。エフェメラルポートの許可が命綱になります。

# プライベートサブネット用 NACL
resource "aws_network_acl" "private_subnet_nacl" {
  vpc_id     = aws_vpc.main.id
  subnet_ids = [aws_subnet.private.id]

  # --- インバウンドルール (Inbound) ---
  
  # 1. 外部からの戻りパケット(エフェメラルポート)を許可する(最重要!)
  ingress {
    protocol   = "tcp"
    rule_no    = 100
    action     = "allow"
    cidr_block = "0.0.0.0/0"
    from_port  = 1024
    to_port    = 65535
  }

  # 2. 内部からのVPC内通信を許可する場合(必要に応じて)
  ingress {
    protocol   = "tcp"
    rule_no    = 200
    action     = "allow"
    cidr_block = "10.0.0.0/16"
    from_port  = 443
    to_port    = 443
  }

  # --- アウトバウンドルール (Outbound) ---

  # 1. 外部へのすべてのTCP通信(HTTP/HTTPSなど)を許可
  egress {
    protocol   = "tcp"
    rule_no    = 100
    action     = "allow"
    cidr_block = "0.0.0.0/0"
    from_port  = 1
    to_port    = 65535
  }

  tags = {
    Name = "prod-private-subnet-nacl"
  }
}

—

5. 現場で使える!デバッグとトラブルシューティングの極意

もし本番環境で「外部APIに繋がらない」という障害が発生したら、以下の手順で切り分けを行ってください。勘や推測で設定をいじるのは時間の無駄です。

ステップ1: curl での疎通確認(詳細モード)

まずは対象のインスタンスにSSHまたはAWS Systems Manager (SSM) Session Managerでログインし、-v(verbose)オプションをつけてリクエストを投げます。

# TLSハンドシェイクのどこで止まっているかを確認する
curl -Iv https://api.example-payment.com/v1/charge
  • もし Connection timed out で即座に落ちる場合: ルートテーブル(NATGWへのルーティング)の欠落、またはセキュリティグループのアウトバウンド遮断が疑われます。
  • もし数秒フリーズした後に Connection timed out になる場合: NACLのインバウンド(エフェメラルポートの開放漏れ)か、宛先側のファイアウォール(セキュリティグループ等)によるドロップが強く疑われます。

ステップ2: VPCフローログ(VPC Flow Logs)の解析

パケットがどこで捨てられているかを確実につかむには、CloudWatch LogsやS3に出力したVPCフローログの reject アクションを検索します。

# VPCフローログの一般的な出力例(一部抜粋)
2 123456789012 eni-0abcd1234efgh5678 10.0.1.50 198.51.100.25 54321 443 6 1 60 1629384000 1629384060 REJECT OK
  • 送信元IP (10.0.1.50: プライベートサブネットのインスタンス) から 宛先IP (198.51.100.25: 外部API) に対するパケットが REJECT されている場合、どのセキュリティレイヤーが弾いたのかを特定します。
  • 注意点として、VPCフローログにはNACLによってドロップされたパケットも記録されますが、SGによるドロップなのかNACLによるものなのかは、ログのメタデータだけでは完全に判別できないケースがあります。そのため、「往路はSGとNACLのアウトバウンド」「復路はNACLのインバウンド」のチェックリストを順番につぶしていくのが近道です。

—

おわりに:セキュリティの二重構造を味方につける

クラウドインフラの設計において、セキュリティグループとネットワークACLは「面倒な二重の手間」のように感じられることがあります。「SGだけでいいじゃないか」という声もよく耳にします。

しかし、この「ステートフルな内部防衛(SG)」と「ステートレスな外周警備(NACL)」の組み合わせこそが、万が一ひとつのレイヤーで設定ミスや脆弱性が露呈した際にも、システム全体を致命的な侵入から守り抜くための最強の多層防御(ディフェンス・イン・ディープ)です。

NATゲートウェイ周辺のパケットの流れる方向、そしてステートフル・ステートレスの違いを頭の中にクリアなネットワーク図として描き出せるようになれば、どんな複雑なネットワークトラブルに直面しても、必ず冷静に原因を突き止められるはずです。

あなたの明日のインフラ運用が、スムーズで安定したものになることを祈っています。それでは、また別の現場でお会いしましょう!

コメント

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