Apitore blog

Apitoreを運営していた元起業家のブログ

Spring-bootでSSL(HTTPS)有効にするならnginxを使え~完結編~

はじめに

Spring-bootでSSL(HTTPS)を使うならnginxがオススメ。今回は今までQiitaに投稿してきたシリーズの総集編です。 amarec (20160724-204416)

関連記事

対象&できること

対象(関連記事が参考になった人にはこの記事も役立つ)

  • 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でやるのは運用上キツイかもしれません。