おそらくはそれさえも平凡な日々

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さん…?

まあ、このモジュール自体は便利だと思うのでぜひご利用ください。

created at
last modified at

2020-04-24T12:11:40+0900

comments powered by Disqus