NginxでHTTP2を有効にする

  • 64
    いいね
  • 0
    コメント

はじめに

今まで僕のサイトはHTTP1.1で通信していたわけですが、せっかくセキュリティミニキャンプでHTTP2に対応したサーバーの立て方を習ってきたのでこれはやるしかないということで一念発起してサーバーを立て直しました。

OSごと入れ替えたわけなのですけれども、結構シンプルな構成にできたので割と気に入ってます。

そういうわけで、どんな設定をしているかとかそういう話をしたいと思います。もちろん、HTTP2なのでTLS対応サーバーとして構築しますよ!

Nginxのインストール

うちのサーバーはお察しの通りArch Linuxなのですが、普通のリポジトリから導入するとちょっと古いバージョンが入ってしまってHTTP2が利用できません。そこで今回はAURからnginx-mainlineを取ってきて導入します。

$ yaourt -S nginx-mainline

インストールはこれだけなのでとっても簡単です。

Nginxの設定

後で使うのでこの時点でサーバーを立ててしまいます。まだTLSには対応できないので、80番ポートで待ち受けておきます。/etc/nginx/nginx.confを編集してみます。

# 省略

http {
  # 省略
  include www.conf;
  # 省略
}

# 省略

www.conf にまとめてから読み込んでいる感じの設定です。ということでwww.confはこんな感じに書きましょう。

server {
  listen 80;
  server_name www.example.com;
  root /usr/share/nginx/www;
  index index.html;
}

とりあえず80番ポートで待ち受ける設定はこんな感じです。文法チェックをしてからサーバーを起動しておきましょう。

$ sudo nginx -t
$ sudo systemctl start nginx

Let's Encryptのインストールと証明書の発行

今回うちのサーバーではDV証明書としてLet's Encryptのものを利用しています。今回オープンベータになりまして、90日間有効な証明書を発行してもらえます。90日後に更新すればいいので、その辺も自動化していきましょう。とりあえずリポジトリにもう入っているので、さくさくっとインストールしてしまいます。

$ yaourt -S letsencrypt

インストールしたら次はドメイン証明書を発行します。管理者権限で行いましょう。なお証明書が複数欲しい場合は以下のような感じでwebroot-pathオプションとdオプションを後ろに足してください。

$ sudo letsencrypt certonly --webroot --webroot-path /usr/share/nginx/www -d www.example.com --webroot-path /usr/share/nginx/hoge -d hoge.example.com

するとダイアログが起動するので、メールアドレスを入力したり規約に同意したりしていけば証明書の発行が完了します。

証明書の登録

証明書と秘密鍵は/etc/letsencrypt/live/www.example.com/に収まっています。これをNginxの設定ファイルに登録しましょう。ついでにhttpからのリダイレクトもしておきます。なお証明書や鍵の入ったディレクトリは厳しいパーミッションが設定されており、容易には覗けないようにできています。

server {
  listen 80;
  server_name www.example.com;
  return 301 https://www.example.com$request_uri; 
}

server {
  listen 443 ssl;
  server_name www.aexample.com;

  root /usr/share/nginx/www;
  index index.php;

  ssl_certificate /etc/letsencrypt/live/www.example.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/www.example.com/privkey.pem;
}

HTTP2への対応

さて、ひとまずTLSでつながらないわけではないのですが、ここからHTTP2対応へと設定を変えていきます。

HTTP2での待ち受け設定

listenの箇所を変更するだけなのでとても簡単です。簡単のため、リダイレクト部分は省略します。

server {
  listen 443 ssl http2;
  server_name www.aexample.com;

  root /usr/share/nginx/www;
  index index.php;

  ssl_certificate /etc/letsencrypt/live/www.example.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/www.example.com/privkey.pem;
}

Deffie-Hellman共有鍵長の延長

デフォルトのDeffie-Hellman鍵は長さが短く、セキュリティ上好ましくありません。独自に2048bitで生成しなおして、これを設定します。ホームディレクトリで作成して、設定ファイルと同じディレクトリに移します。ちなみに生成には結構時間がかかるので気長にお待ちください。

$ openssl dhparam -out dhparam.pem 2048
$ sudo mv ./dhparam.pem /etc/nginx/

そしてこの鍵を使うように設定します。ssl_dhparamでファイルパスを設定するだけです。

server {
  listen 443 ssl http2;
  server_name www.aexample.com;

  root /usr/share/nginx/www;
  index index.php;

  ssl_certificate /etc/letsencrypt/live/www.example.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/www.example.com/privkey.pem;
  ssl_dhparam dhparam.pem;
}

HSTSヘッダの設定

HTTPでアクセスされても強制的にHTTPSで接続させるようにHSTSヘッダを追加します。add_headerで追加できます。max-ageは半年間に設定してあります。どうも短いと怒られるみたいなので。

server {
  listen 443 ssl http2;
  server_name www.aexample.com;

  root /usr/share/nginx/www;
  index index.php;

  ssl_certificate /etc/letsencrypt/live/www.example.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/www.example.com/privkey.pem;
  ssl_dhparam dhparam.pem;

  add_header Strict-Transport-Security "max-age=15768000; includeSubdomains";
}

前方秘匿性の確立

その場限りの暗号鍵を用いることで、万が一秘密鍵が流出してもそれ以前の通信の内容について復号できないようにします。この性質のことを前方秘匿性と呼びます。ECDHE(楕円曲線Deffie-Hellman鍵共有)を有効にすることで実現できます。ssl_ciphersを設定しましょう。ついでに非認証状態やMD5での符号化は拒否する設定にしてしまいます。

server {
  listen 443 ssl http2;
  server_name www.aexample.com;

  root /usr/share/nginx/www;
  index index.php;

  ssl_certificate /etc/letsencrypt/live/www.example.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/www.example.com/privkey.pem;
  ssl_dhparam dhparam.pem;
  ssl_ciphers  ECDHE+AESGCM:DHE+AESGCM:HIGH:!aNULL:!MD5;

  add_header Strict-Transport-Security "max-age=15768000; includeSubdomains";
}

キャッシュなどの設定

TLS通信で使われるキャッシュやその有効期限などを決めてしまいます。

ssl_session_cacheでは1MBだけのキャッシュを使うように設定しておきます。

ssl_session_timeoutでキャッシュの有効期限を設定します。今回は5分です。

ssl_prefer_server_ciphersをonにしておきます。こうすることでサーバー側が提示した暗号化方式を優先するようになります。今回はせっかく前方秘匿性のある暗号を用いていますので、これを強制するようにしておきます。

server {
  listen 443 ssl http2;
  server_name www.aexample.com;

  root /usr/share/nginx/www;
  index index.php;

  ssl_certificate /etc/letsencrypt/live/www.example.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/www.example.com/privkey.pem;
  ssl_dhparam dhparam.pem;
  ssl_ciphers  ECDHE+AESGCM:DHE+AESGCM:HIGH:!aNULL:!MD5;
  ssl_session_cache shared:SSL:1m;
  ssl_session_timeout 5m;
  ssl_prefer_server_ciphers on;

  add_header Strict-Transport-Security "max-age=15768000; includeSubdomains";
}

完成

ということでHTTP2に対応した比較的セキュアなサーバーが完成しました。443番ポートを開けて、アクセスしてみてください。SSL LABSでどのぐらいのレベルになるかチェックしてみるのも面白いかもしれません。この記事を投稿した時点でこのサイトもA+の判定をもらっていますので、割といい感じなのではないかと思います。でもこの結果はいつも同じというわけではなく、なにか重大な脆弱性が見つかったりなどするとそれに応じて対応レベルが上がっていきます。たまにチェックして、常にセキュアな設定にするように心がけるといいかもしれません。

完成したサーバーにアクセスするために、文法チェックをしてからサーバーを再起動しておきましょう。

$ sudo nginx -t
$ sudo systemctl restart nginx

Let's Encryptの自動化

最後に認証鍵の更新を自動化してしまいましょう。/etc/systemd/system/letsencrypt.serviceを新規作成して、以下のような内容を書き込みます。ExecStartのところには先ほど証明書の発行に使用したコマンドにemailの項目を増やしたものとなっているので、間違いなくコピペして入力してください。

[Unit]
Description=Letsencrypt manual renewal

[Service]
Type=oneshot
ExecStart=/usr/bin/letsencrypt certonly --agree-tos --renew-by-default --email email@example.com --webroot -w /usr/share/nginx/www -d example.com

続いてこれを1ヶ月ごとに実行するようタイマーを仕込みます。/etc/systemd/system/letsencrypt.timerを新規作成して、以下のとおりに書き込んでください。

[Unit]
Description=Monthly renewal on letsencrypt's certificates

[Timer]
OnCalendar=monthly
Persistent=true

[Install]
WantedBy=timers.target

あとはこれを有効にしておけばOKです。

$ sudo systemctl enable letsencrypt.timer
$ sudo systemctl start letsencrypt.timer

おわりに

結構簡単にTLS/HTTP2対応のサーバーが出来てしまうところに時代の進歩を感じますね。GoogleはTLS対応ページを優先的にクロールするとか発表していますし、そろそろHTTPSがデフォルトになる時代が来ているのかもなぁと思ったりもします。

となると今まではhttpsかどうかだけで安全なサイトかそうでないサイトかを判断してきたわけなのですが、この手はもう使えなくなってくるということでそれはそれでまた新たな課題がつきつけられた感じです。EV証明書かどうかをきっちり見分けなきゃいけないというところについて技術者じゃない人たちにどう説明していくか、これが今後の課題になるのかなと言ったところです。

ではではこの辺で。お疲れ様でした。