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やインスタンス作るなどの部分は省略します。
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作成
1 2 3 4 5 6 |
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に転送するだけで証明書を入れない感じの設定にします
1 2 3 4 |
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設定
1 2 3 4 |
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}" |
・インスタンス登録
1 2 3 4 5 |
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}" |
・プロキシプロトコル有効化
ポリシーを作成する
1 2 |
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}" |
ポリシーを確認する
1 2 |
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 []
という空の指定をすると無効になります
プロキシプロトコルが有効になっていることを確認する
1 2 3 4 5 6 7 8 9 |
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
を実施する必要があります。
参考:
Using Proxy Protocol With Nginx | chris lea
Module ngx_http_realip_module
#355(プロキシのプロトコルサポート) – nginx
実際のnginxの設定ファイル
(成功当時のバージョンは1.7.4-1です)
1 2 3 4 5 6 7 8 |
/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
からのコピペですが余計であれば外してください)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
/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ですが特にビルドしたりせず素のパッケージをつかって実現できました。
※upstreamやめてresolver 追加しました。
nginxの名前解決はマスタリングNginxのP94みたらsetで変数に入れると毎回名前解決し、
それ以外resolverのvaridで指定したttlのタイミングで聞きに行くそうです。
upstreamと*_passで指定した名前を解決するのは最初の一回だけでresolver必要だそう。
nginxの1.7系(mainlineのやつ)を入れるためのyumリポジトリ
中身は以下のような感じです。
chef的にはepelから入るのを防ぐoptions "--disablerepo=epel"
をpackageリソースに書く必要があったりなど。
1 2 3 4 5 6 |
[nginx] name=nginx repo baseurl=http://nginx.org/packages/mainline/centos/6/$basearch/ gpgcheck=0 enable=1 |
3.アクセスログの送信元IPを確認する
ログイン
1 2 3 |
ssh nginx-srv1-dev ssh nginx-srv2-dev |
確認
1 2 3 4 5 6 7 |
rpm -qa|grep nginx sudo service nginx configtest sudo service nginx restart sudo service nginx status sudo netstat -lnptu chkconfig --list nginx |
elbに追加後にInServiceになったらブラウザからアクセスしてログを確認
1 2 3 4 |
$ 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のプロトコルの指定が間違ってたところですかね。合わせて解説してる記事は特に見つからなかったので書いてみました。
nginxユーザー会のイベントで開発者のイゴールさんがSSLつかうなら新しいバージョンを推奨していた意味の一部分を身を以て知ったのでした。
特定デバイスからかぎっ子したい時に役立つかもしれません。
では読んでいただいてありがとうございました。どなたかの役に立ったら嬉しいです。