SaaSにカスタムドメイン機能を実装するとします。顧客が自分の所有するドメインで、こちらのサービスを利用できるようにする機能です。
顧客A company-a.example
顧客B shop-b.example
顧客C store-c.example
末尾が .com ではなく .example になっているのは意図的です。RFC 2606が、文書中の例として使うために予約しているトップレベルドメインだからです。実在するドメインを例示に使うと、読者のアクセスが実際に飛び、そのドメインの管理者に影響が及びます。同じ理由で、この記事に出てくるIPアドレスもRFC 5737が文書用に予約している範囲から取っています。
顧客の利用者はブラウザーで https://company-a.example を開きます。HTTPSで応答するには、こちら側が company-a.example に対応するTLS証明書を保持している必要があります。証明書が無ければ、ブラウザーは警告を表示して接続を中断します。
顧客が1000社いれば、1000枚以上の証明書が必要になります。証明書には有効期限があるため、失効前の再取得が恒常的に発生します。
以下では、この発行と更新をどう自動化するかを扱います。DNSやCDNの詳細な知識は前提としません。
HTTPSに証明書が必要な理由
TLS証明書は、「このドメイン名を名乗ってよいのはこの相手である」ことを第三者が保証したデータです。その第三者を認証局 (CA、Certificate Authority) と呼びます。ブラウザーは、あらかじめ信頼している認証局の一覧を保持しており、受け取った証明書がそのいずれかによって発行されたものかを検証します。
したがって証明書は、自分で生成して自分で名乗るものではなく、認証局から発行を受けるものです。認証局は発行前に、必ず次の点を確認します。
そのドメインを、要求元が実際に管理しているか。
この確認が無ければ、所有していないドメインの証明書を第三者が取得でき、なりすましが成立します。この確認をどう自動化するかが本題です。
手作業だったころの手順
自動化される前の手順は次のとおりです。
1. サーバーで鍵と証明書署名要求 (CSR) を生成する
2. 認証局のWeb画面にCSRを登録する
3. 指定された確認用ファイルをWebサーバーに配置する
4. 認証局の検証が完了するのを待つ
5. 発行された証明書をダウンロードする
6. サーバーへ配置して再読み込みする
7. 期限が近づいたら 1 に戻る
対象が1サイトなら、年に1〜2回の作業です。手間はかかりますが、運用は成立します。
1000ドメインになると成立しません。7の更新契機が、常にどこかのドメインで発生し続けるためです。
ACMEが自動化したこと
この手順を機械化するために策定されたのがACMEです。Automatic Certificate Management Environmentの略で、RFC 8555で標準化されています。読みは「アクミー」です。
ACMEは証明書そのものではなく、証明書を取得・更新するためのクライアントと認証局の通信手順を定めた仕様です。上の1から7を、プログラムが実行できる形へ再定義したものにあたります。
ACMEクライアント
↓ 証明書の発行を要求する
認証局 (Let's Encrypt など)
↓ ドメイン管理権の証明課題を返す
ACMEクライアント
↓ 指定された方法で課題に応答する
認証局
↓ 検証して証明書を発行する
ACMEクライアント
↓ 期限が近づいたら再実行する
要点は、ドメイン管理権の証明までが機械化されていることです。人手が介在しないため、対象が1000ドメインでも運用できます。
代表的なACME対応の認証局が Let's Encrypt です。発行は無償です。
ドメイン管理権を証明する方法
ACMEは証明方法を複数定義しています。実際によく使われるのは次の2つです。
サイトにファイルを配置するHTTP-01
認証局は、指定した文字列を特定のURLで公開するよう要求します。
http://company-a.example/.well-known/acme-challenge/<トークン>
ACMEクライアントがそのパスへファイルを配置し、認証局がアクセスして内容を照合します。
認証局
↓ 指定URLへアクセスする
company-a.example/.well-known/acme-challenge/<トークン>
↓
内容が一致する
↓
ドメイン管理権ありと判定する
↓
証明書を発行する
ここで証明されるのは、そのドメイン宛のHTTPアクセスを受け取れることです。
DNSにレコードを追加するDNS-01
DNSへ一時的なTXTレコードを追加する方法です。
_acme-challenge.company-a.example TXT "認証局が指定した文字列"
認証局がこのレコードを問い合わせて照合します。ここで証明されるのは、そのドメインのゾーンにレコードを追加できることです。
このほかに、443番ポートのTLSハンドシェイク内で完結するTLS-ALPN-01があります。手順は異なりますが、証明する内容はHTTP-01とほぼ同じです。
顧客の作業量による方式の選択
技術的にはどちらも選択できます。ただしカスタムドメインでは、この選択が顧客側の作業量を直接決めます。
| 方式 | 顧客に依頼する作業 | 制約 |
|---|---|---|
| DNS-01 | ゾーンへのTXTレコード追加 | 更新のたびに全顧客へ依頼することになる |
| HTTP-01 | 不要 | 顧客のDNSが先にこちらを向いている必要がある |
DNS-01は自社ドメインであれば容易です。自社が管理するゾーンなので、レコードを自分で追加できます。
しかし顧客のドメインでは、レコードを追加できるのは顧客だけです。更新のたびに1000社へ依頼する運用は成立しません。そのためカスタムドメインでは、実質的にHTTP-01を選ぶことになります。
顧客へ依頼する作業はDNSの向き先変更だけになります。その代わり、次の問題を提供側が引き受けます。
証明書とDNSの順番の問題
HTTP-01では、認証局が対象ドメインへアクセスして検証します。つまり、そのドメイン宛のアクセスがこちらへ到達していなければ検証が通りません。
証明書を取得するには、顧客が先にDNSをこちらへ向けている必要があります。一方、DNSの向き先を変更した時点から、そのドメインへの利用者のアクセスもこちらへ到達します。
顧客がDNSの向き先を変更する
↓
利用者が https://company-a.example を開く ← 切り替え直後から到達する
↓
証明書が存在しない
↓
ブラウザーが警告を表示する
事前に取得しておくことはできません。DNSが向く前は、認証局からの検証アクセスも到達しないためです。
顧客へ「DNSを設定したら、管理画面で発行して数分待つ」と案内する方法もあります。ただしその数分間、顧客のサイトはエラーになります。顧客から見れば、設定した直後にサイトが停止したのと同じ状態です。
CaddyによるHTTPSの自動化
ここでCaddyというソフトウェアを取り上げます。nginxやApacheと同じ系統の、Webサーバー兼リバースプロキシです。
利用者
↓ HTTPS
Caddy
↓ HTTP / HTTPS
アプリケーション
Caddyの特徴は、HTTPSをあとから設定する機能としてではなく、原則として最初から自動化している点です。
設定ファイル (Caddyfile) に次の3行を書きます。
example.com {
reverse_proxy localhost:3000
}
これだけで、Caddyは次までを担当します。
example.com のDNSを確認する
↓
ACMEで証明書を取得する
↓
HTTPS (443番) で待ち受ける
↓
HTTP (80番) からHTTPSへリダイレクトする
↓
期限切れの前に自動で更新する
公開ドメインでは、Let's EncryptやZeroSSLといったACME対応の認証局を利用します。
By default, Caddy serves all sites over HTTPS.
Caddyは既定で、すべてのサイトをHTTPSで提供する。
nginxで同じ状態にする場合は、nginx本体に加えて、証明書を取得するcertbot、更新を起動するcron、証明書を再読み込みするスクリプトを組み合わせることになります。Caddyはこれらを一体化しています。
ただしこれは、ドメイン名が事前に判明している場合の動作です。設定に example.com と書いてあるからこそ、その名前で証明書を取得できます。カスタムドメインでは、どのドメインが来るかを起動時に確定できません。
接続が来た時点で証明書を取得する Caddy の On-Demand TLS
前節の順番の問題は、証明書を「事前に用意しておくもの」と捉えることから生じています。事前に用意しようとすると、DNSがまだ向いていない段階で取得する必要があり、HTTP-01では成立しません。
順序を逆にします。証明書を事前に用意せず、そのドメインへの最初の接続が到達した時点で取得します。
顧客がDNSを切り替えたあとの最初のアクセスであれば、認証局からの検証アクセスも同じ経路でこちらへ到達します。HTTP-01の前提はその時点で満たされています。「利用者の最初のアクセスが届くこと」と「証明書を取得できる状態になること」は、同時に成立します。この同時性を利用します。
顧客がDNSの向き先を変更する
↓
利用者の最初のアクセスが到達する
↓ ← この時点で、認証局からの検証も通る状態になっている
その場で証明書を取得する
↓
取得した証明書でハンドシェイクを完了する
これを実装したのが、Caddy の On-Demand TLS です。ドキュメントも、この機能が必要になる状況としてカスタムドメインを挙げています。
you do not know all the domain names when you start or reload your server... you are not in control of the domain names (e.g. they are customer domains)
サーバーの起動時や再読み込み時に、すべてのドメイン名が判明していない場合。あるいは、ドメイン名を自分で管理していない場合 (顧客のドメインなど)。
取得のタイミングについては、次のように書かれています。
dynamically obtains a new certificate during the first TLS handshake that requires it
それを必要とする最初のTLSハンドシェイクの最中に、新しい証明書を動的に取得する。
なお、On-Demand TLS は業界共通の用語ではなく、Caddy と、その証明書管理を担うライブラリ CertMagic の機能名です。CertMagic のドキュメントは、この方式を Caddy が最初に実装したと書いています。
Caddy was also the first to sport "on-demand" issuance technology, which obtains certificates during the first TLS handshake for an allowed SNI name.
Caddy は、許可された SNI 名に対して最初の TLS ハンドシェイク中に証明書を取得する「on-demand」発行の技術を、最初に備えた製品でもある。
nginx や Traefik に同名の機能はありません。同等のことを実現する場合は、別の手段を組み合わせることになります。
顧客のDNS切り替え直後の最初のアクセスが、そのまま発行の契機になります。顧客側の作業はDNSの変更だけで完結します。
発行してよい相手かの判断
未知のドメイン名を契機に証明書を取得する仕組みは、同時に、外部から任意のタイミングで発行処理を起動できる仕組みでもあります。任意のドメイン名でTLS接続を確立するだけで、認証局への問い合わせを繰り返し発生させられます。
Caddyのドキュメントも、この点を明記しています。
On-demand TLS must be both enabled and restricted to prevent abuse.
On-demand TLSは、有効にすることと制限をかけることの両方が必要である。悪用を防ぐためである。
制限の中心が ask です。Caddyは未知のドメイン名を受け取ると、証明書を取得する前に、指定されたURLへ照会します。
未知のドメイン名で接続が到達する
↓
Caddyが ask エンドポイントへ照会する
↓
アプリケーションが登録状況を確認する
↓
├ 登録済みの顧客ドメイン → 発行する
└ 未登録のドメイン → 発行せず接続を拒否する
照会の形式は単純です。Caddyはドメイン名をクエリ文字列で渡し、応答はHTTPのステータスコードだけで判定します。
If the endpoint returns a
2xxstatus code, Caddy will be authorized to obtain a certificate for that name. Any other status code will result in cancelling issuance of the certificate and erroring the TLS handshake.エンドポイントが
2xxを返した場合、Caddyはその名前の証明書を取得することを許可される。それ以外のステータスコードでは、証明書の発行を取りやめ、TLSハンドシェイクをエラーにする。
アプリケーション側に必要なのは、そのドメインが登録済みかを照会し、200または404を返すエンドポイントだけです。ただしこの照会は利用者のTLSハンドシェイクの途中で発生するため、応答が遅ければその分だけ接続の確立が遅延します。
証明書の発行そのものは自動化できます。しかし、誰のために発行してよいかの判断は自動化できません。ここは自社サービスの登録情報に基づいて判定します。
CAAレコードによる発行元の制限
ここまでの仕組みを揃えても、顧客側のDNS設定が原因で発行に失敗する場合があります。多いのはCAAレコードです。
CAA (Certification Authority Authorization) は、そのドメインの証明書を発行してよい認証局を、ドメインの管理者がDNS上で宣言するためのレコードです。書式は次のようになります。
company-a.example CAA 0 issue "letsencrypt.org"
↑ ↑ ↑ ↑
レコード型 │ タグ 値 (発行を許可する認証局)
フラグ
タグには、通常の証明書を対象とする issue、ワイルドカード証明書を対象とする issuewild、違反を検知したときの通報先を指定する iodef があります。
このレコードが効力を持つのは、認証局側に確認の義務が課されているためです。RFC 8659はこう規定しています。
Before issuing a certificate, a compliant CA MUST check for publication of a Relevant RRset. If such an RRset exists, a CA MUST NOT issue a certificate unless the CA determines that either (1) the certificate request is consistent with the applicable CAA RRset or (2) an exception specified in the relevant CP or CPS applies.
証明書を発行する前に、準拠する認証局は該当するRRsetが公開されていないかを確認しなければならない。存在する場合、認証局は、(1) その証明書要求が該当のCAA RRsetと矛盾しない、または (2) 関連する認証規程で定められた例外にあたる、と判断できない限り、証明書を発行してはならない。
CAAレコードが存在しなければ、どの認証局でも発行できます。issue が1つでもあれば、そこに列挙された認証局だけが発行できます。
カスタムドメインで問題になるのはこの点です。顧客が過去に別の認証局で証明書を取得しており、次の設定を残している場合があります。
company-a.example CAA 0 issue "digicert.com"
この状態では、Let's Encryptでの発行が拒否されます。こちら側の実装に問題は無く、DNSの向き先も正しく、HTTP-01の検証も通る状態で、発行だけが失敗します。原因の切り分けに時間がかかりやすいのはこのためです。
対処は、顧客に発行元を追加してもらうことです。CAAレコードは複数並べられます。
company-a.example CAA 0 issue "digicert.com"
company-a.example CAA 0 issue "letsencrypt.org"
なお、発行を一切許可しない指定もあります。RFC 8659は次の例を挙げています。
nocerts.example.com CAA 0 issue ";"
カスタムドメインの受け入れ時には、DNSの向き先だけでなくCAAレコードも確認しておくと、この失敗を事前に検出できます。
契約終了とドメイン失効の扱い
CAAのほかに、顧客側のドメイン運用に起因して発行や更新が止まる場合もあります。
顧客がDNSを戻した場合。 契約終了などで顧客がDNSの向き先を変更すると、HTTP-01の検証は通らなくなります。発行済みの証明書は有効期限まで残りますが、更新はできません。こちら側の設定からも顧客ドメインを削除する処理が必要です。
ドメインそのものの期限切れ。 顧客がドメインを更新しなければ、名前解決自体が停止します。ここはACMEの対象外です。
いずれも、証明書の自動発行の外側にある問題です。カスタムドメイン機能を運用に載せる際は、これらを検知して顧客へ通知する仕組みが必要になります。
まとめ
顧客のドメインでHTTPSを提供する構成は、次の形に落ち着きます。
顧客へ依頼する作業 DNSの向き先変更のみ
↓
検証方式 HTTP-01
(DNS-01は顧客の作業が要るため使いにくい)
↓
発行のタイミング 最初の接続が到達した時点 (On-Demand TLS)
↓
発行可否の判定 自社アプリケーションが ask で応答する
↓
更新 自動
ACMEが自動化したのは、ドメイン管理権の証明から証明書の取得までです。誰のために発行してよいかの判定と、発行できなかったときの原因の切り分けは、自動化の外に残ります。
そして、この仕組みには前提が1つあります。顧客がDNSをこちらへ向けられることです。
では、その向き先として顧客に何を案内するのか。CDNを契約すると、たいてい次のような1行を渡されます。
example.com のAレコードに 198.51.100.20 を設定してください
顧客に設定してもらうのは、この1行だけです。ただし、そのIPアドレスが実際に何を指しているのかは、ここまでで何も説明していません。次回以降の記事で説明予定です。
参考情報
- RFC 8555 - Automatic Certificate Management Environment (ACME)
- RFC 8659 - DNS Certification Authority Authorization (CAA) Resource Record
- RFC 2606 - Reserved Top Level DNS Names
- RFC 5737 - IPv4 Address Blocks Reserved for Documentation
- Let's Encrypt
- Caddy - Automatic HTTPS
- Caddy - Global options (on_demand_tls)
- CertMagic - Automatic HTTPS for any Go program