ELBでssl転送してnginxでクライアント認証とssl終端してProxyProtocolで送信元IPを取得する

こんにちは。
タイトル長いですがだいたい成功して時がたったので需要があるかはわかりませんが記録しておきます。
お時間あるときにどうぞ。

目的としては、以下のとおりです
外部ELB→nginx(ssl終端かつクライアント認証)→内部ELB→app

つまり、クライアント認証したいけどELBにその機能はないのでtcp443転送してnginxでやるが上位サーバでとれる送信元IPがELBのIPになってしまう為
ELBでProxyProtocolを有効化してnginxでssl終端しつつProxyProtocolをListenして送信元IPをログに出したいという話です。

securityGroupとかインスタンス作るとかの部分は省略します。

<b>1．elbをawscliで作成、proxy-protocol設定</b>

参考：
<a href="http://canelmo.hatenablog.jp/entry/2013/08/04/163550" target="_blank">AWS ELBのProxy Protocolを触ってみた</a>
<a href="http://docs.aws.amazon.com/ElasticLoadBalancing/latest/DeveloperGuide/enable-proxy-protocol.html" target="_blank">Enable or Disable Proxy Protocol Support - Elastic Load Balancing</a>
<a href="http://inokara.hateblo.jp/entry/2014/01/02/040216" target="_blank">今更 VPC で 複数の AZ をまたいだ ELB を試す（2）〜 awscli を使って 〜 - ようへいの日々精進 XP</a>

・elb作成

```
profile=xxxxx
elbname-ext=xxxx-elb
securitygrops="sg-xxxxxxxx sg-yyyyyyyy"
subnets="subnet-xxxxxxxx subnet-yyyyyyyy"
sudo bash -c "aws elb create-load-balancer --load-balancer-name ${elbname-ext} --listeners Protocol=TCP,LoadBalancerPort=443,InstanceProtocol=TCP,InstancePort=443 --subnets ${subnets} --security-groups ${securitygrops} --profile ${profile}"
```

※外部ELBはTCP443からTCP443に転送するだけで証明書とか入れない感じの設定にします

```
elbname-int=yyyy-elb
securitygrops="sg-xxxxxxxx sg-zzzzzzzz"
sudo bash -c "aws elb create-load-balancer --load-balancer-name ${elbname-int} --listeners Protocol=TCP,LoadBalancerPort=80,InstanceProtocol=TCP,InstancePort=8xxx --subnets ${subnets} --security-groups ${securitygrops} --scheme internal --profile ${profile}"
```

・helthcheck設定

```
sudo bash -c "aws elb configure-health-check --load-balancer-name ${elbname-ext} --health-check Target="TCP:443",Interval=30,Timeout=5,UnhealthyThreshold=2,HealthyThreshold=10 --profile ${profile}"

sudo bash -c "aws elb configure-health-check --load-balancer-name ${elbname-int} --health-check Target="TCP:80",Interval=30,Timeout=5,UnhealthyThreshold=2,HealthyThreshold=10 --profile ${profile}"
```

・インスタンス登録

```
instances-ext="i-xxxxxxxx i-yyyyyyyyy"
instances-int="i-zzzzzzzz i-aaaaaaaaa"
sudo bash -c "aws elb register-instances-with-load-balancer --load-balancer-name ${elbname-ext} --instances ${instances-ext} --profile ${profile}"
sudo bash -c "aws elb register-instances-with-load-balancer --load-balancer-name ${elbname-int} --instances ${instances-int} --profile ${profile}"
```

・プロキシプロトコル有効化
ポリシーを作成する

```
sudo bash -c "aws elb create-load-balancer-policy --load-balancer-name ${elbname-ext} --policy-name EnableProxyProtocol --policy-type-name ProxyProtocolPolicyType --policy-attributes AttributeName=ProxyProtocol,AttributeValue=true --profile ${profile}"
```

ポリシーを確認する

```
sudo bash -c "aws elb set-load-balancer-policies-for-backend-server --load-balancer-name ${elbname-ext} --instance-port 443 --policy-names EnableProxyProtocol --profile ${profile}"
```

無効にする場合は、`--policy-names []` という空の指定をすると無効になります

プロキシプロトコルが有効になっていることを確認する

```
sudo bash -c "aws elb describe-load-balancers --load-balancer-name ${elbname-ext} --profile ${profile}"
"BackendServerDescriptions": [
{
"InstancePort": 443,
"PolicyNames": [
"EnableProxyProtocol"
]
}
```

<b>2．nginxの設定について</b>

chefでばらまいてますが細かいことは省略しまして、関係する設定内容だけのせます。
`proxy-protocol`設定反映は`reload`ではなく`restart`する必要があるのでログイン確認の際にnginxの`restart`を実施する必要があります。
実際のnginxの設定ファイル（成功当時のバージョンは1.7.4-1です）

参考：
<a href="https://chrislea.com/2014/03/20/using-proxy-protocol-nginx/" target="_blank">Using Proxy Protocol With Nginx | chris lea</a>
<a href="http://nginx.org/en/docs/http/ngx_http_realip_module.html" target="_blank">Module ngx_http_realip_module</a>
<a href="http://trac.nginx.org/nginx/ticket/355#no1" target="_blank">＃355（プロキシのプロトコルサポート） - nginx</a>

```
/etc/nginx/nginx.conf
-----------------------------------------------
log_format main '$proxy_protocol_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"'
'$http_x_userid - $http_x_signature - $http_x_sessionkey "$request_body"';
-----------------------------------------------
```

※`$remote_addr`でなく`$proxy_protocol_addr`に変更
※関係あるところだけ抜粋（http_x_sessionkeyとか`マスタリングNginx`からのコピペですが余計かもしれないです）

```
/etc/nginx/conf.d/ssl.conf
-----------------------------------------------
server {
# listen 443 default ssl;
listen 443 default ssl proxy_protocol;

server_name <%= node['nginx']['servername1'] %>;
set_real_ip_from <%= node['nginx']['set_real_ip_from'] %>;
real_ip_header proxy_protocol;

ssl_certificate /etc/nginx/<%= node['nginx']['sslcrt1'] %>;
ssl_certificate_key /etc/nginx/<%= node['nginx']['sslkey1'] %>;

ssl_prefer_server_ciphers on;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_session_cache shared:WEB:10m;
ssl_session_timeout 10m;
ssl_ciphers RC4:HIGH:!aNULL:!MD5:@STRENGTH;

## client-auth configuration
ssl_verify_client on;
ssl_verify_depth 3;
ssl_client_certificate /etc/nginx/<%= node['nginx']['clientcrt'] %>;
# ssl_crl /etc/nginx/<%= node['nginx']['clientcrl'] %>;

resolver <%= node['nginx']['resolver'] %> valid=5s;
resolver_timeout 3s;

location / {
proxy_set_header X-FORWARDED-PROTO https;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
proxy_connect_timeout 10s;
proxy_read_timeout 10s;
proxy_pass http://<%= @upstream1 %>:<%= @upstreamport %>$request_uri;

## backend-serverにclient-certを渡す.
# proxy_set_header ssl_client_cert $ssl_client_cert;

# root /usr/share/nginx/html;
# index index.html index.htm;
}
}
-----------------------------------------------
```

※上記のうち`ProxyProtocol`関係あるディレクティブは、以下のとおりです
`listen`
`set_real_ip_from`
`real_ip_header`

※&lt;%= .. %&gt;で囲んであるところはアトリビュート（変数）になっていて
　chefレシピ実行時に設定しておいた値に差し替えられ、実際の値とは異なりますのでお察しください。
　@の変数はroleとenvironmentで吸収しきれずdata_bagsから引っ張ってきたりなど。
※`set_real_ip_from` は、vpcのセグメントアドレス（例：10.0.0.0/17）を指定してます。
　ELBの実際のアドレスを書いとく必要があるけども特定できないし自動で振られるため。
※`ssl_crl`(破棄証明リスト)コメントにしてるのは、無期限的な事情によります。

※※`ProxyProtocol`を解釈できるバージョンは1.5.12以上です。それ以下は`configtest`でエラーでます。
※※1.4系で`--with-proxy-protocol`とかつけてソースからビルドする必要は全然ありませんし、
　1.7ですが特にビルドしたりせず素のパッケージをつかって実現できました。

nginxの1.7系(mainlineのやつ)を入れるためのyumリポジトリの中身は以下のような感じです。
chef的にはepelから入るのを防ぐ`options "--disablerepo=epel"`をpackageリソースに書く必要があったりなど。

```
[nginx]
name=nginx repo
baseurl=http://nginx.org/packages/mainline/centos/6/$basearch/
gpgcheck=0
enable=1
```



<b>３．アクセスログの送信元IPを確認する</b>

ログイン

```
ssh nginx-srv1-dev
ssh nginx-srv2-dev
```

確認

```
rpm -qa|grep nginx
sudo service nginx configtest
sudo service nginx restart
sudo service nginx status
sudo netstat -lnptu
chkconfig --list nginx
```

elbに追加後にInServiceになったらブラウザからアクセスしてログを確認

```
$ sudo tail -f /var/log/nginx/access.log
119.xxx.xxx.xxx - - [12/Aug/2014:04:39:28 +0000] "GET / HTTP/1.1" 400 648 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36" "-"- - - - - "-"
119.xxx.xxx.xxx - - [12/Aug/2014:04:39:28 +0000] "GET /favicon.ico HTTP/1.1" 400 648 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36" "-"- - - - - "-"
```

こんな感じに接続元のグローバルIPが出ていたので`ProxyProtocol`的には成功のようです。
クライアント認証のことはここではこれ以上のことは触れられませんがあしからずご了承ください。

個人的に苦労したのは記事中にリンクのってるchris leaさんのページに書いてあるとおりにやればいいだけというのを理解するまでの紆余曲折と
ELBのプロトコルの指定が間違ってたところとかですかね。合わせて解説してる記事は特に見つからなかったので書いてみました。

では読んでいただいてありがとうございました。どなたかの役に立ったら嬉しいです。