Edited at

CertbotとBINDの組み合わせでLet's Encryptのワイルドカード証明書を取得・更新する


初めに

Let's Encryptがワイルドカード証明書のサポートの開始をアナウンスしてから5か月近くが経過しました。そろそろいろいろな情報が出回っている頃だし、私も自宅環境でワイルドカード証明書を使ってみようと思い具体的なやり方を調べてみたのですが、


  • クライアントとしてCertbotを使う。

  • DNSサーバとしてBINDを使う。

  • 証明書の取得や更新をコマンド一発で完了するようにする(i.e. 取得や更新に設定ファイルの編集などの手作業を伴わない)。

という条件を付けると、意外なことにあまり情報がありませんでした。それでも試行錯誤を繰り返して、何とか上記の条件でワイルドカード証明書を取得・更新できるようになったので、これから同じことをやる人の参考になるかと思い、設定の手順をこちらに投稿してみることにしました。


前提

以下では証明書のsubjectのCNが*.example.orgであるようなワイルドカード証明書を取得する方法を説明しますが、そのためには以下の4点が必要となります。


  1. BINDを動かすマシン(DNSサーバ)に対して外部からDNSの問い合わせをすることが可能なこと

  2. example.orgの権威DNSサーバが_acme-challenge.example.orgというサブドメインを1のDNSサーバに権限移譲していること

  3. Certbotを動かすマシン(クライアント)から1のDNSサーバに対してDNSの問い合わせをすることが可能なこと

  4. クライアントからLet's Encryptのサーバに対してHTTPSでのアクセスが可能なこと

1は要するにDNSサーバはパブリックであることが必要で、外部からアクセスできない企業の内部ネットワークにあるDNSサーバとかでは駄目です。一方クライアントは必ずしもパブリックな環境にある必要はなく、3と4の条件を満たせば内部ネットワークにあるマシンでも構いません。

2に関しては、1のDNSサーバ自体がexample.orgの権威DNSサーバであるなら何も問題はないですし、そうでなくてもexample.orgのDNSサーバがBINDとかNSDとかPowerDNSを使っているのなら、単純にそのDNSサーバの管理者に設定をお願いできるかという問題になるのですが、レジストラのドメイン管理に付随するDNSサービスを利用しているとかだと話は難しくなるかもしれません。ユーザがゾーンデータをまったく編集できないというのはさすがにないと思いますが、サブドメインを認めていないサービスというのはありうるかもしれませんし、また認めていても_acme-challengeというのがドメイン名としては不正なので、指定できなかったりするかもしれません。この辺りは各サービスの仕様をご確認ください。以下の説明では、1のDNSサーバがexample.orgの権威DNSサーバであるとします。


環境

私が普段使っているのがFreeBSDなので、CertbotやBINDのインストールや設定の手順もFreeBSDを前提としたものになります。Linuxなど他のOSを使っている方は、インストールに関してはそれぞれのOSでの方法を別途調べていただく必要がありますが、設定に関してはファイルやディレクトリのパスを適宜読み替えていただけば他のOSでも通用すると思います。


ソフトウェアのインストール


BINDのインストール

FreeBSDのportsには複数のバージョンのBINDがありますが、現時点での最新の安定版は9.14系列なので、これをインストールします。

Portsを使ってインストールするなら、rootで以下のコマンドを実行します

make -C /usr/ports/dns/bind914 install

またpackageを使ってインストールするなら、同じくrootで以下のコマンドを実行します。

pkg install dns/bind914

インストールが完了したら、/etc/rc.conf.localに以下の一行を追加します。

named_enable="YES"

その後rootで

service named start

とすればnamedが起動されます。またマシンを再起動した場合などには、自動的にnamedが起動されます。


Certbotのインストール

CertobotとBINDの組み合わせでワイルドカード証明書を取得するには、Certbot本体のほかにdns-rfc2136というプラグインをインストールする必要があります。

FreeBSDでは

make -C /usr/ports/security/py-certbot-dns-rfc2136 install

または

pkg install security/py-certbot-dns-rfc2136

とすればCertbot本体とdns-rfc2136プラグインが両方インストールされます。


設定


認証鍵の作成

CertbotとBINDの組み合わせではRFC2136に基づくゾーンデータの動的更新を行うので、更新を認証する鍵を作成する必要があります。

BINDをインストールするとtsig-keygenというコマンドが/usr/local/sbinの下にインストールされますが、これを

tsig-keygen certbot-key

のように実行すると

key "certbot-key" {

algorithm hmac-sha256;
secret "nWvUxOpLj0YvptnyvkzmAm271P3jEXzUgXV+qGYAci8=";
};

このような内容のデータが標準出力に出力されるので、これをDNSサーバの/usr/local/etc/namedb/certbot-key.keyというファイルに書き出し、所有者をbind、パーミッションを0600に設定します。


BINDの設定

ここでは本題であるワイルドカード証明書の取得・更新に関係する設定のみを説明します。その他の一般的な設定についてはBIND 9 Administrator Reference Manualなどを参考にして適切に行ってください。

Certbotは*.example.orgのワイルドカード証明書を取得するにあたり、次のような処理を行います。


  1. _acme-challenge.example.orgのSOAレコードが存在するか確認する

  2. _acme-challenge.example.orgのTXTレコードに認証用のデータを追加する

  3. 2で追加したTXTレコードを取得することによってドメインを認証する

  4. 証明書発行の処理を行う

  5. 2で追加したTXTレコードを削除する

従ってBINDについてはそれに対応した設定を行う必要があります。


named.conf

/user/local/etc/namedb/named.confを以下のように設定します。

include "/usr/local/etc/namedb/certbot-key.key"; // 認証鍵の読み込み

zone "example.org" {
type master;
file "/usr/local/etc/namedb/master/example.org.zone";
masters {
10.100.200.1; // slaveのアドレス
};
check-names ignore;
};

zone "_acme-challenge.example.org" {
type master;
file "/usr/local/etc/namedb/dynamic/_acme-challenge.example.org.zone";
check-names ignore;
update-policy {
grant certbot-key. name _acme-challenge.home.utahime.org. TXT;
};
};

example.orgゾーンに関しては一般的なmasterの設定です。ただ_acme-challengeという不正なサブドメインを利用するためにcheck-names ignoreの設定が必要です。

_acme-challenge.example.orgゾーンに関しては

    update-policy {

grant certbot-key. name _acme-challenge.home.utahime.org. TXT;
};

この設定で_acme-challenge.home.utahime.orgのTXTレコードの動的な編集を許可しています。grantの直後に指定されているのは、認証に用いる鍵の名前です。


example.orgのゾーンファイル

以下のような内容で/usr/local/etc/namedb/master/example.org.zoneを作成します。

$TTL 1h

@ IN SOA ns1.example.org. admin.example.org. (
2018080300 ; Serial
1h ; Reflesh
15m ; Retry
30d ; Expire
1h ; Neg. cache TTL
)
IN NS ns1.example.org.
IN NS ns2.example.org.
ns1 IN A 10.100.100.1
ns2 IN A 10.100.200.1
_acme-challenge IN NS ns1.example.org.

重要なのは_acme-challengeサブドメインの権限をns1.example.orgに委譲していることでしょうか。

このファイルは読み出し専用なので、所有者をrootにしてパーミッションを0444とかにしておきましょう。


_acme-challenge.example.orgのゾーンファイル

以下のような内容で/usr/local/etc/namedb/dynamic/_acme-challenge.example.org.zoneを作成します。

$TTL 1h

@ IN SOA ns1.example.org. admin.example.org. (
2018080300 ; Serial
1h ; Reflesh
15m ; Retry
30d ; Expire
1h ; Neg. cache TTL
)
IN NS ns1.example.org.

このファイルはnamedによって更新されるので、所有者をbindにしてパーミッションを0644にしておきます。またどこか他の場所にこのファイルをバックアップしておいたほうが良いかもしれません。

さらに親ディレクトリである/usr/local/etc/namedb/dynamicも所有者をbindに、パーミッションを0755にしておく必要があります。これはゾーンデータが動的更新されると、namedがこのディレクトリの下に_acme-challenge.examle.org.zone.jnlという名前のファイルを作成するからです。FreeBSDでportsやpackageを使ってBINDをインストールした場合には、このディレクトリが所有者bind、グループbind、パーミッション0755で自動的に作成されます。


Certbotの設定

以下のような内容で/usr/local/etc/letsencrypt/dns-rfc2136.iniを作成します

# _acme-challenge.example.orgゾーンのmasterサーバのアドレス

dns_rfc2136_server = 10.100.100.1
# アクセスするポート番号
dns_rfc2136_port = 53
# 認証鍵の名前
dns_rfc2136_name = certbot-key.
# 認証鍵の値
dns_rfc2136_secret = (生成した鍵の値)
# 鍵の生成に用いたアルゴリズム
dns_rfc2136_algorithm = HMAC-SHA256

認証鍵に関する情報を含むため、このファイルは所有者をrootに、パーミッションを600に設定します。


アカウントの作成

証明書を取得する前にアカウントの作成を行う必要があります。

アカウントを作成するにはrootで以下のコマンドを実行します。

certbot register

実行すると


  • 連絡用アドレスの入力

  • 利用条件を受諾するか否かの確認

  • 連絡用アドレスをCertbotの開発元である電子フロンティア財団とシェアしてよいかの確認

が行われます。これらをすべてコマンド一発で完了させたければ、アドレスをシェアする場合には

certbot register --email admin@example.org --agree-tos --eff-email

シェアしない場合には

certbot register --email admin@example.org --agree-tos --no-eff-email

とすればよいです。


証明書の取得

証明書を取得するにはrootで以下のコマンドを実行します。

certbot certonly --dns-rfc2136 --dns-rfc2136-credentials /usr/local/etc/letsencrypt/dns-rfc2136.ini -d '*.example.org'

BINDの設定などに問題がなければ、証明書の取得が正常に終了します。

取得した証明書や秘密鍵は/usr/local/etc/letsencrypt/live/example.orgの下にあります。


証明書の更新

(2019年4月2日:証明書を利用しているアプリケーションのリロード処理をCertbotのdeploy-hookを用いる方式に変更)

(2019年10月12日:Port/packageに追加されたperiodic scriptを用いてCertbotを定期的に実行する方法を追記)

証明書を更新するにはrootで以下のコマンドを実行します。

certbot renew

このコマンドを実行するとCertbotは証明書の期限を確認して期限が迫っていれば証明書の更新処理を行います。

証明書が更新された場合、証明書を利用しているアプリケーション(Apache HTTP Server. NGINX, Sendmail, Postfix, Dovecot, etc.)に更新された証明書を読み込ませる必要があります。Certbotのrenewサブコマンドには証明書が更新されたときにコマンドを実行する仕組みがあるので、それを利用します。

まず以下のようなシェルスクリプトを/usr/local/sbin/certbot-deploy-hookとして作成します。

#!/bin/sh

LANG=C
PATH=/usr/local/bin:/bin:/usr/bin:/usr/local/sbin:/sbin:/usr/sbin
services="apache24 postfix dovecot"

if [ $(id -u) != 0 ]
then
echo "This command requires root previlege." 1>&2
exit 1
fi

for service in ${services}
do
service ${service} reload || exit $?
done
exit 0

スクリプトの変数servicesの値は証明書を利用しているアプリケーションにあわせて変更してください。なおPATHの値を適切に設定すれば、このスクリプトはLinux系のOSでも利用できるはずです。

次に以下の一行を/usr/local/etc/letsencrypt/cli.iniに追加(なければ作成)します。

deploy-hook = /usr/local/sbin/certbot-deploy-hook

これで証明書が更新された時に/usr/local/sbin/certbot-deploy-hookが実行されるようになります。

あとはrootのcrontabに

45 3 * * 6 /usr/local/bin/certbot renew

というエントリを追加しておけば、毎週土曜日の3時45分に更新処理が自動的に実行されます。

(2019年10月12日追記)

2019年9月9日のリビジョン511693のコミットでsecurity/py-certbotのportに更新処理を実行するweekly periodic scriptが追加されました。Quarterly branchにも2019Q4からこのコミットが含まれるようになったので、port/packageいずれを使っている場合でも、/etc/periodic.conf

weekly_certbot_enable="YES"

の一行を追加すれば、毎週土曜日の4時15分から実行されるweekly periodic scriptの実行処理の中で、更新処理が実行されるようになります。


参考文献