なぜALBのログには「10.0.x.x」ばかり並ぶのか?――X-Forwarded-Forで真のクライアントIPを掴み取る作法
現場でトラブルシューティングをしていると、「アクセスログの送信元IPがALBのプライベートIPばかりで、不正アクセスの遮断もレートリミットもままならない」という相談をよく受けます。
ロードバランサー(ALB)は、クライアントとバックエンドサーバーの間に割って入る「プロキシ」です。ALBがTCPコネクションを一度終端し、バックエンドに対して新しいコネクションを張り直す以上、バックエンドから見れば「通信相手は常にALB」になってしまうのは当然の物理的帰結です。
では、どうやって「本当のクライアント」を特定するのか。今回は、実務で避けて通れない X-Forwarded-For(以下、XFF)ヘッダーの深淵を覗いていきましょう。
—
1. X-Forwarded-For が生まれる仕組みと構造
ALBは、クライアントからのリクエストを転送する際、HTTPヘッダーに X-Forwarded-For というフィールドを付与(または追記)します。このヘッダーは、リクエストが経由してきたIPアドレスの「履歴」を記録する場所です。
通信シーケンスのリアル
1. Client (1.2.3.4) が ALB に対してリクエストを送信。
2. ALB はこれを受け取ると、ヘッダーに X-Forwarded-For: 1.2.3.4 を追加し、バックエンドへ転送。
3. もし途中にさらに別のプロキシ(CDNやWAFなど)があれば、X-Forwarded-For: 1.2.3.4, 11.22.33.44 のようにカンマ区切りでIPが連結されていきます。
ここで重要なのは、「一番左端のIPが、元々の発信元である」 というルールです。
—
2. 実務で遭遇する「落とし穴」
多くのエンジニアが犯しがちなミスは、X-Forwarded-For を無条件に信頼してログに記録したり、認証に使ったりすることです。
もしクライアントがリクエストヘッダーに偽造した X-Forwarded-For: 8.8.8.8 を含めて送ってきたらどうなるでしょうか? ALBは、その偽造された値の後ろに、実際のクライアントIPを付け足します。つまり、X-Forwarded-For: 8.8.8.8, 1.2.3.4 となるわけです。
教訓: セキュリティに関わる処理(IP制限やレートリミット)を行う際は、必ず「信頼できるソース(ALB)」から付与された情報だけを抽出し、文字列の最後(または最初)を機械的に信じないロジックを組む必要があります。
—
3. コードで見る「クライアントIP」の特定方法
実際にバックエンドでクライアントIPを抽出する際の実装を見てみましょう。
Python (Flask) での例
Flaskのようなフレームワークでは、request.headers から情報を取得しますが、素の値をそのまま使うのは危険です。
from flask import request
@app.route('/')
def get_client_ip():
# ALB経由の場合、X-Forwarded-Forヘッダーを確認
xff = request.headers.get('X-Forwarded-For')
if xff:
# 複数のIPが入っている場合、一番左がクライアントIP
client_ip = xff.split(',')[0].strip()
else:
# ALB以外の経路や直接アクセスの場合
client_ip = request.remote_addr
return f"あなたのIPは: {client_ip}"
nginx でのIP特定(ログ設定)
バックエンドにnginxを置いている場合、real_ip モジュールを使うのが鉄則です。設定ファイルに以下を書くだけで、ログには自動的に「正しいクライアントIP」が記録されます。
# /etc/nginx/nginx.conf の設定例
# ALBのIPレンジを信頼できるプロキシとして指定
set_real_ip_from 10.0.0.0/8;
# XFFヘッダーからIPを抽出して $remote_addr を上書きする
real_ip_header X-Forwarded-For;
real_ip_recursive on;
—
4. デバッグの現場から:疎通確認のTips
開発環境で「本当に正しくヘッダーが届いているか」を確認したいときは、curl コマンドでヘッダーを詳細表示させるのが一番早いです。
# -Iでヘッダー情報のみ取得、-vで詳細な通信フローを表示
curl -vI https://your-alb-dns-name.com
また、バックエンドで動いているサーバーが、どのようなヘッダーを受け取っているかを確認するために、以下のような「エコーサーバー」を一時的にデプロイするのも非常に有効です。
# echo_server.py: 受信した全ヘッダーを返す簡易サーバー
from http.server import HTTPServer, BaseHTTPRequestHandler
class EchoHandler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.end_headers()
# 全ヘッダーをレスポンスとして返す
self.wfile.write(str(self.headers).encode())
httpd = HTTPServer(('0.0.0.0', 8080), EchoHandler)
httpd.serve_forever()
これをALBのターゲットグループにぶら下げてリクエストを投げれば、X-Forwarded-For がどのように加工されているか、生の状態を確実に観測できます。
—
まとめ:ネットワークの透明性を確保するために
ALBは便利なブラックボックスですが、その内部で起きている「IPの書き換え」という魔法を理解しておかないと、いざという時のログ解析で詰むことになります。
1. 一番左のIPを見る: X-Forwarded-For の先頭がクライアントIP。
2. 信頼できるプロキシのみ許可する: ヘッダーの偽造リスクを常に考慮する。
3. フレームワークの機能を活用する: nginxの real_ip など、ミドルウェアレベルでの解決を優先する。
インフラとアプリケーションの境界線で起きるこうした挙動を一つひとつ丁寧に紐解くことが、堅牢なシステムを作る第一歩です。皆さんの現場のネットワークが、今日も滞りなくパケットを運びますように。
コメント