はじめに
Spring-bootでSSL(HTTPS)を使うならnginxがオススメ。今回は今までQiitaに投稿してきたシリーズの総集編です。
関連記事
- Spring bootでSSL(HTTPS)を有効にする
- Spring bootでSSL(HTTPS)を有効にする~Let's Encrypt編~
- Spring bootでSSL(HTTPS)を有効にするならnginxを使え
対象&できること
対象(関連記事が参考になった人にはこの記事も役立つ)
- Spring-bootでウェブサービスを作っている
- Linux上で動いている
- ドメインを取得している
- Let's EncryptでSSL証明書を取得している
できること
- とってもセキュア
- 「http」アクセスを「https」へリダイレクトする
- 「https://www.example.com」と「https://example.com」を共存できる
- 「X-Forwarded-For」に正しいアクセス元のIPアドレスが入る
- アクセス制御(rate limiting)ができる
やったこと
ここでは「example.com」のドメインを取得しているとします。あらかじめGoogle Cloud DNSなどで「example.com」と「www.example.com」を同じIPアドレスに結びつけておいてください。Let's Encrypt等でSSL証明書を取得することも忘れずに。稼働しているLinuxサーバーにはnginxをインストールしておくことを忘れずに。 ネットをいくつか調べて思ったんですが、「www.example.com」は全て「example.com」にリダイレクトすべきなのかな。まあ、ウェブサービスなんて繋がって使えれば何でもいいので、そのへんはこだわりがなければお好きな感じでいいと思います。 さて、内容に入ります。
nginx.conf
設定ファイルを以下のようにします。 /etc/nginx/nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
limit_req_zone $binary_remote_addr zone=limit_req_by_ip:10m rate=5r/s;
limit_req_log_level error;
limit_req_status 503;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
include /etc/nginx/conf.d/*.conf;
set_real_ip_from xxx.xxx.xxx.xxx;
real_ip_header X-Forwarded-For;
}
nginxを何も考えずに有効にすると、稼働しているサービスで「X-Forwarded-For」を取ってアクセス解析したいときにハマります。すべて「127.0.0.1」からのアクセスになってしまいます。そこを解消するのが「set_real_ip_from」と「real_ip_header」です。「set_real_ip_from」には稼働しているサービスのIPアドレスを記入してください。 過剰なアクセスを制御する(rate limiting)設定は「limit_req_zone」です。気にすべきところは「rate=1r/s」くらいでしょうか。この設定ではひとつのIPアドレスから1秒間に1リクエストのみ許可するようにしています。DDoSアタックなんかに効果的。
default.conf
デフォルトでは「nginx.conf」の他に「default.conf」を使うので、そちらを変更する話をします。 設定ファイルを以下のようにします。 /etc/nginx/conf.d/default.conf
server {
listen 80 default_server;
listen [::]:80;
server_name example.com;
return 301 https://example.com$request_uri;
}
server {
listen 80;
listen [::]:80;
server_name www.example.com;
return 301 https://example.com$request_uri;
}
server {
listen 443;
listen [::]:443;
server_name www.example.com;
return 301 https://example.com$request_uri;
ssl on;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_certificate /etc/letsencrypt/live/www.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/www.example.com/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/www.example.com/fullchain.pem;
add_header Strict-Transport-Security 'max-age=31536000;';
add_header X-Frame-Options SAMEORIGIN;
#add_header X-XSS-Protection "1; mode=block";
#add_header X-Content-Type-Options nosniff;
}
upstream example_com {
server 127.0.0.1:8080;
}
server {
listen 443 ssl default_server;
listen [::]:443 ssl;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl on;
ssl_prefer_server_ciphers on;
ssl_dhparam /etc/nginx/ssl/dhparam.pem;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers ECDHE+RSAGCM:ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:!aNULL:!eNULL:!EXPO\
RT:!DES:!3DES:!MD5:!DSS;
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
resolver 8.8.8.8;
add_header Strict-Transport-Security 'max-age=31536000;';
add_header X-Frame-Options SAMEORIGIN;
#add_header X-XSS-Protection "1; mode=block";
#add_header X-Content-Type-Options nosniff;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Remote-Addr $remote_addr;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-forwarded-For $proxy_add_x_forwarded_for;
location / {
limit_req zone=limit_req_by_ip burst=10 nodelay;
proxy_pass http://example_com;
}
}
「server」という記述が多いのは、そこで「www.example.com」と「example.com」の共存を図りつつ、「http」から「https」へのリダイレクトを行っているからです。SSL証明書は「www.example.com」と「example.com」の両方を持っていないと、Chromeではリダイレクトさせてくれません。決して、「example.com」の証明書を「www.example.com」の証明書として代用してはいけません(1敗)。 「proxy~」の設定は「X-Forwarded-For」に正しいアクセス元のIPアドレスを入れるために必要なものです。先ほどの「nginx.conf」の設定と合わせることで、稼働しているウェブサービスで取得できる「X-Forwarded-For」が「127.0.0.1」の呪縛から開放されて、アクセス元の正しいIPアドレスを取れるようになります。 最後に「limit_req zone=limit_req_by_ip burst=10 nodelay」でアクセス制御をします。今回の場合は「https://example.com/」以下へのアクセスについて、「nginx.conf」で設定した通りの制御をかけます。「burst」の部分は過剰なアクセスがあったときに少し遅延を挟む処理になります。こうすることで、例えばイライラしたときのf5連打なんかは許容してあげられます。「nodelay」を書くと、サーバーにアクセスが集中しているときにf5連打なんかされたときはもう許さん、みたいな感じになります。
おわりに
一連のSpring-BootでSSL(HTTPS)を有効にするシリーズは、とりあえずこれで完成ではないでしょうか。まだ設定に工夫ができることがわかったら、改めて記事にします。
2016年8月6日 追記
よく知らずに手を出すと余計にリスク増えるけど、Nginxをよりセキュアにする設定情報があったのでリンクしておきます。 Let’s EncryptとNginx : セキュアなWebデプロイメントの現状 「セキュリティヘッダのハードニング」からがこの記事に足りない情報です。ただしやりようによってはセキュリティリスクが増加したり、webjarとか画像の直リンクとかが正常に表示されなくなります。 Let's EncryptのSSL証明書で、Qualys SSLTestでA+評価を獲得するには 「5.HPKP設定 (要注意)」がこの記事に足りない情報です。証明書を更新するたびに作りなおさないといけないと思うので、(セキュアにするためなのでそんなことも言ってられませんが)Let's Encryptでやるのは運用上キツイかもしれません。