ワイルドカード証明書を導入する為、DNS認証を使っています。
Let’s Encryptの場合、更新時も新しく要求されるTXTレコードを設定する必要があります。
今まで2〜3ヶ月毎に手動でやってましたが、さすがに手間なので自動化してみました。

ちなみにACM(AWS Certificate Manager)の場合、1年毎に最初に設定したDNSレコードで認証されるので楽ですが、Let’s Encryptの場合は、無料というの事もあり、もっとセキュアにしたいんでしょうね。

Let’s Encryptの挙動について

# certbot certonly --manual --preferred-challenges dns \
-d nightonly.com -d *.nightonly.com \
--email メールアドレス --agree-tos --no-eff-email

今までは、こんな感じのコマンドを打って、言われたTXTレコードをDNSに設定してをしていました。おそらく「certbot renew」でも同じだったかも。
使ったTXTレコードは不要な事に気づいたいので、毎回クリーニング。

DNS認証導入前は、xdomainのDNSを使っていましたが、反映に時間が掛かりEnter押すタイミングが難しかった為、Amazon LightsailのDNS(Route53にしなかったのはコストと利点が少なかった為)、その後、ConoHaに移行しています。

# certbot certonly --manual --preferred-challenges dns \
-d nightonly.com -d *.nightonly.com \
--email メールアドレス --agree-tos --no-eff-email \
--manual-auth-hook /usr/local/bin/conoha/create_dns_record.sh \
--manual-cleanup-hook /usr/local/bin/conoha/delete_dns_record.sh

–manual-auth-hookと–manual-cleanup-hookを追加。
使っているDNS(今回はConoHaにしました)にAPIで作成と削除のプログラムを指定すればOK。
$CERTBOT_DOMAIN(対象のドメイン名)と$CERTBOT_VALIDATION(TXTレコードの値)が渡される。

ConoHa API設定

管理画面でAPIユーザーを作成してメモ。

テナントIDと使用するAPIのエンドポイント(アカウント作成時期により変わりそう)もメモ。

プログラム(スクリプト)作成

–manual-auth-hookと–manual-cleanup-hookで指定するプログラムを書きました。
GitHubに上げくれていた人もいましたが、動きを把握したいのと単体でも使えるようにしたかったので。あと、いつも通りログを残したり、エラー時のメール通知したりしたかったという理由です。

jq使っているので、インストールしてください。

# yum -y --enablerepo=epel install jq

※Ansibleを使っているので、{{ }}はそれぞれの値に置き換えてください。
(共通化できる所もありますが、あえて1ファイルでわかりやすさ優先にしています)

※mailコマンドのwarningはaliasesで転送先メールアドレスを指定しています。

/usr/local/bin/conoha/create_dns_record.sh

#!/bin/sh

BATCH_NAME='create_conoha_dns'
LOG_FILE="/var/log/$BATCH_NAME.log"
IDENTITY_SERVICE='{{ conoha_identity_service }}'
DNS_SERVICE='{{ conoha_dns_service }}'
USERNAME='{{ conoha_api_username }}'
PASSWORD='{{ conoha_api_password }}'
TENANTID='{{ conoha_tenant_id }}'

[ -z "$CERTBOT_DOMAIN" ] && TARGET_DOMAIN=$1 || TARGET_DOMAIN=$CERTBOT_DOMAIN
[ -z "$CERTBOT_VALIDATION" ] && TARGET_DATA=$2 || TARGET_DATA=$CERTBOT_VALIDATION
[ -z "$3" ] && TARGET_NAME='_acme-challenge' || TARGET_NAME=$3
[ -z "$4" ] && TARGET_TYPE='TXT' || TARGET_TYPE=$4

error_msg=''
write_log() {
	echo -e "`date +"%Y/%m/%d %H:%M:%S"` ($$) [$1] $2" >> $LOG_FILE
	[ $1 = 'ERROR' ] && error_msg="$error_msg[$1] $2\n"
}

send_error_mail() {
	[ -z "$error_msg" ] && return
	echo -e "$error_msg" | mail -s "[WARNING]$BATCH_NAME report for `hostname`" -r crond warning
	write_log 'INFO' 'Send error mail'
}

create_dns() {
	# トークン発行 - Identity API v2.0
	curl $IDENTITY_SERVICE/tokens -X POST -H "Accept: application/json" \
		-d '{ "auth": { "passwordCredentials": { "username": "'$USERNAME'", "password": "'$PASSWORD'" }, "tenantId": "'$TENANTID'" } }' > $1 2> /dev/null
	if [ $? -ne 0 ]; then
		write_log 'ERROR' "POST $IDENTITY_SERVICE/tokens"
		return
	fi

	token_id=`cat $1 | jq -r ".access.token.id"`
	if [ -z "$token_id" ]; then
		write_log 'ERROR' "Not found access.token.id: `cat $1`"
		return
	fi
	write_log 'INFO' "access.token.id: $token_id"

	# ドメイン一覧表示 - DNS API v1.0
	curl $DNS_SERVICE/v1/domains -X GET -H "Accept: application/json" -H "Content-Type: application/json" -H "X-Auth-Token: $token_id" > $1 2> /dev/null
	if [ $? -ne 0 ]; then
		write_log 'ERROR' "GET $DNS_SERVICE/v1/domains"
		return
	fi

	domain_id=`cat $1 | jq -r '.domains[] | select(.name == "'$TARGET_DOMAIN'.") | .id'`
	if [ -z "$domain_id" ]; then
		write_log 'ERROR' "Not found domains.id: `cat $1`"
		return
	fi
	write_log 'INFO' "domains.id: $domain_id"

	# レコード作成 - DNS API v1.0
	curl $DNS_SERVICE/v1/domains/$domain_id/records -X POST -H "Accept: application/json" -H "Content-Type: application/json" -H "X-Auth-Token: $token_id" \
		-d '{ "name": "'$TARGET_NAME'.'$TARGET_DOMAIN'.", "type": "'$TARGET_TYPE'", "data": "'$TARGET_DATA'" }' > $1 2> /dev/null
	if [ $? -ne 0 ]; then
		write_log 'ERROR' "POST $DNS_SERVICE/v1/domains/$domain_id/records"
		return
	fi

	id=`cat $1 | jq -r ".id"`
	if [ -z "$id" ] || [ "$id" = 'null' ]; then
		write_log 'ERROR' "Not found id: `cat $1`"
		return
	fi
	write_log 'INFO' "OK id: $id"
}

write_log 'INFO' '=== START ==='
write_log 'INFO' "TARGET_DOMAIN: $TARGET_DOMAIN, TARGET_DATA: $TARGET_DATA, TARGET_NAME: $TARGET_NAME, TARGET_TYPE: $TARGET_TYPE"
tempfile=$(mktemp)
create_dns $tempfile
rm -f $tempfile
send_error_mail
write_log 'INFO' '=== END ==='
exit 0

/usr/local/bin/conoha/delete_dns_record.sh

#!/bin/sh

BATCH_NAME='delete_conoha_dns'
LOG_FILE="/var/log/$BATCH_NAME.log"
IDENTITY_SERVICE='{{ conoha_identity_service }}'
DNS_SERVICE='{{ conoha_dns_service }}'
USERNAME='{{ conoha_api_username }}'
PASSWORD='{{ conoha_api_password }}'
TENANTID='{{ conoha_tenant_id }}'

[ -z "$CERTBOT_DOMAIN" ] && TARGET_DOMAIN=$1 || TARGET_DOMAIN=$CERTBOT_DOMAIN
[ -z "$CERTBOT_VALIDATION" ] && TARGET_DATA=$2 || TARGET_DATA=$CERTBOT_VALIDATION
[ -z "$3" ] && TARGET_NAME='_acme-challenge' || TARGET_NAME=$3
[ -z "$4" ] && TARGET_TYPE='TXT' || TARGET_TYPE=$4

error_msg=''
write_log() {
	echo -e "`date +"%Y/%m/%d %H:%M:%S"` ($$) [$1] $2" >> $LOG_FILE
	[ $1 = 'ERROR' ] && error_msg="$error_msg[$1] $2\n"
}

send_error_mail() {
	[ -z "$error_msg" ] && return
	echo -e "$error_msg" | mail -s "[WARNING]$BATCH_NAME report for `hostname`" -r crond warning
	write_log 'INFO' 'Send error mail'
}

delete_dns() {
	# トークン発行 - Identity API v2.0
	curl $IDENTITY_SERVICE/tokens -X POST -H "Accept: application/json" \
		-d '{ "auth": { "passwordCredentials": { "username": "'$USERNAME'", "password": "'$PASSWORD'" }, "tenantId": "'$TENANTID'" } }' > $1 2> /dev/null
	if [ $? -ne 0 ]; then
		write_log 'ERROR' "POST $IDENTITY_SERVICE/tokens"
		return
	fi

	token_id=`cat $1 | jq -r ".access.token.id"`
	if [ -z "$token_id" ]; then
		write_log 'ERROR' "Not found access.token.id: `cat $1`"
		return
	fi
	write_log 'INFO' "access.token.id: $token_id"

	# ドメイン一覧表示 - DNS API v1.0
	curl $DNS_SERVICE/v1/domains -X GET -H "Accept: application/json" -H "Content-Type: application/json" -H "X-Auth-Token: $token_id" > $1 2> /dev/null
	if [ $? -ne 0 ]; then
		write_log 'ERROR' "GET $DNS_SERVICE/v1/domains"
		return
	fi

	domain_id=`cat $1 | jq -r '.domains[] | select(.name == "'$TARGET_DOMAIN'.") | .id'`
	if [ -z "$domain_id" ]; then
		write_log 'ERROR' "Not found domains.id: `cat $1`"
		return
	fi
	write_log 'INFO' "domains.id: $domain_id"

	# レコード一覧取得 - DNS API v1.0
	curl $DNS_SERVICE/v1/domains/$domain_id/records -X GET -H "Accept: application/json" -H "Content-Type: application/json" -H "X-Auth-Token: $token_id" > $1 2> /dev/null
	if [ $? -ne 0 ]; then
		write_log 'ERROR' "GET $DNS_SERVICE/v1/domains/$domain_id/records"
		return
	fi

	id=`cat $1 | jq -r '.records[] | select(.name == "'$TARGET_NAME'.'$TARGET_DOMAIN'." and .type == "'$TARGET_TYPE'" and .data == "'$TARGET_DATA'") | .id'`
	if [ -z "$id" ]; then
		write_log 'ERROR' "Not found records.id: `cat $1`"
		return
	fi
	write_log 'INFO' "records.id: $id"

	# レコード削除 - DNS API v1.0
	curl $DNS_SERVICE/v1/domains/$domain_id/records/$id -X DELETE -H "Accept: application/json" -H "Content-Type: application/json" -H "X-Auth-Token: $token_id" > $1 2> /dev/null
	if [ $? -ne 0 ]; then
		write_log 'ERROR' "DELETE $DNS_SERVICE/v1/domains/$domain_id/records/$id"
		return
	fi
	write_log 'INFO' "OK"
}

write_log 'INFO' '=== START ==='
write_log 'INFO' "TARGET_DOMAIN: $TARGET_DOMAIN, TARGET_DATA: $TARGET_DATA, TARGET_NAME: $TARGET_NAME, TARGET_TYPE: $TARGET_TYPE"
tempfile=$(mktemp)
delete_dns $tempfile
rm -f $tempfile
send_error_mail
write_log 'INFO' '=== END ==='
exit 0

ちなみに

searchするとRoute53やいくつかのサービス向けのpluginがありました。
環境によっては使えそうですね。

# yum --enablerepo=epel search certbot
python2-certbot.noarch : Python 2 libraries used by certbot
python2-certbot-apache.noarch : The apache plugin for certbot
python2-certbot-dns-cloudflare.noarch : Cloudflare DNS Authenticator plugin for Certbot
python2-certbot-dns-cloudxns.noarch : CloudXNS DNS Authenticator plugin for Certbot
python2-certbot-dns-digitalocean.noarch : DigitalOcean DNS Authenticator plugin for Certbot
python2-certbot-dns-dnsimple.noarch : DNSimple DNS Authenticator plugin for Certbot
python2-certbot-dns-dnsmadeeasy.noarch : DNS Made Easy DNS Authenticator plugin for Certbot
python2-certbot-dns-gehirn.noarch : Gehirn Infrastructure Service DNS Authenticator plugin for Certbot
python2-certbot-dns-google.noarch : Google Cloud DNS Authenticator plugin for Certbot
python2-certbot-dns-linode.noarch : Linode DNS Authenticator plugin for Certbot
python2-certbot-dns-luadns.noarch : LuaDNS Authenticator plugin for Certbot
python2-certbot-dns-nsone.noarch : NS1 DNS Authenticator plugin for Certbot
python2-certbot-dns-ovh.noarch : OVH DNS Authenticator plugin for Certbot
python2-certbot-dns-rfc2136.noarch : RFC 2136 DNS Authenticator plugin for Certbot
python2-certbot-dns-route53.noarch : Route53 DNS Authenticator plugin for Certbot
python2-certbot-dns-sakuracloud.noarch : Sakura Cloud DNS Authenticator plugin for Certbot
python2-certbot-nginx.noarch : The nginx plugin for certbot
certbot.noarch : A free, automated certificate authority client

まとめ

参考までに、今回のコミットです。
更新(certbot renew)のスクリプトも作成しています。
エラー時にメール送信するようにしているので、問題が表面化する前に気付けて安心です。
https://dev.azure.com/nightonly/vagrant-ansible-origin/_git/vagrant-ansible-centos7/commit/8338dc2a981b412b34c6a33fa9caf6c137c1e539

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です