ELB経由のForward Proxyでアクセス元のIPアドレスを記録する

ELBを使用して(Reverseでない)Forward Proxyサーバを負荷分散させる場合に、アクセス元のIPアドレスを正しく記録したいというのはよく聞くのですが、残念ながらsquidやmod_proxyはProxy Protocolに対応していません。
このページによれば、Foward ProxyでProxy Protocolに対応しているのは、ExaProxyだけのようなので、ExaProxyを試してみる事にします。

ExaProxyをEC2(Amazon Linux)上で動かす

Python 2.7で開発されているらしいので、まずPython 2.7をインストールする(Python 2.6だとエラーが出て動かない)

$ sudo yum install python27
:
インストール:
  python27.x86_64 0:2.7.5-11.32.amzn1

依存性関連をインストールしました:
  python27-libs.x86_64 0:2.7.5-11.32.amzn1

完了しました!

ドキュメント通りにtar ballを落として実行してみる。

$ wget http://exaproxy.googlecode.com/files/exaproxy-1.1.2.tgz
$ tar zxvf exaproxy-1.1.2.tgz
$ cd exaproxy-1.1.2
$ ./sbin/exaproxy
ExaProxy 15282  supervisor    Starting exaproxy version 1.1.2
ExaProxy 15282  supervisor    python version 2.7.5 (default, Feb 24 2014, 15:33:10)  [GCC 4.6.3 20120306 (Red Hat 4.6.3-2)]
ExaProxy 15282  daemon        could not increase file descriptor limit to 65826, limit is still 4096
ExaProxy 15282  daemon        please increase your system maximum limit, alternatively you can reduce
ExaProxy 15282  daemon        exaproxy.http.connections, exaproxy.web.connections and/or configuration.redirector.maximum

ファイルディスクリプタを沢山使うみたいです。とりあえずテストなので1024コネクションもあれば大丈夫でしょう。大規模に使用するのであれば、kernel paramterやlimits.confで上限を上げてください。
ついでに0.0.0.0でListenするようにして、アクセスログも出力するようにしてみます。

$ vi etc/exaproxy/exaproxy.conf
:
$  diff etc/exaproxy/exaproxy.conf.default etc/exaproxy/exaproxy.conf
22c22
< connections = 32768
---
> connections = 1024
66c66
< host = '127.0.0.1'
---
> host = '0.0.0.0'
83c83
< enable = false
---
> enable = true
$ ./sbin/exaproxy
ExaProxy 15389  supervisor    Starting exaproxy version 1.1.2
ExaProxy 15389  supervisor    python version 2.7.5 (default, Feb 24 2014, 15:33:10)  [GCC 4.6.3 20120306 (Red Hat 4.6.3-2)]

とりあえず起動したようです。curlで動作確認。

$ http_proxy=http://172.30.0.202:3128 curl -v http://checkip.amazonaws.com
* About to connect() to proxy 172.30.0.202 port 3128 (#0)
*   Trying 172.30.0.202... connected
* Connected to 172.30.0.202 (172.30.0.202) port 3128 (#0)
> GET http://checkip.amazonaws.com HTTP/1.1
> User-Agent: curl/7.21.4 (x86_64-apple-darwin12.0.0) libcurl/7.21.4 OpenSSL/0.9.8y zlib/1.2.5 libidn/1.20
> Host: checkip.amazonaws.com
> Accept: */*
> Proxy-Connection: Keep-Alive
>
< HTTP/1.1 200 OK
< Date: Fri, 28 Feb 2014 19:26:21 GMT
< Server: lighttpd/1.4.29
< Content-Length: 14
< Connection: keep-alive
<
192.168.1.154
* Connection #0 to host 172.30.0.202 left intact
* Closing connection #0

ちゃんと中継してくれたようです。コンソールにもちゃんとログが出ています。

$ ./sbin/exaproxy
ExaProxy 15908  supervisor    Starting exaproxy version 1.1.2
ExaProxy 15908  supervisor    python version 2.7.5 (default, Feb 24 2014, 15:33:10)  [GCC 4.6.3 20120306 (Red Hat 4.6.3-2)]
ExaProxy 15908  usage         2014-02-28 19:28:52 1393615732.43 1 192.168.1.154 GET checkip.amazonaws.com/ PERMIT/checkip.amazonaws.com

ELBから使う

次にInternal ELBを作成し、TCP listenerをインスタンスの3128ポートに向け、インスタンスを追加します。

$ http_proxy=http://internal-proxy-284387118.us-west-2.elb.amazonaws.com:3128 curl -v http://checkip.amazonaws.com
* About to connect() to proxy internal-proxy-284387118.us-west-2.elb.amazonaws.com port 3128 (#0)
*   Trying 172.30.0.235... connected
* Connected to internal-proxy-284387118.us-west-2.elb.amazonaws.com (172.30.0.235) port 3128 (#0)
> GET http://checkip.amazonaws.com HTTP/1.1
> User-Agent: curl/7.21.4 (x86_64-apple-darwin12.0.0) libcurl/7.21.4 OpenSSL/0.9.8y zlib/1.2.5 libidn/1.20
> Host: checkip.amazonaws.com
> Accept: */*
> Proxy-Connection: Keep-Alive
>
< HTTP/1.1 200 OK
< Date: Sat, 01 Mar 2014 03:42:23 GMT
< Server: lighttpd/1.4.29
< Content-Length: 13
< Connection: keep-alive
<
172.30.0.235
* Connection #0 to host internal-proxy-284387118.us-west-2.elb.amazonaws.com left intact
* Closing connection #0
---
ExaProxy 946    usage         2014-03-01 03:42:23 1393645343.26 5 172.30.0.235 GET checkip.amazonaws.com/ PERMIT/checkip.amazonaws.com

アクセス元がELBのIPアドレスとして判定されてしまいました。これでは、誰が利用したのか分からなくなってしまいます。

ProxyProtocolを有効にする

というわけで、ProxyProtocolを有効にします。手順はこちらのページにある通りです。

$ elb-create-lb-policy proxy --policy-name EnableProxyProtocol --policy-type ProxyProtocolPolicyType  --attribute "name=ProxyProtocol, value=true"
OK-Creating LoadBalancer Policy
$ elb-set-lb-policies-for-backend-server proxy --instance-port 3128 --policy-names EnableProxyProtocol
OK-Setting Policies

これでアクセスしてみましょう。

$ http_proxy=http://internal-proxy-284387118.us-west-2.elb.amazonaws.com:3128 curl http://checkip.amazonaws.com
172.30.0.235
---
ExaProxy 946    usage         2014-03-01 03:49:45 1393645785.84 7 172.30.0.235 GET checkip.amazonaws.com/ PERMIT/checkip.amazonaws.com

IPがELBのままです。ExaProxy側でProxyProtocolを使用する設定を行う必要がありました。

$ vi etc/exaproxy/exaproxy.conf
:
$ diff etc/exaproxy/exaproxy.conf*
27c27
< proxied = false
---
> proxied = true

これでexaproxyを再起動して試してみます。

$ http_proxy=http://internal-proxy-284387118.us-west-2.elb.amazonaws.com:3128 curl http://checkip.amazonaws.com
192.168.1.154
---
ExaProxy 3337   usage         2014-03-01 03:53:24 1393646004.53 3 192.168.1.154 GET checkip.amazonaws.com/ PERMIT/checkip.amazonaws.com

正しくクライアント側のIPアドレスが記録されました!

まとめ

ログはシスログに送れるようなので、シスログサーバに集約〜fluentdでS3にアップロードとかが王道パティーンでしょうか(それぞれのサーバから直接S3でもいいですが)。