Certbot で発行した証明書を Heroku SSL で利用する

Heroku SSL の機能を確認するため、調査目的で Certbot を利用し証明書を発行した。Certbot で Let’s Encrypt の証明書を取得する場合、有効期限が90日と短く、定期的な更新作業が必要となるため、通常は Heroku の提供する Automated Certificate Management を利用するのが良い。

FAQ: What is the lifetime for Let’s Encrypt certificates? For how long are they valid? - Let’s Encrypt

ACME Challenge

デフォルトは HTTP-01 Challenge で /.well-known/acme-challenge/<token> でリクエストを受け付ける必要がある。このためにアプリケーションコードを書き換えたくなかったので、DNS-01 Challenge を利用した。Certbot の Manual プラグインには --preferred-challenges オプションがあり dns を指定できる。

https://certbot.eff.org/docs/using.html#manual

DNS-01 Challenge

コマンドを実行すると DNS に追加する必要がある TXT レコードの値が生成される。デフォルトでは /etc/letsencrypt に保存されるが、今回の例では ~/.certbot を使用している。

$ certbot certonly --manual \
  --config-dir ~/.certbot/config \
  --work-dir ~/.certbot/work \
  --logs-dir ~/.certbot/logs \
  --preferred-challenges dns \
  --agree-tos \
  -m [email protected] \
  -d hello.tetsuya.dev
...

Please deploy a DNS TXT record under the name
_acme-challenge.hello.tetsuya.dev with the following value:

h5VEci-fn-Ia5I8BHCi4yqJKmDM0KFRyEnvb1U41pvw

DNS に TXT レコードを追加後、反映には数分から数十分かかる場合がある。DNS challenge に失敗すると最初からやり直しとなるため、 dig コマンドを使って DNS から期待通りの TXT レコードの値が期待通り返却されるか、あらかじめ確認する。

$ dig +short TXT _acme-challenge.hello.tetsuya.dev
"h5VEci-fn-Ia5I8BHCi4yqJKmDM0KFRyEnvb1U41pvw"

ENTER を押すと認証が始まる。Congratulations! が表示されたら認証成功。

Heroku に証明書をアップロード

Manually uploading certificates and intermediaries の手順に沿ってアップロードする。

$ heroku certs:add \
  ~/.certbot/config/live/hello.tetsuya.dev/fullchain.pem \
  ~/.certbot/config/live/hello.tetsuya.dev/privkey.pem
Resolving trust chain... done
Adding SSL certificate to ⬢ tetsuya... done
Certificate details:
Common Name(s): hello.tetsuya.dev
Expires At:     2021-06-24 02:08 UTC
Issuer:         /C=US/O=Let's Encrypt/CN=R3
Starts At:      2021-03-26 02:08 UTC
Subject:        /CN=hello.tetsuya.dev
SSL certificate is verified by a root authority.

アップロードした証明書は certs:info で確認できる。

$ heroku certs
Name              Common Name(s)     Expires               Trusted  Type  Domains
────────────────  ─────────────────  ────────────────────  ───────  ────  ───────
dryosaurus-15681  hello.tetsuya.dev  2021-06-24 02:08 UTC  True     SNI   0

$ heroku certs:info --name dryosaurus-15681
Fetching SSL certificate dryosaurus-15681 info for ⬢ tetsuya... done
Certificate details:
Common Name(s): hello.tetsuya.dev
Expires At:     2021-06-24 02:08 UTC
Issuer:         /C=US/O=Let's Encrypt/CN=R3
Starts At:      2021-03-26 02:08 UTC
Subject:        /CN=hello.tetsuya.dev
SSL certificate is verified by a root authority.

Heroku へカスタムドメインの追加

Add a custom domain with a subdomain の手順に沿ってカスタムドメインを登録する。

$ heroku domains:add hello.tetsuya.dev
Configure your app's DNS provider to point to the DNS Target boiling-hamlet-2nu90czoepy3tmpgmkid9s8r.herokudns.com.
    For help, see https://devcenter.heroku.com/articles/custom-domains

The domain hello.tetsuya.dev has been enqueued for addition
Run heroku domains:wait 'hello.tetsuya.dev' to wait for completion
Adding hello.tetsuya.dev to ⬢ tetsuya... done

ドメイン登録後に表示される DNS Target を CNAME レコードに追加してしばらく待つと、カスタムドメインでのアクセスが可能になる。CNAME を忘れた場合 heroku domains コマンドで確認ができる。

$ heroku domains
=== tetsuya Heroku Domain

tetsuya.herokuapp.com

=== tetsuya Custom Domains

 Domain Name       DNS Record Type DNS Target                                            SNI Endpoint
 ───────────────── ─────────────── ───────────────────────────────────────────────────── ────────────────
 hello.tetsuya.dev CNAME           boiling-hamlet-2nu90czoepy3tmpgmkid9s8r.herokudns.com dryosaurus-15681

接続確認

アップロードした証明証が利用されているか確認。Issuer が Let’s Encrypt になっていることがわかる。

$ openssl s_client -connect boiling-hamlet-2nu90czoepy3tmpgmkid9s8r.herokudns.com:443 \
  -servername hello.tetsuya.dev 2>/dev/null | \
  openssl x509 -noout -issuer -subject -startdate -enddate
issuer=C=US, O=Let's Encrypt, CN=R3
subject=CN=hello.tetsuya.dev
notBefore=Mar 26 02:08:42 2021 GMT
notAfter=Jun 24 02:08:42 2021 GMT