[AWS] Let's Encrypt で更新した証明書を自動的にアップロードして ELB の SSL 証明書を入れ替える

[AWS Certificate Manager](https://aws.amazon.com/jp/certificate-manager/) 使えよって話なんですが、例えばドメイン認証に使用するためのメールアドレスは使えないけど、Web サーバ上に認証ファイルはおけるっていう限定的な環境で [Let's Encrypt](https://letsencrypt.org/) で更新した SSL 証明書を ELB にアップロードしたいとかって状況があるかもしれませんね。
実際あったんです。
そんな時は ```letsencrypt renew``` の後に aws-cli でアップロードするようにしてあげればいいんでないかなーと。

ELB 配下には複数のインスタンスがぶら下がってることが多いと思いますが、前提条件としては ```letsencrypt renew``` を実施するインスタンスは固定しておいて、ELB (application) のターゲットグループでパス ```/.well-known/*``` をそのインスタンスに流すように設定しておく必要があります。

あとは、そのインスタンスに以下のようなシェルスクリプトを作ります。

ELB v2(Application) の場合

```bash:upload-cert.sh
#!/bin/bash
set -eux

_domain='example.com'
_listener_arn='{ELB_LISTNER_ARN_HERE}'

_cert_path="/${_domain}/"
_date=$(date +%Y%m%d-%H%M%S)
_cert_name="${_domain}-${_date}"

# 新しいSSL証明書をアップロード
cd /etc/letsencrypt/live/${_domain}/
aws iam upload-server-certificate \
 --server-certificate-name ${_cert_name} \
 --certificate-body file://cert.pem \
 --private-key file://privkey.pem \
 --certificate-chain file://chain.pem \
 --path ${_cert_path}

# 新しいSSL証明書を ELB(Application) のターゲットグループにセット
sleep 60
_cert_arn=$(aws iam get-server-certificate --server-certificate-name ${_cert_name} | jq -r '.ServerCertificate.ServerCertificateMetadata.Arn')
aws elbv2 modify-listener \
 --listener-arn ${_listener_arn} \
 --certificates "CertificateArn=${_cert_arn}"

# 古いSSL証明書を削除
_certs=$(aws iam list-server-certificates --path-prefix ${_cert_path} | jq -r '.ServerCertificateMetadataList[].ServerCertificateName')
for _cert in ${_certs}; do
  if [ "${_cert_name}" != "${_cert}" ]; then
    aws iam delete-server-certificate --server-certificate-name ${_cert}
  fi
done
```

ELB v1(classic) の場合

```bash:upload-cert.sh
#!/bin/bash
set -eux

_domain='example.com'
_elb_name='{ELB_NAME_HERE}'
_elb_port='443'

_cert_path="/${_domain}/"
_date=$(date +%Y%m%d-%H%M%S)
_cert_name="${_domain}-${_date}"

# 新しいSSL証明書をアップロード
cd /etc/letsencrypt/live/${_domain}/
aws iam upload-server-certificate \
 --server-certificate-name ${_cert_name} \
 --certificate-body file://cert.pem \
 --private-key file://privkey.pem \
 --certificate-chain file://chain.pem \
 --path ${_cert_path}

# 新しいSSL証明書を ELB(classic) のリスナーにセット
sleep 60
_cert_arn=$(aws iam get-server-certificate --server-certificate-name ${_cert_name} | jq -r '.ServerCertificate.ServerCertificateMetadata.Arn')
aws elb set-load-balancer-listener-ssl-certificate \
 --load-balancer-name ${_elb_name} \
 --load-balancer-port ${_elb_port} \
 --ssl-certificate-id ${_cert_arn}

# 古いSSL証明書を削除
_certs=$(aws iam list-server-certificates --path-prefix ${_cert_path} | jq -r '.ServerCertificateMetadataList[].ServerCertificateName')
for _cert in ${_certs}; do
  if [ "${_cert_name}" != "${_cert}" ]; then
    aws iam delete-server-certificate --server-certificate-name ${_cert}
  fi
done
```

```_domain``` と ```_listener_arn``` (```_elb_name```, ```_elb_port```) は、環境によって変更してあげてくださいね。
あと、[jq](https://stedolan.github.io/jq/) 使ってるんで ```yum install jq``` とかでインストールしてあげてください。


そんで ```letsencrypt renew``` の ```--renew-hook``` オプションで、証明書が更新された時に実行するコマンドとして登録するわけです。

```bash
letsencrypt renew --renew-hook '/root/bin/upload-cert.sh'
```

あとは、上のコマンドを crontab で定期的(週一回とか)に実行してあげれば、Let's Encrypt の SSL証明書が更新された時に自動的に ELB のターゲットグループに設定されてる証明書も更新されて古い証明書が削除されるってわけです。