Postfix と Dovecot と Let's Encrypt
〜 DNS-01 HTTP-01 で暗号化して自動更新 〜
2021-07-30 作成 福島
TOP > tips > postfix-encrypted
0. 設定要件
すでに Postfix/Dovecot が平文で構築されていることを前提に設定します。
まだ構築が完了していなければこちらで構築しておいてください。

項目内容備考
OSCentOS 7.9.2009手元で稼働中の Linux。
ホントは最新の CentOS8 で設定したかったが、
CentOS7 よりもサポートが短くなってしまった。
DNS サーバの IP アドレス192.168.122.3リモート更新可能な設定がされていること。
SMTP サーバソフトウェアPostfix 2.10.1CentOS7 に付属のもの
全体設定/etc/postfix/main.cf
プロトコル設定/etc/postfix/master.cf
POP3/IMAP サーバソフトウェアDovecot 2.2.36CentOS7 に付属のもの。
起動設定ファイルは /etc/dovecot/dovecot.conf
メールファイル設定/etc/dovecot/conf.d/10-master.confconf.d/*.conf は dovecot.conf から追加で読み込まれる。
ログイン認証設定/etc/dovecot/conf.d/10-auth.conf
SSL 設定/etc/dovecot/conf.d/10-ssl.conf
メールサーバ名mail.example.jp-
管理者のメールアドレスadmin@example.jp-
ユーザのメールアドレスwho@example.jp-

1. メールサーバ用証明書の取得について
この手順書は主に下記 1-b を取り扱っている。

1-a. メールサーバが Web サーバと同居可能の場合。
サーバ機がポート 80 や 443 を利用可能な場合は HTTP-01 チャレンジを使用して証明書を取得する。
下記手順 2, 4, 6 ~ 9 を実施する。(6-1 では HTTP-01 チャレンジを利用する)
1-b. リモートから DNS の更新ができる場合。
下記手順 2 ~ 9 を実施する。(6-1 では DNS-01 チャレンジを利用する)
1-c. Web サーバと同居できず、DNS の更新許可も無い場合。
Let's Encrypt は手動で利用できるので、上記 1-a または 1-b を他のサーバで実施し、
証明書ファイルをメールサーバへコピーすることにより取得・更新する。
Let's Encrypt のチャレンジの種類はここに記述がある。

2. OS とエクスチェンジャ、メールサーバのバージョンを確認
$ cat /etc/system-release
CentOS Linux release 7.9.2009 (Core)
$ dig example.jp -t mx +short | sort -n
10 mail.example.jp.
ドメイン名とメールサーバ名が紐づいていること。
複数ある場合は、優先順位の値 (この場合は 10) が小さいものから優先される。
$ /usr/sbin/postconf | grep ^mail_version
mail_version = 2.10.1
$ /usr/sbin/dovecot --version
2.2.36

3. DNS-01 チャレンジ利用の準備
3-1. メールサーバから DNS の更新ができるように設定
ここを参考にしてメールサーバから DNS の更新ができるようにしておく。
3-2. DNS 更新コマンドを確認する。
$ nsupdate -V
nsupdate 9.11.4-P2-RedHat-9.11.4-26.P2.el7_9.5
3-3. メールサーバから DNS の更新ができることを確認
3-3-1. TXT レコードの追加
$ nsupdate << EOF
server 192.168.122.3
update add _acme-challenge.mail.example.jp. 60 in txt "check string"
send
EOF

$ dig @192.168.122.3 -t txt _acme-challenge.mail.example.jp +short
"check string"
3-3-2. TXT レコードの削除
$ nsupdate << EOF
server 192.168.122.3
update delete _acme-challenge.mail.example.jp. txt
send
EOF

$ dig @192.168.122.3 -t txt _acme-challenge.mail.example.jp +short
(何も表示されなければ OK)

4. certbot のインストール
4-1. epel のインストール
$ su
# yum -y install epel-release
# yum -y update epel-release
4-2. certbot のインストール
# yum -y install certbot

# certbot --version
certbot 1.11.0
# exit
$

5. DNS-01 チャレンジ用スクリプト (DNS 更新) を用意
5-1. DNS-01 チャレンジ文字列登録用スクリプトを作成する。
$ su
# cd /etc/sysconfig/
/etc/sysconfig/# vim certbot-dns-authenticator.sh (754)
#!/bin/bash

MYDNS_SERVER=192.168.122.3

nsupdate << EOF
server $MYDNS_SERVER
update add _acme-challenge.$CERTBOT_DOMAIN. 60 in txt $CERTBOT_VALIDATION
send
EOF
・$CERTBOT_DOMAIN は certbot の実行時に指定されたサーバ名が自動的に入る。(下記手順 6-1 における mail.example.jp のこと)
・$CERTBOT_VALIDATION は Let's Encrypt サーバから指示された文字列が自動的に入る。
certbot から呼ばれるスクリプトのマクロはここに説明がある。
/etc/sysconfig/# chmod 754 certbot-dns-authenticator.sh
5-2. DNS-01 チャレンジ文字列削除用スクリプトを作成する。
/etc/sysconfig/# vim certbot-dns-cleanup.sh (754)
#!/bin/bash

MYDNS_SERVER=192.168.122.3

nsupdate << EOF
server $MYDNS_SERVER
update delete _acme-challenge.$CERTBOT_DOMAIN. txt
send
EOF
/etc/sysconfig/# chmod 754 certbot-dns-cleanup.sh
/etc/sysconfig/# exit
$

6. 証明書の取得
6-1. certbot を実行
下記 6-1-a, 6-1-b のどちらかを実行する。

いずれの場合も、最初はオプション --staging を付けて実行し、すべての正常動作が確認出来たら、これを取り除いて再度実行する。
本番サーバに対する証明書取得・更新の頻度は 5 回/週 の制限があります。

$ su
#

6-1-a.「HTTP-01 チャレンジ」を使用する場合。
# 既存 Web サーバが停止中または、ポート 80 を使用可能の場合。(メールサーバ単体なら、こちらを使うはず)
# certbot certonly --staging --standalone -d mail.example.jp

# 既存 Web サーバが稼働中の場合。(メールサーバ名と同一の Web サーバを持たないと使えない*1)
# certbot certonly --staging --webroot -w /var/www/html/ -d mail.example.jp

*1認証サーバが -d で指定したサーバをアクセスするので http://mail.example.jp/ が必要になる。
6-1-b.「DNS-01 チャレンジ」を使用する場合。
# certbot certonly --staging \
    --manual \
    --preferred-challenges=dns \
    --manual-auth-hook /etc/sysconfig/certbot-dns-authenticator.sh \
    --manual-cleanup-hook /etc/sysconfig/certbot-dns-cleanup.sh \
    --renew-hook="systemctl restart postfix; systemctl restart dovecot" \
    -d mail.example.jp
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator manual, Installer None
Enter email address (used for urgent renewal and security notices)
 (Enter 'c' to cancel): admin@example.jp 
Starting new HTTPS connection (1): acme-staging-v02.api.letsencrypt.org/directory

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please read the Terms of Service at
https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf. You must
agree in order to register with the ACME server. Do you agree?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: y 

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Would you be willing, once your first certificate is successfully issued, to
share your email address with the Electronic Frontier Foundation, a founding
partner of the Let's Encrypt project and the non-profit organization that
develops Certbot? We'd like to send you email about our work encrypting the web,
EFF news, campaigns, and ways to support digital freedom.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: n 
Account registered.
Requesting a certificate for mail.example.jp
Performing the following challenges:
dns-01 challenge for mail.example.jp
Running manual-auth-hook command: /etc/sysconfig/certbot-dns-authenticator.sh
Waiting for verification...
Cleaning up challenges
Running manual-cleanup-hook command: /etc/sysconfig/certbot-dns-cleanup.sh
Running deploy-hook command: systemctl restart postfix; systemctl restart dovecot

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at:
   /etc/letsencrypt/live/mail.example.jp/fullchain.pem
   Your key file has been saved at:
   /etc/letsencrypt/live/mail.example.jp/privkey.pem
   Your certificate will expire on 2021-10-20. To obtain a new or
   tweaked version of this certificate in the future, simply run
   certbot again. To non-interactively renew *all* of your
   certificates, run "certbot renew"
 - 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
6-2. 証明書ファイルを確認する。
# cd /etc/letsencrypt/live/mail.example.jp/
/etc/letsencrypt/live/mail.example.jp/# ls -l
-rw-r--r--. 1 root root 692  7月 22 10:40 README
lrwxrwxrwx. 1 root root  38  7月 22 10:40 cert.pem -> ../../archive/mail.example.jp/cert1.pem
lrwxrwxrwx. 1 root root  39  7月 22 10:40 chain.pem -> ../../archive/mail.example.jp/chain1.pem
lrwxrwxrwx. 1 root root  43  7月 22 10:40 fullchain.pem -> ../../archive/mail.example.jp/fullchain1.pem
lrwxrwxrwx. 1 root root  41  7月 22 10:40 privkey.pem -> ../../archive/mail.example.jp/privkey1.pem
/etc/letsencrypt/live/mail.example.jp/# cd ../../archive/mail.example.jp/
/etc/letsencrypt/archive/mail.example.jp/# ls -l
-rw-r--r--. 1 root root 1842  7月 22 10:40 cert1.pem
-rw-r--r--. 1 root root 3749  7月 22 10:40 chain1.pem
-rw-r--r--. 1 root root 5591  7月 22 10:40 fullchain1.pem
-rw-------. 1 root root 1704  7月 22 10:40 privkey1.pem
/etc/letsencrypt/archive/mail.example.jp/# exit
$

7. Dovecot の設定を変更
7-1. POP3s/IMAPs を許可する。(ポート 995/993 を開く)
$ su
# cd /etc/dovecot/conf.d/
/etc/dovecot/conf.d/# vim 10-master.conf

service imap-login { inet_listener imap {
#port = 143 # ポート 143 は禁止する。 } inet_listener imaps { port = 993 ssl = yes }
service pop3-login { inet_listener pop3 { #port = 110 # ポート 110 は禁止する。 } inet_listener pop3s { port = 995 ssl = yes } }
7-2. POP3s/IMAPs の暗号化を指定する。
/etc/dovecot/conf.d/# vim 10-auth.conf

# connection is considered secure and plaintext authentication is allowed. # See also ssl=required setting. #
#disable_plaintext_auth = yes # 平文のパスワードを拒否 # Authentication cache size (e.g. 10M). 0 means it's disabled. Note that
/etc/dovecot/conf.d/# vim 10-ssl.conf

# disable plain pop3 and imap, allowed are only pop3+TLS, pop3s, imap+TLS and imaps # plain imap and pop3 are still allowed for local connections #
ssl = required # SSL 接続を必須にする。(yes だと平文も許可) # PEM encoded X.509 SSL/TLS certificate and private key. They're opened before # dropping root privileges, so keep the key file unreadable by anyone but # root. Included doc/mkcert.sh can be used to easily generate self-signed # certificate, just make sure to update the domains in dovecot-openssl.cnf ssl_cert = </etc/letsencrypt/live/mail.example.jp/fullchain.pem ssl_key = </etc/letsencrypt/live/mail.example.jp/privkey.pem # If key file is password protected, give the password here. Alternatively
7-3. dovecot を再起動する。
/etc/dovecot/conf.d/# systemctl restart dovecot

/etc/dovecot/conf.d/# exit
$
IMAPs の設定は、以下になる。
ポート993
接続の保護SSL/TLS
認証方式通常のパスワード認証

POP3s の設定は、以下になる。
ポート995
接続の保護SSL/TLS
認証方式通常のパスワード認証

8. Postfix の設定を変更
8-1. 全体設定に暗号化を指定する。
$ su
# cd /etc/postfix/
/etc/postfix/# vim main.cf
以下を追加する。

smtpd_use_tls = yes smtp_tls_security_level = may smtpd_tls_cert_file = /etc/letsencrypt/live/mail.example.jp/fullchain.pem smtpd_tls_key_file = /etc/letsencrypt/live/mail.example.jp/privkey.pem smtpd_tls_loglevel = 1 smtpd_tls_received_header = yes smtpd_recipient_restrictions = permit_mynetworks permit_sasl_authenticated reject_unauth_destination
8-2. プロトコル設定に暗号化を指定する。
/etc/postfix/# vim master.cf

#dnsblog unix - - n - 0 dnsblog #tlsproxy unix - - n - 0 tlsproxy
submission inet n - n - - smtpd # -o syslog_name=postfix/submission # -o smtpd_tls_security_level=encrypt -o smtpd_sasl_auth_enable=yes # -o smtpd_tls_auth_only=yes # -o smtpd_reject_unlisted_recipient=no # -o smtpd_client_restrictions=$mua_client_restrictions # -o smtpd_helo_restrictions=$mua_helo_restrictions # -o smtpd_sender_restrictions=$mua_sender_restrictions -o smtpd_recipient_restrictions=permit_sasl_authenticated,reject -o smtpd_relay_restrictions=permit_sasl_authenticated,reject # -o milter_macro_daemon_name=ORIGINATING smtps inet n - n - - smtpd # -o syslog_name=postfix/smtps -o smtpd_tls_wrappermode=yes -o smtpd_sasl_auth_enable=yes # -o smtpd_reject_unlisted_recipient=no # -o smtpd_client_restrictions=$mua_client_restrictions # -o smtpd_helo_restrictions=$mua_helo_restrictions # -o smtpd_sender_restrictions=$mua_sender_restrictions -o smtpd_recipient_restrictions=permit_sasl_authenticated,reject # -o milter_macro_daemon_name=ORIGINATING
/etc/postfix/# systemctl restart postfix
/etc/postfix/# exit
$
SMTP の設定は、以下になる。(定番はこちら)
ポート587
接続の保護STARTTLS
認証方式通常のパスワード認証

smtps を有効にした場合は以下も使用可能。(正規プロトコルだが、デファクトになっていない)
ポート465
接続の保護SSL/TLS
認証方式通常のパスワード認証

9. 証明書の自動更新
9-1. 自動更新オプションを確認する。
$ cat /etc/letsencrypt/renewal/mail.example.jp.conf
# renew_before_expiry = 30 days
version = 1.11.0
archive_dir = /etc/letsencrypt/archive/mail.example.jp
cert = /etc/letsencrypt/live/mail.example.jp/cert.pem
privkey = /etc/letsencrypt/live/mail.example.jp/privkey.pem
chain = /etc/letsencrypt/live/mail.example.jp/chain.pem
fullchain = /etc/letsencrypt/live/mail.example.jp/fullchain.pem

# Options used in the renewal process
[renewalparams]
authenticator = manual
account = 3o8ob75eZ94ea36ii7f673c58b9e2i33
manual_public_ip_logging_ok = None
manual_auth_hook = /etc/sysconfig/certbot-dns-authenticator.sh
server = https://acme-staging-v02.api.letsencrypt.org/directory
manual_cleanup_hook = /etc/sysconfig/certbot-dns-cleanup.sh
pref_challs = dns-01,
renew_hook = systemctl restart postfix; systemctl restart dovecot

下記オプションが指定されていること。
オプション実行のタイミング内容
manual_auth_hook認証時に実行される。DNS-01 チャレンジの TXT を DNS サーバへ登録する。
manual_cleanup_hook認証直後に実行される。DNS-01 チャレンジの TXT を DNS サーバから削除する。
renew_hook証明書取得が成功したときに実行される。Postfix と Dovecot を再起動する。
authenticator更新方法
(http を使用した場合)
上記 6-1-a の --standalone で証明書を取得した場合は authenticator = standalone が設定される。
証明書の更新時に他のプロセスでポート 80 を使用してはいけない。
(postfixadmin 等は 443 のみで起動すること)
9-2. 証明書を更新してみる。
$ su
# certbot renew
まだ更新期限の 30 日前を迎えていないので更新されない。
更新しようともしない (No renewals were attempted) ので、Let's Encrypt サーバに負荷はかからない。
Saving debug log to /var/log/letsencrypt/letsencrypt.log

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Processing /etc/letsencrypt/renewal/mail.example.jp.conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Cert not yet due for renewal

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
The following certificates are not due for renewal yet:
  /etc/letsencrypt/live/mail.example.jp/fullchain.pem expires on 2021-10-20 (skipped)
No renewals were attempted.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
9-3. 証明書の自動更新用ファイルを確認する。
# ls -l /usr/lib/systemd/system/certbot-renew.*
-rw-r--r--. 1 root root 284  1月  6  2021 /usr/lib/systemd/system/certbot-renew.service
-rw-r--r--. 1 root root 195  1月  6  2021 /usr/lib/systemd/system/certbot-renew.timer

[certbot-renew.service] (無変更)
[Unit]
Description=This service automatically renews any certbot certificates found

[Service]
EnvironmentFile=/etc/sysconfig/certbot
Type=oneshot
ExecStart=/usr/bin/certbot renew --noninteractive --no-random-sleep-on-renew $PRE_HOOK $POST_HOOK $RENEW_HOOK $DEPLOY_HOOK $CERTBOT_ARGS

[certbot-renew.timer] (無変更)
[Unit]
Description=This is the timer to set the schedule for automated renewals

[Timer]
OnCalendar=*-*-* 00/12:00:00
RandomizedDelaySec=12hours
Persistent=true

[Install]
WantedBy=timers.target
9-4. 証明書の更新を systemd (timer) に登録する。
# systemctl enable certbot-renew.timer
# systemctl start certbot-renew.timer
# systemctl status certbot-renew.timer | grep Active
OS 起動時に自動更新のタイマーも起動されるようになった。(waiting)
Active: active (waiting) since 木 2021-07-22 22:21:30 JST; 12s ago
# systemctl list-timers | grep certbot-renew.timer
次は 10 時間後 ("10h left") に certbot-renew.service が実行される。
金 2021-07-23 08:31:02 JST  10h left    n/a    n/a     certbot-renew.timer     certbot-renew.service
# exit
$

10. ポートアクセスを許可
$ su
# firewall-cmd --zone=public --add-service=imaps --permanent
# firewall-cmd --zone=public --add-service=pop3s --permanent
# firewall-cmd --zone=public --add-service=smtps --permanent
# firewall-cmd --zone=public --add-service=smtp-submission --permanent # ← 前段「5. ポートアクセスの許可」で実施済のはず。
# firewall-cmd --reload

# firewall-cmd --zone=public --list-all
public (active)
  target: default
  icmp-block-inversion: no
  interfaces: enp1s0 enp8s0
  sources:
  services: cockpit dhcpv6-client http https imap imaps pop3 pop3s smtp smtp-submission smtps ssh
  ports:
  protocols:
  forward: yes
  masquerade: no
  forward-ports:
  source-ports:
  icmp-blocks:
  rich rules:
# exit
$