RM-BLOG

IT系技術職のおっさんがIT技術とかライブとか日常とか雑多に語るブログです。* 本ブログに書かれている内容は個人の意見・感想であり、特定の組織に属するものではありません。/All opinions are my own.*

【Cloudflare】【Heroku】HerokuのACMを使ってCloudflareのTLSフル(厳密)と繋げてみる

まあタイトルの通りなのだが。。。
Herokuのカスタムドメインを使ってSSL”無料で” 実現する方法として、よく言われているのが、前段にCloudflareを配置して、SSL/TLS設定を「フレキシブル」にしたうえでHerokuに繋げる、というやり方である。
Herokuの無料Dyno(Free Dyno)でカスタムドメインを利用する場合、SSLは対応していないので、前段にCloudflareを配置し、受け口をhttpsにして、CDNから先のHerokuにはnon-SSLで通信させて、とりあえずhttps通信を実現させている。

ただ、CloudflareのSSL/TLS設定はアカウント全体で1つなので、Cloudflare配下に複数のドメイン(サイト)を持つ場合、Herokuのためだけに「フレキシブル」にしておけない、ということもあると思う。
というか実際私はあったのだ。
具体的には、「フル(厳密)」を選択したかったのだが、上述の通りで、Heroku側がSSLに対応していないため、単純にこれを選択するとCloudflare→Heroku間の通信が失敗してHTTP 526エラーが返ってくる。
というわけでこれをどうやって実現したらよいか?を検証した記録。
まあ色々検索すれば出てくるのだが、自分のための備忘録を兼ねて。

準備・前提

  • 前提として、これを実現するには費用が発生するので、その点の認識が必要である。
  • Herokuのアカウントと、かつHobby以上のプランのDynoで動くアプリ。今回はHobbyを使った。
  • Cloudflareのアカウントと、かつドメインを移管済みであること。
  • できればHeroku CLIを使える環境があると良いが、Webコンソールでも確認できるので必須ではない
  • HerokuのリージョンはCommon Runtimeである(具体的にはUSを利用)でも多分Common RuntimeならEUでも同様だと思う
  • 記事に記載している画像や文章等の情報は2021年9月時点のものです。

Heroku側の設定

とりあえず適当にアプリを作る。
今回は、前回興味本位で作ったNext.jsのアプリを使ったが、アプリの中身は本旨ではないので、好きなもの用意すればよい。
その後、アプリのダッシュボードからアプリを選択し、Settings>SSL Certificatesの項に移動
f:id:rmrmrmarmrmrm:20210916233025p:plain

[Configure SSL]をクリックし、「Automated Certificate Management(ACM)」を選択、「Next」をクリック
f:id:rmrmrmarmrmrm:20210916233040p:plain

この時点でDynoのプランチェックが入り、DynoがFreeだと「Hobby以上のプランにしろ」と怒られる。
Hobby以上のプランだったらすんなり素通りし、ACMの設定完了となる。

SSL Certificatesの下にある「Domains」の項で「Add Domain」をクリック
f:id:rmrmrmarmrmrm:20210916233052p:plain

利用したいカスタムドメインを入力して「Next」
(このあとSSL設定の有無かなんかを選択するところがあったかも。画像取り忘れで失念。その場合、ACMの設定を選択することになる)
f:id:rmrmrmarmrmrm:20210916233106p:plain

ここでSSL証明書の発行検証待ちになる。
下記の画像は発行済みの画像だけど、最初は「ACM Status」の部分が「Waiting」などに表示されているはずである。
f:id:rmrmrmarmrmrm:20210916233116p:plain

上の画像で言う「Domain Name」と「DNS target」を控えておく。

Cloudflare側の設定

続いてCloudflare側の設定に移る。

DNSのページに移動し、「レコードを追加」
f:id:rmrmrmarmrmrm:20210916233138p:plain

  • タイプは「CNAME」を選択する。
  • 「名前」の部分にサブドメインを入力する。
    例えば自分の持ってるドメインexample.comで、今回使いたいカスタムドメインがtest.example.comなら、ここには「test」とだけ入力する。
  • IPv4アドレス」の部分に、Heroku側でドメイン発行時に生成された「DNS Target」を入力する。
  • Cloudflareとのプロキシ設定はOFFにする(「DNSのみ」を選択する)

あとページ下部にあるDNSSECを有効化しておく。(下の画像はDNSSEC有効化済の画像)
f:id:rmrmrmarmrmrm:20210916233128p:plain

CloudflareのSSL/TLS設定ページに移り、「フル(厳密)」を選択する。(これは選択した段階で更新が走る)
f:id:rmrmrmarmrmrm:20210916233147p:plain

HerokuのSSL証明書発行検証

基本的にはこれで設定完了である。(HerokuのSSL証明書検証だけでいえば、Cloudflareの「SSL/TLS設定」の「フル(厳密)」は不要だが)
しばらく待つとHeroku側でSSL証明書の検証が完了し、証明書が発行される。(上の画像みたいに「ACM Status」欄に「OK」が表示される)

DNSの設定にミスがあったり等、検証に失敗すると、ここで「ACM Status」欄にエラーが出る。
ステータスの種類や発生するエラー内容なんかはここのページに載っている。
DNSの伝搬に時間がかかる等の都合で、最初はエラーになるかもしれない。
上記のページにも「最大24時間は待ってくれ」というような記述もある(まあこれは証明書発行するサービスではよくある記述である)
Herokuの画面上でRefreshボタンを押すか、Heroku CLIheroku certs:auto:refresh -a [アプリ名]を実行すれば検証状況がRefreshされる。
CLIの場合、さらにheroku certs:auto -a [アプリ名]で、Herokuの画面で見ているのと同じ状況の確認ができる。

実際私もここでは最初エラーになった。
DNSの設定(つまりCloudflare側の設定)を何度かいじくってパターンを試して検証に成功した。
最後にDNSをいじくってから成功するまでは5分にも満たない程度だったので、最初からちゃんと設定できているならそのくらいで検証成功するのかもしれない。
Herokuの公式Docには、DNS targetとして、Herokuでドメインを追加した時に生成される「なんかゴチャゴチャした乱数付きの長い値」の他に、「利用したいカスタムドメイン+".herokudns.com"」(例えば利用したいカスタムドメインがtest.example.comだったらtest.example.com.herokudns.comをDNS Targetにする)というシンプルなtargetの2種類が紹介されていて、どっちでも利用できるような記述がされている。
このためこの2つをCNAMEの値とするパターンと、これに加えてCloudflareのプロキシ設定の有無で、合計4つの検証パターンがあった。
そのうち成功したのは1パターンで、本記事ではそれを紹介している形である。
ただ全パターンを検証したわけではなく、最初に成功したのがこのパターンだったというだけで、実際これが唯一の正解かどうかはわからない。
とりあえず試したパターンと結果を載せておく。

DNS Target Cloudflareプロキシ 結果
なんかゴチャゴチャした乱数付きの長い値 プロキシする 失敗
利用したいカスタムドメイン+".herokudns.com" プロキシする 失敗
なんかゴチャゴチャした乱数付きの長い値 DNSのみ 成功
利用したいカスタムドメイン+".herokudns.com" DNSのみ (未検証)

というわけで本記事では唯一(最終的に)成功したパターンを紹介している形である。
Cloudflareのプロキシ設定は、DNSの内容を隠蔽してしまう関係で(名前解決の結果がCloudflare側のAレコードに寄せられる)、恐らく外しておかないと(DNS設定のみにしておかないと)駄目な気はするが、DNS Targetは実際どっちが正しいのかわからない。
Heroku CLIでも、Herokuの画面でも、「なんかゴチャゴチャした乱数付きの長い値」が使われているので、なんとなくこのケースではこれが正しい気もするが、結果的に「なんかゴチャゴチャした乱数付きの長い値」のほうで通ったというだけで、「利用したいカスタムドメイン+".herokudns.com"」でも通るのかもしれない。
これは試してないからわからない。
なお、TokyoなどのPrivate Spaceでは、DNSのTargetも少し変わるらしい。
詳細は上のDocを参照。

他に少し気になった点といえば…

  • どうもHerokuはGoogleのPublic DNS(いわゆる8.8.8.8)で名前解決を図ろうととするらしい(↑のページにそう書いてある)
    このため、設定したDNSの内容がGoogle Public DNSで引けないとエラーになる。
  • 特に明記されていないが、DNSSECが有効でない場合エラーになる旨の記述がある(Incorrect DNS settingsというエラーになる)。
    これもあって前段でDNSSECを有効化するよう書いてるのだが、実際必須条件なのかどうかはわからない。
    とりあえず私のケースではDNSSECは有効なものとして先に進んだ。

余談

  • 冒頭で「 "無料で" SSL通信を実現する方法」と、わざわざ「無料」を強調したのは、少し個人的に引っかかりがあるからで、というのも、HerokuはFree Dynoであれば無料で済むのだが、Cloudflareを使う場合、事前にドメインの移管が必要になるはずで(Cloudflareは2021年9月現在新期ドメインの発行はできない)、そのためのドメイン取得料はかかるはずなのだ。(まあ安いドメインなら1円で買えるけど)
    あるいはそのまま放置していたらドメインの更新料も発生することもあるだろう。
    だから厳密に言うと、Cloudflare+HerokuでSSLを実現する場合、特別明記されていないだけで、こうした前提となる分にお金が発生すると思うのだ。
    このため「無料で」という言い方に少し違和感がある。
    まあCloudflareに入ってから以降の話であれば無料なのは事実なので、嘘は言ってないのだが…
  • CloudflareのSSL/TLS設定には「フル」("厳密"がついてないほう、便宜上ここでは「フル(無印)」と呼ぶことにする)と「フル(厳密)」の2種類が存在する。
    Cloudflare上の表示内容からすると、「フル(無印)」のほうは自己証明書で、「フル(厳密)」のはちゃんとしたCAから発行された証明書で、それぞれSSL通信すると読める。
    ということは「フル(厳密)」ではなく「フル(無印)」のほうを選択すれば、いわゆるオレオレ証明書でもSSL通信できるのかもしれない。
    Herokuでは、Heroku管理のもとで自動更新してくれるACMの他に、自分で用意した証明書を使う方法(いわゆるHeroku SSL)もあるし、ありがたいことに(?)自己証明書の作り方まで公開してくれている。
    つまりCloudflareの「フル(無印)」+Heroku SSLオレオレ証明書」もいけるんじゃねーの?説がある(試していない)
    まあACMだろうがHeroku SSLだろうが使うためにはDynoをHobby以上のプランにする必要がある(つまり課金対象にしなければならない)ため、オレオレ証明書を使う目的でわざわざHeroku SSLを選択する意味はない(同額かかるならACMのほうが絶対お得に決まっているのだ)し、そもそもオレオレ証明書を用意したところで結局通信時にブラウザが警告発してくるので、これを検証することにあまり意味はない、な…
    暇ならやってみようと思う。
  • すごいどうでもいい余談だが、無料でSSL証明書用意して、かつ自動で更新もしてくれる機能で、名前がACMと言われると、どうにも最初はAWSACMAWS Certificate Manager)を連想してしまう。
    たまたま偶然略称が同じACMになるだけで、Herokuの場合は名前が異なる(Automated Certificate Management)ので、注意が必要だ(?)