【備忘録】CloudRunは X-Forwarded-For を上書きする

3行まとめ

  • CloudRunはX-Forwarded-Forを上書きしてクライアントのIPアドレスにしてしまう
  • User - CDN - CloudRun としている場合、X-Forwarded-Forの値はCDNのIPアドレスになってしまう
  • 解決策はCDN独自のヘッダーを付与してアプリケーション側で読むしか方法がなさそう

X-Forwarded-For

X-Forwarded-For というヘッダーがあります。多分MDNを見るのが一番早いですが、色々なプロキシを通ってオリジンに到達するようなサーバー構成のときにクライアントのIPアドレスをうまく伝えるようなヘッダーです。

developer.mozilla.org

形式としてはX-Forwarded-For: <client>, <proxy1>, <proxy2>のようになり、プロキシサーバーやCDNを通るとX-Forwarded-Forの値の右にそのプロキシやCDNのIPアドレスが追加されます。オリジンのWebサーバーはこのヘッダーを右から読んでいきIPアドレスが信頼されているものかどうかを確認して行き信頼できないIPアドレスが来たらそれがクライアントのIPアドレスという風に認識します*1*2

このような仕様となっているX-Forwarded-Forは実はCloudRunでは末尾にIPを追加といったことをせずに直接クライアントのIPアドレスを代入します。こんな感じ、

X-Forwarded-For: 157.52.91.33

これは、実際にCloudRunの前段にFastlyを置いていて X-Forwarded-For | Fastly DocumentationX-Forwarded-Forを渡す設定にしていますがFastly側で設定されているIPアドレスが消えてしまっています。これではうまくWebアプリケーション側でIPアドレスを取得できません。

解決策

解決策としてはCDN側で別のヘッダーにクライアントのIPアドレスを付与することで解決ができます。Fastlyではぶっちゃけどんなヘッダーでもつけることができますが、ドキュメントではFastly-Client-IPが紹介されているのでこれを使うと良さそうです。

www.fastly.com

他にもCloudflareではCF-Connecting-IPが使えるようです。

developers.cloudflare.com

もし、WebアプリケーションをGo/Echoで実装しているなら以下のようにIPExtractorを追加することでカスタムヘッダからIPアドレスを取ることができます(Fastly ver)。

var extractIpFromXFFHeader = echo.ExtractIPFromXFFHeader()

func ExtractIPFromFastlyHeader(req *http.Request) string {
    fastlyClientIp := req.Header.Get("Fastly-Client-IP")
    if fastlyClientIp != "" {
        return fastlyClientIp
    }
    return extractIpFromXFFHeader(req)
}

...

e := echo.New()
e.IPExtractor = ExtractIPFromFastlyHeader

個人的感想

クライアントのIPアドレスを決め打ちで渡すのであればX-Real-IPというヘッダーがnginxなどにあるのでできればこっち使ってくれたらありがたかったけど今から仕様変えると動いているアプリケーションがすべて壊れるので難しいんだろうな。

*1:ちゃんと実装しないとセキュリティリスクがあります。

*2:説明が間違っていたらご指摘ください。