ワイルドカード証明書を導入する為、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