こんにちは、シニアSREの私です。
クラウドインフラの設計をしていると、どうしてもマネージドなNATゲートウェイ(NAT Gateway)のコストや、特定の要件の壁にぶつかる瞬間があります。「マネージドだとルーティングの自由度が足りない」「VPCの外へ出る前に、特定のプロキシを通したりパケットをいじったりしたい」——そんな時、古き良き(そして今なお現場で頼りになる)カスタムNATインスタンスの出番です。
しかし、このNATインスタンスを構築する際、多くのエンジニアが一度はハマる罠があります。「ルーティングテーブルも正しく設定したのに、なぜかプライベートサブネットからインターネットへ通信が抜けない!」というトラブルです。
今回は、この現象の根源であるAWSのセキュリティ機能 「ソース/宛先チェック(Source/Destination Check)」 の正体と、なぜそれを無効化しなければならないのか、パケットの旅路を追いながら徹底的に解説します。
—
1. なぜパケットは消えるのか? AWSの「お節介な親切心」
まずは、VPCの仮想ネットワーク空間で何が起きているのかをイメージしてください。
通常、AWSのEC2インスタンスには、そのネットワークインターフェイス(ENI)に割り当てられたプライベートIPv4アドレス(例: 10.0.1.10)が存在します。AWSの仮想ルーターは非常に厳格で、基本的には次のようなルールを強制しています。
- 送信元チェック: そのEC2から送信されるパケットの「送信元IPアドレス(Source IP)」は、そのENIに割り当てられたIPアドレスでなければならない。
- 宛先チェック: そのEC2宛てに届くパケットの「宛先IPアドレス(Destination IP)」は、そのENIに割り当てられたIPアドレスでなければならない。
この鉄のルールがあるおかげで、AWS基盤内でのIPスプーフィング(なりすまし)や意図しないパケットの迷子を防いでいます。いわば、AWSがネットワークの治安を守るために張っている「お節介な親切心」の警察官のようなものです。
NATインスタンスが直面する矛盾
しかし、NAT(Network Address Translation)の役割を思い出してください。NATインスタンスの本懐は、プライベートサブネットにある他のインスタンス(例: 10.0.2.15)から送られてきたパケットを受け取り、送信元IPを自分のパケット(例: 10.0.1.10)に書き換えて(SNAT)インターネットへ送り出すことです。
ここでAWSの「送信元チェック」が発動します。
NATインスタンスが 10.0.2.15 からのパケットの送信元を書き換えて外へ出そうとした瞬間、AWSの仮想ルーターがこう言います。
*「おいおい、お前のENIのIPは 10.0.1.10 のはずだろ。なんで 10.0.2.15 なんて関係ないIPのパケットを流そうとしてるんだ? 怪しいからドロップするわ!」*
結果、パケットは闇に葬り去られ、プライベートサブネットからの通信はピクリとも動かなくなります。これが、NATインスタンス導入時に誰もが一度は踏む地雷の正体です。
—
2. ソース/宛先チェックの無効化:その設定と意味
このAWSの厳しいセキュリティチェックをかいくぐり、インスタンスを「ルーター」や「NATゲートウェイ」として機能させるための唯一にして必須の操作が、ソース/宛先チェックの無効化(Disable Source/Destination Check) です。
これを無効化すると、AWSの仮想ルーターは「このENIに関しては、自分以外のIPアドレスを送信元・宛先として持つパケットの通過を許可する」という特権を付与します。
AWS CLIによる設定手順
実務ではマネージドコンソールからポチポチ設定することも多いですが、IaC(TerraformやCloudFormation)や緊急時の復旧作業ではAWS CLIを叩けることが必須スキルです。以下に、特定のEC2インスタンス(ENI)のチェックを無効化するコマンドを示します。
# 1. まず、対象のNATインスタンスにアタッチされているENIのIDを確認する
aws ec2 describe-instances \
--instance-ids i-0123456789abcdef0 \
--query "Reservations[*].Instances[*].NetworkInterfaces[*].NetworkInterfaceId" \
--output text
# 出力例: eni-0abcd123456789ef0
# 2. 該当するENIのソース/宛先チェックを無効化する
aws ec2 modify-network-interface-attribute \
--network-interface-id eni-0abcd123456789ef0 \
--no-source-dest-check
# 3. 設定が正しく反映されているか確認する
aws ec2 describe-network-interfaces \
--network-interface-ids eni-0abcd123456789ef0 \
--query "NetworkInterfaces[*].SourceDestCheck"
# 出力結果が "false" になっていればOKです
—
3. パケットの実際の旅路(ルーティングシーケンス)
ソース/宛先チェックを無効化した上で、プライベートサブネットからインターネットへのリクエストがどのように処理されるのか、その一連の流れ(パケットの旅路)を追ってみましょう。
ここでは、プライベートサブネットにあるアプリケーションサーバー(10.0.2.50)から、外部のWeb API(例: https://api.example.com/v1/data)へデータを送信するシナリオを考えます。
[Private Instance] [NAT Instance] [Internet Gateway] [External API]
(10.0.2.50) (10.0.1.10 / ENI) (IGW) (203.0.113.50)
│ │ │ │
│── (1) HTTP Request ──────────────────>│ │ │
│ Src: 10.0.2.50 │ │ │
│ Dst: 203.0.113.50 │ │ │
│ │ │ │
│ │── (2) SNAT & Forward ─────────>│ │
│ │ Src: 10.0.1.10 (書き換え) │ │
│ │ Dst: 203.0.113.50 │ │
│ │ │ │
│ │ │── (3) Out to Internet ──>│
│ │ │ │
1. プライベートインスタンスからの送信
アプリケーションが curl やPythonの requests を使ってリクエストを飛ばします。
- 送信元IP:
10.0.2.50 - 宛先IP:
203.0.113.50(外部API)
プライベートサブネットのルートテーブルには「0.0.0.0/0 のターゲットは NATインスタンスのENI(またはインスタンスID)」と設定されているため、パケットはNATインスタンスへ向かいます。
2. NATインスタンスでのパケット書き換え(SNAT)
NATインスタンスがパケットを受信します。ここでソース/宛先チェックが無効化されているため、パケットはドロップされずにOSのカーネル(Netfilter / iptables)へ渡されます。
OS側で設定されたiptablesのルール(後述)により、パケットの送信元IPがNATインスタンス自身のプライベートIP(10.0.1.10)に書き換えられます。
3. インターネットゲートウェイ(IGW)経由で外部へ
パケットはVPCのIGWを通り、グローバルIPに変換されてインターネットの荒海へと漕ぎ出します。
—
4. OS側のルーティングとiptables設定の裏側
AWS側でソース/宛先チェックを無効化しても、実はそれだけでは動きません。NATインスタンスのOS(Linux)自身が「自分宛てではないパケットを転送(ルーターとして振る舞う)」ことを許可し、さらにSNATのルールを設定してあげる必要があります。
実務でよく使われるAmazon Linux 2023やUbuntuを前提とした、OS側の設定スクリプトの例を以下に示します。
カーネルパラメーターの有効化(IPフォワード)
# /etc/sysctl.d/custom-nat.conf を作成し、パケット転送を有効化する
cat << 'EOF' > /etc/sysctl.d/custom-nat.conf
net.ipv4.ip_forward = 1
EOF
# 設定を即時反映させる
sysctl --system
iptables (または nftables) によるマスカレード設定
次に、OSに入ってきたプライベートIPからのパケットを、NATインスタンスのIPに偽装(マスカレード)する設定を流し込みます。
# プライベートサブネットのCIDR(例: 10.0.0.0/16)からのパケットを、
# 外部へ出ていくインターフェイス(例: eth0)でマスカレードする
# ※環境に合わせてインターフェイス名(eth0等)やCIDRは書き換えてください
iptables -t nat -A POSTROUTING -o eth0 -s 10.0.0.0/16 -j MASQUERADE
iptables -A FORWARD -i eth0 -o eth0 -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -A FORWARD -i eth0 -o eth0 -s 10.0.0.0/16 -j ACCEPT
# 設定を永続化する(Amazon LinuxやRHEL系の場合の例)
iptables-save > /etc/sysconfig/iptables
—
5. 実務で遭遇する「ハマりどころ」とデバッグ手法
「AWS側でソース/宛先チェックを切り、OS側でIPフォワードも有効にした。なのに通信できない!」
――そんなときにシニアエンジニアが現場でどうやって原因を切り分けるか、そのデバッグの流儀を授けます。
デバッグのチェックリスト
1. セキュリティグループの罠
- NATインスタンスのセキュリティグループ(SG)の「インバウンド」だけでなく、「アウトバウンド(送信)」ルールを確認していますか?
- プライベートインスタンスからの通信を受け入れるインバウンドは当然として、外部へ向かうアウトバウンドで全許可(
0.0.0.0/0)または必要なポート(443等)が空いている必要があります。
2. ネットワークACl(NACL)のステートレスの罠
- セキュリティグループはステートフル(往復の通信を自動で覚えていてくれる)ですが、NACLはステートレスです。
- プライベートサブネットのNACLおよびNATサブネットのNACLで、エフェメラルポート(
1024-65535)のインバウンド・アウトバウンドが双方向で許可されているかを必ず確認してください。
3. パケットキャプチャで現実を見る
言葉よりパケットが真実を語ります。NATインスタンス上で tcpdump を回して、パケットが実際に流れているか確認しましょう。
# NATインスタンスにSSHログインし、インターフェイスを流れるパケットを覗き見する
sudo tcpdump -i eth0 -n "host 203.0.113.50"
ここでパケットが全く流れていない場合は「VPCルートテーブルのルーティングミス」、パケットは来ているのに破棄される場合は「ソース/宛先チェックの無効化漏れ」または「セキュリティグループのブロック」と即座に切り分けられます。
—
まとめ
カスタムNATインスタンスの構築における「ソース/宛先チェックの無効化」は、単なるおまじないの設定ではありません。「AWSという厳格な仮想ルーターの世界において、自発的にルーターとして振る舞うために、AWSのセキュリティ機構を一部バイパスする」という、極めてアーキテクチャ的に重要な意味を持つ設定です。
この仕組みとパケットの流れるストーリーを頭に叩き込んでおけば、万が一のネットワーク障害時にも、迷うことなくパケットの動線を追いかけ、一瞬で原因を特定できるはずです。
現場のインフラ運用、そして堅牢なクラウド設計の一助となれば幸いです。それではまた、次の現場でお会いしましょう。
コメント