ネットワークACL(NACL)の罠:ルール番号の昇順評価と「処理の短絡」が生む実務の死角
こんにちは。数々の修羅場をくぐり抜けてきたシニアSREの私です。
本番環境のリリース前夜、あるいは障害対応の真っ只中、「あれ、セキュリティグループ(SG)はちゃんと許可しているのに、なぜかパケットがドロップする……?」と冷や汗をかいた経験はありませんか?
その犯人は、大抵の場合 Amazon VPCのネットワークACL(NACL) です。
セキュリティグループが「インスタンス単位のステートフルな門番」であるのに対し、NACLは「サブネット境界をまたぐステートレスな関所」です。このNACL、挙動を勘違いしていると、思わぬところでパケットを闇に葬り去ってくれます。今回は、NACLの肝である 「ルール番号の昇順評価」と「処理の短絡(Short-circuiting)」 について、実際のパケットの挙動や現場のトラブルシューティングを交えて徹底的に解説します。
—
1. RFCとAWS仕様の根本:ステートレスかつ「評価順序が絶対」の世界
インターネットの基本原則を定めたRFC(例えばIPv4の基本であるRFC 791など)の上位レイヤーにおいて、パケットフィルタリングのアルゴリズムは実装依存とされています。しかし、AWSのVPCインフラストラクチャにおいて、NACLは厳格なルールに基づいて動作します。
NACLの最も重要な特性は以下の2点です。
1. ステートレス(Statefulではない):
セキュリティグループとは異なり、往復の通信を自動で追跡しません。インバウンド(受信)で許可した通信であっても、アウトバウンド(送信)側で明示的に許可していなければ、戻りのパケットは容赦なく捨てられます。
2. ルール番号(Rule Number)の昇順評価:
NACLには上から順に適用されるルール番号(1 から 32766 までの整数)が存在します。AWSはこのルールを番号が小さいものから順に評価します。
「処理の短絡(Short-circuiting)」とは何か?
ここが今日の最重要ポイントです。
NACLは、パケットがルールリストに合致した瞬間、後続のルールの評価を即座に打ち切ります。これをコンピュータサイエンスの用語で「短絡評価(Short-circuiting)」と呼びます。
> 「最初にマッチしたルールの判定結果(Allow / Deny)が、その瞬間に絶対的な運命として確定する」
例えその下流に「いや、さっきのは例外的に許可(あるいは拒否)なんだ!」という矛盾した、あるいはより詳細なルールが控えていたとしても、上のルールにヒットした時点でゲームセットです。この挙動を理解していないと、意図せず特定のエンドポイントへのアクセスを遮断したり、逆にセキュリティホールを空けたりすることになります。
—
2. 通信フローと評価のメカニズム
パケットがVPCのサブネット境界に到達した際、NACLの内部でどのようなドラマが繰り広げられているのか、シーケンスのイメージを紐解きます。
[外部クライアント / 別サブネット]
│
▼ (パケット到着)
┌─────────────────────────────────────────┐
│ Subnet NACL インバウンド評価 │
│ │
│ Rule 100: Deny 192.0.2.50 ──────────┼──> [一致しない] ──┐
│ Rule 200: Allow 192.0.2.0/24 ────────┼──> [一致!] ──┼──> 【評価即時終了(短絡)】
│ Rule 300: Deny All (Default) │ │ (通信許可確定)
└─────────────────────────────────────────┘ ▼
[EC2インスタンスへ到達]
もしここで、ルール番号の設計を誤って以下のように配置していたらどうなるでしょうか?
Rule 100:Allow(送信元0.0.0.0/0)Rule 200:Deny(特定の悪質IP192.0.2.50)
悲劇なのは、悪質IP 192.0.2.50 からのパケットが到着した瞬間です。Rule 100 の 0.0.0.0/0 に見事にヒットするため、後続の Rule 200(拒否ルール)に到達する前にパケットが許可されてしまいます。
「拒否ルールは上に書く」――これがNACL運用の鉄則中の鉄則です。
—
3. 実務で直面する設計の罠とコード例
では、この短絡評価が実務のWeb API設計やインフラ運用にどう影響するのか、具体的なシナリオを見てみましょう。
シナリオ:特定の踏み台サーバー(IP)以外からの管理ポート(SSH/RDP)を完全に遮断しつつ、通常のWebトラフィックを通す
TerraformなどのIaC(Infrastructure as Code)でNACLを定義する際の典型的な設定例を見てみます。
# 良い例:拒否ルールを上に、許可ルールを下に、デフォルトで全拒否
resource "aws_network_acl" "main" {
vpc_id = aws_vpc.main.id
# 【最優先】既知の攻撃者や特定の拒否対象IPからのアクセスをシャットアウト
ingress {
rule_no = 100
protocol = "tcp"
action = "deny"
cidr_block = "203.0.113.50/32" # 悪意ある踏み台IP
from_port = 22
to_port = 22
}
# 【次点】社内ネットワーク(許可)
ingress {
rule_no = 200
protocol = "tcp"
action = "allow"
cidr_block = "198.51.100.0/24" # 社内VPNのIPレンジ
from_port = 22
to_port = 22
}
# 【Web用】一般公開HTTP/HTTPS
ingress {
rule_no = 300
protocol = "tcp"
action = "allow"
cidr_block = "0.0.0.0/0"
from_port = 80
to_port = 80
}
ingress {
rule_no = 310
protocol = "tcp"
action = "allow"
cidr_block = "0.0.0.0/0"
from_port = 443
to_port = 443
}
# エフェメラルポート(戻りパケット用)の許可もお忘れなく
ingress {
rule_no = 400
protocol = "tcp"
action = "allow"
cidr_block = "0.0.0.0/0"
from_port = 1024
to_port = 65535
}
tags = {
Name = "secure-production-nacl"
}
}
アプリケーション層からの検証(Pythonによる死活監視の例)
NACLの設定変更後、意図通りにパケットが遮断(あるいは短絡評価)されているかをテストするため、Pythonの requests やソケット通信を使ってAPIの疎通確認を行うスクリプトの断片です。
import socket
import sys
target_host = "api.internal.example.com"
target_port = 443
def check_tcp_connection(host, port):
"""
指定されたホストとポートへのTCPコネクション確立を試みる。
NACLの短絡評価によるドロップが発生している場合、タイムアウトが発生する。
"""
print(f"Connecting to {host}:{port}...")
try:
# 3秒でタイムアウトを設定
with socket.create_connection((host, port), timeout=3.0) as sock:
print("[SUCCESS] Connection established. Packet passed NACL rules.")
except socket.timeout:
print("[ERROR] Connection timed out. Likely dropped by NACL Deny rule or short-circuiting.")
except socket.error as e:
print(f"[ERROR] Socket error occurred: {e}")
if __name__ == "__main__":
check_tcp_connection(target_host, target_port)
もし、短絡評価の順序を間違えていたり、ステートレスなNACLのアウトボード側にエフェメラルポートの許可(1024-65535)を入れ忘れたりすると、このスクリプトは虚しくタイムアウトを吐き続けます。
—
4. 現場のシニアが教えるデバッグTips
NACLのトラブルシューティングにおいて、私が後輩によく指導する手順をいくつか伝授します。
1. VPCフローログ(VPC Flow Logs)を即座に有効化する
NACLによるドロップは、VPCフローログの REJECT ステータスとして記録されます。ここで src-addr と dst-addr、そしてどのパケットが引っかかったのかを確認します。ただし、VPCフローログの出力には数分のタイムラグがあるため、リアルタイムの検証には不向きです。
2. 「エフェメラルポート(Ephemeral Ports)」の罠を疑う
NACLの最大のハマりどころはこれです。クライアント側が 443 に接続する際、クライアント側の送信元ポートはランダムな高位ポート(例: 54321)になります。アウトバウンド側で 1024-65535 の許可がないと、サーバー側は応答を返せても、クライアント側が受け取れません。「インバウンドだけ許可して動かない」と悩んだら、まずアウトバウンドのポート範囲を確認してください。
3. ルール番号には「十の位・百の位」で余裕を持たせる
ルール番号を 1, 2, 3... と連番で振るのは絶対にやめましょう。後から緊急で拒否ルールを挿入したくても、間に割り込ませることができなくなります。実務では 100, 200, 300 とインクリメントし、緊急用にはその隙間(例えば 150 など)を空けておくのがプロの作法です。
—
まとめ
ネットワークACLのルール番号評価と処理の短絡は、強力なセキュリティコントロールであると同時に、一歩間違えると原因究明が極めて困難なネットワーク障害を引き起こす「諸刃の剣」です。
- ルールは必ず番号の小さい順(昇順)に評価される
- 最初にマッチした時点で後続の評価はスキップされる(短絡評価)
- 拒否ルールは必ず許可ルールより上位(若い番号)に配置する
- ステートレスの原則を忘れず、往復のトラフィック(特にエフェメラルポート)を両方向で考慮する
この基本原則を頭に叩き込んでおけば、どんなに複雑なマルチVPC環境のネットワーク設計であっても、パケットの迷子を防ぎ、堅牢なクラウドインフラストラクチャを構築・運用することができます。
今日のインフラ作業が、スムーズで安全なものになることを祈っています。それでは、また別の現場でお会いしましょう!
コメント