X-Forwarded-For は、ただのデファクトスタンダード

## 前書き
- 本記事は Forwarded ヘッダーとX-Forwarded-For についてあまり知らない方向けの記事になります。既に Forwarded ヘッダーと、X-Forwarded-For の関係をご存知の方は読み飛ばしてください。

## 経緯
X-Forwarded-For はプロキシやロードバランサーを介してリクエストが来る際に、それらが付与したクライアントの発信元IP アドレスを示す HTTPヘッダーです。

[Actix-web の公式サンプル](https://github.com/actix/examples/blob/master/http-proxy/src/main.rs#L32-L33)を眺めていた際に、下記のコメントがふとめにつきました。

>    // TODO: This forwarded implementation is incomplete as it only handles the unofficial
>    // X-Forwarded-For header but not the official Forwarded one.

この文章をDeepLで翻訳すると下記になります

>    // TODO:このforwardedの実装は不完全である。
>    // X-Forwarded-For ヘッダーのみを扱い、公式の Forwarded ヘッダー は扱わないためです。

この記事のタイトルを読まれた方は、この時点で薄々感じているかもしれませんがIETFには、 X-Forwarded-For に関するIETFの公式な仕様は存在しないようです。

Mozillaの [X-Forwarded-For のページ](https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/X-Forwarded-For)には[下記の様](https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/X-Forwarded-For#%E4%BB%95%E6%A7%98%E6%9B%B8)には下記のように記載されています。

> 仕様書
> 現行のどの仕様書にも含まれていません。このヘッダーの標準化版は Forwarded ヘッダーです。

そうです、X-Forwarded-For は仕様書としてIETFで管理しているものはなく、標準化された Forwarded ヘッダー があります。

## そもそも X-Forwarded-For とは何なのか

- プロキシーサーバーやロードバランサーを介してリクエストが来る際に、それらが付与したクライアントの発信元IP アドレスを示す送信元 IP アドレスを特定するために "事実上の標準" となっているヘッダーです。
    - デバッグや統計情報、及びロケーションに依存するコンテンツの生成などに使われるケースもあります。
　　
- Squid(キャッシング・プロキシサーバ)の開発者により初めて導入されたとされており、デファクトスタンダードに近い形となっています。
    - Apache を含む多くのプロキシサーバやロードバランサーなどでサポートされています。
        - [Squid](http://www.squid-cache.org/Doc/config/forwarded_for/)
        - [Apache mod proxy](https://httpd.apache.org/docs/trunk/mod/mod_proxy.html)
        - [AWSのLB](https://docs.aws.amazon.com/ja_jp/elasticloadbalancing/latest/application/x-forwarded-headers.html)
　　
### X-Forwarded-Forの構文
下記の構成をとり、要素はカンマ区切りで、前後にスペースを含めることもできます。
多くの場合は、右端の IP アドレスが最も後のプロキシーで、左端の IP アドレスがクライアントの IP アドレスとなります。

- `X-Forwarded-For: <client>, <proxy1>, <proxy2>, <proxy3>` 

|要素|説明|
|:-----|:------------------|
|`<client>`|クライアントの IP アドレス|
|`<proxy1~3>`|プロキシーの IP アドレス|

#### 例
```
X-Forwarded-For: 2001:db8:85a3:8d3:1319:8a2e:370:7348

X-Forwarded-For: 203.0.113.195

X-Forwarded-For: 203.0.113.195, 2001:db8:85a3:8d3:1319:8a2e:370:7348

X-Forwarded-For: 203.0.113.195,2001:db8:85a3:8d3:1319:8a2e:370:7348,150.172.238.178
```

情報元: https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/X-Forwarded-For#proxy1_proxy2

### セキュリティ的な問題
- クライアントと全てのプロキシーが悪意がなく設定ミスがない場合は想定した動きをしますが、ヘッダーの一部（ないしは全て）がなりすましの可能性を含み、 **比較的に変更が容易** です。
　　
- 仕様がデファクトスタンダードであり、IETFの仕様書に基づいていないため **意図していない実装** が含まれている可能性もあります。
　　
- もしレートリミットなどを設定する場合は、 **信頼できるプロキシによって追加された箇所まで** を使うことをおすすめします。信頼できない ないしは信頼できるかわからない値までを含んで判定してしまうと、レートリミットなどを回避してしまうことが考えられます。

## Forwarded ヘッダーとは
では次に Forwarded も見てみます。

- 実装間の相互運用性の向上のために標準化した、オプションのヘッダーフィールドです。
　　 
- X-Forwarded-For と同じように、セキュリティ的な問題は同程度含む可能性があります。
    - リクエストが複数のプロキシを通過している場合、各プロキシは一連のパラメータを追加できるし、以前に追加された Forwarded ヘッダーフィールドを削除することもできます。
    - このヘッダーフィールドはオプションであるため、プロキシなどは **このヘッダーフィールドを更新しないことを選択できます。**
　　
- リストの最初の要素は、このヘッダーフィールドを実装および使用する最初のプロキシによって追加された情報を保持し、後続の各要素は、後続の各プロキシによって追加された情報を保持します。

### Forwarded の構文
下記の構成をとり、各フィールド値はセミコロンで区切りです。

- `Forwarded: by=<identifier>;for=<identifier>;host=<host>;proto=<http|https>`

identiferとはプロキシーの使用時に変更または失われた情報を公開する識別子。次のいずれかです。
- IP アドレス (v4 または v6、任意でポート番号付き、 ipv6 は引用符と角括弧で囲まれます)
　　
- 難読化された識別子 ("_hidden" や "_secret" など): 生成された識別子は、内部IPアドレスを秘密にしたい場合。難読化された識別子を他の識別子と区別するには、先頭にアンダースコア「_」を付ける必要があります。
　　
- unknown: 先行するエンティティが不明な場合 (およびリクエストの転送が行われたことを示したい場合) 

|要素|説明|
|:-----|:------------------|
|`by=<identifier>`|リクエストがプロキシサーバーに入ってきたインターフェイス|
|`for=<identifier>`|リクエストを発行したクライアントと、その後のプロキシーチェーン内のプロキシー|
|`host=<host>`|プロキシーから受信したときの Host リクエストヘッダー|
|`proto=<http|https>`|そのプロトコルがリクエストの作成に使用されたか (ふつうは "http" または "https")|

#### 例
```
# 大文字小文字の区別なし
Forwarded: For="[2001:db8:cafe::17]:4711"

# セミコロン区切り
Forwarded: for=192.0.2.60; proto=http; by=203.0.113.43

# 複数の値をコンマで区切って追加可能
Forwarded: for=192.0.2.43, for=198.51.100.17
```

情報元: https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Forwarded#%E6%A7%8B%E6%96%87
情報元: https://www.rfc-editor.org/rfc/rfc7239

#### X-Forwarded-For と Forwarded の置き換え
アプリケーションやサーバー・プロキシーがForwarded ヘッダーに対応している場合は、置き換えることができます。

```
X-Forwarded-For: 12.34.56.78
Forwarded: for=12.34.56.78

X-Forwarded-For: 192.0.2.3, "[2001:db8:cafe::17]"
Forwarded: for=192.0.2.3, for="[2001:db8:cafe::17]"
```

## 後書き
ついつい標準仕様だと思っていたけれど、あくまでデファクトスタンダードであるので取り扱いには注意が必要です。
また、プロキシ経由のヘッダー周りについては十二分に扱いを注意していきたいと思います。

この記事がどなたかの参考になれば幸いです。