カイワレの大冒険 Third

技術的なことや他愛もないことをたまに書いてます

ELBでクライアントのGIPが取得できるProxy Protocolを試してみた

HTTPでは簡単に取得できるクライアントのGIP(グローバルIP)。

ただ、必ずしもHTTPを使うわけではなく、色々なプロトコルを使いたいときもありまして。WebsocketとかMQTTとかソケット通信とか色々。

そういうときに、ELBではTCPだとデフォルトではGIPが取得できないわけで、その対策をして、GIPを取得しましょうという話。

前提:

  • クライアントのGIPを取得したい
  • ELB作れるぐらいの知識がある
  • サーバ側もproxy protocolの設定が必要になる(たとえば、nginxであれば1.5.12以上のバージョンを使い、かつnginx.confに手を入れる必要がある。この辺ちゃんと書かれた資料が少なくハマった…

ということで、やっていきましょう。

nginxインストール

まずnginxインストールする。 ubuntuだったら以下で最新版をインストールできる。

$ curl http://nginx.org/keys/nginx_signing.key | sudo apt-key add -
$ sh -c "echo 'deb http://nginx.org/packages/ubuntu/ trusty nginx' >> /etc/apt/sources.list"
$ sh -c "echo 'deb-src http://nginx.org/packages/ubuntu/ trusty nginx' >> /etc/apt/sources.list"
$ apt-get update
$ apt-get install nginx

serverの項目に以下を追加。

server {
    listen 80 proxy_protocol; //proxy_protocolという文字列追加
    server_name localhost; //適当に
    set_real_ip_from 10.50.0.0/16; //ELBなりVPCのセグメント追加する
    real_ip_header proxy_protocol; // この行まるごと追加

nginx起動させる。curl http://localhost して動作確認したいけど、Proxy Protocol対応しないとクライアントと通信できないので、最悪違うVirtual host切って確認する。

次にELB作成する。難しいところはないけど、以下のようにTCPにすることを忘れないようにする。

f:id:masudaK:20160330132022p:plain

次にELBをproxy protocol対応をさせる。マネージメントコンソールが対応してないので、awscliからやる。

まずはawscliのインストール。

$ python -V
Python 2.7.10
 
$ pip install awscli
 
# pyenvを使っている場合。
$ pyenv rehash
 
$ aws --version
aws-cli/1.7.44 Python/2.7.10 Darwin/14.4.0 
 
$ aws configure
AWS Access Key ID [None]: XXXX
AWS Secret Access Key [None]: XXXXXXXX
Default region name [None]: ap-northeast-1
Default output format [None]: json

便利なので、jqもインストールしておく。

# macでbrew使っているなら以下でインストールできます。
$ brew install jq

次に、ELBの設定・ポリシーを確認しておく。「sample-elb-proxy-protocol」のところは自分が作ったELB名に変えてください。

$ aws elb describe-load-balancers \
    --load-balancer-name sample-elb-proxy-protocol | \
    jq '.LoadBalancerDescriptions [] .Policies'
{
  "LBCookieStickinessPolicies": [],
  "AppCookieStickinessPolicies": [],
  "OtherPolicies": []
}

OtherPoliciesに何もないので、何も適用されていないことがわかります。SSLに対応したELBの場合、何か出てくるかもしれないので、それをメモっておいてください。以下も確認。

$ aws elb describe-load-balancers \
    --load-balancer-name sample-elb-proxy-protocol | \
    jq '.LoadBalancerDescriptions [] .BackendServerDescriptions'
[]

こちらもBackendServerDescriptionsが空なので、何も適用されていない。

次に適用すべきポリシーを確認する。といっても、何を設定するかは決まってるから、ここはスルーしてもいい。

$ aws elb describe-load-balancer-policy-types | \
    jq '.PolicyTypeDescriptions[] | select(.PolicyTypeName=="ProxyProtocolPolicyType")'
{
  "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"
}

"PolicyTypeName": "ProxyProtocolPolicyType"とあるので、ProxyProtocolPolicyTypeをtrueにする必要がある。

そんで、trueにする。

$ aws elb create-load-balancer-policy \
    --load-balancer-name sample-elb-proxy-protocol \
    --policy-name my-proxy-protocol-policy \
    --policy-type-name ProxyProtocolPolicyType \
    --policy-attributes '[{"attribute_name":"ProxyProtocol","attribute_value":"True"}]'
Parameter validation failed:
Unknown parameter in PolicyAttributes[0]: "attribute_name", must be one of: AttributeName, AttributeValue
Unknown parameter in PolicyAttributes[0]: "attribute_value", must be one of: AttributeName, AttributeValue

どっかのブログに書いてあったこのやりかただとUnkwon parameterエラーになってしまう。--policy-attributesの書き方が悪いらしい。

ので、以下にする。

$ aws elb create-load-balancer-policy \
    --load-balancer-name sample-elb-proxy-protocol \
    --policy-name my-proxy-protocol-policy \
    --policy-type-name ProxyProtocolPolicyType \
    --policy-attributes AttributeName=ProxyProtocol,AttributeValue=true

設定できました。 確認してみると、OtherPoliciesに設定が入っていることがわかります。

$ aws elb describe-load-balancers --load-balancer-name sample-elb-proxy-protocol | jq '.LoadBalancerDescriptions [] .Policies'
{
  "LBCookieStickinessPolicies": [],
  "AppCookieStickinessPolicies": [],
  "OtherPolicies": [
    "my-proxy-protocol-policy"
  ]
}

最後に、このポリシーを特定のポートに対して有効にさせる必要があります。 SSLに対応したELBの場合、policy-namesに既存のポリシーも追加してください。「my-proxy-protocol-policy existing-policy」のように。

$ aws elb set-load-balancer-policies-for-backend-server \
    --load-balancer-name sample-elb-proxy-protocol \
    --instance-port 80 \
    --policy-names my-proxy-protocol-policy

これで完了。 設定を確認します。

$ aws elb describe-load-balancers \
    --load-balancer-name sample-elb-proxy-protocol | \
    jq '.LoadBalancerDescriptions [] .BackendServerDescriptions'
[
  {
    "InstancePort": 80,
    "PolicyNames": [
      "my-proxy-protocol-policy"
    ]
  }
]

nginxのログに以下のようなものが成功。

123.123.123.123 - - [07/Aug/2015:19:38:39 +0900] "GET / HTTP/1.1" 200 612 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.125 Safari/537.36" "-"

Proxy Protocolを無効にしたい場合は以下のようにしてください。

$ aws elb set-load-balancer-policies-for-backend-server \
    --load-balancer-name sample-elb-proxy-protocol \
    --instance-port 80 \
    --policy-names "[]"

policy-namesを空にしてあげる。これだけだとnginxが対応してないので、nginx.confもいじって、proxy protocolが適用されていない状態にする必要がある。

nginxが古い場合

nginxのログにこういうログが出るものの400エラーを返してしまうので、最新版をいれること。これなかなか分からずハマりました。

# tailf /var/log/nginx/access.log
10.100.100.101 - - [07/Aug/2015:15:08:25 +0900] "PROXY TCP4 10.100.100.101 10.0.1.11 56232 80" 400 181 "-" "-"
10.0.1.248 - - [07/Aug/2015:15:08:28 +0900] "PROXY TCP4 10.0.1.248 10.0.30.251 2697 80" 400 181 "-" "-"
10.100.100.101 - - [07/Aug/2015:15:08:35 +0900] "PROXY TCP4 10.100.100.101 10.0.1.11 56236 80" 400 181 "-" "-"
10.0.1.248 - - [07/Aug/2015:15:08:38 +0900] "PROXY TCP4 10.0.1.248 10.0.30.251 2702 80" 400 181 "-" "-"
10.100.100.101 - - [07/Aug/2015:15:08:45 +0900] "PROXY TCP4 10.100.100.101 10.0.1.11 56239 80" 400 181 "-" "-"
10.0.1.248 - - [07/Aug/2015:15:08:48 +0900] "PROXY TCP4 10.0.1.248 10.0.30.251 2705 80" 400 181 "-" "-"
127.0.0.1 - - [07/Aug/2015:15:08:49 +0900] "GET / HTTP/1.1" 200 612 "-" "curl/7.35.0"
10.100.100.101 - - [07/Aug/2015:15:08:55 +0900] "PROXY TCP4 10.100.100.101 10.0.1.11 56243 80" 400 181 "-" "-"
10.100.100.101 - - [07/Aug/2015:15:08:55 +0900] "PROXY TCP4 124.35.68.250 10.0.1.11 33048 80" 400 181 "-" "-"
10.0.1.248 - - [07/Aug/2015:15:08:58 +0900] "PROXY TCP4 10.0.1.248 10.0.30.251 2708 80" 400 181 "-" "-"

ブラウザのログはこんな感じ。

Error response
Error code 400.
Message: Bad request syntax ('PROXY TCP4 10.0.1.248 10.0.1.13 54238 80').
Error code explanation: 400 = Bad request syntax or unsupported method.

終わりに

GIPに依存する設計はあまり好きではないのですが、要件で必要になるケースも多く検証してみました。awscliを使わないと設定できないので、ツールが増えるのがちょっと嫌ですが、それを除けば、そこまで辛いところはないように思います。

最新情報はこちらの公式ドキュメントにあるので、記事が古くなったら、ドキュメントも是非見てください。

何か問題等あれば遠慮なくご指摘ください!ではでは!

AWSシリーズ:
- CloudFrontで署名付きURLによるストリーミング配信をしてみた(Ruby編)