[toc]
Certbot(旧Let's Encrypt)は無料でSSL/TLS証明書を発行できる認証局(CA)です。有効期限が90日(約3ヶ月)と短いですが、コマンドによる自動化が可能で定期的に実行することで常に証明書を更新し続けることができます。
証明書を取得するにあたり、ドメインを自分で管理しているかの認証方式が以下の三つがサポートされています。
- HTTP-01
Let's Encryptの認証局からワンタイムトークンを発行してもらい、Webサーバに認証用ファイルを設置する。 認証局からHTTP(80番ポート)でアクセスしてもらい、ワンタイムトークンと認証用ファイルとの妥当性を検証する。 TLS-SNI-01
HTTP-01と同じ方法だが、HTTPS(443ポート)を使用する。DNS-01
Let's Encryptの認証局から発行してもらったワンタイムトークンを対象ドメインのTXTレコードに登録することで検証する。
アクセスが制限されているサーバに対して証明書を取得する場合はDNS-01方式を採用したいと考えるはずです。 Certbotコマンドで今までできなかったため、サードパーティ製のスクリプトであるdehydrated(旧letsencrypt.sh)を使っていましたが、v0.9.0からCertbotでもサポートされるようになりました。
https://github.com/certbot/certbot/pull/2061 https://github.com/certbot/certbot/milestone/22
そこで、CertbotでDNS-01方式によるSSL/TLS証明書を取得する方法をご紹介します。
実行環境
Certbotのインストール
$ sudo su -
% rpm -ivh https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
% yum install certbot
手動による証明書の取得
初回のみ手動で行います。 コマンドのオプションなどはドキュメントをご参照ください。
% certbot certonly \
--manual \
--domain jicoman.info \
--email <メールアドレス> \
--agree-tos \
--manual-public-ip-logging-ok \
--preferred-challenges dns
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Starting new HTTPS connection (1): acme-v01.api.letsencrypt.org
Would you be willing 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 EFF and
our work to encrypt the web, protect its users and defend digital rights.
(Y)es/(N)o: N # Nでおk
Obtaining a new certificate
Performing the following challenges:
DNS-01 challenge for jicoman.info
Please deploy a DNS TXT record under the name
_acme-challenge.jicoman.info with the following value:
pWQR1O6Qrp8_aajNVXuu5bIdo9nv6SLvpzzTrdviTG8
Once this is deployed,
Press Enter to Continue
ここで上記のようなワンタイムトークンが発行されますので、acme-challenge.<対象ドメイン>
(今回はacme-challenge.jicoman.info)のTXTレコードにワンタイムトークンを登録してください。
登録後、エンターを押すと再開します。レコード登録後すぐだと反映されていないので少し待ちましょう。
成功すると以下のように表示されます。
Waiting for verification...
Resetting dropped connection: acme-v01.api.letsencrypt.org
Cleaning up challenges
Generating key (2048 bits): /etc/letsencrypt/keys/0000_key-certbot.pem
Creating CSR: /etc/letsencrypt/csr/0000_csr-certbot.pem
IMPORTANT NOTES:
- Congratulations! Your certificate and chain have been saved at
/etc/letsencrypt/live/jicoman.info/fullchain.pem. Your cert will
expire on 2017-07-11. 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"
- Your account credentials have been saved in your Certbot
configuration directory at /etc/letsencrypt. You should make a
secure backup of this folder now. This configuration directory will
also contain certificates and private keys obtained by Certbot so
making regular backups of this folder is ideal.
- 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
証明書の確認
取得した証明書を見てみます。 証明書が更新されて取得する度に、ファイル名の数字がインクリメントされたファイルが生成されます。 例えば、次に更新された場合は、xxx2.pem が新たに生成されます。
% ls -la /etc/letsencrypt/archive/jicoman.info/
total 16
-rw-r--r--. 1 root root 1789 Apr 12 11:36 cert1.pem # SSL/TLS サーバ証明書 (公開鍵を含む)
-rw-r--r--. 1 root root 1647 Apr 12 11:36 chain1.pem # 中間証明書
-rw-r--r--. 1 root root 3436 Apr 12 11:36 fullchain1.pem # サーバ証明書と中間証明書が結合されたもの
-rw-r--r--. 1 root root 1704 Apr 12 11:36 privkey1.pem # 秘密鍵
しかし、これだと更新の度にWebサーバ(Apache, nginxなど)の設定を書き換えなければならず大変面倒です。 そこで次の証明書を見てみましょう。
% ls -la /etc/letsencrypt/live/jicoman.info/
total 4
drwxr-xr-x. 2 root root 88 Apr 12 11:36 .
drwx------. 3 root root 25 Apr 12 11:36 ..
lrwxrwxrwx. 1 root root 36 Apr 12 11:36 cert.pem -> ../../archive/jicoman.info/cert1.pem
lrwxrwxrwx. 1 root root 37 Apr 12 11:36 chain.pem -> ../../archive/jicoman.info/chain1.pem
lrwxrwxrwx. 1 root root 41 Apr 12 11:36 fullchain.pem -> ../../archive/jicoman.info/fullchain1.pem
lrwxrwxrwx. 1 root root 39 Apr 12 11:36 privkey.pem -> ../../archive/jicoman.info/privkey1.pem
-rw-r--r--. 1 root root 543 Apr 12 11:36 README
シンボリックリンクが張られていることがわかります。 新たな証明書が取得される度にシンボリックリンクの向き先を更新してくれるため、ApacheやNginxの設定ではこちらのパスを指定することで、更新されたとしても設定を変更しなくても済みます。
次に証明書の妥当性を確認します。
% /etc/letsencrypt/live/jicoman.info
% wget http://pastebin.com/raw.php?i=z7SP4pb9 -O ca.crt
% printf "\n" >> ca.crt
% cat chain.pem >> ca.crt
% openssl verify -CAfile ca.crt fullchain.pem
fullchain.pem: OK
% openssl verify -CAfile ca.crt chain.pem
chain.pem: OK
OKと出ているので妥当性は問題なさそうです。
証明書の取得の自動化
二回目以降は自動的に証明書を更新することができます。
フックスクリプト
dehydrated(旧letsencrypt.sh)の場合と同様に認証チャレンジするフックスクリプトを用意する必要があります。DNSサービスはRoute53を利用しているため、Route53と連携しているスクリプト(Python製)を使用することにします。
# pipコマンドがインストールされていない場合
% curl https://bootstrap.pypa.io/get-pip.py | python
% pip install certbot-external-auth
% wget https://gist.githubusercontent.com/rmarchei/98489c05f0898abe612eec916508f2bf/raw/a7f51af111c98544c0cee8739ebd0b88c39b3afa/route53.py
% pip install boto
% chmod +x route53.py
% mv route53.py /usr/local/bin/
Route53へレコード登録・削除するAWSアクセス権限を設定
EC2インスタンスであればIAMロール、そうでなければAWSアクセスキーを発行します。 IAMポリシーは AmazonRoute53DomainsFullAccess ポリシーをアタッチしてください。
% pip install awscli
% aws configure
AWS Access Key ID [None]: xxxxxxxxxxxxx
AWS Secret Access Key [None]: xxxxxxxxxxxxxxxxxxxxxxxxxx
Default region name [None]: ap-northeast-1
Default output format [None]: json
% aws --version
aws-cli/1.11.76 Python/2.7.5 Linux/3.10.0-327.el7.x86_64 botocore/1.5.39
実行
% certbot certonly \
--domain jicoman.info \
--email <メールアドレス> \
--agree-tos \
--preferred-challenges dns \
--renew-by-default \
--text \
--configurator certbot-external-auth:out \
--certbot-external-auth:out-public-ip-logging-ok \
--certbot-external-auth:out-handler /usr/local/bin/route53.py
先ほど手動で取得した場合といくつかオプションが異なっています。
--renew-by-default
を指定しているため、既存の証明書の期限関係なく証明書を更新します。
実行すると以下のような実行結果になります。
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Starting new HTTPS connection (1): acme-v01.api.letsencrypt.org
Renewing an existing certificate
Performing the following challenges:
DNS-01 challenge for jicoman.info
Handler output (pre-perform):
- Stdout:
hook: pre-perform
domain: None
txt_challenge: None
- Stderr:
{"cmd": "perform_challenge", "type": "DNS-01", "domain": "jicoman.info", "token": "lBaIBOfClJNCtbaJemzZEy4V7Sy4x0eNdo_MGHVjYoc", "validation": "pWQR1O6Qrp9_aajNVXuJ5bIyo9nv6SLvpzzTrkviTG8", "txt_domain": "_acme-challenge.jicoman.info", "key_auth": "lBaIBOfClJNCtbaJemzZEy4V7Sy4x0eNdo_MGHVjYoc.vmmEovcNYjx_TQDf5enLiwHclrCqxjfIx8OxEK82_w8"}
Handler output (perform):
- Stdout:
hook: perform
domain: None
txt_challenge: None
- Stderr:
Handler output (post-perform):
- Stdout:
hook: post-perform
domain: None
txt_challenge: None
- Stderr:
Waiting for verification...
Cleaning up challenges
Handler output (pre-cleanup):
- Stdout:
hook: pre-cleanup
domain: None
txt_challenge: None
- Stderr:
{"cmd": "cleanup", "type": "DNS-01", "status": "valid", "domain": "jicoman.info", "token": "lBaIBOfClJNCtbaJemzZEy4V7Sy4x0eNdo_MGHVjYoc", "validation": "pWQR1O6Qrp9_aajNVXuJ5bIyo9nv6SLvpzzTrkviTG8", "key_auth": "lBaIBOfClJNCtbaJemzZEy4V7Sy4x0eNdo_MGHVjYoc.vmmEovcNYjx_TQDf5enLiwHclrCqxjfIx8OxEK82_w8", "validated": null, "error": null}
Handler output (cleanup):
- Stdout:
hook: cleanup
domain: None
txt_challenge: None
- Stderr:
Handler output (post-cleanup):
- Stdout:
hook: post-cleanup
domain: None
txt_challenge: None
- Stderr:
Generating key (2048 bits): /etc/letsencrypt/keys/0001_key-certbot.pem
Creating CSR: /etc/letsencrypt/csr/0001_csr-certbot.pem
{"cmd": "report", "messages": [{"priority": 1, "on_crash": true, "lines": ["Congratulations! Your certificate and chain have been saved at /etc/letsencrypt/live/jicoman.info/fullchain.pem. Your cert will expire on 2017-07-11. 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\""]}, {"priority": 2, "on_crash": true, "lines": ["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", ""]}]}
生成されたファイル群を見ても更新されていることがわかります。
% ls -la /etc/letsencrypt/live/jicoman.info/
total 8
drwxr-xr-x. 2 root root 101 Apr 12 12:40 .
drwx------. 3 root root 25 Apr 12 11:36 ..
-rw-r--r--. 1 root root 2866 Apr 12 11:57 ca.crt
lrwxrwxrwx. 1 root root 36 Apr 12 12:40 cert.pem -> ../../archive/jicoman.info/cert2.pem
lrwxrwxrwx. 1 root root 37 Apr 12 12:40 chain.pem -> ../../archive/jicoman.info/chain2.pem
lrwxrwxrwx. 1 root root 41 Apr 12 12:40 fullchain.pem -> ../../archive/jicoman.info/fullchain2.pem
lrwxrwxrwx. 1 root root 39 Apr 12 12:40 privkey.pem -> ../../archive/jicoman.info/privkey2.pem
-rw-r--r--. 1 root root 543 Apr 12 11:36 README
定期的に実行する場合
Cronなどで定期的に実行し、期限が近づいたら更新するようにします。
% certbot certonly \
--domain jicoman.info \
--email <メールアドレス> \
--agree-tos \
--preferred-challenges dns \
--keep-until-expiring \
--expand \
--text \
--configurator certbot-external-auth:out \
--certbot-external-auth:out-public-ip-logging-ok \
--certbot-external-auth:out-handler /usr/local/bin/route53.py 2> /dev/null
{"cmd": "report", "messages": []}
--keep-until-expiring
オプションを指定することで、現証明書の有効期限が近づくまでは更新しません。
最後に
CertbotでDNS-01方式によるSSL/TLS証明書を手動・自動・定期実行で取得する方法を紹介しました。 Certbotはオプションがかなり多いですが、ドキュメントを見ながらやれば想像以上に簡単にできます。 無料でSSL/TLS証明書が使えるので個人サービスを持っている人はぜひ導入を検討した方がよいかと思います。
更新がきたらSlack通知したり、WindowsのIIS対応などは別記事で書こうと思います。
参考
- Certbot本家
- Let's Encrypt総合ポータル(日本語)
- Let's EncryptのDNS-01を使用して無料のSSL証明書をWebサーバなしで取得する -- ぺけみさお
- Let's EncryptでDNS-01方式を使った際にはとてもお世話になりました(会社の同僚でもあります)
- How to use Let's Encrypt DNS challenge validation? - Server Fault
- github.com/EnigmaBridge/certbot-external-auth
- Examples for DNS 01 hooks