fix(acme): wait for DNS propagation and clean up challenge tokens individually
This commit is contained in:
1 parent
7e5adb1b2e
commit
11ca7fdd08
2 files changed
+132
-3
No files matched your search
+6
-2
@@ -7,13 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## Unreleased
|
||||
|
||||
## 1.1.0 - 2026-11-01
|
||||
|
||||
### Added
|
||||
|
||||
- Added an early dns-01 prerequisite check in certificate creation for wildcard certificates.
|
||||
- Added an early DNS-01 prerequisite check in certificate creation for wildcard certificates.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed silent failures for wildcard dns-01 runs by aborting certificate creation and logging a clear error when the configured TSIG/RNDC key file is missing.
|
||||
- Fixed silent failures for wildcard DNS-01 runs by aborting the current certificate creation step and logging a clear error when the configured TSIG/RNDC key file is missing or unreadable.
|
||||
- Fixed premature DNS-01 validation requests by checking all pending challenge TXT values on dynamically discovered authoritative nameservers before requesting ACME validation. The check performs up to ten rounds, with five-second delays between unsuccessful rounds, and aborts issuance with cleanup if the values remain unavailable.
|
||||
- Fixed DNS-01 cleanup to remove only the TXT value belonging to each challenge, preserving other challenge tokens at the same record name when validating base domains and wildcards together.
|
||||
|
||||
## 1.0.0 - 2026-06-28
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user