TechNow!
ITテクノロジー関連の記事を中心に発信しています
CertbotとPorkbun APIの安全な設定

Porkbunでワイルドカード証明書を自動更新する方法

By Author
Porkbunでワイルドカード証明書を自動更新する方法
サーバへ複数のサブドメインを追加するたびに、証明書を個別発行する運用は手間がかかります。Porkbunで管理しているドメインなら、DNS APIを通じてDNS-01認証を行い、`*.example.com`に対応するワイルドカード証明書を自動取得できます。

ワイルドカード証明書を初回発行したあとは、自動継続するための更新設定をすることで安定して運用できます。AlmaLinux上にCertbotとPorkbun用プラグインを導入し、APIキーを適切に保護しながら、更新後にWebサーバを再読み込みする流れまで設定します。

ワイルドカード証明書ではDNS-01認証を使う

Let's Encryptがドメインの管理権限を確認する方法には、Webサーバ上へ検証用ファイルを置くHTTP-01と、DNSへ検証用のTXTレコードを追加するDNS-01があります。ワイルドカード証明書の発行にはDNS-01が必要であり、_acme-challenge.example.comへ一時的なTXTレコードを登録しなければなりません。認証の詳しい仕組みは、Let's Encryptのチャレンジ方式の解説でも確認できます。

手作業でもTXTレコードは登録できますが、自動更新には向きません。証明書の有効期限が近づくたびにPorkbunへログインし、Certbotが表示した値をコピーしてDNS反映を待つことになるからです。PorkbunのDNS APIをCertbotから操作し、TXTレコードの作成と削除を自動化するのが今回の構成です。

ここで、*.example.comwww.example.comapi.example.comを保護しますが、ドメイン直下のexample.comは含みません。また、dev.api.example.comのように階層が二つ深い名前も対象外です。たとえばWebサイトをexample.comでも公開するなら、証明書の要求時にexample.com*.example.comを両方指定します。

DNS-01では外部からAlmaLinuxサーバの80番ポートへ接続できる必要がありません。社内サーバやロードバランサー配下のサーバでも、Porkbun APIへのHTTPS通信とDNSの名前解決ができれば発行できます。実際にHTTPを公開する前の検証環境へ証明書を用意したい場合にも、この性質が役立ちます。

作業を始める前に、対象ドメインの権威DNSが本当にPorkbunであるか確認します。Porkbunで購入したドメインでも、ネームサーバーをCloudflareなどへ変更していれば、Porkbun側へTXTレコードを追加しても認証されません。次の例では、返されたネームサーバーが対象ドメインのDNSをどこで管理しているかを調べています。

dig +short NS example.com    

CAAレコードを設定しているドメインでは、Let's Encryptによる発行が許可されているかも確認します。CAAが存在しない場合は通常、特定の認証局だけに発行を制限している状態ではありません。CAAを使っている例では、letsencrypt.orgを許可する設定が必要になるため、既存ポリシーを管理者と確認してください。

dig +short CAA example.com    

PorkbunのAPIキーを安全に用意する

Porkbunへログインし、アカウントのAPI設定画面でAPIキーとSecret API Keyを発行します。加えて、対象ドメインの管理画面でAPIアクセスを許可する操作が必要になる場合があります。画面の名称や有効化手順は変更されることがあるため、PorkbunのAPIドキュメントと管理画面を照合してください[要確認]。

APIキーは、CertbotがTXTレコードを追加するときに使います。漏えいすればDNSレコードを書き換えられるおそれがあり、Webサイトの転送やメール配送にも影響しかねません。証明書の秘密鍵と同様に、APIキーもrootだけが読めるファイルへ保存します

ここでは認証情報を/etc/letsencrypt/porkbun.iniへ保存します。ファイルをコマンドライン引数へ直接書くと、シェルの履歴やプロセス一覧に残る可能性があるため避けます。次のディレクティブ名はcertbot-dns-porkbunのバージョンによって変更される可能性があるので、インストールしたパッケージの説明も確認してください[要確認]。

sudo install -d -m 700 -o root -g root /etc/letsencrypt    
    
sudo tee /etc/letsencrypt/porkbun.ini > /dev/null <<'EOF'    
dns_porkbun_key = pk1_ここをAPIキーへ置き換える    
dns_porkbun_secret = sk1_ここをSecret API Keyへ置き換える    
EOF    
    
sudo chown root:root /etc/letsencrypt/porkbun.ini    
sudo chmod 600 /etc/letsencrypt/porkbun.ini    

権限はstatで確認できます。期待する状態は、所有者がrootで、root以外に読み取り権限がないことです。たとえば-rw-------と表示されれば、パーミッションは600になっています。

sudo stat -c '%A %a %U:%G %n' /etc/letsencrypt/porkbun.ini    

APIキーをGitリポジトリへ保存したり、構成管理ツールの平文変数へ入れたりしてはいけません。バックアップ対象に含める場合も、保管先で暗号化し、復元できる管理者を絞ります。私なら検証用と本番用でキーを分けられるか確認し、不要になったキーはPorkbun側で直ちに無効化します。

PorkbunのAPIキーがアカウント内の複数ドメインへ作用する仕様なら、一つのサーバへ置いたキーの漏えいがほかのドメインにも波及します[要確認]。その場合は、証明書更新専用のPorkbunアカウントへドメインを移すという単純な対処ができないこともあります。サーバへログインできる利用者、バックアップの保存先、監視ツールが収集するファイルを先に確認しておくほうが安全です。

AlmaLinuxへCertbotとPorkbunプラグインを導入する

Porkbun用のDNSプラグインは、Certbot本体の標準機能ではなく、第三者が公開しているPythonパッケージです。AlmaLinuxのRPM版Certbotと、pipで入れたプラグインを別々のPython環境へ置くと、Certbotからプラグインが見えないことがあります。そこで、専用のPython仮想環境へCertbotとプラグインを一緒に導入します。

以下は主にAlmaLinux 9系を想定した例です。利用中のAlmaLinuxとPythonのバージョンが、最新版のCertbotおよびcertbot-dns-porkbunの要件を満たすかは、導入時にCertbotのドキュメントPyPIのパッケージ情報で確認してください[要確認]。AlmaLinux 8系などで標準Pythonが要件を満たさない場合は、OSが提供する新しいPythonパッケージを選ぶ必要があります。

最初にPythonと仮想環境の作成に必要なパッケージを入れます。続いて/opt/certbotへ仮想環境を作り、その環境内のpipだけを使ってCertbotとPorkbunプラグインをインストールします。既存のシステムPythonへ直接sudo pip installする方法は、OS管理のPythonパッケージと衝突しやすいため採用しません。

sudo dnf install -y python3 python3-pip    
    
sudo python3 -m venv /opt/certbot    
sudo /opt/certbot/bin/python -m pip install --upgrade pip    
sudo /opt/certbot/bin/pip install certbot certbot-dns-porkbun    

python3 -m venvが利用できない場合は、AlmaLinuxのバージョンに対応する仮想環境用パッケージが不足していないか確認します。パッケージ名は利用するPythonの版によって異なる可能性があります[要確認]。別のPythonを追加した場合は、その実行ファイルを明示して仮想環境を作成してください。

python3 --version    
sudo dnf search python | grep -i venv    

毎回フルパスを書く手間を減らすなら、仮想環境内のCertbotへシンボリックリンクを張ります。すでに/usr/local/bin/certbotが存在する環境では上書きせず、どのCertbotを使っているのか先に調べます。たとえばRPM版やSnap版が導入済みなら、更新ジョブの二重起動を避けるため、どれか一つへ統一してください。

command -v certbot || true    
sudo ln -s /opt/certbot/bin/certbot /usr/local/bin/certbot    
certbot --version    

インストール後は、CertbotがPorkbunプラグインを認識しているか確認します。**dns-porkbunが一覧に出なければ、証明書を発行する前にPython環境の混在を解消します**。たとえばwhich certbot/usr/bin/certbotを示し、プラグインを/opt/certbotへ入れている場合は、別環境のCertbotを実行しています。

/opt/certbot/bin/certbot plugins    

プラグインは第三者のコードとしてroot権限で実行されます。公開元、更新履歴、依存パッケージを確認し、いきなり本番サーバへ最新版を入れるのではなく検証環境で発行試験を行います。再現性を優先する環境では動作確認済みのバージョンを固定し、更新時に改めてrenew --dry-runを実行する運用が適しています。

証明書を試験発行してWebサーバへ設定する

最初から本番の認証局へ何度も要求を送ると、設定ミスを直している間に発行制限へ達する可能性があります。まず--dry-runを付け、Let's Encryptのステージング環境でDNSレコードの作成から検証まで通るか確認します。メールアドレス、ドメイン名、Porkbun認証情報のパスは実際の値へ置き換えてください。

sudo /opt/certbot/bin/certbot certonly \    
  --dry-run \    
  --non-interactive \    
  --agree-tos \    
  --email admin@example.com \    
  --authenticator dns-porkbun \    
  --dns-porkbun-credentials /etc/letsencrypt/porkbun.ini \    
  --dns-porkbun-propagation-seconds 120 \    
  --preferred-challenges dns \    
  --cert-name example.com \    
  -d example.com \    
  -d '*.example.com'    

--dns-porkbun-propagation-seconds 120は、TXTレコードを作成してから検証を始めるまで待つ秒数です。120秒は出発点にすぎず、DNSの反映が間に合わない環境では長くします。反対に、安定して短時間で反映されることを複数回確認できたなら、更新処理を速めるために調整できます。

シェルが*をファイル名として展開しないよう、'*.example.com'は引用符で囲みます。たとえば作業ディレクトリに偶然api.example.comというファイルがあると、引用符のないワイルドカードが別の引数へ展開される場合があります。証明書に意図しない名前を含めないためにも、引用符は省略しないでください。

プラグインのオプション名が認識されない場合は、インストールしたバージョンが提供するヘルプを確認します。認証情報ファイルの項目名も、プラグインのREADMEに記載された形式と一致させます。パッケージの更新によって本記事の例と差が生じる可能性があるためです[要確認]。

sudo /opt/certbot/bin/certbot --help dns-porkbun    

API認証エラーが出た場合は、キーの転記ミスだけでなく、Porkbun側で対象ドメインのAPIアクセスが有効かを見ます。DNS検証がタイムアウトした場合は、ネームサーバーの向き先とTXTレコードの反映時間を調べます。認証中に別の端末から次のコマンドを繰り返すと、公開DNSで値が見えるまでの時間を確認できます。

dig +short TXT _acme-challenge.example.com    
dig @1.1.1.1 +short TXT _acme-challenge.example.com    
dig @8.8.8.8 +short TXT _acme-challenge.example.com    

試験が成功したら、同じコマンドから--dry-runだけを外して本番証明書を取得します。**example.com*.example.comを同じ要求へ含めることで、ドメイン直下と一階層のサブドメインを一組の証明書で扱えます**。不要なサブドメインまで個別に-dで列挙する必要はありません。

sudo /opt/certbot/bin/certbot certonly \    
  --non-interactive \    
  --agree-tos \    
  --email admin@example.com \    
  --authenticator dns-porkbun \    
  --dns-porkbun-credentials /etc/letsencrypt/porkbun.ini \    
  --dns-porkbun-propagation-seconds 120 \    
  --preferred-challenges dns \    
  --cert-name example.com \    
  -d example.com \    
  -d '*.example.com'    

発行に成功すると、通常は/etc/letsencrypt/live/example.com/の下から証明書を参照できます。fullchain.pemはサーバー証明書と中間証明書を含むファイルで、privkey.pemは外部へ出してはいけない秘密鍵です。これらは実体ファイルへのシンボリックリンクなので、Webサーバにはarchive配下の番号付きファイルではなくlive配下を設定します。

sudo ls -l /etc/letsencrypt/live/example.com/    
sudo /opt/certbot/bin/certbot certificates    

Nginxでは、対象のserverブロックへ次のように設定します。Nginxの親プロセスをrootで動かす一般的な構成なら、/etc/letsencrypt内の秘密鍵を直接参照できます。設定後は構文検査に成功した場合だけ再読み込みします。

server {    
    listen 443 ssl;    
    server_name example.com *.example.com;    
    
    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;    
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;    
    
    root /var/www/example.com;    
}    
sudo nginx -t    
sudo systemctl reload nginx    

Apache HTTP Serverを使う場合は、SSL対応のVirtualHostへ証明書と秘密鍵のパスを指定します。設定ファイルの場所やモジュール構成は、AlmaLinuxへ導入したApacheの状態に合わせます。変更後はapachectl configtestで検査してからhttpdを再読み込みしてください。

<VirtualHost *:443>    
    ServerName example.com    
    ServerAlias *.example.com    
    
    SSLEngine on    
    SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem    
    SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem    
    
    DocumentRoot /var/www/example.com    
</VirtualHost>    
sudo apachectl configtest    
sudo systemctl reload httpd    

秘密鍵をアプリケーション専用ディレクトリへコピーする構成もありますが、コピーしたファイルはCertbotの更新だけでは置き換わりません。その場合は更新後のdeploy hookでコピー、所有者変更、SELinuxコンテキストの復元、サービス再読み込みまで実行する必要があります。NginxやApacheがlive配下を直接読めるなら、コピーを作らないほうが更新経路を単純に保てます。

systemdで更新と再読み込みを自動化する

Certbotは、初回発行時のドメイン名、認証方式、認証情報ファイルの場所などを/etc/letsencrypt/renewal/へ保存します。そのため、通常の更新では発行時の長いコマンドを毎回書き直す必要がありません。まず保存された更新設定にPorkbunプラグインと認証情報のパスが記録されていることを確認します。

sudo sed -n '1,200p' /etc/letsencrypt/renewal/example.com.conf    

このファイルにはAPIキーそのものではなく、認証情報ファイルへのパスが保存される構成が一般的です。更新設定はCertbotが管理するため、理由なく手編集すると次回更新を壊すことがあります。ドメイン構成を変える場合は発行コマンドを再実行し、その後にrenew --dry-runで確かめます。

次に、保存された設定だけで更新試験が通るか確認します。初回発行には成功しても、認証情報ファイルを移動したりプラグインを削除したりすると、自動更新時に失敗します。**renew --dry-runが成功し、Webサーバの再読み込みまで完了する状態を運用開始の基準にします**。

Nginxを利用する場合は、次のようにdeploy hookを付けて試験できます。deploy hookは証明書が実際に更新された後に実行されるため、期限がまだ十分残っていて更新が見送られた場合は呼び出されません。--dry-run時のhook実行挙動はCertbotのバージョンによって差があり得るため、ログでも確認してください[要確認]。

sudo /opt/certbot/bin/certbot renew \    
  --dry-run \    
  --deploy-hook "/usr/bin/systemctl reload nginx"    

Apacheなら、hook内のサービス名をhttpdへ変更します。証明書を複数サービスで使う場合は、検査と再読み込みをまとめたスクリプトを作り、そのスクリプトをdeploy hookから呼び出すほうが管理しやすくなります。たとえばNginxの設定検査に失敗したとき、再読み込みを実行しないスクリプトにできます。

sudo tee /usr/local/sbin/reload-after-certbot > /dev/null <<'EOF'    
#!/bin/bash    
set -euo pipefail    
    
/usr/sbin/nginx -t    
/usr/bin/systemctl reload nginx    
EOF    
    
sudo chown root:root /usr/local/sbin/reload-after-certbot    
sudo chmod 750 /usr/local/sbin/reload-after-certbot    
sudo /opt/certbot/bin/certbot renew \    
  --dry-run \    
  --deploy-hook /usr/local/sbin/reload-after-certbot    

仮想環境へCertbotを導入しただけでは、OS側に自動更新用のsystemd timerが作られないことがあります。そこで、更新処理を実行するoneshotサービスと、一日に複数回起動するtimerを用意します。証明書の期限直前だけに起動するのではなく、定期的にCertbotへ更新可否を判断させれば、一時的なDNS API障害が起きても次の実行で再試行できます。

sudo tee /etc/systemd/system/certbot-renew.service > /dev/null <<'EOF'    
[Unit]    
Description=Renew Let's Encrypt certificates with Certbot    
After=network-online.target    
Wants=network-online.target    
    
[Service]    
Type=oneshot    
ExecStart=/opt/certbot/bin/certbot renew --quiet --deploy-hook /usr/local/sbin/reload-after-certbot    
PrivateTmp=true    
EOF    
sudo tee /etc/systemd/system/certbot-renew.timer > /dev/null <<'EOF'    
[Unit]    
Description=Run Certbot renewal periodically    
    
[Timer]    
OnCalendar=*-*-* 03,15:20:00    
RandomizedDelaySec=1h    
Persistent=true    
    
[Install]    
WantedBy=timers.target    
EOF    

RandomizedDelaySecを設定すると、同じ時刻に多数のサーバが認証局へ接続するのを避けられます。Persistent=trueは、予定時刻にサーバが停止していた場合、次の起動後に未実行分を動かすための設定です。たとえば夜間だけ停止する検証サーバでも、起動後に更新判定を行えます。

systemdへ新しい定義を読み込ませ、timerを有効化します。その後、次回実行時刻が一覧に表示されることを確認してください。既存のcertbot.timerが同時に存在する場合は、パッケージ版Certbotのジョブが残っていないか調べ、重複実行を解消します。

sudo systemctl daemon-reload    
sudo systemctl enable --now certbot-renew.timer    
    
sudo systemctl status certbot-renew.timer    
sudo systemctl list-timers --all | grep certbot    

サービス単体も一度起動し、終了状態とログを確認します。証明書がまだ更新時期でなければ、更新せず正常終了するのが通常です。失敗した場合は、Porkbun API、DNS反映、Pythonプラグイン、Webサーバの再読み込みのどこで止まったかをログから切り分けます。

sudo systemctl start certbot-renew.service    
sudo systemctl status certbot-renew.service    
sudo journalctl -u certbot-renew.service --since today    

自動更新を設定しても、プラグインの破損やAPIキーの失効は放置すると気づけません。systemdサービスの失敗を監視へ通知し、月に一度程度はrenew --dry-runを実行した結果も確認します。PorkbunでAPIキーを再発行したときはporkbun.iniを更新し、ファイル権限を600へ戻してから試験してください。

CertbotやPorkbunプラグインの更新も、証明書更新とは別に管理します。仮想環境内のパッケージを更新した直後は、プラグイン一覧、発行設定、dry-runの順に検査し、問題があれば確認済みバージョンへ戻せるよう記録を残します。たとえば次のコマンドを無条件の自動ジョブにせず、検証日を決めて実行するほうが、突然の依存関係変更で更新処理が止まる事態を減らせます。

sudo /opt/certbot/bin/pip list --outdated    
sudo /opt/certbot/bin/pip install --upgrade certbot certbot-dns-porkbun    
sudo /opt/certbot/bin/certbot plugins    
sudo /opt/certbot/bin/certbot renew --dry-run \    
  --deploy-hook /usr/local/sbin/reload-after-certbot    

PorkbunのAPIキーをroot専用ファイルへ隔離し、DNS-01の試験発行、deploy hook、systemd timer、失敗監視まで通しておけば、ワイルドカード証明書を人手に頼らず更新し続けられます。

おすすめの記事