dehydrated
dehydrated copied to clipboard
dynamic subzone for DNS 2136 update
I have a setup with a dynamic subdomain zone where bind can create a text entry and this text is referenced by an alias in the main zone. The main zone is static and not changeable dynamically. The idea of it is that this should be more safe.
Dynamic Zone wich can be edited by dehydrated auto.mydomain.de Static Zone mydomain.de Alias in static zone _acme-challenge.mydomain.de for _acme-challenge.auto.mydomain.de
Text record which is set by dehydrated hook script in bind DNS _acme-challenge.auto.mydomain.de.
Text record which is checked by dehydrated for validation: _acme-challenge.mydomain.de. - this is not existing but due to alias it looks like it is existing.
Due to changes of letsencrypt (they claim that this setup should never has worked and was never supported but it did work with dehydrated and with certbot) I had to change the auto.mydomain.de subzone to _acme-challenge.mydomain.de subzone. As a result I had to remove the alias entry because subzone and alias could not exist both with identical names.
After that change certbot is working again but dehydrated does not. I adapted my hook file and basically the log is looking good exept the result. All parts of the hookfile seem to work. The text entry is set and text entry is verified in the hook file but the dehydrated script does not seem to check the text entry in the subzone but in the mainzone which does not exist and due to missing alias is not found.
Is there a way to get this running?
Logging:
sudo /etc/dehydrated/dehydrated -c -d "*.mydomain.de mydomain.de" --alias _wildcard.mydomain.de -k /etc/dehydrated/hookmydomain.sh -t dns-01
# INFO: Using main config file /etc/dehydrated/config
Processing *.mydomain.de with alternative names: mydomain.de
+ Checking domain name(s) of existing cert... unchanged.
+ Checking expire date of existing cert...
+ Valid till Sep 29 00:11:26 2022 GMT (Less than 30 days). Renewing!
+ Signing domains...
+ Generating signing request...
+ Requesting new certificate order from CA...
+ Received 2 authorizations URLs from the CA
+ Handling authorization for mydomain.de
+ Handling authorization for mydomain.de
+ 2 pending challenge(s)
+ Deploying challenge tokens...
+ Adding ACME challenge record via RFC2136 update to mydomain.de...
server ns1.mydomain.de 53
zone _acme-challenge.mydomain.de
update add _acme-challenge._acme-challenge.mydomain.de. 300 in TXT "njiETjfA98Us0-_fQp19UQGMXUD28pY0JgIdWMM3uhI"
show
send
+ Checking for dns propagation via Google's recursor... (1/30)
+ domain mydomain.de
+ text _acme-challenge._acme-challenge.mydomain.de
+ token 1 njiETjfA98Us0-_fQp19UQGMXUD28pY0JgIdWMM3uhI
+ token 2 njiETjfA98Us0-_fQp19UQGMXUD28pY0JgIdWMM3uhI
+ token 3 njiETjfA98Us0-_fQp19UQGMXUD28pY0JgIdWMM3uhI
+ Propagation success!
+ Adding ACME challenge record via RFC2136 update to mydomain.de...
server ns1.mydomain.de 53
zone _acme-challenge.mydomain.de
update add _acme-challenge._acme-challenge.mydomain.de. 300 in TXT "cAKVS6jjhCj3uZtGSsnY8ocxtqrMOCT29PasexNGUdg"
show
send
+ Checking for dns propagation via Google's recursor... (1/30)
+ domain mydomain.de
+ text _acme-challenge._acme-challenge.mydomain.de
+ token 1 cAKVS6jjhCj3uZtGSsnY8ocxtqrMOCT29PasexNGUdg
+ token 2 cAKVS6jjhCj3uZtGSsnY8ocxtqrMOCT29PasexNGUdg
+ token 3 cAKVS6jjhCj3uZtGSsnY8ocxtqrMOCT29PasexNGUdg
+ Propagation success!
+ Responding to _acme-challenge for mydomain.de authorization...
+ Cleaning challenge tokens...
+ Removing ACME challenge record via RFC2136 update to mydomain.de...
server ns1.mydomain.de 53
zone _acme-challenge.mydomain.de
update delete _acme-challenge._acme-challenge.mydomain.de. 300 in TXT "njiETjfA98Us0-_fQp19UQGMXUD28pY0JgIdWMM3uhI"
show
send
+ Removing ACME challenge record via RFC2136 update to mydomain.de...
server ns1.mydomain.de 53
zone _acme-challenge.mydomain.de
update delete _acme-challenge._acme-challenge.mydomain.de. 300 in TXT "cAKVS6jjhCj3uZtGSsnY8ocxtqrMOCT29PasexNGUdg"
show
send
+ Challenge validation has failed :(
ERROR: Challenge is invalid! (returned: invalid) (result: ["type"] "dns-01"
["status"] "invalid"
["error","type"] "urn:ietf:params:acme:error:unauthorized"
["error","detail"] "No TXT record found at _acme-challenge.mydomain.de"
["error","status"] 403
["error"] {"type":"urn:ietf:params:acme:error:unauthorized","detail":"No TXT record found at _acme-challenge.mydomain.de","status":403}
["url"] "https://acme-v02.api.letsencrypt.org/acme/chall-v3/153375141527/8mSBgw"
["token"] "J4yzcZYTu0XUGx5ujls4IzylTm0sYm__ss6Qq549R-Xk"
["validated"] "2022-09-14T18:55:40Z")
I think the relevant part is this one: "No TXT record found at _acme-challenge.mydomain.de","status":403
Threre can't be any text record because that's the subzone byself and this is needed for certbot otherwise it is not working anymore. So basically only the subzone has to be added for the check.
Old hook file:
# NSUPDATE - Path to nsupdate binary
[ -z "${NSUPDATE}" ] && NSUPDATE="sudo /usr/bin/nsupdate -k /etc/dehydrated/Kbackprod.de.+157+03869.private -v"
# SERVER - Master DNS server IP
[ -z "${SERVER}" ] && SERVER="mydomain.de"
# Dynamic Subzone Prefix
[ -z "${SUBPRE}" ] && SUBPRE="auto"
# PORT - Master DNS port (likely to be 53)
[ -z "${PORT}" ] && PORT=53
# TTL - DNS Time-To-Live of ACME TXT record
[ -z "${TTL}" ] && TTL=300
# ATTEMPTS - Wait $ATTEMPTS times $SLEEP seconds for propagation to succeed, then bail out.
[ -z "${ATTEMPTS}" ] && ATTEMPTS=30
# SLEEP - Amount of seconds to sleep before retrying propagation check.
[ -z "${SLEEP}" ] && SLEEP=60
_log() {
echo >&2 " + ${@}"
}
_checkdns() {
local ATTEMPT="${1}" DOMAIN="${2}" TOKEN_VALUE="${3}"
if [ $ATTEMPT -gt $ATTEMPTS ];
then
_log "Propagation check failed after ${ATTEMPTS} attempts. Bailing out!"
exit 2
fi
_log "Checking for dns propagation via Google's recursor... (${ATTEMPT}/${ATTEMPTS})"
host -t txt _acme-challenge.${DOMAIN} 8.8.8.8 | grep -- ${TOKEN_VALUE} >/dev/null 2>&1
if [ "$?" -eq 0 ];
then
host -t txt _acme-challenge.${DOMAIN} 192.174.68.104 | grep -- ${TOKEN_VALUE} >/dev/null 2>&1
if [ "$?" -eq 0 ];
then
host -t txt _acme-challenge.${DOMAIN} 176.97.158.104 | grep -- ${TOKEN_VALUE} >/dev/null 2>&1
if [ "$?" -eq 0 ];
then
_log "Propagation success!"
return
fi
fi
else
_log "Waiting ${SLEEP}s..."
sleep ${SLEEP}
_checkdns $((ATTEMPT+1)) ${DOMAIN} ${TOKEN_VALUE}
fi
}
deploy_challenge() {
local DOMAIN="${1}" TOKEN_FILENAME="${2}" TOKEN_VALUE="${3}"
printf "server ns1.%s %s\nzone %s.%s\nupdate add _acme-challenge.%s.%s. %d in TXT \"%s\"\nshow\nsend\n\n" "${SERVER}" "${PORT}" "${SUBPRE}" "${SERVER}" "${SUBPRE}" "${DOMAIN}" "${TTL}" "${TOKEN_VALUE}" | $NSUPDATE > /dev/null 2>&1
if [ "$?" -ne 0 ];
then
_log "Failure reported by nsupdate. Bailing out1!"
exit 2
fi
# Allow at least a little time to propagate to slaves before asking Google
sleep 5
_checkdns 1 ${DOMAIN} ${TOKEN_VALUE}
}
clean_challenge() {
local DOMAIN="${1}" TOKEN_FILENAME="${2}" TOKEN_VALUE="${3}"
_log "Removing ACME challenge record via RFC2136 update to ${SERVER}..."
printf "server ns1.%s %s\nzone %s.%s\nupdate delete _acme-challenge.%s.%s. %d in TXT \"%s\"\nshow\nsend\n\n" "${SERVER}" "${PORT}" "${SUBPRE}" "${SERVER}" "${SUBPRE}" "${DOMAIN}" "${TTL}" "${TOKEN_VALUE}" | $NSUPDATE > /dev/null 2>&1
if [ "$?" -ne 0 ];
then
_log "Failure reported by nsupdate. Bailing out2!"
exit 2
fi
}
deploy_cert() {
local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}" CHAINFILE="${5}" TIMESTAMP="${6}"
if [ "$DESTINATION" != "" ];
then
_log "Copying certificate files to destination repository"
mkdir -p ${DESTINATION}/${DOMAIN}
if [ "$CERTDIR_MODE" != "" ];
then
chmod ${CERTDIR_MODE} ${DESTINATION}/${DOMAIN}
fi
if [ "$CERTDIR_OWNER" != "" ];
then
chown ${CERTDIR_OWNER}:${CERTDIR_GROUP} ${DESTINATION}/${DOMAIN}
fi
if [ "$CERTDIR_MODE" != "" ];
then
chmod ${CERTDIR_MODE} ${DESTINATION}/${DOMAIN}
fi
for FILE in ${KEYFILE} ${CERTFILE} ${CHAINFILE}
do
FILENAME=$(basename $FILE)
cp ${FILE} ${DESTINATION}/${DOMAIN}
if [ "$CERT_OWNER" != "" ];
then
chown ${CERT_OWNER}:${CERT_GROUP} ${DESTINATION}/${DOMAIN}/${FILENAME}
fi
if [ "$CERT_MODE" != "" ];
then
chmod ${CERT_MODE} ${DESTINATION}/${DOMAIN}/${FILENAME}
fi
done
fi
# Add DOMAIN to domains.txt if not already there
grep ^$HOST\$ ${DOMAINS_TXT} > /dev/null 2>&1
if [ "$?" -ne 0 ];
then
echo ${DOMAIN} >> ${DOMAINS_TXT}
fi
}
unchanged_cert() {
local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}" CHAINFILE="${5}"
}
invalid_challenge() {
local DOMAIN="${1}" RESPONSE="${2}"
:
}
request_failure() {
local STATUSCODE="${1}" REASON="${2}" REQTYPE="${3}" HEADERS="${4}"
}
generate_csr() {
local DOMAIN="${1}" CERTDIR="${2}" ALTNAMES="${3}"
}
startup_hook() {
:
}
exit_hook() {
:
}
HANDLER="$1"; shift
if [[ "${HANDLER}" =~ ^(deploy_challenge|clean_challenge|deploy_cert|unchanged_cert|invalid_challenge|request_failure|generate_csr|startup_hook|exit_hook)$ ]]; then
"$HANDLER" "$@"
fi
New hook File:
[ -z "${NSUPDATE}" ] && NSUPDATE="sudo /usr/bin/nsupdate -k /etc/dehydrated/Kbackprod.de.+157+03869.private -v"
# SERVER - Master DNS server IP
[ -z "${SERVER}" ] && SERVER="torstens-buecherecke.de"
# Dynamic Subzone Prefix
#[ -z "${SUBPRE}" ] && SUBPRE="auto"
[ -z "${SUBPRE}" ] && SUBPRE="_acme-challenge"
# PORT - Master DNS port (likely to be 53)
[ -z "${PORT}" ] && PORT=53
# TTL - DNS Time-To-Live of ACME TXT record
[ -z "${TTL}" ] && TTL=300
# ATTEMPTS - Wait $ATTEMPTS times $SLEEP seconds for propagation to succeed, then bail out.
[ -z "${ATTEMPTS}" ] && ATTEMPTS=30
# SLEEP - Amount of seconds to sleep before retrying propagation check.
[ -z "${SLEEP}" ] && SLEEP=60
# DOMAINS_TXT - Path to the domains.txt file containing all requested certificates.
[ -z "${DOMAINS_TXT}" ] && DOMAINS_TXT="${BASEDIR}/domains.txt"
_log() {
echo >&2 " + ${@}"
}
_checkdns() {
local ATTEMPT="${1}" DOMAIN="${2}" TOKEN_VALUE="${3}"
if [ $ATTEMPT -gt $ATTEMPTS ];
then
_log "Propagation check failed after ${ATTEMPTS} attempts. Bailing out!"
exit 2
fi
_log "Checking for dns propagation via Google's recursor... (${ATTEMPT}/${ATTEMPTS})"
host -t txt _acme-challenge.${SUBPRE}.${DOMAIN} 8.8.8.8 | grep -- ${TOKEN_VALUE} >/dev/null 2>&1
if [ "$?" -eq 0 ];
then
host -t txt _acme-challenge.${SUBPRE}.${DOMAIN} 192.174.68.104 | grep -- ${TOKEN_VALUE} >/dev/null 2>&1
if [ "$?" -eq 0 ];
then
host -t txt _acme-challenge.${SUBPRE}.${DOMAIN} 176.97.158.104 | grep -- ${TOKEN_VALUE} >/dev/null 2>&1
if [ "$?" -eq 0 ];
then
_log "Propagation success!"
return
fi
fi
else
_log "Waiting ${SLEEP}s..."
sleep ${SLEEP}
_checkdns $((ATTEMPT+1)) ${DOMAIN} ${TOKEN_VALUE}
fi
}
deploy_challenge() {
local DOMAIN="${1}" TOKEN_FILENAME="${2}" TOKEN_VALUE="${3}"
_log "Adding ACME challenge record via RFC2136 update to ${SERVER}..."
printf "server ns1.%s %s\nzone %s.%s\nupdate add _acme-challenge.%s.%s. %d in TXT \"%s\"\nshow\nsend\n\n" "${SERVER}" "${PORT}" "${SUBPRE}" "${SERVER}" "${SUBPRE}" "${DOMAIN}" "${TTL}" "${TOKEN_VALUE}" | $NSUPDATE > /dev/null 2>&1
if [ "$?" -ne 0 ];
then
_log "Failure reported by nsupdate. Bailing out1!"
exit 2
fi
# Allow at least a little time to propagate to slaves before asking Google
sleep 5
_checkdns 1 ${DOMAIN} ${TOKEN_VALUE}
}
clean_challenge() {
local DOMAIN="${1}" TOKEN_FILENAME="${2}" TOKEN_VALUE="${3}"
_log "Removing ACME challenge record via RFC2136 update to ${SERVER}..."
printf "server ns1.%s %s\nzone %s.%s\nupdate delete _acme-challenge.%s.%s. %d in TXT \"%s\"\nshow\nsend\n\n" "${SERVER}" "${PORT}" "${SUBPRE}" "${SERVER}" "${SUBPRE}" "${DOMAIN}" "${TTL}" "${TOKEN_VALUE}" | $NSUPDATE > /dev/null 2>&1
if [ "$?" -ne 0 ];
then
_log "Failure reported by nsupdate. Bailing out2!"
exit 2
fi
}
deploy_cert() {
local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}" CHAINFILE="${5}" TIMESTAMP="${6}"
if [ "$DESTINATION" != "" ];
then
_log "Copying certificate files to destination repository"
mkdir -p ${DESTINATION}/${DOMAIN}
if [ "$CERTDIR_MODE" != "" ];
then
chmod ${CERTDIR_MODE} ${DESTINATION}/${DOMAIN}
fi
if [ "$CERTDIR_OWNER" != "" ];
then
chown ${CERTDIR_OWNER}:${CERTDIR_GROUP} ${DESTINATION}/${DOMAIN}
fi
if [ "$CERTDIR_MODE" != "" ];
then
chmod ${CERTDIR_MODE} ${DESTINATION}/${DOMAIN}
fi
for FILE in ${KEYFILE} ${CERTFILE} ${CHAINFILE}
do
FILENAME=$(basename $FILE)
cp ${FILE} ${DESTINATION}/${DOMAIN}
if [ "$CERT_OWNER" != "" ];
then
chown ${CERT_OWNER}:${CERT_GROUP} ${DESTINATION}/${DOMAIN}/${FILENAME}
fi
if [ "$CERT_MODE" != "" ];
then
chmod ${CERT_MODE} ${DESTINATION}/${DOMAIN}/${FILENAME}
fi
done
fi
# Add DOMAIN to domains.txt if not already there
grep ^$HOST\$ ${DOMAINS_TXT} > /dev/null 2>&1
if [ "$?" -ne 0 ];
then
echo ${DOMAIN} >> ${DOMAINS_TXT}
fi
}
unchanged_cert() {
local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}" CHAINFILE="${5}"
}
invalid_challenge() {
local DOMAIN="${1}" RESPONSE="${2}"
:
}
request_failure() {
local STATUSCODE="${1}" REASON="${2}" REQTYPE="${3}" HEADERS="${4}"
}
generate_csr() {
local DOMAIN="${1}" CERTDIR="${2}" ALTNAMES="${3}"
}
startup_hook() {
:
}
exit_hook() {
:
}
HANDLER="$1"; shift
if [[ "${HANDLER}" =~ ^(deploy_challenge|clean_challenge|deploy_cert|unchanged_cert|invalid_challenge|request_failure|generate_csr|startup_hook|exit_hook)$ ]]; then
"$HANDLER" "$@"
fi
Found a solution which was way easier than I thought. So basically it's creating the text entries without a name directly in the subzone.
NEW:
host -t txt ${SUBPRE}.${DOMAIN} 8.8.8.8 | grep -- ${TOKEN_VALUE} >/dev/null 2>&1
SAME for the other two hosts
printf "server ns1.%s %s\nzone %s.%s\nupdate add %s.%s. %d in TXT \"%s\"\nshow\nsend\n\n" "${SERVER}" "${PORT}" "${SUBPRE}" "${SERVER}" "${SUBPRE}" "${DOMAIN}" "${TTL}" "${TOKEN_VALUE}" | $NSUPDATE > /dev/null 2>&1
printf "server ns1.%s %s\nzone %s.%s\nupdate delete %s.%s. %d in TXT \"%s\"\nshow\nsend\n\n" "${SERVER}" "${PORT}" "${SUBPRE}" "${SERVER}" "${SUBPRE}" "${DOMAIN}" "${TTL}" "${TOKEN_VALUE}" | $NSUPDATE > /dev/null 2>&1
OLD:
host -t txt _acme-challenge.${SUBPRE}.${DOMAIN} 8.8.8.8 | grep -- ${TOKEN_VALUE} >/dev/null 2>&1
SAME for the other two hosts
printf "server ns1.%s %s\nzone %s.%s\nupdate add _acme-challenge.%s.%s. %d in TXT \"%s\"\nshow\nsend\n\n" "${SERVER}" "${PORT}" "${SUBPRE}" "${SERVER}" "${SUBPRE}" "${DOMAIN}" "${TTL}" "${TOKEN_VALUE}" | $NSUPDATE > /dev/null 2>&1
printf "server ns1.%s %s\nzone %s.%s\nupdate delete _acme-challenge.%s.%s. %d in TXT \"%s\"\nshow\nsend\n\n" "${SERVER}" "${PORT}" "${SUBPRE}" "${SERVER}" "${SUBPRE}" "${DOMAIN}" "${TTL}" "${TOKEN_VALUE}" | $NSUPDATE > /dev/null 2>&1