世はまさに総 HTTPS 時代!ということで、このブログも長らくほったらかしにしていたので、たまにはなんかしないとなと HTTP/2 化をやってみます。
まずは HTTPS を使えるようにする
HTTP/2 は現状 HTTP/2 over TLS しかサポートされていないため、まともに使おうと思うと HTTPS に対応しなければなりません。が、 SSL 証明書を購入するとなるとお金がかかります(正直1万円程度だからさっと買っても良いんだけど)。
んじゃあ無料で使える SSL 証明書ってないのか、というと、現代では Let’s encrypt という無料で証明書を発行してくれるサービスがあります。めっちゃくちゃ便利なので、ぜひ活用していきましょう。
Let’s encrypt から証明書を手に入れる
Let’s encrypt で証明書を発行する際、認証方式として HTTP-01 もしくは DNS-01 が選択できます。
HTTP-01 を利用する場合、事前に Web サーバーを構築してうんぬん……みたいな処理が必要になるため、どうしても少しだけ手間です。なので、今回は単純にコマンド一発で証明書の発行、保存まで出来る方法を取るべく、 DNS-01 での認証を行います。
DNSimple と DSLimple と dehydrated を利用した自動認証
認証チャレンジを行う部分については、シンプルにことが済む dehydrated を利用します。一個の小さな shell script で作られており、また、フックを利用してさまざまな更新を自動化することが可能です。
zeny.io のドメインは DNSimple で運用され、かつコードベースで言えば DSLimple を利用した DSL で管理されているので、自動化も簡単だろう、ということで DNS-01 方式を選びました。
dehydrated の hook script を組む
dehydrated は フックする shell script を追加することで、認証時にコマンドを挟み込む事ができます。
$ ./bin/dehydrated -c -d zeny.io -k hook.sh
↑ にかかれている hook.sh
がまさにそれです。このシェルスクリプトに DSLimple の呼び出しを挿入することで、ドメインの自動更新を行わせます。
#!/usr/bin/env bash
function deploy_challenge {
echo "==> Deploy challenge"
# ここで dehydrated から渡された値を環境変数にセット
export ACME_DOMAIN="${1}" ACME_TOKEN_FILENAME="${2}" ACME_TOKEN_VALUE="${3}"
bundle exec dslimple apply -y
sleep 5
}
function clean_challenge {
local DOMAIN="${1}" TOKEN_FILENAME="${2}" TOKEN_VALUE="${3}"
echo "==> Clean challenge"
# clean 時は何もセットせずに実行
bundle exec dslimple apply -y
sleep 5
}
function deploy_cert {
local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}" CHAINFILE="${5}" TIMESTAMP="${6}"
echo "==> Deploy cert"
# 新しい証明書が発行されたときはこの関数が呼ばれるので、アップロードなどはここで
}
function unchanged_cert {
local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}" CHAINFILE="${5}"
echo "==> Unchanged cert"
# 証明書に変更がなかったときの関数。特になにもすることがない?
}
HANDLER=$1; shift; $HANDLER $@
ここでの肝は ACME_DOMAIN
と ACME_TOKEN_VALUE
を環境変数としてセットした状態で DSLimple を呼び出すことです。後に改造する DSLimple の DSL のときに、その環境変数を見て、 DNS レコードを操作します。
また、新たに SSL 証明書が発行された場合は deploy_cert
関数が実行されるため、忘れずに証明書をどこかに保存するなりするコードを挟み込みましょう。僕の場合はその部分に S3 へのアップロードコードが書いてあります。
DSLimple で自動的に ACME challenge レコードを追加する
DNS-01 での認証の場合、指定したドメイン + _acme-challenge
の TXT レコードに先程 dehydrated から渡された ACME_TOKEN_VALUE
を設定してやる必要があります。
また、 clean 時にそのレコードを消しておかないと認証に失敗するため、 ACME_*
な環境変数がなければ、削除されるように書いておく必要もあります。ということでこんなコードを書きました。
domain 'zeny.io' do
# ... 他のレコード群 ...
if ENV['ACME_DOMAIN'] =~ /zeny\.io\Z/
subdomain = ENV['ACME_DOMAIN'].sub(/\.?zeny\.io\Z/, '')
parts = ['_acme-challenge', subdomain].reject(&:empty?)
txt_record(parts.join('.'), ttl: 1) { ENV['ACME_TOKEN_VALUE'] }
end
end
ACME_DOMAIN
から最後の zeny.io
部分を取り除き、先頭に _acme-challenge
を追加する、といった具合です。この形のコードにしておくことで zeny.io
で認証しても www.zeny.io
で認証しても sub.domain.www.zeny.io
で認証しても、問題なく実行できるようになります。
clean_challenge
の際には環境変数が設定されていませんから、単純に dslimple apply -y
で削除できる、という点でも素敵です。
あとはこれらを CI 上に設置して、毎日実行にするだけで
かんせーい!といった具合になります。僕の場合は Circle CI 上で実行させながら、 Google App Script で毎日朝9時ぐらいに API 経由で実行を kick する、といったようなことをしています。
なぜ毎日実行するかというと、 Let’s encrypt が発行してくれる証明書は息が短く、 90 日程度で(dehydrated のデフォルトは 30 日)失効してしまいます。なので、更新忘れて失効した!?といったトラブルをなくすべく、毎日更新にしています。
HTTP/2 サーバーを立てる
Let’s encrypt から自動で SSL 証明書が手に入るようになったのは万々歳ですが、やりたいのは HTTPS 化ではなく、 HTTP/2 化なので、それに対応した Web サーバーを立てるなり、別の Web サービスを利用するなりの対策が必要になります。
h2o を利用する場合
h2o の設定
h2o を利用して HTTP/2 化を進めます。
今回 HTTP/2 化したいのは、このブログ、ということで、特に動きのあるサイトではありません。単純な静的 HTML だけなので、簡単に設定ファイルを記述してしまいます。
hosts:
"*:80":
listen:
port: 80
paths:
/:
redirect:
status: 301
url: "https://zeny.io/"
"*:443":
listen:
port: 443
ssl:
key-file: /secret/certs/privkey.pem
certificate-file: /secret/certs/fullchain.pem
paths:
/:
header.set: "Strict-Transport-Security: max-age=31536000; preload; includeSubdomains"
header.merge: "Cache-Control: max-age=86400, must-revalidate"
file.dir: /var/www/html
file.dirlisting: OFF
file.etag: ON
file.send-compressed: ON
*:80
で HTTP 通信をしてきたら HTTPS へリダイレクト、あとは HSTS ヘッダをつけるのと、 Cache-Control をつけている程度です。 privkey.pem
と fullchain.pem
は dehydrated が出力してくれたものを採用します。
Docker コンテナ化
どういった形で配置するか悩みましたが、 Docker 形式で配置することにしました。幸い、 h2o 本家公認の Docker コンテナがあるので、それを使えば簡単に作ることが出来ます。
FROM lkwg82/h2o-http2-server:v2.0.2
ADD h2o.conf /etc/h2o
ADD certs /secret/certs
ADD build /var/www/html
certs
には dehydrated が出力したものを。 build
は Middleman が生成している HTML 群です。 Alpine ベースのコンテナなので、ごくごく小さなサイズで生成できるかと思います。
あとはこの Docker コンテナを配置して完了です。
AWS Cloud Front を利用する場合
このサイトはもともと S3 でホスティングされているので、特に手間を考えなければ単純に Cloud Front で配信してしまうのが簡単です。前はできなかったけど、今なら Cloud Front が HTTP/2 に対応しているので簡単です。
AWS Certification Manager に証明書をインポートする
前準備として、 us-east-1
に生成された証明書をアップロードしておく必要があります。
アップロード先のリージョンが us-east-1
なのは、後に Cloud Front で利用するためです。 ELB で利用する場合は、その ELB を構築する先のリージョンに証明書をインポートする必要があります。
Cloud Front の構築
ここはまぁ Web 上に情報がいっぱいあるので、その辺を参考にするのがいいかと思います。
完成!
という具合で、長くかかりましたが、なんとか AWS 上で HTTP/2 配信出来るようになりました……お疲れ様でした。
証明書更新の自動化
Cloud Front で運用できるところまではできたので、次はこれらの証明書の更新を自動化する部分を作ります。
と言いたいところですが、記事が長くなりすぎたので別の記事で……