ALBの「X-Forwarded-Proto」を制する者は、セキュアなWeb APIの設計を制す
現場でインフラを見ていると、時折「なぜかリダイレクトループが止まらない」「HTTPSでアクセスしているはずなのに、バックエンドがHTTPと判定してエラーを吐く」といった悲鳴を耳にします。
クラウドのロードバランサー、特にAWSの ALB (Application Load Balancer) を使っていると、手元のブラウザとバックエンドの間には「見えない壁」が存在します。今日は、その壁を突き抜けてクライアントの接続状態を正しく把握するための鍵、X-Forwarded-Proto ヘッダーについて、実務の視点から深掘りしていきましょう。
—
1. なぜ「X-Forwarded-Proto」が必要なのか
まず、現場のアーキテクチャを想像してください。クライアントはHTTPSでALBに接続します。しかし、ALBとバックエンドのインスタンス(またはコンテナ)の間は、多くの場合HTTPで通信しています。これを「SSLオフロード」と呼びます。
この時、バックエンドのアプリケーションから見ると、自分に入ってくる通信は常にHTTPです。これでは、「クライアントがセキュアな通信を求めているか」をアプリケーション側で判断できません。
そこでALBが気を利かせて付与してくれるのが X-Forwarded-Proto ヘッダーです。
- 役割: クライアントがALBに対して「どのようなプロトコルで接続したか」をバックエンドに伝えるための伝言メモ。
- 値:
httpまたはhttpsが格納されます。
—
2. 通信フローを追いかける
パケットがどう動いているかを可視化すると、ヘッダーの重要性がよく分かります。
1. クライアント: https://api.example.com にリクエスト送信。
2. ALB: SSL/TLSハンドシェイクを終端。バックエンドへのリクエストヘッダーに X-Forwarded-Proto: https を追加して転送。
3. バックエンド: 受け取ったヘッダーを確認。「お、これはHTTPSで来ているな」と判断し、安全な処理を継続。
もしこのヘッダーを無視して、「自分の受信したプロトコル」だけを基準にリダイレクト判定を書くと、永遠にHTTPからHTTPSへリダイレクトし続ける「地獄のループ」が完成します。
—
3. 実践:バックエンドでの正しい判定とリダイレクト
では、コードレベルでどう扱うべきか。Python(FastAPI/Flask)やPHP、あるいはNginxでの設定例を見ていきましょう。
Python (FastAPIの場合)
FastAPIでは ProxyHeadersMiddleware を有効にするのが定石ですが、手動で判定する場合は以下のようになります。
from fastapi import Request, HTTPException
@app.middleware("http")
async def enforce_https(request: Request, call_next):
# ALBから送られてくるヘッダーを確認
proto = request.headers.get("x-forwarded-proto")
# HTTPSではない場合、恒久的にHTTPSへリダイレクトを促す
if proto == "http":
url = str(request.url).replace("http://", "https://", 1)
return RedirectResponse(url=url, status_code=301)
return await call_next(request)
Nginxで判定する場合
アプリケーションの手前にNginxを置いているなら、設定ファイルで一括処理するのがスマートです。
# ALB経由の場合、X-Forwarded-Protoを使って判定する
map $http_x_forwarded_proto $is_https {
default off;
https on;
}
server {
listen 80;
# $is_httpsがoffなら、すべてHTTPSへ強制リダイレクト
if ($is_https = off) {
return 301 https://$host$request_uri;
}
}
—
4. デバッグの鉄則:現場での確認コマンド
「本当にALBがヘッダーを付与しているのか?」と疑うことは、SREの第一歩です。手元からcurlで叩いて確かめるのが一番の近道です。
# -v でヘッダー情報を表示し、バックエンドからのレスポンスを観察する
curl -Iv https://api.example.com/health-check
もし自作の検証用サーバーで受け取ったヘッダーを確認したいなら、Pythonの簡易サーバーが便利です。
# 簡易サーバーでヘッダーをダンプする
from http.server import HTTPServer, BaseHTTPRequestHandler
class HeaderDumper(BaseHTTPRequestHandler):
def do_GET(self):
print(self.headers) # ここに X-Forwarded-Proto が表示されるはず
self.send_response(200)
self.end_headers()
HTTPServer(('0.0.0.0', 8080), HeaderDumper).serve_forever()
—
5. 最後に:設計時の落とし穴
最後に、シニアエンジニアとして一つ忠告しておきます。「ALBのヘッダーは信頼しすぎないこと」です。
もしALBを経由しない直接アクセス(例えばローカル環境や、VPN経由の直接IPアクセス)を許容している場合、X-Forwarded-Proto はクライアントから偽装可能です。インターネットに公開するAPIであれば、ヘッダーの有無だけでなく、接続元IP(X-Forwarded-For)との組み合わせや、ALBのセキュリティグループによる制限とセットで考えるのが「現場の正解」です。
技術は教科書通りには動きません。ヘッダー一つとっても、その背景にあるネットワークの設計思想を理解すれば、障害対応も怖くなくなるはずです。それでは、良いインフラライフを!
コメント