AWSのネットワーク設計において、セキュリティグループ(SG)と並んで頭を悩ませるのが、サブネット境界の番人である「ネットワークACL(NACL)」だ。
「セキュリティグループで十分にポート制御しているから、NACLはデフォルト(すべて許可)のままでいいや」――そんな油断から、本番環境のインシデント対応で泥沼にハマったエンジニアを、私は何人も見てきた。特に、ステートレスであるNACLの特性と、ルール番号の評価順序を誤解していると、意図しないパケットドロップを引き起こし、デバッグの迷宮へと迷い込むことになる。
今回は、NACLの心臓部である「ルール番号の評価順序と最初のマッチングによる確定」について、パケットの挙動から実務的なトラブルシューティングまで、現場の知見を交えて徹底的に解説しよう。
—
1. NACLの基本思想:ステートレスと「評価順序」の罠
セキュリティグループ(SG)が「ステートフル(通信の状態を記憶する)」であるのに対し、NACLは「ステートレス(通信の状態を一切記憶しない)」なパケットフィルターだ。
そのため、インサイド(Inbound)の通信を許可しても、レスポンス(Outbound)の通信を自動で通してくれない。往復両方のルールを明示的に記述する必要がある。
そして、NACLの挙動を理解する上で最も重要なのが、「ルール番号の若い順(昇順)に上から評価され、最初に条件に一致(マッチ)した時点で評価が即座に確定する」という仕様だ。
最初のマッチングで即確定する(First-Match Wins)
プログラミング言語の switch 文や if-else チェーンを思い出してほしい。条件にヒットした瞬間に処理が抜け、後続の条件は無視される。NACLも全く同じだ。
例えば、以下のようなインバウンドルールが並んでいたとする。
1. ルール 100: 10.0.0.0/16 からの全通信を 拒否 (Deny)
2. ルール 200: 10.0.1.50 からの HTTP (TCP 80) 通信を 許可 (Allow)
この状態では、IPアドレス 10.0.1.50 からのHTTPリクエストは、ルール 100(10.0.0.0/16 全拒否)に先にヒットしてしまうため、ルール 200の「許可」に到達する前に容赦なくドロップされる。
実務において、特定のIPだけ例外的に許可したい場合、必ず「許可ルールをより若い番号(上)」に配置しなければならない。この順序を間違えると、「設定したのに繋がらない」という古典的かつ厄介なパケットロスに直面する。
—
2. NACLのデフォルトルールという「最後の防壁」
AWSのコンソールで新しくNACLを作成すると、必ず最後に自動生成されるルールが存在する。それが、ルール番号 32768(または 100 のみのデフォルトNACLであれば明示されないが内部的に存在する)の「全拒否(Deny All)」ルールだ。
- インバウンドのデフォルト: すべてのトラフィックを拒否
- アウトバウンドのデフォルト: すべてのトラフィックを拒否
もし、自分で作成したカスタムルール(例:100番〜200番)のどれにもパケットがヒットしなかった場合、パケットは最後にこのデフォルトの「全拒否」ルールに到達し、闇に葬り去られる。
これが、「NACLは何も設定しない状態だと、すべての通信をブロックする」と言われる理由である。
—
3. 実践:Web APIサーバーを守るNACLの設計と設定例
では、インターネットから公開されるWeb APIサーバー(パブリックサブネット)と、その背後にあるプライベートサブネットのデータベースを守る構成を想定し、具体的なNACLの設計を見てみよう。
今回は、「特定の管理用IP(203.0.113.50/32)からのみSSHを許可し、それ以外のSSHは徹底的にシャットアウトしつつ、Webトラフィック(HTTP/HTTPS)は全世界から受け付ける」という要件をNACLで実装する。
インバウンドルール(Inbound Rules)の設計
| ルール番号 | プロトコル | ポート範囲 | ソース (CIDR) | アクション | 解説 |
| :— | :— | :— | :— | :— | :— |
| 100 | TCP | 22 | 203.0.113.50/32 | ALLOW | 管理者からのSSHのみ許可 |
| 110 | TCP | 80 | 0.0.0.0/0 | ALLOW | HTTP(Web閲覧・API)の開放 |
| 120 | TCP | 443 | 0.0.0.0/0 | ALLOW | HTTPSの開放 |
| 130 | TCP | 1024-65535 | 0.0.0.0/0 | ALLOW | エフェメラルポート(戻り通信用)の受け入れ |
| 32768 | すべて | すべて | 0.0.0.0/0 | DENY | デフォルトの全拒否(自動適用) |
アウトバウンドルール(Outbound Rules)の設計
NACLはステートレスなため、外部からのリクエストに対する「返り値(エフェメラルポート)」もアウトバウンド側で許可してやる必要がある。ここを忘れると、TCPの3ウェイハンドシェイクですら完了しない。
| ルール番号 | プロトコル | ポート範囲 | デスティネーション | アクション | 解説 |
| :— | :— | :— | :— | :— | :— |
| 100 | TCP | 80 | 0.0.0.0/0 | ALLOW | 外部へのHTTP通信・APIコール許可 |
| 110 | TCP | 443 | 0.0.0.0/0 | ALLOW | 外部へのHTTPS通信許可 |
| 120 | TCP | 1024-65535 | 0.0.0.0/0 | ALLOW | クライアントへ返すエフェメラルポート許可 |
| 32768 | すべて | すべて | 0.0.0.0/0 | DENY | デフォルトの全拒否(自動適用) |
—
4. コード&デバッグ:APIリクエストとネットワーク検証
実際に構築したAPIサーバーに対して、クライアント側(Pythonの requests や curl)からアクセスし、NACLの評価順序や拒否ルールが正しく機能しているかを検証するスクリプトと手順を見ていこう。
接続確認用 Python スクリプト
以下のスクリプトは、対象のAPIエンドポイントに対してHTTPリクエストを送り、タイムアウトやレスポンスコードを検証するものだ。
import sys
import requests
from requests.exceptions import RequestException
# 検証対象のAPIエンドポイント
API_URL = "https://api.example.com/v1/health"
def test_api_connectivity():
print(f"Connecting to {API_URL} ...")
try:
# 接続タイムアウトを3秒に設定し、パケットドロップ時の挙動を素早く検知する
response = requests.get(API_URL, timeout=3.0)
print(f"Status Code: {response.status_code}")
print(f"Response Body: {response.text}")
if response.status_code == 200:
print("SUCCESS: NACL allows the traffic correctly.")
else:
print("WARNING: Reached server, but returned non-200 status.")
except RequestException as e:
print(f"ERROR: Failed to connect. Details: {e}")
print("HINT: Check if NACL inbound/outbound rules are dropping packets or ephemeral ports are blocked.")
sys.exit(1)
if __name__ == "__main__":
test_api_connectivity()
現場で使える!NACLトラブルシューティングの極意
もし上記のスクリプトがタイムアウト(Connection timed out)で落ちた場合、パケットがどこで消えているのかを切り分ける必要がある。セキュリティグループとNACLのどちらが原因かを特定するための鉄板のステップを伝授しよう。
1. VPC Flow Logsを有効化する
CloudWatch LogsやS3にVPCフローログを出力し、該当通信の reject レコードを確認する。
ログの dstport や packets を見れば、NACLで弾かれているのか(パケット数がインバウンド側でカウントされて即座に切れているか)が一目瞭然だ。
2. ルール番号の「隙間」を意識する
AWSのNACLルール番号は、後からルールを追加・挿入しやすいように、通常は 100, 200, 300 といった100刻みで作成するのが業界標準のベストプラクティスだ。
急遽 Deny ルールを挟み込みたいとき、番号を詰めすぎていると既存のルールの間に割り込ませることができず、全削除して作り直すハメになる。必ず余裕を持たせた番号付与を行おう。
3. エフェメラルポートの枯渇・閉塞を見逃すな
NACLでのトラブルの8割は、「アウトバウンドのエフェメラルポート(TCP 1024-65535 または 32768-65535)の許可抜け」に起因する。
「インバウンドで443を開けたからいいや」ではなく、「サーバーから外へ出ていく通信(OSの動的ポート)と、外部から返ってくる通信」の両方をステートレスの原則に則って許可しているか、常に頭の中でパケットの往復を描きながら確認してほしい。
—
まとめ
NACLのルール評価順序と最初のマッチングの仕様は、極めてシンプルである。しかし、そのシンプルさゆえに、ルール番号の若さやステートレスな往復通信の考慮漏れといった「うっかりミス」が、致命的な通信障害を引き起こす。
- ルール番号は若い順(昇順)に評価され、最初にヒットした時点で決定する(First-Match Wins)
- 許可ルールは必ず拒否ルールより上の番号に配置する
- ステートレスであることを忘れず、インバウンド・アウトバウンド両方でエフェメラルポートの許可を忘れない
この3点を現場の共通認識として持っておけば、AWSのネットワークレイヤーで迷子になることはもうないはずだ。日々のインフラ運用・設計の現場で、ぜひ役立ててほしい。
コメント