ELBでProxyProtocolを使ってnginxでTCP接続時のアクセス元IPを取得する

ELBへHTTP及びHTTPSでの接続の場合、アクセス元のIPアドレスはHTTPヘッダの`http_x_forwarded_for`より取得できますが、TCPをELBでバランシングした場合、何も設定をしないとアクセス元のIPアドレスの取得がバックエンドのEC2で出来ないようです。

上記についてELBをProxyProtoclを利用することでバックエンドのEC2でもアクセス元IPアドレスが取得できるのでメモ。

# 参考

+ [【AWS】ELBがProxy Protocolをサポート 〜 RubyでEchoサーバ作って確かめてみた](http://dev.classmethod.jp/etc/elb-support-proxy-protocol/)
+ [【AWS発表】 Elastic Load BalancingがProxy Protocolをサポート](http://aws.typepad.com/aws_japan/2013/07/elastic-load-balancing-adds-support-for-proxy-protocol.html)
+ [ロードバランサーの Proxy Protocol のサポートを設定する](https://docs.aws.amazon.com/ja_jp/ElasticLoadBalancing/latest/DeveloperGuide/enable-proxy-protocol.html)
+ [ELB(Elastic Load Balancer)の後ろをTCPにしてHTTP2をサポートしつつ、リクエスト元IPも記録](http://qiita.com/sawanoboly/items/38cf8e4fdf14b7f4e2c9)

# デフォルトの状態で試す

AWSでは以下を設定します。

+ EC2->sshできるもの、ELBから80ポートを受け付けるSecurityGroupの設定
+ ELB->上記EC2にTCPの80ポートのトラフィックを転送するように設定

次にEC2にsshログインしてnginxのインストールと設定確認をしてみます。

```bash
# インストール
$sudo yum install nginx -y

# 起動
$sudo service nginx start

# /etc/nginx/nginx.confのログの確認（抜粋)
$cat 

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

```

では実際にローカルのPCからEC2及びELB経由でアクセスし、nginxのアクセスログ(/var/log/nginx/access.log)を確認します。

```bash
# 直接EC2のPublicIPを指定.先頭のIPがアクセス元のIP
101.111.248.34 - - [20/Jan/2016:03:04:42 +0000] "GET /nginx-logo.png HTTP/1.1" 304 0 "http://52.192.82.149/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36" "-"

# ELB経由.先頭のアクセス元のIPがELBのIP。末尾のhttp_x_forwarded_forもなし
172.31.5.107 - - [20/Jan/2016:03:06:40 +0000] "GET /nginx-logo.png HTTP/1.1" 304 0 "http://testprojectelb-320584224.ap-northeast-1.elb.amazonaws.com/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36" "-"

```

なお、HTTPによるトラフィックの転送の場合、以下のように末尾の`http_x_forwarded_for`でアクセス元IPが判定できているのが分かります。

```bash

# ELB経由
172.31.5.107 - - [20/Jan/2016:03:08:09 +0000] "GET /poweredby.png HTTP/1.1" 304 0 "http://testprojectelb-320584224.ap-northeast-1.elb.amazonaws.com/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36" "101.111.248.34"
```

# ELBにProxyProtocolを指定する

以下を参考にELBにProxyProtoclを設定してみます。

[ロードバランサーの Proxy Protocol のサポートを設定する](https://docs.aws.amazon.com/ja_jp/ElasticLoadBalancing/latest/DeveloperGuide/enable-proxy-protocol.html)

マネージメントコンソールでは設定できないのでAWS CLIから実施します。

最初にELBでサポートされているポリシーの中から、ProxyProtocolの設定を確認します。

```bash
# 出力結果は一部省略
$aws elb describe-load-balancer-policy-types
{
    "PolicyTypeDescriptions": [
        {
            "PolicyAttributeTypeDescriptions": [
                {
                    "Cardinality": "ONE",
                    "AttributeName": "ProxyProtocol",
                    "AttributeType": "Boolean"
                }
            ],
            "PolicyTypeName": "ProxyProtocolPolicyType",
            "Description": "Policy that controls whether to include the IP address and port of the originating request for TCP messages. This policy operates on TCP/SSL listeners only"
        },
```

次にProxyProtocolを有効にするポリシーを設定します。事前にELBの名称を確認して`--load-balancer-name`で指定します。

```bash
$aws elb create-load-balancer-policy --load-balancer-name my-loadbalancer --policy-name my-ProxyProtocol-policy --policy-type-name ProxyProtocolPolicyType --policy-attributes AttributeName=ProxyProtocol,AttributeValue=true
```

次に先ほど作成したポリシーで特定のポート（今回は80)を有効にします。

```bash
$aws elb set-load-balancer-policies-for-backend-server --load-balancer-name my-loadbalancer --instance-port 80 --policy-names my-ProxyProtocol-policy 
```

最後にELBにポリシーが設定されているか確認します。

```bash
# 出力結果は一部省略
$aws elb describe-load-balancers --load-balancer-name  my-loadbalancer
            "BackendServerDescriptions": [
                {
                    "InstancePort": 80,
                    "PolicyNames": [
                        "my-ProxyProtocol-policy"
                    ]
                }
            ],
```

# nginxの設定を変更する

[CONFIGURING NGINX TO ACCEPT THE PROXY PROTOCOL](https://www.nginx.com/resources/admin-guide/proxy-protocol/)

次にnginxの設定を変更します。
diffを以下に記載します。

```bash
$git diff
diff --git a/nginx.conf b/nginx.conf
index 5d1233b..367c5e0 100644
--- a/nginx.conf
+++ b/nginx.conf
@@ -44,9 +44,11 @@ http {
     index   index.html index.htm;

     server {
-        listen       80;
+        listen       80 proxy_protocol;
         server_name  localhost;
         root         /usr/share/nginx/html;
+        set_real_ip_from 172.31.0.0/16;
+        real_ip_header proxy_protocol;

         #charset koi8-r;
```

+ listenディレクティブ->ProxyProtocolの接続を許可
+ set_real_ip_from->RealIPの変換を適応するCIDR。ELBのあるVPCのCIDRを設定
+ real_ip_header->Porxyプロトコルにのっとり、接続元IPを設定する

公式だとlogformatを`$remote_addr`から`$proxy_protocol_addr`に変えてますが、上記設定を行えば、どちらでも取得できる値は同じです。

設定を反映させます。

```bash
$sudo service nginx reload
```

次にELBのヘルスチェックをHTTPからTCPの80に変更します。

これで準備ができたのでELBのCNAMEでアクセスしてみます。

```bash
# 先頭のremote_addがアクセス元のIPアドレスになっている
101.111.248.34 - - [20/Jan/2016:04:09:06 +0000] "GET /poweredby.png HTTP/1.1" 304 0 "http://testprojectelb-320584224.ap-northeast-1.elb.amazonaws.com/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36" "-"
```

無事にTCPのバランシングでもアクセス元IPアドレスが取得できました!

今回は80ポートでやってみましたが、同じように他のポートでも実施可能だと思います。