dehydrated
はshell scriptでできた letsencrypt/acme のクライアントです。certbotやlegoなど様々なツールがある中で、わりと長いこと使ってますが、安定してワイルドカード・マルチドメイン対応の証明書の取得・更新ができています。
dehydratedとさくらのクラウドのDNS機能と先週末つくったクライアント sacloudns
を使うことで、ほぼ自動的に証明書の取得と更新が行えるので、その紹介です。
こちらのツールは、リリースページからのダウンロードもしくはMacなら homebrew
でインストールが可能です。
% brew install kazeburo/tap/sacloudns
dehydrated の準備
dehydrated はGitHubからcloneしてきます
$ git clone https://github.com/dehydrated-io/dehydrated.git $ cd dehydrated
そして設定ファイルを用意します。
まず、 config
ファイルを作成
CERTDIR="${BASEDIR}/certs" ACCOUNTDIR="${BASEDIR}/accounts" HOOK="${BASEDIR}/hook.sh" HOOK_CHAIN="no" CHALLENGETYPE="dns-01" KEY_ALGO="prime256v1"
configのサンプルは docs/example
以下にあります。KEY_ALGOにprime256v1を指定することで楕円曲線暗号であるECDSAを使った証明書を作成できます。デフォルトはRSAになります
つぎに証明書を作成するドメイン一覧を記した domains.txt
を作ります
# スペース区切りで複数のドメインを記せます。ワイルドカードも使えますが行頭にはかけません example.com *.example.com example.jp *.example.jp > certalias chocon.me *.chocon.me
この記事で紹介する方法で証明書を作成する場合、こちらのドメインはすべてさくらのクラウドのDNS機能で管理されている必要があります。試してはないですが、Let's Encryptではマルチドメイン証明書のSAN(Subject Alternative Name)に100個までFQDNを追加することができるようです。
そして最後に、dns-01
で利用するDNSレコードの作成などを行う hook.sh
を用意します。
こちらはAmazon Route 53のクライアント、 cli53
を使う以下コードを参考にして作りました
https://github.com/whereisaaron/dehydrated-route53-hook-script/blob/master/hook.sh
#!/bin/bash set -e # This hook script is written based on dehydrated-route53-hook-script # https://github.com/whereisaaron/dehydrated-route53-hook-script deploy_challenge() { local DOMAIN="${1}" TOKEN_FILENAME="${2}" TOKEN_VALUE="${3}" local ZONE=$(find_zone "${DOMAIN}") if [[ -n "$ZONE" ]]; then echo "Creating challenge record for ${DOMAIN} in zone ${ZONE}" sacloudns radd --wait --zone ${ZONE} --name _acme-challenge.${DOMAIN}. --ttl 60 --type TXT --data ${TOKEN_VALUE} else echo "Could not find zone for ${DOMAIN}" exit 1 fi } clean_challenge() { local DOMAIN="${1}" TOKEN_FILENAME="${2}" TOKEN_VALUE="${3}" local ZONE=$(find_zone "${DOMAIN}") if [[ -n "$ZONE" ]]; then echo "Deleting challenge record for ${DOMAIN} from zone ${ZONE}" sacloudns rdelete --zone ${ZONE} --name _acme-challenge.${DOMAIN}. --type TXT --data ${TOKEN_VALUE} else echo "Could not find zone for ${DOMAIN}" exit 1 fi } deploy_cert() { local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}" CHAINFILE="${5}" TIMESTAMP="${6}" # NOP } unchanged_cert() { local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}" CHAINFILE="${5}" # NOP } function invalid_challenge { local DOMAIN="${1}" RESPONSE="${2}" local HOSTNAME="$(hostname)" (>&2 echo "Failed to issue SSL cert for ${DOMAIN}: ${RESPONSE}") } function get_base_name() { local HOSTNAME="${1}" if [[ "$HOSTNAME" == *"."* ]]; then HOSTNAME="${HOSTNAME#*.}" echo "$HOSTNAME" return 0 else echo "" return 1 fi } function find_zone() { local DOMAIN="${1}" local ZONELIST=$(sacloudns list | jq -r '.DNS[].DNSZone'|xargs echo -n) local TESTDOMAIN="${DOMAIN}" while [[ -n "$TESTDOMAIN" ]]; do for zone in $ZONELIST; do if [[ "$zone" == "$TESTDOMAIN" ]]; then echo "$zone" return 0 fi done TESTDOMAIN=$(get_base_name "$TESTDOMAIN") done return 1 } # # This hook is called at the end of a dehydrated command and can be used # to do some final (cleanup or other) tasks. # exit_hook() { : } HANDLER="$1"; shift if [[ "${HANDLER}" =~ ^(deploy_challenge|clean_challenge|deploy_cert|unchanged_cert|invalid_challenge|request_failure|exit_hook)$ ]]; then "$HANDLER" "$@" fi
さくらのクラウドのAPIキーを環境変数を設定
さくらのクラウドのコントロールパネルからAPIキーを作成し、SAKURACLOUD_ACCESS_TOKEN
と SAKURACLOUD_ACCESS_TOKEN_SECRET
の環境変数に設定します。
export SAKURACLOUD_ACCESS_TOKEN=xxx export SAKURACLOUD_ACCESS_TOKEN_SECRET=yyy
sacloudns は作業ディレクトリに、.env
ファイルを作成し、
SAKURACLOUD_ACCESS_TOKEN=xxx SAKURACLOUD_ACCESS_TOKEN_SECRET=yyy
のように記すと、コマンド実行時に自動で読み込むこともできます。
dehydrated の実行
準備ができたのでdehydratedを実行
# 初回はアカウントの作成が必要 $ ./dehydrated --register --accept-terms -f config $ ./dehydrated -c -f config
これで、DNSレコードの作成と検証が行われたのち、証明書が certs
ディレクトリ以下に作成されます。
% ls -l certs/chocon.me cert-1612763291.csr cert-1612763291.pem cert.csr -> cert-1612763291.csr cert.pem -> cert-1612763291.pem chain-1612763291.pem chain.pem -> chain-1612763291.pem fullchain-1612763291.pem fullchain.pem -> fullchain-1612763291. privkey-1612763291.pem privkey.pem -> privkey-1612763291.pem
証明書は nginx や Apache などのWebサーバや各クラウドにアップロードして使うこともできます。
おまけ: さくらのクラウド エンハンスドロードバランサーへの証明書アップロード
取得した証明書をさくらのクラウドのエンハンスドロードバランサーに設定してみます。
エンハンスドロードバランサーとは大規模なHTTP/HTTPSサービスに最適な高性能・高機能なロードバランサアプライアンスです。詳しくはこちら
こちらも参考になります。
エンハンスドロードバランサーの構築が済んでいるといるとして、以下のようにすると証明書が登録できます。
$ cat template.jq { "ProxyLB": { "PrimaryCert": { "ServerCertificate": $ServerCertificate, "IntermediateCertificate": $IntermediateCertificate, "PrivateKey": $PrivateKey }, "AdditionalCerts": [] } } $ jq -n \ -f template.jq --rawfile ServerCertificate certs/chocon.me/cert.pem \ --rawfile IntermediateCertificate certs/chocon.me/chain.pem \ --rawfile PrivateKey certs/chocon.me/privkey.pem \ | \ curl -d @- -X PUT \ -H "Content-Type: application/json" \ --user $SAKURACLOUD_ACCESS_TOKEN:$SAKURACLOUD_ACCESS_TOKEN_SECRET \ https://secure.sakura.ad.jp/cloud/zone/is1a/api/cloud/1.1/commonserviceitem/${リソース番号}/proxylb/sslcertificate
jqのテンプレート機能と、rawfileオプションとても便利ですね!
なお、この方法で証明書を登録した場合は、let's encryptでの証明書取得・更新機能は停止しておく必要があります。
エンハンスドロードバランサーのlet's encryptを利用した証明書取得では、1つのロードバランサーあたり、1個のドメインの証明書しか作れませんが、この方法だとそれよりも多くの証明書を登録できるので、たくさんのドメインを利用される場合は便利かもしれません。
最後に、ここまでの方法を一つのシェルスクリプトにまとめるなど自動化しておくのがいいでしょう。
何かのお役に立てれば幸いです。