クライアントのIPアドレスを正しく取得する


失敗を多く経験してきましたが、久々にヒヤッとしました・・。

IPアドレスを使って何かしらの処理を行うことは多々あります。
恥ずかしながら、私自身正直よく解らず、「こうやるんだな…」と作られたものを見ていただけで、使ってはいても深掘りせず、そのままとしていた状態でした。

幸い大きな問題には繋がらなかったのですが、内容が内容だったために、一歩間違えれば問題にも直結します…。また、教えていただいたことでようやく理解できた次第・・。

一般的な知識なのかもしれませんが、反省の意味も込めてまとめようと思います。


# 復習

アクセス元のIPアドレスを参照する方法 を改めて、学び直しました。
重要なのは、 `remote_addr` と `カスタムヘッダー` の二つです。

大事な違いは、編集できてしまうか否かだと思います。

## remote_addr

* IPレイヤ(OSI参照モデルの レイヤ3) の情報
* クライアント(=client) の IPアドレスが必須でセットされる
    - 厳密には、リクエスト元サーバーのIPアドレスがセットされる
* 例. AのIPアドレス「10.0.0.1」, BのIPアドレス「172.16.0.1」, CのIPアドレス「192.168.0.1」
    - A ⇒ B ⇒ C の経路で通信があった場合
        + Bが参照したremote_addr は「10.0.0.1」
        + Cが参照したremote_addr は「172.16.0.1」

https://www.ietf.org/rfc/rfc3875

https://www.vwnet.jp/Windows/Network/WhatIsNetwork.htm

## X-Forwarded-For

* アプリケーション層(OSI参照モデルの レイヤ7) の情報
    - HTTPヘッダー の 一つ
    - 非標準のヘッダーだが、事実上、こちらの方が多く使われる
        + ※ 標準化されているのは「Forwarded」ヘッダー
* 送信元 IP アドレスを特定するために利用する
    - カンマ区切りで複数のIPアドレスを設定可能
    - 一つ前のIPアドレスを 後ろに追加(Append) する利用が多い
* XFFと略されることが多い

https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/X-Forwarded-For



# 具体例

IPアドレスを参照して何かをする機能(例. IPアドレス制限 など)が備わっている場合、どのようにすれば良いのか。
設定や経路、どうやって取得すれば良いのかも含めて整理します。

アーキテクチャにもよりますが、今回は下記の構成を例に挙げます。

Client -> ALB -> Ingress Gateway -> Nginx -> Application

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2802395/74198657-0d8f-1048-4341-dd06c9357f69.png)

## 前提

Application で取得するため、 X-Forwarded-For の値を利用するしかありません。
(設定次第では、 `X-Real-IP` を利用するパターンもあると思います)


### 設定

サーバーなどを介すると 取得できるIPアドレスが何か、設定によって異なります。
ALB と Ingress Gateway は下記の設定であることを前提とします。

* ALB
    - XFFヘッダの末尾にカンマ区切りで追加する形式
    - ※ [AWS ALB なら、Append](https://docs.aws.amazon.com/ja_jp/elasticloadbalancing/latest/application/x-forwarded-headers.html)
* [Ingress Gateway](https://istio.io/latest/docs/ops/configuration/traffic-management/network-topologies/)
    - XFFヘッダの末尾にカンマ区切りで追加する形式
    - ※ [skip_xff_append](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/headers#x-forwarded-for) の設定なしの状態


## 経路ごとのXFF

Client のIPアドレス「10.0.0.1」, ALB のIPアドレス「172.16.0.1」,
Ingress Gateway のIPアドレス「192.168.0.1」 , Nginx の IPアドレス「172.31.0.1」 を例にします。

※ 全てプライベートIPアドレスのため、ありえない状態となりますがご理解ください。

### 例1.

Nginx の設定が下記の状態だったとします。

* `real_ip_header X-Forwarded-For;`
    - ※ TCP接続の場合、 `real_ip_header proxy_protocol;` の設定
* `proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;`

#### Client が 通常のアクセスをした場合

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2802395/1ad8f96d-8b8d-7896-f4f1-bbc8af898aea.png)


1. Client -> ALB
    - XFF: なし
    - remote_addr: 「10.0.0.1」
1. ALB
    - XFF の末尾に「10.0.0.1」を付与
1. ALB -> Ingress Gateway
    - XFF: 「10.0.0.1」
    - remote_addr: 「172.16.0.1」
1. Ingress Gateway
    - XFF の末尾に「172.16.0.1」を付与
1. Ingress Gateway -> Nginx
    - XFF: 「10.0.0.1, 172.16.0.1」
    - remote_addr: 「192.168.0.1」
1. Nginx
    - XFF の末尾に「192.168.0.1」を付与
1. Nginx -> Application
    - XFF: 「10.0.0.1, 172.16.0.1, 192.168.0.1」
    - remote_addr: 「172.31.0.1」
1. Application

#### Client が「172.25.255.255」をXFF に付与していた場合

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2802395/2c438978-10a2-ee85-44bb-c022b354d28d.png)


1. Client -> ALB
    - XFF: 「172.25.255.255」
    - remote_addr: 「10.0.0.1」
1. ALB
    - XFF の末尾に「10.0.0.1」を付与
1. ALB -> Ingress Gateway
    - XFF: 「172.25.255.255, 10.0.0.1」
    - remote_addr: 「172.16.0.1」
1. Ingress Gateway
    - XFF の末尾に「172.16.0.1」を付与
1. Ingress Gateway -> Nginx
    - XFF: 「172.25.255.255, 10.0.0.1, 172.16.0.1」
    - remote_addr: 「192.168.0.1」
1. Nginx
    - XFF の末尾に「192.168.0.1」を付与
1. Nginx -> Application
    - XFF: 「172.25.255.255, 10.0.0.1, 172.16.0.1, 192.168.0.1」
    - remote_addr: 「172.31.0.1」
1. Application

#### IPアドレスを取得するには

XFF の値から、 ALB と Ingress Gateway の IPアドレスを削除し、その末尾の IPアドレスを取得する。
（つまり、「172.25.255.255, 10.0.0.1, 172.16.0.1, 192.168.0.1」から「172.16.0.1, 192.168.0.1」を削除した「172.25.255.255, 10.0.0.1」 の末尾「10.0.0.1」）

### 例2.

例1 だと、 Application側で面倒な処理をする必要があります。この面倒な処理をなくすために、Nginx側の設定で回避することができます。

* `real_ip_header X-Forwarded-For;`
    - ※ TCP接続の場合、 `real_ip_header proxy_protocol;` の設定
* `real_ip_recursive on;`
* `set_real_ip_from 172.16.0.1;`
* `proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;`

#### Client が 通常のアクセスをした場合

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2802395/76c251b6-edaa-a8de-4dc6-4416e5094fde.png)


1. Client -> ALB
    - XFF: なし
    - remote_addr: 「10.0.0.1」
1. ALB
    - XFF の末尾に「10.0.0.1」を付与
1. ALB -> Ingress Gateway
    - XFF: 「10.0.0.1」
    - remote_addr: 「172.16.0.1」
1. Ingress Gateway
    - XFF の末尾に「172.16.0.1」を付与
1. Ingress Gateway -> Nginx
    - XFF: 「10.0.0.1, 172.16.0.1」
    - remote_addr: 「192.168.0.1」
1. Nginx
    - XFF の末尾に「10.0.0.1」を付与
        + XFF の 「10.0.0.1, 172.16.0.1」から、set_real_ip_from で設定されている「172.16.0.1」を削除した(末尾の)値
1. Nginx -> Application
    - XFF: 「10.0.0.1, 172.16.0.1, 10.0.0.1」
    - remote_addr: 「172.31.0.1」
1. Application

#### Client が「172.25.255.255」をXFF に付与していた場合

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2802395/d4ba0fec-c9f6-355c-c9bd-d05dd64549be.png)


1. Client -> ALB
    - XFF: 「172.25.255.255」
    - remote_addr: 「10.0.0.1」
1. ALB
    - XFF の末尾に「10.0.0.1」を付与
1. ALB -> Ingress Gateway
    - XFF: 「172.25.255.255, 10.0.0.1」
    - remote_addr: 「172.16.0.1」
1. Ingress Gateway
    - XFF の末尾に「172.16.0.1」を付与
1. Ingress Gateway -> Nginx
    - XFF: 「172.25.255.255, 10.0.0.1, 172.16.0.1」
    - remote_addr: 「192.168.0.1」
1. Nginx
    - XFF の末尾に「10.0.0.1」を付与
        + XFF の 「172.25.255.255, 10.0.0.1, 172.16.0.1」から、set_real_ip_from で設定されている「172.16.0.1」を削除した末尾の値
1. Nginx -> Application
    - XFF: 「172.25.255.255, 10.0.0.1, 172.16.0.1, 10.0.0.1」
    - remote_addr: 「172.31.0.1」
1. Application

#### IPアドレスを取得するには

XFF の値の末尾の IPアドレスを取得するだけでOKです。


# まとめ

重要なのは、「インフラの設定がどうなっているか」を把握し、「どうやって取得をするべきなのか」を知ることです。
やはり、インフラ関連の知識も最低限、アプリケーションに辿り着くまでの部分まででも良いので知っておくことが大事だと、今回の経験によって再認識しました。

「・・なるほど、そうなっていたのか！」となっていただければ幸いです。


## 要点

1. remote_addr には、リクエスト元のIPアドレス がセットされ、参照できる
    - リクエスト元のIPアドレスしか取得できないため、複数のサーバーを経由してアクセスされた場合は、本当のクライアントのIPアドレスを取得することはできない(かもしれない)
1. X-Forwarded-For には、経路を辿ったIPアドレスがカンマ区切りでセットされていく(ように利用する)
1. リクエスト時clientで、できる操作
    - remote_addr に値のセットは不可
    - X-Forwarded-For に値のセットは可能
1. 経由するサーバーの設定にもよるが、基本、 X-Forwarded-For の カンマ区切りの最後の値 を取得する
    - Application の場合ローカル起動が多く、サーバーを経由しないことを考慮し、 XFFの値がなければ remote_addr の値を取得するように実装する


## Nginx の 設定

私自身、この部分の整理に時間かかったため、見直しのためにもまとめます。

* Nginx が扱っている、 remote_addr の変数の値を X-Forwarded-For の値に書き換える
    - `real_ip_header X-Forwarded-For;` の設定記載
        + ※ TCP接続の場合、 `real_ip_header proxy_protocol;` の設定
    - X-Forwarded-For の中で、 set_real_ip_from に記載されていないIPアドレスの最後の値を利用するように設定
        + `real_ip_recursive on;`
            + ※ 設定がないと、set_real_ip_from 関係なく、IPアドレスの最後の値を利用する状態となる
        + `set_real_ip_from {信頼先CIDR形式のIPアドレス};`
            + ※ 信頼先IPアドレス = 予め分かっている経路のサーバーのIPアドレス
    - 結果的に、 set_real_ip_from で排除したIPアドレス = clientのIPアドレス = remote_addrの値 となる
* XFFヘッダの末尾にカンマ区切りで Nginxが扱っている remote_addr の **変数の値** を 追加する
    - `proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;`
* 「X-Real-IP」 のヘッダでも、client の IPアドレスを取得できるようにする
    - `proxy_set_header X-Real-IP $remote_addr;`