ワイルドカード付きSSL証明書の自動発行には Cloudflare が便利
この記事は jsys Advent Calendar 2024 の 3 日目の記事です。(jsys なんも関係なくてごめんなさい)
皆さん、セキュアな通信をしていますか?
どうも、n4mlz です。今回は、「Let's Encryptと Cloudflare を利用して、無料で SSL 証明書を自動発行・更新する」というテーマで書いていきます。
今まではどうしていたのか
まず、HSTS というものがあります。HSTS (HTTP Strict Transport Security) とは HTTP によるアクセスを HTTPS によるアクセスに強制するための技術です。私は n4mlz.dev
という dev
ドメインを利用しています。全ての dev
ドメインは HSTS preload list というものに登録されているので、ブラウザによるアクセスでは必ず SSL/TLS (以降 SSL) でアクセスされることになります。しかし私は怠惰なので、SSL 証明書の発行を面倒臭がっていました。結果どうしていたかというと、Cloudflare プロキシの SSL/TLS Encryption モード を利用していました。
これは、ドメイン名の解決先を Cloudflare のサーバーにすることでプロキシし、クライアント/Cloudflare 間のみを SSL 化するものです。これは Cloudflare のダッシュボードから簡単に設定でき、また自分のサーバーの IP が隠れるというメリットもあります。
しかし、Cloudflare/自分のサーバー間の通信は HTTP で行われることになります。自分はこれがモヤモヤしていて、SSL 化したいと思いました。
Let's Encrypt について
無料で SSL 証明書を発行するには、Let's Encrypt がよく用いられます。certbot
のような CLI ツールを使って、簡単に利用することができます。この最初の発行にはそのドメインを所有している証明が必要なのですが、これには ACME 標準で定義されている「チャレンジ」という手続きを行います。チャレンジには何種類かあるのですが、有名なものでは http-01
と dns-01
があります。なお、Let's Encrypt の証明書は 3 ヶ月で失効してしまうので、証明書が失効しないように、このチャレンジは定期的に行う必要があります。
チャレンジ
http-01
http-01
チャレンジでは、証明書を取得したいドメインの決まった箇所 (http://<YOUR_DOMAIN>/.well-known/acme-challenge/<TOKEN>
) に指定されたファイルを配置することで、そのサーバーが自分のものであることを証明します。実際には Let’s Encrypt とのやり取りやこのファイルの配置は、全て certbot
が自動で行ってくれます。
この方式の渋いところは、その性質上いちいちサブドメインまで含めた URL でやり取りする必要があるので、サブドメインのワイルドカード指定が出来ないということです。つまり、証明書を取得したいドメインをあらかじめ列挙しておく必要があります。これは新たにサブドメインを増やすときに面倒な作業が増えるので、あまり嬉しくありません。
また、チャレンジに URL によるアクセスを必要とする都合上、そのドメインの証明書を取得したいタイミングで、既にサーバーに HTTP でアクセスできる状態でなければならないという点も渋いです。しかも、チャレンジを行う際には certbot
が 80 番ポートをリッスンしている必要があるので、もし元々 80 番で動いているサーバーがあるなら一時的に停止する必要があります。簡単に利用できるという利点があるのですが、色々制約があるので、私は http-01
チャレンジをあまりおすすめしません。
dns-01
dns-01
は直接ドメインの解決先のサーバーと通信はせず、ドメイン名の TXT レコード (_acme-challenge.<YOUR_DOMAIN>
) に特定の値を設定することで、そのドメイン名が登録されている DNS が自分の制御下にあることを証明します。これは大抵の場合自分の手で行う必要があるので、http-01
よりかは設定するのが大変です。
dns-01
では実際のサーバー URL にアクセスする必要がないため、サーバーがまだ HTTP 通信を受け付けない (サーバーが存在しない) 状況でも証明書を発行できるという利点があります。また、http-01
のように動いているサーバーを一度止めないといけないということもありません。そしてなんといっても、サブドメインのワイルドカード指定ができます。
ということは、certbot
に自動で TEXT レコードを追加させることができれば、この問題は解決しそうですね。
certbot-dns-cloudflare を利用する
certbot-dns-cloudflare
は、まさに上記の問題を解決するものです。Cloudflare はいくつかの種類の API アクセストークンを用意しており、これには DNS レコードを操作するものもあります。certbot-dns-cloudflare
はこの API トークンを利用し、チャレンジのたびに毎回自動で指定された TEXT レコードを追加し、不要になった古い TEXT レコードを削除します。
まず、Cloudflare の API トークンを発行します。Cloudflare ダッシュボードの右上から、マイプロフィール
>APIトークン
>トークンを作成する
>ゾーン DNS を編集する
と進みます。
そして、以下のようにゾーンリソースを設定します。
その後、概要に進む
をクリックするとトークンが表示されるページに飛ぶので、このトークンをメモしておきます。このトークンは一度表示されると二度と再表示出来ないので、どこかでミスをしてトークンを無くしてしまった場合はトークンの発行からやり直してください。
これを、サーバーの安全そうな場所に配置します。以下では、/root/.cloudflare/
としています。ファイル名は自分のドメイン名にしていますが、別になんでも大丈夫です (私は区別のために n4mlz.dev.ini
としています)。
$ sudo mkdir /root/.cloudflare
$ sudo touch /root/.cloudflare/n4mlz.dev.ini
$ sudo echo 'dns_cloudflare_api_token=<発行したAPIトークン>' > n4mlz.dev.ini
$ sudo chmod 700 /root/.cloudflare
$ sudo chmod 600 /root/.cloudflare/n4mlz.dev.ini
次に、certbot をインストールします。公式 を見ると、snap
または pip
でインストールするのがよさそうです。ここでは、snap
を利用します。
$ sudo apt install snapd
$ sudo snap install core
$ sudo snap install --classic certbot
$ sudo ln -s /snap/bin/certbot /usr/bin/certbot
次に、certbot-dns-cloudflare
プラグインをインストールします。
$ sudo snap set certbot trust-plugin-with-root=ok
$ sudo snap install certbot-dns-cloudflare
これで準備が整いました!実際に証明書を発行してみます。
$ sudo certbot certonly \
--agree-tos \
-n \
--dns-cloudflare \
--dns-cloudflare-credentials /root/.cloudflare/n4mlz.dev.ini \
-d "n4mlz.dev,*.n4mlz.dev" \
-m <メールアドレス>
これで証明書が発行できました!
また、私の環境では /etc/cron.d/certbot
が勝手に追加されていて、放置しても cron によって 1 日に 2 度 certbot renew
が行われるようになっていました。他の環境でどうなのかはわかりませんが、/etc/cron.d/certbot
がない場合は自分で書いておくと良さそうです。
/etc/cron.d/certbot
# /etc/cron.d/certbot: crontab entries for the certbot package
#
# Upstream recommends attempting renewal twice a day
#
# Eventually, this will be an opportunity to validate certificates
# haven't been revoked, etc. Renewal will only occur if expiration
# is within 30 days.
#
# Important Note! This cronjob will NOT be executed if you are
# running systemd as your init system. If you are running systemd,
# the cronjob.timer function takes precedence over this cronjob. For
# more details, see the systemd.timer manpage, or use systemctl show
# certbot.timer.
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
0 */12 * * * root test -x /usr/bin/certbot -a \! -d /run/systemd/system && perl -e 'sleep int(rand(43200))' && certbot -q renew --no-random-sleep-on-renew
ここまでの操作により、証明書チェーンは /etc/letsencrypt/live/n4mlz.dev/fullchain.pem
に、秘密鍵は /etc/letsencrypt/live/n4mlz.dev/privkey.pem
に配置されます。
例: Nginx を SSL 化してみる
発行された証明書はワイルドカードを含んでいるので、せっかくならサブドメインを含んだドメインで試してみます。
/etc/nginx/nginx.conf
を以下のように設定しました。
events {
worker_connections 1024;
}
http {
server {
listen 80;
server_name ssl-test.n4mlz.dev;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name ssl-test.n4mlz.dev;
ssl_certificate /etc/letsencrypt/live/n4mlz.dev/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/n4mlz.dev/privkey.pem;
location / {
...
}
}
}
証明書を確認してみる
Cloudflare DNS に先ほどの ssl-test.n4mlz.dev
を登録し、プロキシをしない設定にしてみました。これでクライアントとの通信相手が Cloudflare プロキシではなく自分のサーバーになるので、自分の SSL 証明書が有効かどうかを直接確認することができます。
openssl
コマンドによって、以下のように証明書チェーンの確認ができます。ちゃんと SSL 化できていますね。
$ openssl s_client -connect ssl-test.n4mlz.dev:443 -showcerts </dev/null
CONNECTED(00000003)
depth=2 C=US, O=Internet Security Research Group, CN=ISRG Root X1
verify return:1
depth=1 C=US, O=Let's Encrypt, CN=E6
verify return:1
depth=0 CN=n4mlz.dev
verify return:1
---
Certificate chain
0 s:CN=n4mlz.dev
i:C=US, O=Let's Encrypt, CN=E6
a:PKEY: id-ecPublicKey, 256 (bit); sigalg: ecdsa-with-SHA384
v:NotBefore: Dec 2 08:04:11 2024 GMT; NotAfter: Mar 2 08:04:10 2025 GMT
...
また、いわゆるオレオレ証明書ではないので、ブラウザで表示してももちろん警告はでません。
Cloudflare プロキシを利用してみる
Cloudflare プロキシが提供する SSL/TLS Encryption モードはエンドツーエンドの暗号化をすることができます。こうすると、自分のサーバーも SSL に対応しつつ、Cloudflare プロキシの恩恵も受けられるようになります。
これは、Cloudflare ダッシュボードの左側のメニューから SSL/TLS
>SSL/TLS 暗号化
と進むことで設定することができます。また、DNS のページからプロキシを有効にすることを忘れないでください。
こうすることで、Cloudflare の IP アドレスに名前解決されることが確認できます。
$ dig ssl-test.n4mlz.dev @bonnie.ns.cloudflare.com. +norec +noedns
; <<>> DiG 9.20.3 <<>> ssl-test.n4mlz.dev @bonnie.ns.cloudflare.com. +norec +noedns
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 44301
;; flags: qr aa; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;ssl-test.n4mlz.dev. IN A
;; ANSWER SECTION:
ssl-test.n4mlz.dev. 300 IN A 172.67.211.243
ssl-test.n4mlz.dev. 300 IN A 104.21.59.36
;; Query time: 10 msec
;; SERVER: 108.162.192.76#53(bonnie.ns.cloudflare.com.) (UDP)
;; WHEN: Mon Dec 02 20:56:24 JST 2024
;; MSG SIZE rcvd: 66
まとめ
いかがでしたか?意外と簡単にワイルドカード付きの証明書の発行ができたと思います。この記事が誰かの役に立てば幸いです。いいねもお待ちしております!
Discussion