Certbotでdns-rfc2136プラグインを使った自動更新を行う

     

公開日:2022.03.12

     

最終更新日:2022.05.08

先月、うっかりCertbotによるLet’s Encryptの手動証明書更新を漏らしていて数分証明書エラーを出してしまったのを機に、しっかり自動更新設定入れねばと、ようやく重い腰を上げて設定入れました。ワイルドカードを利用した証明書を利用しているのと、2ドメイン分管理しているため設定が面倒、自分や家族用だから期限切れたとしても問題にならないからという理由で手動更新にしていたのですが、歳のせいかいろいろなことが面倒になりつつある自分を反省する意味もあって対応しました。もともとEPELリポジトリのCertbotを利用していたのですが、使っているのはAlmaLinuxだし、今後のことも考えてディストリビューションに依存しないsnapを利用したCertbotに切り替えました。今まではcertbot renewを使っていなかったのもあります。実際の作業の流れは下記のような感じです。

インストール済みcertbotのアンインストール

まずは利用中のEPELリポジトリを利用してインストールしたcertbotをアンインストールします。結局使っていなかったのですが、導入していたrfc2126プラグインも同時にアンインストールします。

$ sudo dnf erase certbot python3-certbot-dns-rfc2136

certbot renewなどは使っていなかったのでcron周りのお掃除は不要でした。環境によってはcron周りの設定を削除しておいた方が良いです。

snap版certbotのインストール

次にsnapdを導入します。

$ sudo dnf -y install snapd
$ sudo systemctl enable --now snapd.socket
$ sudo ln -s /var/lib/snapd/snap /snap
$ sudo snap install core
$ sudo snap refresh core (<-念のため)

ちなみに家の環境(AlmaLinux 8.4)では、apache arrows導入時に設定していたリポジトリの向き先がcentos8用であったためそこでエラーとなりましたので、apache arrowsのリポジトリの向き先をAlmaLinux用に直しました。具体的には下記のようにAlmaLinux側を「enabled=1」として、Centos側を「enabled=0」としました。

$ cat /etc/yum.repos.d/Apache-Arrow.repo
------------------- snip -------------------
[apache-arrow-almalinux]
name=Apache Arrow for AlmaLinux $releasever - $basearch
baseurl=https://apache.jfrog.io/artifactory/arrow/almalinux/$releasever/$basearch/
gpgcheck=1
enabled=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-Apache-Arrow
------------------- snip -------------------
[apache-arrow-centos]
name=Apache Arrow for CentOS $releasever - $basearch
baseurl=https://apache.jfrog.io/artifactory/arrow/centos/$releasever/$basearch/
gpgcheck=1
enabled=0
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-Apache-Arrow
------------------- snip -------------------

続けてsnapを利用してcertbot、certbot-dns-rfc2136プラグインをインストールします。

$ sudo snap install --classic certbot
$ sudo snap set certbot trust-plugin-with-root=ok
$ sudo snap install certbot-dns-rfc2136
$ sudo ln -s /snap/bin/certbot /usr/bin/certbot

certbotのバージョンと、プラグインがインストールされ有効になっていることを確認します。

$ certbot --version
certbot 1.24.0
$ certbot plugins
------------------- snip -------------------
* dns-rfc2136
Description: Obtain certificates using a DNS TXT record (if you are using BIND
for DNS).
Interfaces: Authenticator, Plugin
Entry point: dns-rfc2136 =
certbot_dns_rfc2136._internal.dns_rfc2136:Authenticator
------------------- snip -------------------

certbot関連のインストールが完了したので、BIND側の設定に入ります。

TSIGキーを作成する

DDNSを利用してゾーンファイルにTXTレコードを反映させるため、認証用にTSIG(Transaction Signature)を利用します。TSIGで使用する鍵ファイルを作成します。なお、私の環境は自宅サーバーですので、直にサーバーで鍵作成を行い、鍵の受け渡しは発生しないことになります。鍵ファイル生成にはいくつかコマンド利用できますが、私はddns-confgenコマンドを使用しました。鍵ファイル名は何でもよいです。サーバー間を跨いだDDNSを行う場合は「ホスト名1-ホスト名2」みたいにするのがわかりやすいという感じでしょうか。ddns-confgenコマンドのオプションは後で修正可能なものだったりなので、ここではそこまで気にしないでも良いと思いますが、-aオプションのアルゴリズム指定だけはなるべく強固なものをお勧めします。

$ sudo ddns-confgen -k <鍵ファイル名> -a hmac-sha512 -r /dev/urandom
------------------- snip -------------------
# will be run:
key "<鍵ファイル名>" {
	algorithm hmac-sha512;
	secret "X2lM1Mg6U0dwaKQHxPfaI8BYfuPm8NVHdSkUq+uSlkzv1UknAgGxaS1vSWpQVWBgwgNiYGb09jYQwxqIj0LJug==";
};
------------------- snip -------------------

ddns-confgenコマンドの結果、上記のような鍵情報が出力されます。これをBIND設定ファイル、およびcertbot側で共に共通鍵として使います。

BIND設定ファイルを設定する

BIND設定ファイルnamed.confに、共通鍵情報とゾーンファイル設定を追加します。共通鍵情報は直接named.confに書いても良いのですが、セキュリティの関係上、外出しにしておいた方が良いのでそれに倣います。私の環境ではnamed-chrootを使っていて、共通鍵設定ファイルを/var/named/key/ディレクトリにおいているため、一旦named-chrootサービスを止めた状態で/etc/named.confファイルを修正しました。

$ sudo mkdir /var/named/key
$ sudo chown named:named /var/named/key
$ sudo chmod 600 /var/named/key
$ sudo vi /var/named/key/forcertbot.key
------------------- snip -------------------
key "certbot-key" {
	algorithm hmac-sha512;
	secret "X2lM1Mg6U0dwaKQHxPfaI8BYfuPm8NVHdSkUq+uSlkzv1UknAgGxaS1vSWpQVWBgwgNiYGb09jYQwxqIj0LJug==";
};
------------------- snip -------------------
$ sudo chown root:named /var/named/key/forcertbot.key
$ sudo chmod 640 /var/named/key/forcertbot.key
$ sudo systemctl stop named-chroot
$ sudo vi /etc/named.conf
------------------- snip -------------------
include "/var/named/key/forcertbot.key";
------------------- snip -------------------
# for Internal
view "internal" {
	match-clients {
		localhost;
		localnets;
		192.168.0.0/24;
	};
	zone "example.com" IN {
		type master;
		file "db.example.com.internal";
		check-names ignore;
	};
	zone "_acme-challenge.example.com" IN {
		type master;
		file "db.acme-challenge.example.com";
		allow-transfer { none; };
		allow-query { any; };
		check-names ignore;
		update-policy {
			grant certbot-key. name _acme-challenge.example.com. txt;
		};
	};
------------------- snip -------------------
};

# for Internet
view "forInternet" {
	match-clients {
		"any";
	};
	zone "example.com" IN {
		type master;
		file "db.example.com";
		allow-query { any; };
		allow-transfer { <委譲元IPアドレス>/32; };
		also-notify { <委譲元IPアドレス> ; };
	};
	zone "_acme-challenge.example.com" IN {
		in-view internal;
	};
------------------- snip -------------------

ゾーンファイルとしては実際には逆引きや、別ドメインの設定も入れていますが、長いのでここでは記載を省略しています。別ドメインでも同様にLet’s Encryptの証明書を利用していますが、同じサーバーで自分で管理しているため、共通鍵は同じものを利用しています。私は内向き(LAN環境用)と外向き(インターネット用)とで別ゾーン設定にしているため、viewを利用しています。LAN環境からDDNSを使うと内向きゾーン側に更新に行くので、_acme-challenge用のゾーンファイルも内向き側に作成します。このままでは外側から見えませんので、外向き側には同じゾーン名で「in-view」を使って見えるようにします。こうすることで、certbotでの確認時にきちんと外から見に来てもらえます。なお、サブドメイン名でアンダースコアを使用するため、「check-names ignore」を対象のゾーンの設定で加えておく必要があります。続けてTXTレコード更新用のゾーンファイルを作成します。

BINDゾーンファイルを作成する

TXTレコード更新用のゾーンファイル「db.acme-challenge.example.com」を作成します。このファイルは、namedが更新しにいくので、所有者をnamedにして、パーミッションを640にしておきます。

$ sudo vi /var/named/db.acme-challenge.example.com

$TTL 86400
_acme-challenge.example.com      IN SOA ns.example.com. root.example.com. (
			                     2022031201 ; serial
			                     3600       ; refresh (1 hour)
			                     600        ; retry (10 minutes)
			                     1209600    ; expire (2 weeks)
			                     60         ; minimum (1 minute)
			                     )
			                     NS	ns.example.com.

$ sudo chown named:named /var/named/db.acme-challenge.example.com
$ sudo chmod 640 /var/named/db.acme-challenge.example.com

「_acme-challenge.example.com」はサブドメインですので、親のドメイン側のゾーンファイルでサブドメインを委任する設定をしておきます。

$ sudo vi /var/named/db.exaple.com.internal

$TTL 86400
example.com.                        IN SOA	example.com. root.example.com. (
			                        2022030701
------------------- snip -------------------
			                        IN NS	ns.example.com.
------------------- snip -------------------
_acme-challenge.example.com.        IN NS	ns.example.com.
ns.example.com.                     IN A	192.168.24.200
------------------- snip -------------------

設定が完了したら、BIND設定ファイル、およびゾーンファイルの文法チェックを行ないます。BIND設定ファイルのチェックでは何も出ないこと、ゾーンファイルのチェックではエラーがでず、「OK」となることを確認します。

$ sudo named-checkconf /etc/named.conf
$ sudo named-checkzone _acme-challenge.example.com /var/named/db.acme-challenge.example.com

問題なければBINDを起動させます。

$ sudo systemctl start named-chroot

BINDを極力止めたくない場合は、chroot使っている際には「/var/named/chroot」内にあるファイル類を対象にしてください。

BINDゾーンファイルのTXTレコードが更新されることを確認する

一通りBINDの設定が完了したら、DDNSの動作確認を行います。必要なのは、設定を入れたTXTレコードの更新と、外部からの参照が可能かどうかとなります。まずは、「_acme-challenge.example.com」に対するTXTレコードが追加できることを確認します。DDNSの確認には、BINDに含まれるnsupdateコマンドを利用します。

$ sudo nsupdate -d -k /var/named/key/forcertbot.key
------------------- snip -------------------
> server 192.168.0.1       ;DNSの内部IPアドレスが「192.168.0.1」の場合
> add _acme-challenge.example.com 60 TXT "txtrecordaddtest"
> show
Outgoing update query:
;; ->>HEADER<<- opcode: UPDATE, status: NOERROR, id:      0
;; flags:; ZONE: 0, PREREQ: 0, UPDATE: 0, ADDITIONAL: 0
;; UPDATE SECTION:
_acme-challenge.example.com. 60 IN TXT	"txtrecordaddtest"

> send
------------------- snip -------------------
;; TSIG PSEUDOSECTION:
certbot-key.	0	ANY	TSIG	hmac-sha512. ...

> quit

エラーが出ずにupdate queryのreplyがくればOKです。では、外部から参照できるか確認しましょう。一番楽なのはcmanなど、外部からDNSレコードを確認できるサイトを利用することです。対象ホスト名(FQDN)として「_acme-challenge.example.com」を、オプションで「TXT」を、問い合わせ先を「ns.example.com」としてdigを実行して、想定通りANSWER SECTIONで「_acme-challenge.example.com. 60 IN TXT “txtrecordaddtest”」となっていれば外部からの参照も問題なくできています。テストが完了したら、TXTレコードは不要なので削除しておきます。

$ sudo nsupdate -d -k /var/named/key/forcertbot.key
------------------- snip -------------------
> server 192.168.0.1
> delete _acme-challenge.example.com TXT
> send
------------------- snip -------------------
> quit

先程と同様に、外部からDNSのTXTレコードを参照し、今度はANSWER SECTIONで先程設定したTXTレコードが参照できなくなっていればOKです。

これでようやくcertbotのdns-rfc2136プラグインを使った自動更新を行うプラグインを利用したLet’s Encryptの証明書取得および更新の準備が完了しました。続けてcertbotの設定を行います。

certbotで証明書を取得する

まず、certbotでrfc2136プラグインを利用してLet’s Encryptの証明書を取得するために必要となる設定ファイルを作成します。ファイルの場所、ファイル名は任意です。共有鍵の値は、ダブルクォートで囲みません。

$ sudo mkdir /etc/letsencrypt/.secret
$ sudo vi /etc/letsencrypt/.secret/rfc2136-certbot.ini

dns_rfc2136_server = 192.168.0.1
dns_rfc2136_port = 53
dns_rfc2136_name = certbot-key.
dns_rfc2136_secret = X2lM1Mg6U0dwaKQHxPfaI8BYfuPm8NVHdSkUq+uSlkzv1UknAgGxaS1vSWpQVWBgwgNiYGb09jYQwxqIj0LJug==
dns_rfc2136_algorithm = HMAC-SHA512

$ sudo chown root:root /etc/letsencrypt/.secret /etc/letsencrypt/.secret/rfc2136-certbot.ini
$ sudo chmod 400 /etc/letsencrypt/.secret
$ sudo chmod 400 /etc/letsencrypt/.secret/rfc2136-certbot.ini

続けて証明書を取得します。失敗してリトライを行うことを想定して、エラーが出ないことを確認できるまでは「dry-run」オプションをつけてテストします。本来、初回ではないのでcertbot renewで更新なのですが、今まではmanualオプションで手動で対応していたものを今回はrfc2136オプション利用となるため、初回と同じように実施します。証明書の暗号化方式はecdsaを指定しました。同じサーバー内のDNSなので、propagation-secondsも短めにしています。

$ sudo certbot certonly --debug --dry-run \
  --dns-rfc2136 \
  --dns-rfc2136-credentials /etc/letsencrypt/.secrets/rfc2136-certbot.ini \
  --server https://acme-v02.api.letsencrypt.org/directory \
  --agree-tos \
  --key-type ecdsa \
  --dns-rfc2136-propagation-seconds 10 \
  -m example@example.com \
  -d example.com \
  -d *.example.com
------------------- snip -------------------
 - The dry run was successful.  

しばらく待って、エラーが出ず、成功表示が出ればOKです。リハーサルで問題なければ、「dry-run」オプションを外して実行すれば良いです。なお、すでにCertbotアカウントは登録済みですので、今回はアカウント登録はしていません。最後に、Webサーバー、メールサーバーを再起動して証明書を読み込ませます。

$ sudo systemctl reload httpd postfix dovecot

対象のサイトにアクセスして証明書が更新されていることを確認します。これは環境に応じて確認してみてください。

certbotで自動更新されることを確認する

snap版のCertbotの場合、cronで登録せずとも自動更新は組み込まれています。下記コマンドで確認できます。

$ snap list
Name                 Version    Rev    Tracking       Publisher     Notes
certbot              1.24.0     1842   latest/stable  certbot-eff✓  classic
certbot-dns-rfc2136  1.24.0     1425   latest/stable  certbot-eff✓  -
core                 16-2.54.3  12725  latest/stable  canonical✓    core
core20               20220304   1376   latest/stable  canonical✓    base

$ systemctl list-timers
NEXT                         LEFT          LAST                         PASSED    UNIT                         ACTIVATES
------------------- snip -------------------
Sat 2022-03-12 15:44:00 JST  2h 42min left Sat 2022-03-12 03:35:00 JST  9h ago    snap.certbot.renew.timer     snap.certbot.renew.service
------------------- snip -------------------

これ以上の確認は良いかなと思ってこのままにしています。なお、Certbotによる証明書更新は期限切れ30日前にならないと実行されません。そのタイミングでWebサーバーやメールサーバーのリロードをするかどうかですが、通常ログローテーションなどでプロセスは再起動されます。その後の30日間にリロードされることがほとんどなのであえて明示的にcertbot renew時にはリロードさせていません。これで様子を見ようかなと思います。

2022/05/08追記

昨日certbot renewが実行されて証明書の更新が完了していました。また、本日Apacheのログローテーションで証明書が再読み込みされて期待通りの挙動をすることが確認できました。

関連記事

コメント

  • トラックバックは利用できません。

  • コメント (0)

  1. この記事へのコメントはありません。

2022年9月
 1234
567891011
12131415161718
19202122232425
2627282930