ちょっとした用事で Forwarded ヘッダーの解析をしたくなったので、解析ライブラリを書いてみました。
背景
Amazon API GatewayのHTTP Proxy Integrationを利用しているプロジェクトで、 クライアントのIPアドレスを知りたい用件がありました。
リバースプロキシの運用に慣れている人なら、「クライアントのIPアドレスを取得したい」と聞いて真っ先に思いつくのは X-Forwarded-For ヘッダーでしょう。 しかし、HTTP Proxy Integrationはこのヘッダーを付与しません。
なんとかIPアドレスを取得できないかと調べてみると、HTTP Proxy IntegrationはForwardedヘッダーを付与することがわかりました。
Forwardedヘッダーは RFC 7239 で標準化されているヘッダーです。 リバースプロキシによって失われてしまう情報を補完するために利用します。
Forwardedヘッダーを見ればクライアントのIPアドレスもわかります。
たとえば以下の例では 192.0.2.60
がクライアントのIPアドレスです。
Forwarded: for=192.0.2.60; proto=http; by=203.0.113.43
X-Forwarded-Forヘッダーは単純なカンマ区切りのテキストだったので、Split関数で十分でした。 一方Forwardedヘッダーは構造化されているため、パーサーが必要です。 そこまで複雑ではないので、実装してみました。
使い方
解析は Parse関数を呼び出すだけです。
package main
import (
"fmt"
"net/http"
"os"
forwardedheader "github.com/shogo82148/forwarded-header"
)
func main() {
header := make(http.Header)
header.Set("Forwarded", "by=203.0.113.43;for=192.0.2.60;proto=http")
// parse the Forwarded Header
parsed, err := forwardedheader.Parse(header.Values("Forwarded"))
if err != nil {
panic(err)
}
for _, f := range parsed {
fmt.Println(f)
}
// Output:
// by=203.0.113.43;for=192.0.2.60;proto=http
}
解析済みの値をEncode関数でエンコードすることもできます。
package main
import (
"fmt"
"net/http"
"net/netip"
"os"
forwardedheader "github.com/shogo82148/forwarded-header"
)
func main() {
// build the Forwarded Header
forwarded := []*forwardedheader.Forwarded{
{
For: forwardedheader.Node{
IP: netip.MustParseAddr("192.0.2.60"),
},
Proto: "http",
By: forwardedheader.Node{
IP: netip.MustParseAddr("203.0.113.43"),
},
},
}
header := make(http.Header)
header.Set("Forwarded", forwardedheader.Encode(forwarded))
header.Write(os.Stdout)
// Output:
// Forwarded: by=203.0.113.43;for=192.0.2.60;proto=http
}
まとめ
RFC 7239で定義されている ForwardedヘッダーのパーサーをGoで書きました。 プロキシサーバーを経由している場合に、クライアントのIPアドレスを知るために使えます。
しかし、Forwardedヘッダーってどの程度利用されているんですかね・・・? Forwardedヘッダーについてググっても、あまり情報が出てこないんですよね。 このまま廃れてなくなってしまうのではないかと心配です。
まあ、「クライアントのIPアドレスなんか気にするな」って話かもしれませんね。 (でもこっちにも事情があってだな・・・)