【実務・中級編】 NATゲートウェイ配下のクライアントIP保存手法(Proxy Protocolの活用) – クラウド&コンテナネットワーク実践ガイド

はじめに:NATの向こう側で「あの日のクライアントIP」を見失う絶望

夜中の3時、PagerDutyの鋭いアラート音で叩き起こされる。
「不正アクセス検知システムが、特定のプライベートIPからのリクエストをすべてブロックし続けている。しかも、そのIPがなぜかAWSのNAT GatewayのグローバルIPになっているせいで、社内からの正当なAPIリクエストまで巻き込んで弾かれている!」

クラウドインフラの設計に携わるエンジニアなら、誰もが一度はこの「送信元IP消失の呪い」に頭を抱えたことがあるはずです。

私たちはセキュリティと可用性を担保するため、プライベートサブネットにバックエンドのAPIサーバーを配置し、インターネットや外部パートナーへのリクエストはNAT Gateway(あるいはGCPのCloud NAT)を介してルーティングします。このとき、L3/L4のネットワーク層において、クライアントのプライベートIPアドレス(例: 10.0.1.100)は、NAT Gatewayの持つElastic IP(例: 203.0.113.50)に送信元NAT(SNAT / Source NAT)によって書き換えられます。

結果として、バックエンドのアプリケーションサーバーやロードバランサーに届くパケットの送信元IPは、すべて「NAT Gatewayの顔」になってしまいます。「これでは誰がリクエストを送ってきたのか分からない。アクセスログ監査も、IPベースのレートリミットも機能しないじゃないか!」――そう嘆く君に、シニアSREの私が教えよう。

この難題をエレガントに解決する切り札が、L7層の X-Forwarded-For ヘッダーであり、そしてL4層における Proxy Protocol なのだ。今回は、パケットの挙動から具体的なコード実装、そして現場のトラブルシューティングまで、徹底的に解説しよう。

—

1. NATの宿命と、IPアドレスが隠蔽されるメカニズム

まずは、なぜ送信元IPが消えてしまうのか、そのネットワークの裏側をパケットの旅になぞらえておさらいしておこう。

プライベートサブネット(例: 10.0.0.0/16)にいるコンテナやEC2インスタンスから、外部の宛先へ向けたTCPコネクションが張られるとする。
1. パケットの生成: クライアント(10.0.1.50:54321)が、外部API(198.51.100.20:443)宛てのSYNパケットを送信する。
2. NATの介入: パケットがルートテーブルに従ってNAT Gatewayを通過する際、ルーター(SNAT機構)はIPヘッダーの送信元IPアドレスを、自身が持つグローバルIP(203.0.113.10)に書き換える。同時に、TCPポート番号もポート多重化(NAPT)のために書き換えられることがある。
3. 宛先への到達: 宛先サーバーに届くパケットの送信元は 203.0.113.10:60000 のように見え、元のプライベートIPは完全に隠蔽される。

この仕組みはセキュリティ上(プライベートIPの秘匿)極めて重要だが、「リバースプロキシやロードバランサーの背後で動くマイクロサービスが、真のクライアントIPを知りたい」というユースケースにおいては、最大の壁となる。

—

2. IP伝達の2大アプローチ:L7層の「HTTPヘッダー」 vs L4層の「Proxy Protocol」

真のクライアントIPをバックエンドに伝えるアプローチには、大きく分けて2つのレイヤーが存在する。それぞれの特徴と使い分けを理解することが、アーキテクチャ設計の第一歩だ。

① L7層アプローチ: X-Forwarded-For (XFF) ヘッダー

HTTP/HTTPSなどのWebトラフィックであれば、リバースプロキシ(AWS ALBやNginxなど)がリクエストを受け付けた際、HTTPヘッダーに元のクライアントIPを挿入してバックエンドへ転送するのが最もポピュラーだ。

  • メリット: アプリケーションコードから容易に取得できる。HTTPの標準的なデファクトスタンダード。
  • デメリット: L7(HTTP)限定であること。TCPストリームやTLSのまま中継するTCPプロキシ(NLBなど)では利用できない。

② L4層アプローチ: Proxy Protocol(v1 / v2)

HTTPのようなアプリケーション層のプロトコルに依存せず、TCPコネクションの確立直後に「元の接続元・宛先IPとポート情報」をバイナリまたはテキストのヘッダーとしてねじ込む仕組みが Proxy Protocol だ。Haproxyが発案し、現在ではAWS NLBやGCP TCP Proxy、Nginxなどが幅広くサポートしている。

  • メリット: TCPレベルで動作するため、TLSパススルーやデータベース接続、gRPC、WebSocketsなど、あらゆるプロトコルでクライアントIPを保存できる。
  • デメリット: バックエンドのサーバー(またはアプリケーション)側がProxy Protocolを解釈できる設定になっていなければ、不正なパケットとみなされてコネクションが切断される。

—

3. Proxy Protocolのデータ構造と通信シーケンス

現場でトラブルが起きたとき、パケットキャプチャ(tcpdumpやWireshark)を眺めるスキルはSREの必須科目だ。Proxy ProtocolがTCPストリームの中でどのように振る舞うのか、その構造を覗いてみよう。

Proxy Protocolには、人間が読めるテキスト形式の v1 と、効率的なバイナリ形式の v2 が存在する。実務ではパフォーマンスと堅牢性の面から v2 を使うのがデファクトだ。

Proxy Protocol v2 のシグネチャとパケット構造

Proxy Protocol v2のTCPストリームは、必ず以下の12バイトの固定シグネチャから始まる。

\x0D \x0A \x0D \x0A \x00 \x0D \x0A \x51 \x55 \x49 \x54 \x0A

その後に、コマンド(LOCALかPROXYか)、プロトコルファミリー(IPv4/IPv6/UNIX)、そしてアドレス長を示すヘッダーブロックが続き、最後に実際のクライアントIPとポート番号が格納される。

[ TCP 3-way Handshake 完了 ]
       ↓
[ プロキシサーバー ] -- (Proxy Protocol v2 ヘッダー + クライアント情報) --> [ バックエンドサーバー ]
       ↓
[ 通常のTCPデータ通信開始 ]

もし、バックエンド側(例:Nginxや自製アプリ)がProxy Protocolを受け付ける設定になっていないのに、前段のNLB等がProxy Protocolを有効にしてパケットを送ると、最初の12バイトがHTTPリクエストやアプリケーションプロトコルとして解釈できず、Invalid protocol エラーで即死する。ここが現場で最も多いハマりどころだ。

—

4. 実装・設定のハンズオン

理論はこのくらいにして、実際にAWS環境やコンテナネットワークを想定した設定・コードの実例を見ていこう。

パターンA: AWS ALB + X-Forwarded-For (HTTP/HTTPS APIのケース)

AWSのApplication Load Balancer (ALB) は、デフォルトで X-Forwarded-For ヘッダーを付与してバックエンドのターゲットグループ(ECSやEC2)へ転送する。Node.js (Express) でこのヘッダーを安全に取得するコードを見てみよう。

const express = require('express');
const app = express();

// ALBなどの信頼できるプロキシの背後にあることをExpressに伝える
// これにより req.ip が X-Forwarded-For の先頭IPを正しく指すようになる
app.set('trust proxy', true);

app.get('/api/v1/status', (req, res) => {
    // req.ip または req.headers['x-forwarded-for'] からクライアントIPを取得
    const clientIp = req.ip;
    const forwardedFor = req.headers['x-forwarded-for'];

    console.log(`Received request from Client IP: ${clientIp}`);
    console.log(`Full X-Forwarded-For chain: ${forwardedFor}`);

    res.json({
        status: "healthy",
        your_ip: clientIp,
        message: "Successfully captured your real IP through NAT/ALB!"
    });
});

app.listen(3000, () => {
    console.log('API Server running on port 3000');
});

パターンB: AWS NLB + Proxy Protocol v2 + Nginx (TCP/gRPCのケース)

ネットワークロードバランサー (NLB) を使ってTCPトラフィックを処理し、背後のNginxでProxy Protocol v2を受け取る設定だ。

1. Nginxの設定ファイル (nginx.conf)

バックエンドのNginxでProxy Protocolを有効化するには、listen ディレクティブに proxy_protocol を付与する。また、リアルIPをログやFastCGIに正しく引き渡すために set_real_ip_from を設定する。

http {
    # 信頼するプロキシ(AWS NLBのVPC内プライベートIPレンジ等)を指定
    set_real_ip_from 10.0.0.0/16;
    
    # Proxy Protocolヘッダーから実際のクライアントIPを $remote_addr に上書きする
    real_ip_header proxy_protocol;

    server {
        listen 80 proxy_protocol; # ポート80でProxy Protocolを受け付ける
        listen 443 ssl proxy_protocol; # HTTPS(パススルーまたは終端)

        ssl_certificate /etc/nginx/certs/server.crt;
        ssl_certificate_key /etc/nginx/certs/server.key;

        location / {
            proxy_pass http://backend-app:8080;
            
            # バックエンドのアプリケーションへも本当のIPとプロトコルを伝える
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }
    }
}

—

5. 現場のトラブルシューティング:パケットとログの追い方

「設定したはずなのに、アプリのログを見ると相変わらずNAT GatewayのIPが表示される……」
そんな絶望的な状況に直面したとき、シニアSREが取るべき黄金のデバッグ手順を伝授しよう。

Step 1: tcpdumpでパケットの中身を直接覗く

バックエンドサーバーのインターフェースで、実際にどのようなバイナリが流れてきているのかを tcpdump でキャプチャする。

# インターフェース eth0 のポート8080への流入パケットをHEX(16進数)付きでキャプチャ
sudo tcpdump -i eth0 -nnvvXS tcp port 8080

チェックポイント:
通信の最初の数バイトに、先ほど紹介したProxy Protocol v2のシグネチャ(0d 0a 0d 0a 00 0d 0a 51 55 49 54)が含まれているか確認する。含まれていない場合、前段のロードバランサー(ALBやNLB)側でProxy Protocolが無効になっている可能性が高い。

Step 2: セキュリティグループとルーティングの確認

NAT Gatewayを経由するトラフィックは、往復のルーティングが複雑になりがちだ。

  • ALBやNLBのターゲットグループの設定で、Proxy Protocolのチェックボックスが正しくオンになっているか?
  • バックエンドサーバーのセキュリティグループ(Security Group)が、ロードバランサーからのインバウンドトラフィックを許可しているか?(クライアントのセキュリティグループではなく、LBのSGが送信元になる点に注意)

—

おわりに:インフラとアプリの境界線を融かす設計美

NAT Gateway配下のクライアントIP保存は、単なる「設定項目の有無」の問題ではない。L3/L4のネットワーク層と、L7のアプリケーション層がどのように連携しているか、そのパケットの全貌をイメージできるかどうかが、インフラエンジニアの腕の見せ所だ。

X-Forwarded-For でスマートにHTTPリクエストを彩るか、あるいは Proxy Protocol v2 で泥臭くTCPのレイヤーから真実のIPを救い出すか。
システム要件に合わせ、最適なレイヤーでルーティングとプロトコルを選択できるようになれば、夜中に突然鳴り響くアラートの音色も、少しは心地よいBGMに聞こえてくるはずだ――冗談はさておき、あなたの設計するクラウドネットワークが、安全かつ透明性の高いものでありますように。

コメント

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