2023/01/23(Mon)セカンダリDNSサーバを運用しながらLet's Encryptワイルドカード証明書を発行する方法

2023/01/23 22:29 NetService
SSL証明書取得にLet's Encryptを使うケースをよく見ます。
2018年からワイルドカード証明書も取得可能になり、ホスト名をSAN(Subject Alternative Names)に全て列挙しなくても正当な証明書として認識できて便利なものの、セカンダリDNSを運用している場合や、既にあるプライマリDNSの設定を動的にし辛い/したくない場合はハードルが少し高く、既存構成に影響なく構築するのは少しテクニックが要るので、解説記事を書いておく。

チャレンジ

証明書を取得するに当たって、ドメインの正当なオーナーであることを示すために、いずれかの「チャレンジ」をpassする必要があります。
例えば、外部からアクセス出来るWebサーバの一定位置 http://<YOUR_DOMAIN>/.well-known/acme-challenge/<TOKEN> に発行されたトークンを含むファイルを置くだけで良い「HTTP-01チャレンジ」は一番ポピュラーで楽ですが、そのURLでアクセス可能であることしか担保できないので、これではあらゆるサブドメインにマッチ可能なワイルドカード証明書は取得できません。替わりに「DNS-01チャレンジ」が必要です。
「DNS-01チャレンジ」では、ファイルを置く替わりにトークンをTXTレコードにセットして権威DNSサーバが返答するようにしなくてはなりません。

しかし、このチャレンジは証明書の有効期限が切れる前に繰り返しDNSの変更を要し、(大概ルートドメインを管理する)DNSサーバの構成を動的に変更となると、色々難しい状況が出てきます。例えば、権威DNSサーバが1台でない場合や、変更がセカンダリに即時同期されない場合などでは、チャレンジの検証に必要なトークンをDNSサーバが応答できない状況となってしまい失敗するからです。
未確認ですが、ホスティングサイトが提供するAPIコールによって更新する場合は、独自の仕組みで即時反映するのかもしれませんが、自前環境のDNSサーバでは、rndcでのnotifyによるゾーン転送をやっても間に合いませんでした。
wildcert01.png

解決策

では、どうすればよいのかというと、(公式にも文章で書いてあるのですが)サブドメインへ委譲するという手が使えます。
設定的には、ns1.example.comで証明書を発行するとして、ルートドメイン側のゾーンに
example.com.                 86400 IN  SOA     ns1.example.com. 略
example.com.                 86400 IN  NS      ns1.example.com.
example.com.                 86400 IN  NS      ns2.example.com.
example.com.                 86400 IN  NS      ns3.example.com.
example.com.                 86400 IN  NS      ns4.example.com.

_acme-challenge.example.com. 86400 IN  NS      ns1.example.com.  <= この一行を追加
サブドメイン側のゾーンを新たに作成し、
_acme-challenge.example.com.    60 IN  SOA     ns1.example.com. 略
_acme-challenge.example.com.    60 IN  NS      ns1.example.com.
と定義します。
そして、証明書取得/更新時、動的にサブドメインゾーン側
_acme-challenge.example.com.    10 IN  TXT     "RRoaiPDdVmHdG...ZS3dgfcl0k"
のようなエントリが追加されたり削除されたりします。

絵で描くとこういう感じ。
wildcert02.png


こうしておけば、チャレンジは常にプライマリDNS ns1.example.com へ向くことになり、常に最新の応答を返す事が可能、そしてルートドメイン側のゾーンには全く影響を及ばさないので、↑のようにセカンダリDNSが4つも居る場合にも影響が出ません。
問題が起こるとすれば、ns1.example.comが_acme-challenge.example.comのゾーンを解決できない状況、つまり、ns1.example.com自体がハングしている場合などに問題が起きます。
このサーバ自体のDNSサーバ死活監視と共に、serveしているHTTPSサーバなどの有効期限が切れないかどうか別途監視しておく必要はあります。

注意1

ルート直下で無いサブドメインにワイルドカードを含む証明書を作る場合(*.sub.example.com等)、そのサブドメイン用のSOA(_acme-challenge.sub.example.com)も作る必要があります。

注意2

ワイルドカード証明書に限らずですが、certbotなどを用いて証明書発行する場合、テスト中は必ず --dry-run を付けて、ステージング環境で発行されるように実行して下さい。
本番環境では、1アカウント、1ホスト、1時間毎に5回以上検証に失敗すると、レート制限が掛かり新たな証明書を要求できなくなりますが、ステージング環境では、かなり制限が緩くなります。詳細は公式を参照下さい。

実行例

上記の説明の通り
  • ルートゾーンには、委譲用の_acme-challenge.example.com及び_acme-challenge.sub.example.comのNSとして、ns1.example.comのみが登録されている*1
  • ns1.example.comは、唯一_acme-challenge.example.com及び_acme-challenge.sub.example.comのSOAを別に管理している
  • 証明書取得/更新中、_acme-challenge.example.com及び_acme-challenge.sub.example.comのゾーンへTXTレコードの追加/削除がかかりserialが更新される
という感じです。
なおcertbotを実行したところでは、一番最初の-dで指定した名称がCommon Nameになるようです。
# certbot certonly \
   -d example.com \
   -d '*.example.com' \
   -d '*.sub.example.com' \
   -m root@example.com \
   --preferred-challenges dns-01 \
   --manual \
   --manual-auth-hook "/root/acme-dns.sh auth" \
   --manual-cleanup-hook "/root/acme-dns.sh cleanup" \
   --post-hook "/root/acme-dns.sh post"
/root/acme-dns.shは、ざっとこういうことをしています。
auth
チャレンジを行うドメイン$CERTBOT_DOMAINと、チャレンジトークン$CERTBOT_VALIDATIONを受け取り、自身に向けて_acme-challenge.$CERTBOT_DOMAINのTXTレコードとして$CERTBOT_VALIDATIONをセットするnsupdateを実行
cleanup
上記と同様だが、反対に$CERTBOT_DOMAINのTXTレコードを削除する
post
これはオプションだが、取得した証明書をリロードするためにHTTPサーバを再起動したり、イントラネットのみで使用しているサーバ類へ証明書を転送して使う、等の作業を実行する

*1 : ルートゾーンは、セカンダリDNSへゾーン転送されていても構わない。このNSの情報自体は基本的に変わらないので。

参考文献