証明書を取得
最初に Let's Encrypt の証明書を取得するためのプログラムをインストールする。1
# apt install certbot
certbot は Let's Encrypt のサーバと通信して、証明書を取得する。この時、利用者が本当に example.jp ドメインの管理者であることを確認するために、Let's Encrypt のサーバは http://example.jp/.well-known/acme-challenge/
にアクセスする。したがって、この URL がアクセス可能であることを確認しておく必要がある。2
以下のようなテストファイルを用意して、http://example.jp/.well-known/acme-challenge/
にアクセス出来ることを確認する。
# mkdir /var/www/html/.well-known/acme-challenge
# echo HELLO > /var/www/html/.well-known/acme-challenge/index.txt
筆者の環境では、http://example.jp/
は基本的にアクセスを禁止しているため、以下のように明示的に /.well-known/acme-challenge/
に対するアクセスを許可した。
<VirtualHost *:80>
ServerName example.jp
ServerAdmin webmaster@example.jp
DocumentRoot /var/www/html
<Location /.well-known/acme-challenge/>
Require all granted
</Location>
(略)
</VirtualHost>
確認が完了したら、テスト用のディレクトリを丸ごと消しておく。
# rm -rf /var/www/html/.well-known
以下のコマンドで、SSL 証明書を取得する。
# certbot certonly --webroot -m yourname@example.jp -w /var/www/html -d example.jp
すると、/etc/letsencrypt/live/example.jp/
配下に証明書が保存される。
Apache の設定
以下のように証明書の場所を設定する。
<IfModule mod_ssl.c>
<VirtualHost _default_:443>
ServerName example.jp:443
ServerAdmin webmaster@example.jp
(略)
SSLCertificateFile /etc/letsencrypt/live/example.jp/cert.pem
SSLCertificateKeyFile /etc/letsencrypt/live/example.jp/privkey.pem
SSLCertificateChainFile /etc/letsencrypt/live/example.jp/chain.pem
(略)
</VirtualHost>
</IfModule>
その上で、証明書を再読込するように指示する。
# service apache2 restart
OpenLDAP の設定
証明書の場所を指定する LDIF ファイルを用意する。
dn: cn=config
add: olcTLSCertificateFile
olcTLSCertificateFile: /etc/letsencrypt/live/example.jp/cert.pem
-
add: olcTLSCertificateKeyFile
olcTLSCertificateKeyFile: /etc/letsencrypt/live/example.jp/privkey.pem
-
add: olcTLSCACertificateFile
olcTLSCACertificateFile: /etc/letsencrypt/live/example.jp/chain.pem
なお、新規にサーバ証明書を設定する場合は、上記のように add 操作を指定するが、既存のサーバ証明書を変更する場合は、下記のように replace 操作を指定する。
dn: cn=config
replace: olcTLSCertificateFile
olcTLSCertificateFile: /etc/letsencrypt/live/example.jp/cert.pem
-
replace: olcTLSCertificateKeyFile
olcTLSCertificateKeyFile: /etc/letsencrypt/live/example.jp/privkey.pem
-
replace: olcTLSCACertificateFile
olcTLSCACertificateFile: /etc/letsencrypt/live/example.jp/chain.pem
この LDIF ファイルを、以下のコマンドで読み込ませる。
# ldapmodify -Y EXTERNAL -H ldapi:/// -f sslconfig.ldif
SASL/EXTERNAL authentication started
SASL username: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
SASL SSF: 0
modifying entry "cn=config"
正しく読み込まれた場合は、上記のように特にエラーは表示されない。しかし、
- slapd の実行ユーザ(Debian の場合は openldap ユーザ)権限で証明書を読み取ることができない場合
- slapd が ldaps:/// を listen していない場合
のどちらかのトラブルがあると、以下のようなエラーになる。
# ldapmodify -Y EXTERNAL -H ldapi:/// -f replace.ldif
SASL/EXTERNAL authentication started
SASL username: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
SASL SSF: 0
modifying entry "cn=config"
ldap_modify: Other (e.g., implementation specific) error (80)
slapd の実行ユーザが証明書を読み取れるようにするには、以下のコマンドを実行する。
# setfacl -m u:openldap:rx /etc/letsencrypt/live
# setfacl -m u:openldap:rx /etc/letsencrypt/archive
slapd が ldaps:/// を listen していない場合は、以下のように /etc/default/slapd
を編集する。
SLAPD_SERVICES="ldap:/// ldaps:/// ldapi:///"
なお、設定内容を確認するには slapcat コマンド。
# slapcat -b cn=config
Dovecot の設定
ssl = yes
ssl_cert = </etc/letsencrypt/live/example.jp/fullchain.pem
ssl_key = </etc/letsencrypt/live/example.jp/privkey.pem
等号 =
直後の不等号 <
は省略できないので注意。
tls_ca_cert_file = /etc/letsencrypt/live/example.jp/chain.pem
# setfacl -m u:dovecot:rx /etc/letsencrypt/archive
# setfacl -m u:dovecot:rx /etc/letsencrypt/live
IMAP over SSL が正常に接続できているかどうかは、以下のコマンドでチェック。
openssl s_client -connect example.jp:993 -verify 4
Postfix の設定
smtpd_tls_security_level = may
smtpd_tls_cert_file = /etc/letsencrypt/live/example.jp/cert.pem
smtpd_tls_key_file = /etc/letsencrypt/live/example.jp/privkey.pem
smtpd_use_tls = yes
smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
なお、この設定では 25/tcp で待ち受けて、必要な場合に STARTTLS で暗号化を開始するという挙動になる。そのため、動作確認には、以下のように openssl コマンドに -starttls smtp
オプションを指定する。
$ openssl s_client -connect mail.example.jp:25 -starttls smtp
465/tcp で待ち受ける SMTP over SSL サーバを立てる必要がある場合は、/etc/postfix/master.cf
に設定を加える必要がある。
証明書の自動更新
#!/bin/sh
service apache2 reload
#!/bin/sh
service slapd force-reload
#!/bin/sh
service dovecot reload
#!/bin/sh
service postfix reload
以下のコマンドを実行して、証明書が更新されること、かつ、証明書更新後にその証明書を読み込むスクリプトが実行されること、を確認しておく。
# certbot renew --force-renewal
DNS を利用して証明書を取得
certbot は Let's Encrypt のサーバと通信して、証明書を取得する。この時、利用者が本当に example.jp ドメインの管理者であることを確認するために、Let's Encrypt のサーバは http://example.jp/.well-known/acme-challenge/
にアクセスする。ところが、プライベートIPアドレスを利用している閉鎖ネットワークであるとか、セキュリティ上の要件から 80/tcp が公開できない、等の各種の理由で、HTTP 経由で認証できない場合がある。
そのような場合は、DNS-01 Challenge が必要になる。DNS-01 Challenge は、大雑把に言うと以下のような処理の流れになっている。
- Certbot は Let's Encrypt に共有鍵の発行を申請する。
- 管理者は
_acme-challenge.example.jp
というホスト名の TXT レコードに、共有鍵を設定し、設定完了を通知する。 - Let's Encrypt は上記の TXT レコードに共有鍵が設定されていることを確認し、確認できたらサーバ証明書を発行する。
サーバ証明書の更新を自動化するには、TXT レコードの設定を自動化しなければならないという点がネックになる。DNS サーバとして Route53 を利用している場合には、専用のプラグイン(certbot-dns-route53)を利用する方法があるらしい。筆者の場合は、DNS サーバとして BIND を使っているので、Dynamic DNS の設定を行うことにした。
ただ、主要サービスに利用している example.jp ゾーンを直接 Dynamic DNS 管理下に置くことはセキュリティ上ためらわれたので、Dynamic DNS 専用のゾーンとして acme-challenge.example.jp ゾーンを別に用意した。その上で、example.jp ゾーンは以下のように設定し、_acme-challenge.example.jp に対する CNAME レコードを用意しておく。
example.jp IN A 198.51.100.11
_acme-challenge.example.jp IN CNAME example.jp.acme-challenge.example.jp.
次に、Certbot と Dynamic DNS を連携するスクリプトを用意する。Certbot と連携スクリプトは CERTBOT_DOMAIN と CERTBOT_VALIDATION という環境変数を使って情報を連携するので、必要な TXT レコードを登録するためのスクリプトは以下のようになる。
#!/bin/sh
[ -z "${CERTBOT_DOMAIN}" ] && exit 1
[ -z "${CERTBOT_VALIDATION}" ] && exit 1
# 実際には Dynamic DNS の設定を行ったサーバの IP アドレス
server=198.51.100.10
dynzone=acme-challenge.example.jp
cat <<EOF | nsupdate
server ${server}
update add ${CERTBOT_DOMAIN}.${dynzone}. 300 IN TXT "${CERTBOT_VALIDATION}"
send
EOF
# DNS情報の反映に要する時間.筆者の環境では,待機時間をゼロにすると認証失敗が多発した.
sleep 3
利用が終わった TXT レコードを抹消するスクリプトも用意しておく。
#!/bin/sh
[ -z "${CERTBOT_DOMAIN}" ] && exit 1
# 実際には Dynamic DNS の設定を行ったサーバの IP アドレス
server=198.51.100.10
dynzone=acme-challenge.example.jp
cat <<EOF | nsupdate
server ${server}
update delete ${CERTBOT_DOMAIN}.${dynzone}. 300 IN TXT
send
EOF
以下のように、上記スクリプトを使って DNS-01 Challenge を行うように指定して、証明書取得を行う。
# certbot certonly -m yourname@example.jp -d example.jp --preferred-challenges=dns --manual --manual-auth-hook /etc/letsencrypt/dns-auth.sh --manual-cleanup-hook /etc/letsencrypt/dns-cleanup.sh
証明書が無事に取得できたら、残る設定作業は HTTP 経由で証明書を取得した場合と同じである。
-
Let's Encrypt の証明書を取得するためのプログラムとしては getssl もある。Debian9 では、certbot はパッケージ化されていたのに対して、getssl はパッケージ化されていなかったという点が、本稿で certbot を採用した理由である。他に理由がなければ、依存ライブラリが少ない getssl の方が良いかも知れない。 ↩
-
standalone モードで certbot を利用すれば、この確認や設定は省略可能。しかし、証明書の更新を自動化するためには、Apache を起動したままで証明書を更新できるように設定する必要があるため、今回は、最初の証明書取得時から Apache を起動したままで取得する手順を検討した。 ↩