無償で利用できるTLS(SSL)証明書として当たり前のように用いられるようになったLet’s Encryptですが、 組織内部で利用するサーバなどでこの証明書を用いたい場合、一般的に用いられるHTTP-01チャレンジでは インターネット側からWebサーバへのアクセスを受け入れる必要がある上、ドメインごとにチャレンジ手続きが必要で面倒です。
そこで今回は、Let’s Encryptで複数のサブドメインの証明書をひとつにまとめられるワイルドカード証明書を入手する環境を構築します。
Let’s Enctyptでワイルドカード証明書を入手したい時は、DNSサーバを用意しDNS-01チャレンジに対応する必要があります。
必要なもの
最低限下記のものが必要です。
- てきとうなLinuxサーバ
- Ubuntu Server(22.04)を想定
- ホスト名は
letsencrypt.negix.org
- DNSサーバとしてnsd、Let’s Encryptのクライアントとしてcertbotをインストール
- 外部からUDP/TCP53を受け付ける
- グローバルIPv4アドレス
- 固定のもの
- ドメイン名
- 自身がNSレコードを設定できるもの
- 今回は
negix.org
とhachune.net
を用いる - value-domainで取得し、value-domainの標準DNSサーバを利用
今回のように証明書を取得・管理するだけであれば、サーバはConoHaや さくらのVPSの512MB~1GBプランで大丈夫です。 (ついでにWebサーバを立てるぐらいのことはできます。)
letsencrypt.negix.org
のサーバ上で動かすDNSサーバとして、
nsdをインストールします。
証明書の取得には、毎度おなじみcertbotを用います。
いずれもubuntu-22.04の標準パッケージのもので大丈夫です。
また、レコードの確認にdrill
コマンドを用いるため、ldnsutils
もインストールしておくとよいでしょう。
取得する証明書
下記の証明書をそれぞれ取得します。
negix.org
と*.negix.org
の証明書をひとつhachune.net
と*.hachune.net
の証明書をひとつ
value-domain標準DNSサーバ側のレコード設定
DNS-01チャレンジでは、_acme-challenge.対象ドメイン名
(negix.orgの場合は_acme-challenge.negix.org
)のTXTレコードに、
Let’s Encrypt側から指定された値を設定することで、ドメインの所有権が確認されます。
ここで指定される値は毎回ランダムであるため、TXTレコードの設定は自動化が必須となっています。
しかし、value-domainの標準レンタルDNSサーバなどを用いているとなかなか自動化し辛いのが現状です。 (Route53やCloudflare DNSなどのイマドキなクラウドサービスを利用しているのであれば話は別かもしれませんが…) また、DNSサーバを完全に自前で運用しようとすると、それなりに面倒なことになりがちです。
自動化し辛いDNSサーバを利用しており、現状をなるべく維持したい場合は、
_acme-challenge.対称ドメイン名
のNSレコードを自身が用意したDNSサーバ(letsencrypt.negix.org
)に向け、
そこだけ自前で管理する手法が手軽でしょう。
今回は、「value-domainのDNSサーバのnegix.org
用レコード」に、下記のように追記しました。
1
2
ns _acme-challenge letsencrypt.negix.org.
a letsencrypt 160.251.76.38
これにより、_acme-challenge.negix.org
以下に関するクエリは、letsencrypt.negix.org
に飛んでくるようになります。
drill
コマンドで確認してみましょう。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
% drill @8.8.8.8 NS _acme-challenge.negix.org.
;; ->>HEADER<<- opcode: QUERY, rcode: NOERROR, id: 60654
;; flags: qr rd ra ; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;; _acme-challenge.negix.org. IN NS
;; ANSWER SECTION:
_acme-challenge.negix.org. 1 IN NS letsencrypt.negix.org.
;; AUTHORITY SECTION:
;; ADDITIONAL SECTION:
;; Query time: 40 msec
;; SERVER: 8.8.8.8
;; WHEN: Fri Feb 24 18:21:20 2023
;; MSG SIZE rcvd: 65
他のドメインもまとめて管理したい場合
他のドメインもまとめて管理したい場合は、CNAMEレコードを設定します。
例えば、hachune.net
もまとめて管理したい場合は、「value-domainのDNSサーバのhachune.net
用レコード」に
下記のように記述し、_acme-challenge.hachune.net
を_acme-challenge.negix.org
のエイリアスにします。
1
cname _acme-challenge _acme-challenge.negix.org.
設定できたら、またdrill
コマンドで確認してみましょう。
_acme-challenge.hachune.net.
のNSレコードを参照すると、_acme-challenge.negix.org.
のエイリアスであることが分かり、
_acme-challenge.negix.org.
のNSレコードはletsencrypt.negix.org.
である、となっていることが確認できます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
% drill @8.8.8.8 NS _acme-challenge.hachune.net.
;; ->>HEADER<<- opcode: QUERY, rcode: NOERROR, id: 6535
;; flags: qr rd ra ; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;; _acme-challenge.hachune.net. IN NS
;; ANSWER SECTION:
_acme-challenge.hachune.net. 1800 IN CNAME _acme-challenge.negix.org.
_acme-challenge.negix.org. 1 IN NS letsencrypt.negix.org.
;; AUTHORITY SECTION:
;; ADDITIONAL SECTION:
;; Query time: 197 msec
;; SERVER: 8.8.8.8
;; WHEN: Fri Feb 24 18:23:13 2023
;; MSG SIZE rcvd: 104
自前DNSサーバ(letsencrypt.negix.orgのnsd)の設定
_acme-challenge.negix.org.
のクエリを処理できるよう、自前DNSサーバを構築します。
/etc/nsd/nsd.conf
インストール時のまま変更していません。
設定の追記は、/etc/nsd/nsd.conf.d/*.conf
ファイルに対して行います。
1
2
3
4
5
server:
# log only to syslog.
log-only-syslog: yes
include: "/etc/nsd/nsd.conf.d/*.conf"
/etc/nsd/nsd.conf.d/negix.org.conf
サーバの設定と、ゾーンごとの設定を記述します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
server:
# 0.0.0.0:53をbindする
ip-address: 0.0.0.0
port: 53
# IPv4を用いる
do-ip4: yes
# nsdのバージョンを隠す
hide-version: yes
# drill CH TXT id.server.した際に返される値
identity: "negix.org authoritative DNS"
# ゾーンファイルを置くディレクトリ
zonesdir: "/etc/nsd/zone"
# _acme-challenge.negix.orgゾーンの設定を記述
zone:
name: _acme-challenge.negix.org.
# ゾーンファイルとして/etc/nsd/zone/_acme-challenge.negix.org.zoneが読まれる
zonefile: _acme-challenge.negix.org.zone
/etc/nsd/zone/_acme-challenge.negix.org.zone
とりあえず、_acme-challenge.negix.org
のNSレコードを問い合わせられた際に、
自分自身(letsencrypt.negix.org
)であることを返すようにしましょう。
1
2
3
4
5
6
7
8
9
10
11
12
$ORIGIN _acme-challenge.negix.org.
$TTL 1
@ IN SOA letsencrypt.negix.org. admin.negix.org. (
1674698406 ; serial number
600 ; Refresh
300 ; Retry
86400 ; Expire
600 ; Min TTL
)
@ IN NS letsencrypt.negix.org.
後でこのファイルは、Let’s Encryptからの指示に従って自動的に置き換えられるようにします。
サーバの起動とレコードの確認
systemctl
でnsd
を有効化しつつ起動します。
systemctl status
で確認し、active (running)
になっていればとりあえず動作はしているはずです。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
% sudo systemctl enable --now nsd
% sudo systemctl status nsd
● nsd.service - Name Server Daemon
Loaded: loaded (/lib/systemd/system/nsd.service; enabled; vendor preset: enabled)
Active: active (running) since Fri 2023-02-24 19:03:59 JST; 2min 49s ago
Docs: man:nsd(8)
Main PID: 2103013 (nsd: xfrd)
Tasks: 3 (limit: 1030)
Memory: 117.0M
CPU: 177ms
CGroup: /system.slice/nsd.service
├─2103013 /usr/sbin/nsd -d -P ""
├─2103015 /usr/sbin/nsd -d -P ""
└─2103016 /usr/sbin/nsd -d -P ""
Feb 24 19:03:58 lumia systemd[1]: Starting Name Server Daemon...
Feb 24 19:03:59 lumia nsd[2103013]: nsd starting (NSD 4.3.9)
Feb 24 19:03:59 lumia nsd[2103015]: nsd started (NSD 4.3.9), pid 2103013
Feb 24 19:03:59 lumia systemd[1]: Started Name Server Daemon.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
% drill @127.0.0.1 ns _acme-challenge.hachune.net
;; ->>HEADER<<- opcode: QUERY, rcode: NOERROR, id: 3777
;; flags: qr aa rd ; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;; _acme-challenge.negix.org. IN NS
;; ANSWER SECTION:
_acme-challenge.negix.org. 1 IN NS letsencrypt.negix.org.
;; AUTHORITY SECTION:
;; ADDITIONAL SECTION:
;; Query time: 1 msec
;; SERVER: 127.0.0.1
;; WHEN: Fri Feb 24 19:07:38 2023
;; MSG SIZE rcvd: 65
nsdがうまく起動しない場合
systemd-resolved
がUDP53を掴んでいて、nsd
の起動に失敗することがよくあります。
そのような時は、/etc/systemd/resolved.conf
を下記のように編集してDNSSutbListener
を無効化し、
systemd-resolvedを再起動してみてください。
1
2
[Resolve]
DNSStubListener=no
今どのプロセスがどのポートを掴んでいるかは、ss
コマンドで確認できます。
1
2
3
4
5
6
7
% sudo ss -4 -l -p -n
Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
udp UNCONN 0 0 0.0.0.0:53 0.0.0.0:* users:(("nsd: server 1",pid=2103016,fd=4),("nsd: main",pid=2103015,fd=4),("nsd: xfrd",pid=2103013,fd=4))
udp UNCONN 0 0 160.251.76.38%eth0:68 0.0.0.0:* users:(("systemd-network",pid=149157,fd=18))
tcp LISTEN 0 256 0.0.0.0:53 0.0.0.0:* users:(("nsd: server 1",pid=2103016,fd=5),("nsd: main",pid=2103015,fd=5),("nsd: xfrd",pid=2103013,fd=5))
tcp LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=148045,fd=3))
tcp LISTEN 0 16 127.0.0.1:8952 0.0.0.0:* users:(("nsd: server 1",pid=2103016,fd=7),("nsd: main",pid=2103015,fd=7),("nsd: xfrd",pid=2103013,fd=7))
レコード更新スクリプトを作成
certbotを実行すると、Let’s EncryptのサーバからDNS-01チャレンジ用の値が送られてきます。 この値をTXTレコードに自動的に登録するためのスクリプトを用意します。
nsdの場合、レコードの動的な追加ができないため、ゾーンファイルを更新するスクリプトを記述します。
/etc/letsencrypt/template-acme-challenge.zone
ゾーンファイルの元となるファイルです。 これにLet’s Encryptから送られてきた値を追記し、nsdに再読み込みさせます。
1
2
3
4
5
6
7
8
9
10
11
12
$ORIGIN _acme-challenge.negix.org.
$TTL 1
@ IN SOA letsencrypt.negix.org. admin.negix.org. (
SERIAL_NUMBER ; serial number
600 ; Refresh
300 ; Retry
86400 ; Expire
600 ; Min TTL
)
@ IN NS letsencrypt.negix.org.
/etc/letsencrypt/auth.sh
certbotがLet’s Encryptからチャレンジの値を受け取った際に実行されるスクリプトです。
チャレンジの値はCERTBOT_VALIDATION
変数に入るようになっているので、
これをゾーンファイルのTXTレコードに追記し、nsd-control reload
でゾーンファイルを再読み込みさせます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/bin/sh
# env | tee /tmp/letsencrypt.log
# 変数の例
# CERTBOT_VALIDATION=A7sjrWIkY5r67aG7zSiYh0FE9_0_M14Qe8-UyCuKnNE
# CERTBOT_DOMAIN=hachune.net
SERIAL_NUMBER=`date '+%s'`
TMP_RECORD_FILE='/etc/letsencrypt/record.zone'
ZONE_TEMPLATE_FILE='/etc/letsencrypt/template-acme-challenge.zone'
echo DOMAIN: ${CERTBOT_DOMAIN}
echo CODE: $CERTBOT_VALIDATION
echo "@ IN TXT ${CERTBOT_VALIDATION}" >> "${TMP_RECORD_FILE}"
if [ "$CERTBOT_REMAINING_CHALLENGES" = "0" ]; then
cat "${ZONE_TEMPLATE_FILE}" "${TMP_RECORD_FILE}" |
sed -e "s/SERIAL_NUMBER/${SERIAL_NUMBER}/" \
> /etc/nsd/zone/_acme-challenge.negix.org.zone
nsd-control reload
sleep 3
drill @127.0.0.1 TXT _acme-challenge.negix.org.
fi
/etc/letsencrypt/cleanup.sh
DNS-01チャレンジが完了した後に実行される、不要になったTXTレコードを削除するスクリプトです。
1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/sh
TMP_RECORD_FILE='/etc/letsencrypt/record.zone'
ZONE_TEMPLATE_FILE='/etc/letsencrypt/template-acme-challenge.zone'
SERIAL_NUMBER=`date '+%s'`
echo Remove "${TMP_RECORD_FILE}"
rm -f "${TMP_RECORD_FILE}"
sed -e "s/SERIAL_NUMBER/${SERIAL_NUMBER}/" "${ZONE_TEMPLATE_FILE}" \
> /etc/nsd/zone/_acme-challenge.negix.org.zone
nsd-control reload
certbotの実行
negix.org
と*.negix.org
の証明書が必要な際は、下記のようにcertbot
を実行します。
今回はサーバ証明書の公開鍵にECDSAを用いたかったため、--key-type ecdsa
と--elliptic-curve secp384r1
を追加しています。
(デフォルトではRSA 2048bitになるはずです。)
DNS-01チャレンジを用いて証明書を取得するため、--preferred-challenges dns01
を指定します。
また、DNSサーバにチャレンジの値を追加/削除するスクリプトを--manual-auth-hook
と--manual-cleanup-hook
で指定します。
1
2
3
4
5
6
7
8
9
10
11
12
13
% sudo certbot certonly \
--manual \
--expand \
--domain "negix.org" --domain "*.negix.org" \
--key-type ecdsa \
--elliptic-curve secp384r1 \
--email "てきとうな自分のメールアドレス" \
--agree-tos \
--manual-public-ip-logging-ok \
--manual-auth-hook /etc/letsencrypt/auth.sh \
--manual-cleanup-hook /etc/letsencrypt/cleanup.sh \
--keep-until-expiring \
--preferred-challenges dns-01
証明書を発行できる回数には制限がある(時間・IPアドレス単位)ので注意しましょう。
実際に証明書は発行せず、チャレンジの動作テストを行いたい時は、
--dry-run
と--server "https://acme-staging-v02.api.letsencrypt.org/directory
オプションを追加します。
出力される証明書
生成された証明書は、/etc/letsencrypt/live/ドメイン名
以下に出力されます。
(各ファイルが/etc/letsencrypt/archive/ドメイン名
からのシンボリックリンクになっています。
archive
ディレクトリ以下にはこれまで発行した証明書と秘密鍵が保存されており、
最新のものがlive
ディレクトリにシンボリックリンクされる形となっています。)
live/ドメイン名
ディレクトリ内の各ファイルの内容は下記の通りです。
ファイル名 | 役割 |
---|---|
cert.pem | サーバ証明書(通常単体では使用しない) |
chain.pem | ルート認証局と中間局の証明書。OCSPで用いる。 |
fullchain.pem | ルート~中間~サーバ証明書をすべてまとめたもの。Webサーバの設定ではこれを用いる。 |
privkey.pem | サーバ証明書の公開鍵に対する秘密鍵。秘密なのでアクセス権限に注意(通常は600)。 |
証明書の更新
次回以降証明書を更新する際は、certbot renew
コマンドを実行するだけです。
systemdのtimerで、数日~1週間ぐらいおきに定期実行するようにしておきましょう。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
% sudo certbot renew
Saving debug log to /var/log/letsencrypt/letsencrypt.log
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Processing /etc/letsencrypt/renewal/hachune.net.conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Certificate not yet due for renewal
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Processing /etc/letsencrypt/renewal/negix.org.conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Certificate not yet due for renewal
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
The following certificates are not due for renewal yet:
/etc/letsencrypt/live/hachune.net/fullchain.pem expires on 2023-04-26 (skipped)
/etc/letsencrypt/live/negix.org/fullchain.pem expires on 2023-04-26 (skipped)
No renewals were attempted.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -