acme.sh/dnsapi/dns_gcloud.sh

171 lines
4.9 KiB
Bash
Executable File

#!/usr/bin/env sh
# Author: Janos Lenart <janos@lenart.io>
######## Public functions #####################
# Usage: dns_gcloud_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_gcloud_add() {
fulldomain=$1
txtvalue=$2
_info "Using gcloud"
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
_dns_gcloud_find_zone || return $?
# Add an extra RR
_dns_gcloud_start_tr || return $?
_dns_gcloud_get_rrdatas || return $?
echo "$rrdatas" | _dns_gcloud_remove_rrs || return $?
printf "%s\n%s\n" "$rrdatas" "\"$txtvalue\"" | grep -v '^$' | _dns_gcloud_add_rrs || return $?
_dns_gcloud_execute_tr || return $?
_info "$fulldomain record added"
}
# Usage: dns_gcloud_rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
# Remove the txt record after validation.
dns_gcloud_rm() {
fulldomain=$1
txtvalue=$2
_info "Using gcloud"
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
_dns_gcloud_find_zone || return $?
# Remove one RR
_dns_gcloud_start_tr || return $?
_dns_gcloud_get_rrdatas || return $?
echo "$rrdatas" | _dns_gcloud_remove_rrs || return $?
echo "$rrdatas" | grep -F -v -- "\"$txtvalue\"" | _dns_gcloud_add_rrs || return $?
_dns_gcloud_execute_tr || return $?
_info "$fulldomain record added"
}
#################### Private functions below ##################################
_dns_gcloud_start_tr() {
if ! trd=$(mktemp -d); then
_err "_dns_gcloud_start_tr: failed to create temporary directory"
return 1
fi
tr="$trd/tr.yaml"
_debug tr "$tr"
if ! gcloud dns record-sets transaction start \
--transaction-file="$tr" \
--zone="$managedZone"; then
rm -r "$trd"
_err "_dns_gcloud_start_tr: failed to execute transaction"
return 1
fi
}
_dns_gcloud_execute_tr() {
if ! gcloud dns record-sets transaction execute \
--transaction-file="$tr" \
--zone="$managedZone"; then
_debug tr "$(cat "$tr")"
rm -r "$trd"
_err "_dns_gcloud_execute_tr: failed to execute transaction"
return 1
fi
rm -r "$trd"
for i in $(seq 1 120); do
if gcloud dns record-sets changes list \
--zone="$managedZone" \
--filter='status != done' |
grep -q '^.*'; then
_info "_dns_gcloud_execute_tr: waiting for transaction to be comitted ($i/120)..."
sleep 5
else
return 0
fi
done
_err "_dns_gcloud_execute_tr: transaction is still pending after 10 minutes"
rm -r "$trd"
return 1
}
_dns_gcloud_remove_rrs() {
if ! xargs -r gcloud dns record-sets transaction remove \
--name="$fulldomain." \
--ttl="$ttl" \
--type=TXT \
--zone="$managedZone" \
--transaction-file="$tr" --; then
_debug tr "$(cat "$tr")"
rm -r "$trd"
_err "_dns_gcloud_remove_rrs: failed to remove RRs"
return 1
fi
}
_dns_gcloud_add_rrs() {
ttl=60
if ! xargs -r gcloud dns record-sets transaction add \
--name="$fulldomain." \
--ttl="$ttl" \
--type=TXT \
--zone="$managedZone" \
--transaction-file="$tr" --; then
_debug tr "$(cat "$tr")"
rm -r "$trd"
_err "_dns_gcloud_add_rrs: failed to add RRs"
return 1
fi
}
_dns_gcloud_find_zone() {
# Prepare a filter that matches zones that are suiteable for this entry.
# For example, _acme-challenge.something.domain.com might need to go into something.domain.com or domain.com;
# this function finds the longest postfix that has a managed zone.
part="$fulldomain"
filter="dnsName=( "
while [ "$part" != "" ]; do
filter="$filter$part. "
part="$(echo "$part" | sed 's/[^.]*\.*//')"
done
filter="$filter) AND visibility=public"
_debug filter "$filter"
# List domains and find the zone with the deepest sub-domain (in case of some levels of delegation)
if ! match=$(gcloud dns managed-zones list \
--format="value(name, dnsName)" \
--filter="$filter" |
while read -r dnsName name; do
printf "%s\t%s\t%s\n" "$(echo "$name" | awk -F"." '{print NF-1}')" "$dnsName" "$name"
done |
sort -n -r | _head_n 1 | cut -f2,3 | grep '^.*'); then
_err "_dns_gcloud_find_zone: Can't find a matching managed zone! Perhaps wrong project or gcloud credentials?"
return 1
fi
dnsName=$(echo "$match" | cut -f2)
_debug dnsName "$dnsName"
managedZone=$(echo "$match" | cut -f1)
_debug managedZone "$managedZone"
}
_dns_gcloud_get_rrdatas() {
if ! rrdatas=$(gcloud dns record-sets list \
--zone="$managedZone" \
--name="$fulldomain." \
--type=TXT \
--format="value(ttl,rrdatas)"); then
_err "_dns_gcloud_get_rrdatas: Failed to list record-sets"
rm -r "$trd"
return 1
fi
ttl=$(echo "$rrdatas" | cut -f1)
# starting with version 353.0.0 gcloud seems to
# separate records with a semicolon instead of commas
# see also https://cloud.google.com/sdk/docs/release-notes#35300_2021-08-17
rrdatas=$(echo "$rrdatas" | cut -f2 | sed 's/"[,;]"/"\n"/g')
}