HSTS の導入

独自ドメイン seaoak.jp を独自サーバに移行している中で、 HSTS (HTTP Strict Transport Security) をいう技術を知りました。

もともと全コンテンツを HTTPS 化するつもりだったので、 HSTS も導入したいところです。

せっかくなので、各ブラウザの HSTS Preload List に登録してもらいたい。そのためには、以下が必要っぽい:

最終的に、h2o.conf に次の1行を追加すればよいと思われます:

header.set: "Strict-Transport-Security: max-age=63072000; includeSubDomains; preload"

なお、https://hstspreload.org/ に何回も繰り返し書かれているように、安易に preload 指定するのは避けたほうがよさそうです。いったん HSTS Preload List に掲載してしまうと、 HTTPS 化できないサブドメインがどうしても必要になった時に非常に困ります。
https://hstspreload.org/#removal

とりあえず、しばらくは様子見ですね。

Let's Encrypt の導入(root 権限なし)

独自ドメインを全面的に HTTPS 化するべく、無料で発行してもらえる Let’s Encrypt のサーバ証明書を導入しました。

ドキュメントによるとサーバの root 権限が必要とのことですが、個人的な趣味としてそれは避けたい。

  • Web サーバを実行するユーザに sudo 権限を与えたくない
  • Web サーバの設定ファイルを自動的に書き換えられるのはイヤ(そもそも H2O は非対応ですが)
  • 証明書ファイル (key file / cert file) はローカルに作成できれば十分

というわけで、なにか手を考えないといけません。

Certbot のドキュメントで、 root 権限を使わない ACME client が紹介されています:

README を読むと letsencrypt-nosudo は root 権限での手作業が必要とのことなので、全自動化できそうな simp_le を試しました。

まず、各ドメインのドキュメントルートの直下に .well-known というディレクトリがあり(なければ作り)、そのディレクトリへの書き込み権限があることが前提です。また、そのディレクトリ内のファイル/ディレクトリに対して外部から(Let’s Encrypt のサーバから) TCP 80 番ポートで HTTP GET できなければなりません。 HTTPS ではないので注意。全面的に HTTPS 化する場合、すべての HTTP アクセス(80番ポート)を HTTPS (443番ポート)に 301 リダイレクトすることがあると思いますが、 /.well-known 配下へのアクセスだけはリダイレクトから除外します。 H2O の場合、h2o.conf で次のようにします:

hosts:
  "example.com:443":
    paths:
      "/":
        file.dir: /path/to/doc-root
  "example.com:80":
    paths:
      "/":
        redirect:
          url: "https://example.com/"
          status: 301
      "/.well-known":
        file.dir: /path/to/doc-root/.well-known

ちなみに、H2O ではデフォルトで /.well-known 配下がそのまま見えました。 H2O は隠しファイル(名前がドットで始まるもの)を特別扱いしないようです。

さて、ここからは simp_le のインストールの話です。

simp_le の公式ドキュメントでは bootstrap.sh を root 権限 (sudo) で実行するようにと書かれていますが、中身は apt-get install だけなので、手動でやれば十分です(ここだけ root 権限が必要ですすみません)。また、今回は pyenv / pyenv-virtualenv を利用したかったので、 venv.sh も中身を見て手動で実行しました。

python の初期設定については過去記事「Python の導入(root 権限なし)」を参照してください。

まず、bootstrap.sh 相当のことを手動でやる:

$ sudo apt-get install -y ca-certificates gcc libssl-dev libffi-dev python python-dev
$ cd
$ git clone https://github.com/kuba/simp_le.git
$ cd simp_le
$ pyenv shell 2.7.13

次に、venv.sh 相当のことを手動でやる:

$ pyenv virtualenv --no-site-packages venv-simp_le
$ pyenv virtualenvs
$ pyenv versions
$ pyenv local venv-simp_le
$ pyenv virtualenvs
$ pyenv versions
$ pyenv exec pip list
$ pyenv exec pip install -U setuptools
$ pyenv exec pip install -U pip
$ pyenv exec pip install -U wheel
$ pyenv exec pip install -e .
$ pyenv exec pip list

PATH はすでに通ってるので、設定不要でした。

ヘルプはちゃんと読みましょう:

$ simp_le --help

以上で simp_le のインストールは完了です。

続いて、証明書を新規に取得します。

解説記事を読むと account_key.json をあらかじめ用意しないといけないように思えますが、不要です。 -f オプションで指定するファイルはすべて simp_le が生成してくれます。 simp_le を最初に実行するディレクトリは空でよい。

$ cd ~/h2o
$ mkdir letsencrypt
$ cd letsencrypt
$ pyenv local venv-simp_le
$ simp_le -v --email 'foobar@example.com' -f account_key.json -f cert.pem -f chain.pem -f fullchain.pem -f key.pem -d example.com:../doc-root-1 -d www.example.com:../doc-root-2 -d blog.example.com:../doc-root-3

ここで、エラーになってしまいました。

DeserializationError: Deserialization error: Wrong directory fields

Unhandled error has happened, traceback is above

Debugging tips: -v improves output verbosity. Help is available under --help.

GitHub の Issue#114 に従って --tos_sha256 オプションを追加してみる:
https://github.com/kuba/simp_le/issues/114#issuecomment-236744611

$ simp_le -v --tos_sha256 6373439b9f29d67a5cd4d18cbc7f264809342dbf21cb2ba2fc7588df987a6221 --email 'foobar@example.com' -f account_key.json -f cert.pem -f chain.pem -f fullchain.pem -f key.pem -d example.com:../doc-root-1 -d www.example.com:../doc-root-2 -d blog.example.com:../doc-root-3

しかし変わらず。

ここで、GitHub の Issue を追っていると、 Fork して修正してくださったものを見つけました。感謝!!

https://github.com/zenhack/simp_le

$ cd
$ pyenv virtualenv-delete venv-simp_le
$ rm -rf simp_le
$ git clone https://github.com/zenhack/simp_le.git
$ cd simp_le
$ pyenv virtualenv --no-site-packages 2.7.13 venv-simp_le
$ pyenv virtualenvs
$ pyenv versions
$ pyenv local venv-simp_le
$ pyenv virtualenvs
$ pyenv versions
$ pyenv exec pip list
$ pyenv exec pip install -U setuptools
$ pyenv exec pip install -U pip
$ pyenv exec pip install -U wheel
$ pyenv exec pip install -e .
$ pyenv exec pip list
$ cd ~/h2o/letsencrypt
$ ls -a    ←中身は `.python-version` のみ
$ simp_le -v --email 'foobar@example.com' -f account_key.json -f cert.pem -f chain.pem -f fullchain.pem -f key.pem -d example.com:../doc-root-1 -d www.example.com:../doc-root-2 -d blog.example.com:../doc-root-3

今度は成功! インタラクティブな問い合わせとか無くて、全自動で完了です。

カレントディレクトリに -f オプションで指定した5個のファイルが生成されています。

$ ls -1
account_key.json
cert.pem
chain.pem
fullchain.pem
key.pem
$

あとは h2o.conf に次のように指定してあげて kill -HUP すれば OK。

listen:
  port: 443
  ssl:
    certificate-file: letsencrypt/fullchain.pem
    key-file: letsencrypt/key.pem

ついでに自動更新スクリプトも作成。 解説記事の update_cert.sh を参考にさせていただきました。

update_cert.sh.20170113a.txt

rotatelogs コマンドが無い人は apt-get install -y apache2-utils で入ります。

Certbot のドキュメントで一日2回やることを推奨しているので、crontab を設定:
https://certbot.eff.org/#ubuntuxenial-other

$ crontab -l
13 10 * * * /home/foobar/h2o/letsencrypt/update_cert.sh
37 23 * * * /home/foobar/h2o/letsencrypt/update_cert.sh
$

以上、root 権限なしで Let’s Encrypt のサーバ証明書が取得できました。