初めに
Let's Encryptがワイルドカード証明書のサポートの開始をアナウンスしてから5か月近くが経過しました。そろそろいろいろな情報が出回っている頃だし、私も自宅環境でワイルドカード証明書を使ってみようと思い具体的なやり方を調べてみたのですが、
という条件を付けると、意外なことにあまり情報がありませんでした。それでも試行錯誤を繰り返して、何とか上記の条件でワイルドカード証明書を取得・更新できるようになったので、これから同じことをやる人の参考になるかと思い、設定の手順をこちらに投稿してみることにしました。
前提
以下では証明書のsubjectのCNが*.example.org
であるようなワイルドカード証明書を取得する方法を説明しますが、そのためには以下の4点が必要となります。
- BINDを動かすマシン(DNSサーバ)に対して外部からDNSの問い合わせをすることが可能なこと
- example.orgの権威DNSサーバが
_acme-challenge.example.org
というサブドメインを1のDNSサーバに権限移譲していること - Certbotを動かすマシン(クライアント)から1のDNSサーバに対してDNSの問い合わせをすることが可能なこと
- クライアントから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
のワイルドカード証明書を取得するにあたり、次のような処理を行います。
- _acme-challenge.example.orgのSOAレコードが存在するか確認する
- _acme-challenge.example.orgのTXTレコードに認証用のデータを追加する
- 2で追加したTXTレコードを取得することによってドメインを認証する
- 証明書発行の処理を行う
- 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.example.org. TXT;
};
};
example.orgゾーンに関しては一般的なmasterの設定です。ただ_acme-challenge
という不正なサブドメインを利用するためにcheck-names ignore
の設定が必要です。
_acme-challenge.example.orgゾーンに関しては
update-policy {
grant certbot-key. name _acme-challenge.example.org. TXT;
};
この設定で_acme-challenge.example.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でも利用できるはずです。ただsystemd
を使っているのならば、
service ${service} reload || exit $?
のところは
systemctl reload ${service} || exit $?
とした方がより自然かもしれません。
次に以下の一行を/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の実行処理の中で、更新処理が実行されるようになります。