GoのWebアプリでクライアントIPを検出するrealipモジュール
github.com/natureglobal/realip
これはngx_http_realip_moduleと同様の挙動を、Goのhttpハンドラをラップするミドルウェアレイヤで実現するものです。
アプリケーションが信頼できるNginx等のproxy配下にあれば、X-Real-IP
ヘッダなどをそのままクライアントIPとして採用すればよいのですが、クラウドのロードバランサー、例えばALBなどに直接Goのアプリケーションをぶら下げている場合、ALBはX-Real-IP
を付けてくれないので、アプリケーション側でクライアントのIP検出をおこなう必要があります。そういったときにこのモジュールが有用です。
X-Forwarded-For
を見れば良いという話ではあるのですが、HTTPヘッダは簡単に偽装できますし、CDNを使っているなど多段になっているケースでも判別は地味に厄介です。ヘッダを付けてきたREMOTE_ADDR
が信頼できるか、数珠つなぎになっているX-Forwarded-For
ヘッダの、どのエントリをクライアントIPとみなせばよいか、などの問題があります。
冒頭にも書いたとおり、Nginxではngx_http_realip_module
を使うことによりクライアントIP検出が実現できますが、realipモジュールはそれと同様の設定項目を使ってクライアントIPの検出をします。
利用例
_, ipnet, _ := net.ParseCIDR("172.16.0.0/12")
var middleware func(http.Handler) http.Handler = realip.MustMiddleware(&realip.Config{
RealIPFrom: []*net.IPNet{ipnet},
RealIPHeader: "X-Forwarded-For",
SetHeader: "X-Real-IP",
RealIPRecursive: true,
})
var handler http.HandlerFunc = func(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, req.Header.Get("X-Real-IP"))
})
handler = middleware(handler)
この場合、X-Forwarded-For
ヘッダから、クライアントIPの検出をおこない、それをX-Real-IP
ヘッダにセットしてから内側のハンドラにリクエストを渡します。なので内部のハンドラではX-Real-IP
ヘッダを見ればクライアントIPを知ることができます。
また、上記の設定だと、172.16.0.0/12
のIPレンジを信頼できるレンジとみなし、そのIP帯から来たリクエストヘッダを信頼するとともに、X-Forwarded-For
の途中経路にそのレンジにアドレスが入っている場合はそれを飛ばし、「最初の信頼できないIPアドレス」をクライアントIPとして採用するという挙動になります。
作ったきっかけとか
Nginxを使わずに、ALBに後ろにGoのアプリケーションをぶら下げようとしているのでこういうの必要だよなーと思って作りました。先行実装が無いことはなかったんですが、案外ちゃんとしたやつがなかったのと、ngx_http_realip_module
の設定に合わせてある、というところが独自の優位性です。
同時に、ngx_http_realip_module
についての理解も整理できたのでその解説も書こうかと思ってたんですが、いつまで経っても書けそうにないのでまずはモジュールの紹介に留めます。
本当は、ALBがngx_http_realip_module
同様の設定ができるようになってくれると嬉しいんですけどね…。どうですかね、AWSさん…?
まあ、このモジュール自体は便利だと思うのでぜひご利用ください。