【実務・中級編】 ネットワークACL(NACL)のステートレス挙動とエフェメラルポートの双方向許可要件 – クラウドインフラと仮想化ネットワーク実践ガイド

はじめに:なぜ、そのWeb API通信は「無言で」タイムアウトするのか

クラウドインフラの現場に身を置いていると、「セキュリティグループ(SG)は完璧に開けたのに、なぜかパブリックサブネット上のEC2から外部のWeb APIへリクエストが届かない」「あるいは、レスポンスが一切返ってこずにしびれを切らしたクライアント側でタイムアウトする」という、古くて新しい怪奇現象に遭遇することがあります。

AWSのコンソールを開き、セキュリティグループを何度見直しても、アウトバウンドは 0.0.0.0/0 で全開放されている。VPCフローログを見ても、パケットが送信されている形跡はあるのに、返り値が全く返ってこない。

原因の多くは、セキュリティグループではなく、その一段外側にそびえ立つ黒幕――ネットワークACL(NACL)の「ステートレス」な挙動、そしてエフェメラルポート(Ephemeral Port)の双方向許可の設計漏れにあります。

今回は、パケットがAWSの仮想ネットワークをどのように駆け巡り、なぜNACLの前で悲鳴を上げるのか。数々の修羅場を潜り抜けてきたシニアSREの視点から、そのリアルな挙動とトラブルシューティングの極意を紐解いていきましょう。

—

セキュリティグループとNACLの決定的な違い:「ステートフル」と「ステートレス」

AWSのネットワークレイヤーにおけるセキュリティの二大巨頭といえば、インスタンスレベルの「セキュリティグループ(SG)」と、サブネットレベルの「ネットワークACL(NACL)」です。

多くのエンジニアが混乱するポイントは、この二者が全く異なるパケットフィルタリングの思想で動いている点にあります。

セキュリティグループ(SG)は「ステートフル(Stateful)」

SGは、通信の「文脈」を記憶しています。例えば、インスタンスから外に向かってHTTPS(ポート443)の通信を開始した場合、SGは「あ、今こいつは外と通信を始めたな」と記憶(ステートの保持)します。そのため、外部のサーバーから帰ってきたレスポンスパケットは、インバウンドルールで明示的にポート443やエフェメラルポートを許可していなくても、自動的に通過させます。非常にスマートで、現代のクラウドネイティブな感覚にマッチした挙動です。

ネットワークACL(NACL)は「ステートレス(Stateless)」

一方、NACLは一切の文脈を記憶しません。文字通り「冷徹な門番」です。
インバウンド(入ってくる通信)はインバウンドのルールだけで判定し、アウトバウンド(出ていく通信)はアウトバウンドのルールだけで判定します。往復の通信において、行きと帰りは完全に別個の独立したパケットとして扱われます。

これが何を意味するか。
あなたがクライアントとして外部のWeb APIサーバーへリクエストを投げるとき、パケットの流れは以下のようになります。

1. リクエスト(往き): クライアント(エフェメラルポート) $\rightarrow$ 外部APIサーバー(ポート443)
2. レスポンス(帰り): 外部APIサーバー(ポート443) $\rightarrow$ クライアント(エフェメラルポート)

NACLを使う場合、この「帰り」のパケットを迎え入れるために、アウトバウンドだけでなく、インバウンド側でも宛先ポートとしてのエフェメラルポートを明示的に許可しなければならないのです。これが、NACLのステートレス挙動に起因する最大の罠です。

—

エフェメラルポートの正体とRFCの仕様

ここで、通信の宛先・送信元として頻繁に登場する「エフェメラルポート(一時ポート)」についておさらいしておきましょう。

クライアント側(OS)が外向きのTCP/UDPコネクションを確立する際、オペレーティングシステムは空いているポートを動的に割り当てます。これがエフェメラルポートです。

IANA(Internet Assigned Numbers Authority)およびRFC 6335等の標準仕様において、エフェメラルポートの範囲はOSによって異なりますが、一般的なモダンOS(Linux kernel 2.4以降、Windows、macOSなど)では、主に以下の範囲が使われます。

  • IANA推奨範囲: 49152 ~ 65535
  • Linuxのデフォルト範囲 (net.ipv4.ip_local_port_range): 32768 ~ 65535
  • AWSのVPCおよび一般的なサービス(Amazon Linux、NATゲートウェイ等): 1024 ~ 65535

AWSのNACLやセキュリティ製品でエフェメラルポートの範囲を指定する場合、安全策として 1024 から 65535 の全域をカバーするようにルールを組むのがインフラ運用のデファクトスタンダードとなっています。

—

発生しがちな障害シナリオ:APIクライアントからの「沈黙」

ある日のこと、開発チームからこんな緊急連絡が入りました。

> 「パブリックサブネットに配置したバッチ用EC2から、外部のSaaS型Web APIに対して curl でリクエストを投げているのですが、数分間固まった挙句に Connection timed out で失敗します。セキュリティグループのインバウンド・アウトバウンドはすべて 0.0.0.0/0 で許可しています!」

この現場の構成を確認すると、セキュリティグループは確かに全開放されているものの、そのサブネットには厳格なカスタムNACLがアタッチされていました。

当時のNACL設定(問題のあった状態)

| ルール番号 | 方向 | プロトコル | ポート範囲 | 許可/拒否 | 送信元 / 宛先 |
| :— | :— | :— | :— | :— | :— |
| 100 | インバウンド | TCP | 80, 443 | 許可 | 0.0.0.0/0 |
| 100 | アウトバウンド | TCP | 443 | 許可 | 0.0.0.0/0 |
| * | インバウンド | すべて | すべて | 拒否 | 0.0.0.0/0 |
| * | アウトバウンド | すべて | すべて | 拒否 | 0.0.0.0/0 |

一見すると、Web通信に必要なポート443が往きも帰りも開いているように見えます。しかし、ここに大きな勘違いがあります。

アウトバウンドのルール(ルール番号100)で TCP 443 を許可しているため、EC2から外部APIへのリクエスト(宛先ポート443)は外へ出て行きます。
しかし、外部APIサーバーからのレスポンスパケットがAWSのサブネットに戻ってきたとき、NACLはそれを「インバウンドパケット」として評価します。

インバウンドのルール(ルール番号100)は TCP 80, 443 のみを許可しています。
外部APIサーバーから返ってくるレスポンスパケットの宛先ポートは、EC2側が動的に割り当てたエフェメラルポート(例: 55123など)です。このポートは80でも443でもありません。

結果として、NACLのインバウンドルールに合致せず、帰りのパケットは冷酷にドロップ(破棄)されます。これが「沈黙のタイムアウト」の正体です。

—

解決策:NACLにおけるエフェメラルポートの正しい設定手順

この問題を解決するには、NACLのアウトバウンドだけでなく、インバウンド側にもエフェメラルポートの許可ルールを明記する必要があります。

正しいNACLの設定例

インバウンドルール(Inbound Rules)

| ルール番号 | プロトコル | ポート範囲 | 許可/拒否 | 送信元 | 説明 |
| :— | :— | :— | :— | :— | :— |
| 100 | TCP | 443 | 許可 | 0.0.0.0/0 | 外部からのインバウンド通信用(必要に応じて) |
| 110 | TCP | 1024-65535 | 許可 | 0.0.0.0/0 | 外部API等からのレスポンス(エフェメラルポート)を受信するため |
| * | すべて | すべて | 拒否 | 0.0.0.0/0 | デフォルト拒否 |

アウトバウンドルール(Outbound Rules)

| ルール番号 | プロトコル | ポート範囲 | 許可/拒否 | 宛先 | 説明 |
| :— | :— | :— | :— | :— | :— |
| 100 | TCP | 443 | 許可 | 0.0.0.0/0 | 外部のHTTPSサーバーへのリクエスト用 |
| 110 | TCP | 1024-65535 | 許可 | 0.0.0.0/0 | 動的ポートへの送信(念のため全域許可する場合) |
| * | すべて | すべて | 拒否 | 0.0.0.0/0 | デフォルト拒否 |

※なお、AWSのマネージドNATゲートウェイなどを経由する場合、NATゲートウェイ側のNACLやサブネット設計でも同様の双方向ポート考慮が必要になるケースがあります。

—

実践:アプリケーションコードとデバッグ手法

ここでは、実際にエフェメラルポートの通信を伴うスクリプトの例と、現場で使えるトラブルシューティングのコマンドを紹介します。

1. Python (Requests / urllib) による外部API呼び出し

Pythonの requests ライブラリなどを用いて外部APIを叩く際も、内部ではOSがエフェメラルポートをバインドしてTCPコネクションを張っています。

import requests
import sys

def call_external_api():
    # 外部のパブリックなWeb APIエンドポイント
    api_url = "https://api.ipify.org?format=json"
    
    try:
        print(f"Connecting to {api_url} ...")
        # タイムアウトを5秒に設定(NACLでドロップしている場合はここで確実にタイムアウトする)
        response = requests.get(api_url, timeout=5)
        
        # ステータスコードのチェック
        response.raise_for_status()
        
        print("Successfully connected!")
        print("Response data:", response.json())
        
    except requests.exceptions.Timeout:
        print("[ERROR] Connection timed out. Please check NACL ephemeral port settings (1024-65535).", file=sys.stderr)
    except requests.exceptions.RequestException as e:
        print(f"[ERROR] An error occurred: {e}", file=sys.stderr)

if __name__ == "__main__":
    call_external_api()

2. トラブルシューティング:現場でのデバッグコマンド

もし本番環境や検証環境で同様の通信障害に遭遇した場合、以下の手順で切り分けを行います。

① curl での疎通確認と詳細なトレース

-v (verbose) オプションをつけて実行し、どこでコネクションがハングアップしているかを確認します。

# タイムアウトの挙動を詳細に確認する
curl -v https://api.ipify.org

*出力例(NACLで止められている場合):*

*   Trying 192.0.2.1:443...
* Connected to api.ipify.org (192.0.2.1) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
*  CAfile: /etc/ssl/certs/ca-bundle.crt
*  CApath: none
* OpenSSL/1.0.2k-fips...
* SSL connection handshake completed
* Connected to api.ipify.org (192.0.2.1) port 443
> GET /?format=json HTTP/1.1
> Host: api.ipify.org
> User-Agent: curl/7.61.1
> Accept: */*
> 
*(ここで数分間完全にフリーズし、最終的にタイムアウトする)*

SSLハンドシェイクが完了してリクエストを送信した直後に止まる場合、サーバーからのレスポンスパケットがNACLのインバウンドルールに阻まれて自ホストに届いていない可能性が極めて高いです。

② tcpdump によるパケットキャプチャ

EC2のネットワークインターフェースに直接ログインし、パケットが帰ってきているかを低レイヤーで確認します。

# 443ポートに関連するパケットをリアルタイムでキャプチャ
sudo tcpdump -nnvv -i eth0 'tcp port 443'

この状態で別画面から curl を実行したとき、Flags [S](SYN)や [S.](SYN-ACK)が流れるにもかかわらず、レスポンスのパケット([P.] や [.])が一切受信できていない(あるいはリトライパケットばかりが送出されている)場合、NACLやセキュリティグループ、あるいはルートテーブルのルーティング不備を疑います。

—

まとめ:インフラエンジニアとしての心構え

クラウドの抽象化が進んだ現在でも、ネットワークの基礎原則――特に「ステートレスなフィルタリングにおける双方向のポート制御」というプリミティブな仕様は変わりません。

「セキュリティグループが通っているから大丈夫」という思い込みを捨て、NACLを触る際には必ず以下のチェックリストを頭に思い浮かべるようにしましょう。

1. 往きだけでなく、帰りのパケットの宛先ポートを意識しているか?
2. エフェメラルポート(1024-65535)のインバウンド・アウトバウンドルールは正しく記述されているか?
3. トラブルシューティングの際は、アプリケーション層のログだけでなく、tcpdump やVPCフローログを活用して「パケットがどこで消えているか」を物理(論理)レイヤーで追えているか?

この泥臭いパケットの挙動をイメージできるかどうかが、一人前のクラウドアーキテクトと、単なるコンソールポチポチくんを分かつ分水嶺です。日々のインフラ運用に、ぜひこの知見を役立ててください。

コメント

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