メールサーバのSSL証明書をLet's Encryptで作る
20年くらい自宅サーバで運用しているメールがあり、自分一人しか利用者がいないため、これまではずっと自己署名証明書を使っていました。ところがiPhoneをiOS18にしたところ、メールアプリからそのIMAPサーバに接続できなくなりました。
自己署名証明書を「信頼」してもiPhoneのメールアプリで次のようなメッセージが表示されてしまうようになりました。
IMAPサーバはdovecotを使用していて、次のようなログが記録されています。
imap-login: Disconnected: Connection closed: SSL_accept() failed: error:
0A000416:SSL routines::sslv3 alert certificate unknown: SSL alert number
46 (no auth attempts in 0 secs): user=<>, rip=**.**.**.**, lip=192.168.
0.4, TLS handshaking: SSL_accept() failed: error:0A000416:SSL routines::
sslv3 alert certificate unknown: SSL alert number 46, session=<3jJ2OjwpB
u3Zsjxc>
なお、Macの方も最新のSequoiaにしましたが、メールアプリからは引き続き問題なく接続できています。
自己署名証明書を作り直してみたりなどしても解決しなかったので、Let's Encryptで証明書を作ってみることにしました。結果を先に書くと、無事IMAPサーバに接続できるようになりました。
以下は、その際にやったことになります。同様の記事は他にも多数ありますが、前提条件が違うと微妙に手順が異なるところがあったので記録として残しておきます。
前提条件
- OS: Debian bookworm
- IMAPサーバ: dovecot(Debian bookwormのパッケージのもの)
- SMTPサーバ: postfix(同上)
- DNSサーバ: BIND(同上)
- メールサーバのホスト名: mail.example.com
- 作成する証明書: ワイルドカードではないもの
- Let's Encryptのチャレンジの種類: DNS-01
対象のメールサーバは外部からのHTTP/HTTPS通信が届かないネットワーク構成のため、HTTP-01チャレンジは使用できません。そのためDNS-01チャレンジを使用します。DNS-01チャレンジを使用する場合、ワイルドカード証明書を作成することができるようですが、今回はワイルドカード証明書を必要としないので、ワイルドカードにはしません。
OSはDebian bookwormで、dovecotとpostfixは自己署名証明書での運用が既にできている前提です。そのため、dovecotとpostfixの設定変更は証明書の切り替えのみとなります。
certbotのインストール
# apt-get install certbot python3-certbot-dns-rfc2136
DNS-01チャレンジを使用するので、certbot
本体の他に python3-certbot-dns-rfc2136
が必要です。他に依存パッケージが40個くらいインストールされました。
ゾーンの追加
DNS-01チャレンジを使用するためには、DNSの動的更新(RFC2136)によってTXTレコードの追加/削除ができるようにする必要があります。この記事ではメールサーバのホスト名を mail.example.com
としているので、_acme-challenge.mail.example.com
という名前に対してDNSの動的更新によるTXTレコードの追加/削除が必要となります。動的更新で何かトラブルがあった時に example.com
のゾーン全体に影響してしまうのを避けるため、ゾーンを分けます。
分けるゾーンは _acme-challenge.mail.example.com
です(mail.example.com
ではありません)。
_acme-challenge.mail.example.com
のゾーンを分けるために、ゾーンファイルを次のように作成します。なお、example.com
のネームサーバは仮に ns1.example.com
とします。
$TTL 86400
@ IN SOA ns1.example.com. hostmaster.example.com. (
2024122701 ; serial
86400 ; refresh (1 day)
3600 ; retry (1 hour)
1209600 ; expire (2 weeks)
1200 ; minimum (20 minutes)
)
@ IN NS ns1.example.com.
このゾーンファイルは /var/cache/bind/_acme-challenge.mail.example.com.zone
に保存します。
このゾーンを追加するために /etc/bind/named.conf
を修正します。
// 追加
zone "_acme-challenge.mail.example.com" {
type master;
file "/var/cache/bind/_acme-challenge.mail.example.com.zone";
check-names ignore;
update-policy {
// certbot-key はDNS動的更新用の鍵の名前(後述)
grant certbot-key. name _acme-challenge.mail.example.com. TXT;
};
};
_acme-challenge
という文字列はドメイン名としては不正な名前のため、check-names ignore;
の設定で名前のチェックを無効にする必要があります。
grant
で始まる行の certbot-key
は次に説明するDNS動的更新用の鍵の名前です。
DNS動的更新用の鍵の作成
tsig-keygen
コマンドを使用します。
tsig-keygen -a アルゴリズム 鍵の名前
という引数を取ります。アルゴリズムは hmac-sha512
にしました。名前は、前述の certbot-key
を指定します。
# tsig-keygen -a hmac-sha512 certbot-key
実行すると、標準出力に次のように出力されます。
key "certbot-key" {
algorithm hmac-sha512;
secret "AC9cpGlhJXW765kxS+NRV7IauQThXvI2NFNwwkJfhzUwOMukiBySR4jN4LqjDsJd7aorV/28NvJMHuvq5ChzZQ==";
};
これを /etc/bind/certbot-key.key
に保存し、オーナーとグループを bind:bind
、パーミッションを 600
にします。
この鍵をBINDで利用するために /etc/bind/named.conf
に次の行を追加します。
include "/etc/bind/certbot-key.key";
設定変更を反映します。
# systemctl reload named
変更内容の確認をします。
# dig @DNSサーバのIPアドレス _acme-challenge.mail.example.com SOA
; <<>> DiG 9.10.6 <<>> @**.**.**.** _acme-challenge.mail.example.com SOA
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 33832
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;_acme-challenge.mail.example.com. IN SOA
;; ANSWER SECTION:
_acme-challenge.mail.example.com. 86400 IN SOA ns1.example.com. hostmaster.example.com. 2024122701 86400 3600 1209600 1200
;; Query time: 19 msec
;; SERVER: **.**.**.**#53(**.**.**.**)
;; WHEN: Fri Dec 27 15:56:42 JST 2024
;; MSG SIZE rcvd: 119
RFC2136クレデンシャルファイルの作成
certbot
をDNS-01チャレンジで実行するときに必要になるファイルを作成します。
次の内容のファイルを作成し、/etc/letsencrypt/dns-rfc2136.ini
に保存します。
dns_rfc2136_server = DNSサーバのIPアドレス
dns_rfc2136_port = 53
dns_rfc2136_name = certbot-key.
dns_rfc2136_secret = AC9cpGlhJXW765kxS+NRV7IauQThXvI2NFNwwkJfhzUwOMukiBySR4jN4LqjDsJd7aorV/28NvJMHuvq5ChzZQ==
dns_rfc2136_algorithm = HMAC-SHA512
dns_rfc2136_name
と dns_rfc2136_algorithm
は前述の tsig-keygen
で指定した名前とアルゴリズムです。dns_rfc2136_secret
はそのときに生成された secret
です。
certbotの実行
テスト実行
まず --dry-run
を付けてテスト実行します。
# certbot certonly --dns-rfc2136 --dns-rfc2136-credentials /etc/letsencrypt/dns-rfc2136.ini -d 'mail.example.com' --dry-run
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Simulating a certificate request for mail.example.com
Waiting 60 seconds for DNS changes to propagate
The dry run was successful.
と出ればOKです。
Waiting 60 seconds for DNS changes to propagate
のところで60秒待ちますので、その隙に
# dig @DNSサーバのIPアドレス _acme-challenge.mail.example.com TXT
として、_acme-challenge.mail.example.com
の TXT レコードが動的に設定されているか確認します。
; <<>> DiG 9.10.6 <<>> @**.**.**.** _acme-challenge.mail.example.com TXT
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 59372
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;_acme-challenge.mail.example.com. IN TXT
;; ANSWER SECTION:
_acme-challenge.mail.example.com. 120 IN TXT "UxbZwwS7ShMQZy1E9_9sQ3wue75CTrz4MwOSiI8BBVk"
;; Query time: 18 msec
;; SERVER: **.**.**.**#53(**.**.**.**)
;; WHEN: Fri Dec 27 13:53:45 JST 2024
;; MSG SIZE rcvd: 115
60秒過ぎるとTXTレコードは削除されます。
本番
--dry-run
を外して本番を行います。
# certbot certonly --dns-rfc2136 --dns-rfc2136-credentials /etc/letsencrypt/dns-rfc2136.ini -d 'mail.example.com'
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Requesting a certificate for mail.example.com
Waiting 60 seconds for DNS changes to propagate
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/mail.example.com/fullchain.pem
Key is saved at: /etc/letsencrypt/live/mail.example.com/privkey.pem
This certificate expires on 2025-03-27.
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this certificate in the background.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
If you like Certbot, please consider supporting our work by:
* Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
* Donating to EFF: https://eff.org/donate-le
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Certbot has set up a scheduled task to automatically renew this certificate in the background.
とメッセージが出ているので、更新処理はこのまま特に何もしなくても自動で行われるようです。
# systemctl status certbot.timer
とすると、更新処理のステータスがわかります。
* certbot.timer - Run certbot twice daily
Loaded: loaded (/lib/systemd/system/certbot.timer; enabled; preset: enabled)
Active: active (waiting) since Fri 2024-12-20 17:39:02 JST; 6 days ago
Trigger: Fri 2024-12-27 19:41:18 JST; 5h 4min left
Triggers: * certbot.service
dovecotとpostfixの設定
はじめに書いたように、証明書の切り替えのみを行います。
ssl_cert = </etc/letsencrypt/live/mail.example.com/fullchain.pem
ssl_key = </etc/letsencrypt/live/mail.example.com/privkey.pem
smtpd_tls_cert_file=/etc/letsencrypt/live/mail.example.com/fullchain.pem
smtpd_tls_key_file=/etc/letsencrypt/live/mail.example.com/privkey.pem
証明書の変更を反映します。
# systemctl reload postfix dovecot
まとめ
Debian bookworm で postfix と dovecot にLet's EncryptのSSL証明書(DNS-01チャレンジ)使うには、
-
certbot
とpython3-certbot-dns-rfc2136
のパッケージをインストール - DNSの動的更新(RFC2136)によってTXTレコードの追加/削除ができるようにする
- ゾーンの追加
-
tsig-keygen
で鍵の作成
-
certbot
で使用するRFC2136クレデンシャルファイルの作成 -
certbot
の実行 - postfix と dovecot の証明書の設定
という手順になります。
Discussion