dehydrated とさくらのクラウドのDNS機能をつかってワイルドカード・マルチドメイン証明書取得
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個のドメインの証明書しか作れませんが、この方法だとそれよりも多くの証明書を登録できるので、たくさんのドメインを利用される場合は便利かもしれません。
最後に、ここまでの方法を一つのシェルスクリプトにまとめるなど自動化しておくのがいいでしょう。
何かのお役に立てれば幸いです。