本日も乙

ただの自己満足な備忘録。

CertbotでDNSによる認証(DNS-01)で無料のSSL/TLS証明書を取得する

[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

そこで、CertbotDNS-01方式によるSSL/TLS証明書を取得する方法をご紹介します。

実行環境

Certbotのインストール

epelリポジトリを持ってきてYumでインストールします。

$ 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 ポリシーをアタッチしてください。

AWS CLIのインストールと設定を行います。

% 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 オプションを指定することで、現証明書の有効期限が近づくまでは更新しません。

最後に

CertbotDNS-01方式によるSSL/TLS証明書を手動・自動・定期実行で取得する方法を紹介しました。 Certbotはオプションがかなり多いですが、ドキュメントを見ながらやれば想像以上に簡単にできます。 無料でSSL/TLS証明書が使えるので個人サービスを持っている人はぜひ導入を検討した方がよいかと思います。

更新がきたらSlack通知したり、WindowsIIS対応などは別記事で書こうと思います。

参考