Compare commits

...
2 Commits
3 changed files with 148 additions and 1 deletions

No files matched your search

+14
View File
@@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
## 1.1.0 - 2026-11-01
### Added
- Added an early DNS-01 prerequisite check in certificate creation for wildcard certificates.
### Fixed
- 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
### Added
+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
+8
View File
@@ -1085,6 +1085,14 @@ _create_cert() {
if echo "$DYNTLS_SAN_LIST" | grep -E -q '(^\*\.)|( \*\.)'; then
DYNTLS_DNS_ZONE=$(_Hostname get domain)
_log "Wildcard detected in SAN list; using dns-01 challenge for zone '$DYNTLS_DNS_ZONE'." 1
_log "Check for dns-01 TSIG/RNDC key." 1
if [ ! -r "$DYNTLS_DNS_TSIG" ]; then
_log "dns-01 TSIG/RNDC key not readable: '$DYNTLS_DNS_TSIG'; cannot perform dns-01 challenge." 4
rm -f "$out_file_tmp"
return 1
fi
DYNTLS_DNS_OPTIONS="$DYNTLS_DNS_OPTIONS -l dns-01 -d $DYNTLS_DNS_SERVER -t $DYNTLS_DNS_TSIG -z $DYNTLS_DNS_ZONE"
_log "DNS challenge options: -l dns-01 -d $DYNTLS_DNS_SERVER -t $DYNTLS_DNS_TSIG -z $DYNTLS_DNS_ZONE" 1
else