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

  • 26
    いいね
  • 3
    コメント
この記事は最終更新日から1年以上が経過しています。

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

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

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

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

1.elbをawscliで作成、proxy-protocol設定

参考:
AWS ELBのProxy Protocolを触ってみた
Enable or Disable Proxy Protocol Support - Elastic Load Balancing
今更 VPC で 複数の AZ をまたいだ ELB を試す(2)〜 awscli を使って 〜 - ようへいの日々精進 XP

・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"
]
}

2.nginxの設定について

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

参考:
Using Proxy Protocol With Nginx | chris lea
Module ngx_http_realip_module
#355(プロキシのプロトコルサポート) - nginx

/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

※<%= .. %>で囲んであるところはアトリビュート(変数)になっていて
 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

3.アクセスログの送信元IPを確認する

ログイン

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のプロトコルの指定が間違ってたところとかですかね。合わせて解説してる記事は特に見つからなかったので書いてみました。

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