fix(acme): wait for DNS propagation and clean up challenge tokens individually

This commit is contained in:
sduesterhaupt committed 2026-11-01 13:04:38 +01:00
1 parent 7e5adb1b2e
commit 13d5387c8e
2 files changed
+132 -3

No files matched your search

+126 -1
View File
@@ -867,7 +867,8 @@ domain_dns_challenge() {
MyDNSChallengeContent="${MyDNSChallengeContent}update add _acme-challenge.${DOMAIN}. 300 IN TXT \"${DNS_CHALLENGE}\"\n"
;;
delete)
MyDNSChallengeContent="${MyDNSChallengeContent}update delete _acme-challenge.${DOMAIN}. TXT\n"
# Remove only the TXT value belonging to this challenge.
MyDNSChallengeContent="${MyDNSChallengeContent}update delete _acme-challenge.${DOMAIN}. TXT \"${DNS_CHALLENGE}\"\n"
;;
*)
err_exit "Unsupported dns-01 action '$1'"
@@ -980,6 +981,103 @@ request_domain_verification() {
fi
}
# Wait for all pending DNS-01 challenge values on all configured nameservers.
# Requires DOMAIN_DATA groups: domain, challenge URL, token, authorization URL.
# Returns 0 when ready, or 1 after ten unsuccessful check rounds.
wait_dns_response() (
[ "$CHALLENGE_TYPE" = "dns-01" ] || return 0
if [ -z "$DOMAIN_DATA" ]; then
log "No pending DNS challenges; skipping DNS propagation check"
return 0
fi
command -v dig >/dev/null 2>&1 || {
errmsg "DNS propagation check requires dig"
return 1
}
# Public authoritative nameservers for this deployment.
# Discover authoritative nameservers for the configured DNS zone.
[ -n "$DNS_ZONE" ] || {
errmsg "DNS zone is empty; cannot discover authoritative nameservers"
return 1
}
DNS_CHECK_SERVERS=$(
dig "$DNS_ZONE" NS +short +time=2 +tries=1 2>/dev/null |
awk '/^[[:alnum:]_.-]+\.$/ { print }'
)
[ -n "$DNS_CHECK_SERVERS" ] || {
errmsg "Could not discover authoritative nameservers for $DNS_ZONE"
return 1
}
log "Checking DNS propagation on authoritative nameservers: $DNS_CHECK_SERVERS"
# Disable pathname expansion when splitting DOMAIN_DATA and server lists.
set -f
attempt=1
while [ "$attempt" -le 10 ]; do
log "Checking DNS propagation (attempt $attempt/10)"
ready=yes
# shellcheck disable=SC2086 # Intentional splitting into four-field challenge groups.
set -- $DOMAIN_DATA
while [ "$#" -gt 0 ]; do
[ "$#" -ge 4 ] || {
errmsg "Invalid DOMAIN_DATA structure"
return 1
}
domain=$1
token=$3
shift 4
expected=$(
printf '%s' "$token.$ACCOUNT_THUMB" |
openssl dgst -sha256 -binary |
base64url
)
[ -n "$expected" ] || {
errmsg "Could not calculate DNS challenge value for $domain"
return 1
}
for ns in $DNS_CHECK_SERVERS; do
answer=$(
dig @"$ns" "_acme-challenge.$domain." TXT \
+short +norecurse +time=1 +tries=1 2>/dev/null
)
query_status=$?
if [ "$query_status" -ne 0 ] ||
! printf '%s\n' "$answer" |
grep -F -x -q "\"$expected\""; then
ready=no
log "DNS challenge value not yet available for $domain on $ns"
fi
done
done
if [ "$ready" = yes ]; then
log "All pending DNS challenge values are available"
return 0
fi
[ "$attempt" -ge 10 ] && break
sleep 5
attempt=$((attempt + 1))
done
errmsg "DNS propagation check failed after 10 attempts; skipping ACME validation"
return 1
)
request_verification() {
set -- $DOMAIN_DATA
@@ -1594,6 +1692,33 @@ fi
request_challenge
push_response
if ! wait_dns_response; then
# Remove published tokens using the existing cleanup function.
(
set -f
# shellcheck disable=SC2086 # Intentional splitting into four-field challenge groups.
set -- $DOMAIN_DATA
while [ "$#" -ge 4 ]; do
DOMAIN=$1
# shellcheck disable=SC2034 # Consumed by domain_dns_challenge via remove_domain_response.
DOMAIN_TOKEN=$3
shift 4
# Isolate cleanup failures so other tokens can still be removed.
(remove_domain_response) || true
done
domain_commit
) || true
err_exit "DNS challenge values are not ready; certificate issuance aborted" 1
fi
request_verification
check_verification
if [ "$ACTION" = "revoke" ] ;then