diff options
Diffstat (limited to 'dehydrated/bin/dehydrated-nsupdate')
-rwxr-xr-x | dehydrated/bin/dehydrated-nsupdate | 212 |
1 files changed, 212 insertions, 0 deletions
diff --git a/dehydrated/bin/dehydrated-nsupdate b/dehydrated/bin/dehydrated-nsupdate new file mode 100755 index 0000000..d59e5ff --- /dev/null +++ b/dehydrated/bin/dehydrated-nsupdate @@ -0,0 +1,212 @@ +#!/bin/sh + +# Open Infrastructure: service-tools + +# Copyright (C) 2014-2023 Daniel Baumann <daniel.baumann@open-infrastructure.net> +# +# SPDX-License-Identifier: GPL-3.0+ +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +set -e + +HOOK="$(basename "${0}")" +HOOK_ACTION="$(echo "${HOOK}" | awk -F. '{ print $1 }')" + +# set nsupdate action +case "${HOOK}" in + clean_challenge.*) + HOOK_ACTION="delete" + ;; + + deploy_challenge.*) + HOOK_ACTION="add" + ;; + + *) + echo "'${HOOK}': no such hook action '${HOOK_ACTION}'" >&2 + echo "'${HOOK}': use 'clean_challenge.' or 'deploy_challenge.' as prefix in your symlink" >&2 + exit 1 + ;; +esac + +# alternatives handling for dig +if command -v kdig > /dev/null 2>&1 +then + # knot-dnsutils + DIG_VARIANT="knot" +elif command -v dig > /dev/null 2>&1 +then + # bind-dnsutils + DIG_VARIANT="bind" +else + echo "'${HOOK}': need dig from bind-dnsutils or knot-dnsutils" >&2 + exit 1 +fi + +case "${DIG_VARIANT}" in + knot) + DIG="kdig +noidn" + ;; + + bind) + DIG="dig +noidnout" + ;; +esac + +# alternatives handling for nsupdate +if command -v knsupdate > /dev/null 2>&1 +then + # knot-dnsutils + NSUPDATE_VARIANT="knot" +elif command -v nsupdate > /dev/null 2>&1 +then + # bind-dnsutils + NSUPDATE_VARIANT="bind" +else + echo "'${HOOK}': need nsupdate from bind-dnsutils or knot-dnsutils" >&2 + exit 1 +fi + +case "${NSUPDATE_VARIANT}" in + knot) + NSUPDATE="knsupdate" + ;; + + bind) + NSUPDATE="nsupdate" + ;; +esac + +# config +for FILE in /etc/default/dehydrated-nsupdate /etc/default/dehydrated-nsupdate.d/* +do + if [ -e "${FILE}" ] + then + . "${FILE}" + fi +done + +# find txt record to update +CNAME="$(${DIG} +nocomments +noquestion "_acme-challenge.${DOMAIN}" 2>&1 | grep -v '^;' | awk '/CNAME/ { print $5 }' | tail -n1)" + +if [ -n "${CNAME}" ] +then + TXT_RECORD="${CNAME}" +else + TXT_RECORD="_acme-challenge.${DOMAIN}" +fi + +ZONE="${TXT_RECORD}" + +# find all nameservers to update +while true +do + NAMESERVERS="$(${DIG} +nocomments +noquestion NS "${ZONE}" 2>&1 | grep -v '^;' | awk '/NS/ { print $5 }')" + + if [ -n "${NAMESERVERS}" ] + then + ZONE="$(${DIG} +nocomments +noquestion NS "${ZONE}" 2>&1 | grep -v '^;' | awk '/NS/ { print $1 }' | tail -n1)" + break + else + ZONE="$(echo "${ZONE}" | cut -d '.' -f 2-)" + fi +done + +NAMESERVERS_IPV6="" +NAMESERVERS_IPV4="" + +for NAMESERVER in ${NAMESERVERS} +do + if [ -n "$(${DIG} +nocomments +noquestion +short AAAA "${NAMESERVER}")" ] + then + NAMESERVERS_IPV6="${NAMESERVERS_IPV6} ${NAMESERVER}" + fi + + if [ -n "$(${DIG} +nocomments +noquestion +short A "${NAMESERVER}")" ] + then + NAMESERVERS_IPV4="${NAMESERVERS_IPV4} ${NAMESERVER}" + fi +done + +# filter nameservers by available IP protocol +NAMESERVERS="" + +if hostname -I | grep -qs ':' +then + NAMESERVERS="${NAMESERVERS} ${NAMESERVERS_IPV6}" +fi + +if hostname -I | grep -qs '\.' +then + NAMESERVERS="${NAMESERVERS} ${NAMESERVERS_IPV4}" +fi + +NAMESERVERS="$(echo "${NAMESERVERS}" | sed -e 's| |\n|g' | sort -u -V)" + +# update nameservers +for NAMESERVER in ${NAMESERVERS} +do + if [ -e "/etc/dehydrated/tsig/$(basename "${TXT_RECORD}" .).key" ] + then + # specific key per record + KEY="/etc/dehydrated/tsig/$(basename "${TXT_RECORD}" .).key" + elif [ -e "/etc/dehydrated/tsig/$(basename "${ZONE}" .).key" ] + then + # specific key per zone + KEY="/etc/dehydrated/tsig/$(basename "${ZONE}" .).key" + elif [ -e "/etc/dehydrated/tsig/$(basename "${NAMESERVER}" .).key" ] + then + # specific key per nameserver + KEY="/etc/dehydrated/tsig/$(basename "${NAMESERVER}" .).key" + elif [ -e "/etc/dehydrated/tsig.key" ] + then + # global key (filesystem) + KEY="/etc/dehydrated/tsig.key" + elif [ -n "${TSIG_KEYFILE}" ] && [ -e "${TSIG_KEYFILE}" ] + then + # global key (conffile) + KEY="${TSIG_KEYFILE}" + else + # no key + KEY="" + fi + + # ignoring comments to allow empty keyfiles to disable TSIG individually + TSIG="$(grep -sv '^#' "${KEY}" || true)" + + if [ -n "${KEY}" ] && [ -n "${TSIG}" ] + then + case "${NSUPDATE_VARIANT}" in + knot) + NSUPDATE_OPTIONS="-k ${KEY}" + ;; + + bind) + NSUPDATE_OPTIONS="-y $(cat "${KEY}")" + ;; + esac + fi + + echo -n " + sending '${HOOK_ACTION}' for ${TXT_RECORD} to ${NAMESERVER}.." + +# shellcheck disable=SC2086 +echo "server ${NAMESERVER} +zone ${ZONE} +ttl 0 +update ${HOOK_ACTION} ${TXT_RECORD} 0 TXT ${TOKEN_VALUE} +send" | "${NSUPDATE}" ${NSUPDATE_OPTIONS} + + echo " done." +done |