ハラミTech

技術系ブログです

Let's EncryptのDNS認証による証明書発行/更新の自動化をやってみた

Let's Encryptは、無料で取得できる証明書で、自動化が簡単にできます。
Let's Encryptから証明書を取得する際、
一般的には「http-01」または「tls-sni-01」チャレンジがよく使用されると思いますが、
サーバーの80(または443)ポートを開放しないといけません。
インターネットに公開したくないサーバーの場合、常時そのポートを開けておくわけにはいきません。

そのため、初回の証明書取得時は自動で証明書を取得できるのですが
証明書の更新のたびにポートを開放して手動でLet's Encryptのコマンドを叩く…などの運用をしていました。。

更新も自動化したいし、でもインターネットには公開したくないしと調べてみたところ、
dns-01チャレンジによる完全自動化ができるようになったので、試してみたいと思います。

今回は公式のLet's Encryptクライアント(certbot)を使用せず、
Go言語で書かれたLet's Encryptクライアント「Lego」を使用していきます。

https://github.com/xenolf/lego

前提

実行環境

OS: Ubuntu16.04(AWS)
DNS: Route53

DNSについて

事前にテスト用のドメインがRoute53に設定済みであること。

DNS認証とは

DNS認証というよりはLet's Encryptの仕組みですが、以下を参照してください。

https://letsencrypt.jp/technology/

Legoのインストール

Go言語ですので、Goが使える環境の方は以下のコマンドでバイナリファイルを取得してください。

$ go get -u github.com/xenolf/lego

Goのビルド環境がない方や、バイナリファイルのみ欲しい方は
以下のコマンドでバイナリファイルのみ取得できます。

$ wget https://github.com/xenolf/lego/releases/download/v0.4.0/lego_linux_amd64.tar.xz

$ xz -dc lego_linux_amd64.tar.xz | tar xfv -
$ mv lego_linux_amd64 lego

# Pathが通ってるところに移動
$ mv lego /usr/local/bin/lego

証明書を発行するサーバー上にこのlegoバイナリファイルが存在する必要があります。

IAM Userの作成

クライアントサーバーからRoute53の操作が出来なければならないので
その操作のためのIAM Userを作成します。

以下のポリシーを持ったIAM Userを作成し、AWSアクセスキーやシークレットキーを取得してください。
IAM Roleでも同様の設定を行うことができるので、使えるならIAM Roleを使うほうがアクセスキーなどの管理がなく楽だと思います。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "route53:GetChange",
                "route53:ListHostedZonesByName"
            ],
            "Resource": [
                "*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "route53:ChangeResourceRecordSets"
            ],
            "Resource": [
                "arn:aws:route53:::hostedzone/<INSERT_YOUR_HOSTED_ZONE_ID_HERE>"
            ]
        }
    ]
}

証明書の発行

  • legoコマンドの用意
  • IAM User(またはIAM Role)の用意

が完了したので、これで証明書の発行ができるようになりました。

ではやってみましょう!

# 以下の環境変数はIAM Roleを使用している場合は不要
$ export AWS_REGION=<aws region>
$ export AWS_ACCESS_KEY_ID=<your aws access key id>
$ export AWS_SECRET_ACCESS_KEY=<your aws secret key>

$ sudo lego --accept-tos \
          --path=/etc/letsencrypt \
          --email="<your email address>" \
          --dns="route53" \
          --domains="<your site domain>" \
          run

#2017/08/12 05:31:20 [INFO][example.com] acme: Obtaining bundled SAN certificate
#2017/08/12 05:31:20 [INFO][example.com] AuthURL: https://acme-v01.api.letsencrypt.org/acme/authz/xxxxxxxxxxxxxxx
#2017/08/12 05:31:20 [INFO][example.com] acme: Could not find solver for: tls-sni-01
#2017/08/12 05:31:20 [INFO][example.com] acme: Could not find solver for: http-01
#2017/08/12 05:31:20 [INFO][example.com] acme: Trying to solve DNS-01
#2017/08/12 05:32:04 [INFO][example.com] Checking DNS record propagation using [xxx.xxx.xxx.xxx:53]
#2017/08/12 05:32:06 [INFO][example.com] The server validated our request
#2017/08/12 05:32:49 [INFO][example.com] acme: Validations succeeded; requesting certificates
#2017/08/12 05:32:49 [INFO] acme: Requesting issuer cert from https://acme-v01.api.letsencrypt.org/acme/issuer-cert
#2017/08/12 05:32:49 [INFO][example.com] Server responded with a certificate.

--pathで指定したディレクトリに証明書ファイルが出力されています。

$ ls -l /etc/letsencrypt/certificates/

#-rw------- 1 root root 3452 Aug 12 05:32 example.com.crt
#-rw------- 1 root root 1647 Aug 12 05:32 example.com.issuer.crt
#-rw------- 1 root root  230 Aug 12 05:32 example.com.json
#-rw------- 1 root root 1675 Aug 12 05:32 example.com.key

crtとkeyファイルをNginxやApacheで読み込ませれば使用できます。

証明書の更新

発行時に使用したコマンドとほぼ同じです。

$ sudo lego --accept-tos \
          --path=/etc/letsencrypt \
          --email="<your email address>" \
          --dns="route53" \
          --domains="<your site domain>" \
          renew

#2017/08/12 05:41:03 [INFO][example.com] acme: Trying renewal with 2158 hours remaining
#2017/08/12 05:41:03 [INFO][example.com] acme: Obtaining bundled SAN certificate
#2017/08/12 05:41:03 [INFO][example.com] AuthURL: https://acme-v01.api.letsencrypt.org/acme/authz/xxxxxxxxxxx
#2017/08/12 05:41:03 [INFO][example.com] acme: Authorization already valid; skipping challenge
#2017/08/12 05:41:03 [INFO][example.com] acme: Validations succeeded; requesting certificates
#2017/08/12 05:41:04 [INFO] acme: Requesting issuer cert from https://acme-v01.api.letsencrypt.org/acme/issuer-cert
#2017/08/12 05:41:04 [INFO][example.com] Server responded with a certificate.

証明書のファイルは同名で上書きされます。
上記コマンドに --days 30 を付与すると、証明書発行から30日以降のものを更新 となります。
cronなどに設定しておくとよいでしょう。

最後に

DNS認証のみで証明書の発行/更新ができました。
これで証明書の更新作業も完全に自動化できそうです。

ちなみに今回はAWSを前提にやってみましたが、legoでは様々なプラットフォームを想定しているようです。

$ lego dnshelp

#Credentials for DNS providers must be passed through environment variables.
#
#Here is an example bash command using the CloudFlare DNS provider:
#
#  $ CLOUDFLARE_EMAIL=foo@bar.com \
#    CLOUDFLARE_API_KEY=b9841238feb177a84330febba8a83208921177bffe733 \
#    lego --dns cloudflare --domains www.example.com --email me@bar.com run
#
#Valid providers and their associated credential environment variables:
#
#        azure:          AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_SUBSCRIPTION_ID, AZURE_TENANT_ID, AZURE_RESOURCE_GROUP
#        auroradns:      AURORA_USER_ID, AURORA_KEY, AURORA_ENDPOINT
#        cloudflare:     CLOUDFLARE_EMAIL, CLOUDFLARE_API_KEY
#        digitalocean:   DO_AUTH_TOKEN
#        dnsimple:       DNSIMPLE_EMAIL, DNSIMPLE_OAUTH_TOKEN
#        dnsmadeeasy:    DNSMADEEASY_API_KEY, DNSMADEEASY_API_SECRET
#        exoscale:       EXOSCALE_API_KEY, EXOSCALE_API_SECRET, EXOSCALE_ENDPOINT
#        gandi:          GANDI_API_KEY
#        gcloud:         GCE_PROJECT
#        linode:         LINODE_API_KEY
#        manual:         none
#        namecheap:      NAMECHEAP_API_USER, NAMECHEAP_API_KEY
#        rackspace:      RACKSPACE_USER, RACKSPACE_API_KEY
#        rfc2136:        RFC2136_TSIG_KEY, RFC2136_TSIG_SECRET,
#                        RFC2136_TSIG_ALGORITHM, RFC2136_NAMESERVER
#        route53:        AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION
#        dyn:            DYN_CUSTOMER_NAME, DYN_USER_NAME, DYN_PASSWORD
#        vultr:          VULTR_API_KEY
#        ovh:            OVH_ENDPOINT, OVH_APPLICATION_KEY, OVH_APPLICATION_SECRET, OVH_CONSUMER_KEY
#        pdns:           PDNS_API_KEY, PDNS_API_URL
#        dnspod:         DNSPOD_API_KEY
#
#For a more detailed explanation of a DNS provider's credential variables,
#please consult their online documentation.

AWSでサーバー運用されてる方もされてない方も、
legoを使用してLet's Encryptの証明書の自動化をやってみてはいかがでしょうか!