概要
Certbotがプラグインで対応しているDNSサービスのうち、無料プランを持つサービスの一つである LuaDNS を使用し、Docker上のCertbotからのDNS-01チャレンジによりLet's Encryptのワイルドカード証明書の取得、およびcronによる自動更新を行う。
使い方にもよるが扱うドメインが3個までであれば完全に無料でワイルドカード証明書を利用できる。
※本稿はRHEL8互換環境を前提とする
1. LuaDNS
その名の通り「Luaスクリプトを使用してgitリポジトリ経由でレコードを編集できる」という特徴的な機能を持つDNSサービスで、3ゾーン30レコードまで使える無料プランがある。
無料プランでもほぼフル機能が使用でき、クエリ数は無制限、TTLは最短5分に設定できる。
なお、本稿ではWebUIから手動で設定する。
1-1. サインアップ
サインアップ画面でメールアドレスとパスワードを入力して「Sign Up」ボタンを押し、入力したメールアドレスに届いた確認メールからアカウント作成承認画面を開いて完了する。
1-2. レコードの編集
ヘッダーメニューの「Zones」→「Add Zone」ボタン→「Name」入力欄にドメイン名を入力して「Add Zone」ボタンで編集画面を開く。
- 記法はbind互換
- 保存後にヘッダーメニューのZones→Zone名と移動するとほぼbindのゾーンファイルそのままの内容が表示される、確認に便利
- SOAレコードとNSレコードは自動で設定される
- ドメインを管理しているレジストラ等で対象ドメインのネームサーバーをここで表示されるLuaDNSのものに設定する(末尾の
.
は不要)
- ドメインを管理しているレジストラ等で対象ドメインのネームサーバーをここで表示されるLuaDNSのものに設定する(末尾の
- Certbotは動作時に
--domains
オプションで指定したドメインの数だけ一時的にDNS認証用のTXTレコードを追加する- 無料プランの場合は「手動設定のレコード数+Certbotが一時的に追加するレコード数」の合計を30件以下に収める必要がある点に注意
- DKIMレコードなども使っていると無料枠ではドメイン2個が限界になる場合もある
- 無料プランの場合は「手動設定のレコード数+Certbotが一時的に追加するレコード数」の合計を30件以下に収める必要がある点に注意
1-3. APIアクセスの有効化とCertbot用認証情報ファイルの作成
- LuaDNSのヘッダーメニューから「Account」に入る
- 「Show Token」のボタンを押して表示されたトークンをメモ
- 「Enable API Access」のチェックを入れる
- 「Update Settings」ボタンを押す
-
Certbot用のLuaDNSの認証情報ファイルを作成する
- ファイル名は例に倣って
luadns.ini
とする - LuaDNSに登録したメールアドレスと先の手順でメモしたトークンを記入する
- ファイル名は例に倣って
# LuaDNS API credentials used by Certbot
dns_luadns_email = webmaster@example.com
dns_luadns_token = 1234567890abcdef1234567890abcdef
2. Certbot
Certbot公式のdockerイメージのうち、LuaDNS用プラグインの入ったものを使用する。Certbotの詳細は公式ドキュメントを参照のこと。
なお、本稿では手順1-3で作成した luadns.ini
を除き、Certbotが入出力するファイルの置き場所はDocker上とホスト上で一致させる。
2-2. 証明書取得
Composeファイル
証明書の取得は通常1回のみの操作になるため、Composeファイルのファイル名をドメインに一致させ、コマンドラインオプションで明示的に指定して実行することとする。
services:
certbot:
image: certbot/dns-luadns:latest
command: >-
certonly
--dns-luadns
--dns-luadns-credentials /root/.secrets/certbot/luadns.ini
--dns-luadns-propagation-seconds 30
--non-interactive
--agree-tos
--email <EMAIL>
--domains <DOMAIN>,*.<DOMAIN>
volumes:
- /etc/letsencrypt:/etc/letsencrypt
- /var/lib/letsencrypt:/var/lib/letsencrypt
# LuaDNS認証情報は適当な場所に置いてマウントする
- ./luadns.ini:/root/.secrets/certbot/luadns.ini:ro
# ログを取りたい場合コメントを外す
#- /var/log/letsencrypt:/var/log/letsencrypt
-
--dns-luadns-propagation-seconds 30
はTXTレコード追加の実行からDNS認証の実行までの待機秒数- DNS認証用TXTレコードは使用後に削除されるため、LuaDNSの管理画面でレコードの変化を確認したい場合は長めにしておく
- 本番時の適切な設定は不明だが、何度か試した限りではLuaDNSへのTXTレコード追加の反映はほぼ即時だった
-
--email <EMAIL>
で設定するメールアドレスは有事の際に唯一の連絡手段になるため使用可能なものを設定しておくのが無難 -
--domains <DOMAIN>,*.<DOMAIN>
でベースドメイン(サブドメインなし)とワイルドカード(すべてのサブドメイン)の両方を対象としている - 実行せずにテストしたい場合はcommandに
--dry-run
オプションを足す - 取得時の設定がホストの
/etc/letsencrypt/renewal/<DOMAIN>.conf
等に保存され、更新時に使用される - 証明書はホストの
/etc/letsencrypt/archive/<DOMAIN>
に保存され、最新のものが/etc/letsencrypt/live/<DOMAIN>
にシンボリックリンクで配置される- 通常はliveに配置された最新の証明書を使用する
実行
# 初回実行時のみ実施
$ docker compose -f <DOMAIN>.yml pull
# 取得実行
$ docker compose -f <DOMAIN>.yml up
一時的にDNS認証用のTXTレコードが追加されるとLuaDNSのレコード一覧は以下のようになる。
2-3. 証明書更新
Composeファイル
services:
certbot:
image: certbot/dns-luadns:latest
command: >-
renew
--deploy-hook="echo DEPLOY_HOOK_CALLED"
volumes:
- /etc/letsencrypt:/etc/letsencrypt
- /var/lib/letsencrypt:/var/lib/letsencrypt
# LuaDNS認証情報
- ./luadns.ini:/root/.secrets/certbot/luadns.ini:ro
# ログを取りたい場合コメントを外す
#- /var/log/letsencrypt:/var/log/letsencrypt
- ホストの
/etc/letsencrypt/renewal
に保存されている設定が読み込まれるためオプション等の指定は不要 - すべての設定に対して更新を試みるため、証明書を複数保持している場合でも1回実行すればよい
- 更新が行われた場合のみ
--deploy-hook
でログにDEPLOY_HOOK_CALLED
の文字列を出力する- この文字列を検出したら証明書更新時に必要な処理を実行する(後述)
- 実行せずにテストしたい場合はcommandに
--dry-run
オプションを足す
自動実行
定期的に前節のComposeファイルを実行すればよい。なおCertbotの仕事が終わるとコンテナは停止状態( docker compose stop
と同等)になる。
$ docker compose up
例えば /etc/cron.weekly
に以下のようなシェルスクリプトを置き、 chmod +x /etc/cron.weekly/certbot-renew.sh
で実行権限を付与すれば1週間毎に実行される。
#!/bin/bash
# cronのシェルはPATHが通っていないので通す
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
# 更新用の docker-compose.yml が /root/docker/certbot に置いてある場合
cd /root/docker/certbot || exit 1
res=$(docker compose up)
# DEPLOY_HOOK_CALLED の文字列がログにあれば更新されたと見なす
if echo "${res}" | grep -q "DEPLOY_HOOK_CALLED"; then
# 更新実行時の処理
# 例: Nginxのプロセス順次再起動
docker exec nginx nginx -s reload
# 例: Apacheのプロセス順次再起動
docker exec httpd apachectl graceful
# 例: php-fpmの再起動
docker restart php74
# 例: SMTPサーバの再起動
docker restart smtp
fi
- 証明書が更新された場合、証明書を使用するすべてのサービスやアプリケーションに読み込ませる必要がある
- ホストにインストールされているnginxなら実行後の処理は
systemctl reload nginx
等になる - ここでログを出力してもよい
2-3. Certbotの更新
Releases等を参考にして必要があれば更新する。
# コンテナを削除
$ docker compose down
# その時点のlatestをpull
$ docker compose pull
# 更新後の動作に問題がなければ古いイメージを削除
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
certbot/dns-luadns latest a271df674f35 3 weeks ago 138MB
certbot/dns-luadns <none> 0023be3c29c4 7 months ago 133MB
$ docker image rm 0023be3c29c4
参考
更新スキップ時のログ
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Processing /etc/letsencrypt/renewal/<DOMAIN>.conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Certificate not yet due for renewal
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
The following certificates are not due for renewal yet:
/etc/letsencrypt/live/<DOMAIN>/fullchain.pem expires on 2022-09-06 (skipped)
No renewals were attempted.
No hooks were run.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
更新実行時のログ
2-3で設定したフックにより DEPLOY_HOOK_CALLED
の文字列が出力されている。
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Processing /etc/letsencrypt/renewal/<DOMAIN>.conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Renewing an existing certificate for <DOMAIN> and *.<DOMAIN>
Waiting 30 seconds for DNS changes to propagate
Hook 'deploy-hook' ran with output:
DEPLOY_HOOK_CALLED
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Congratulations, all renewals succeeded:
/etc/letsencrypt/live/<DOMAIN>/fullchain.pem (success)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -