aboutsummaryrefslogtreecommitdiff
path: root/kolla/docker
diff options
context:
space:
mode:
authorTimon Schnell <timon.schnell@btc-it-services.com>2026-06-09 12:10:42 +0000
committerPremysl Kouril <premysl.kouril@ultimum.io>2026-08-05 16:16:39 +0000
commitabe305634a9dd5a8a0ba06df8f349617a745ea31 (patch)
treeedca95f7a51a9f1e7e69a306ec4a5b723c64cf57 /kolla/docker
parent8e912a9db7f013a1abfc1e70a829e435d50622db (diff)
downloadkolla-abe305634a9dd5a8a0ba06df8f349617a745ea31.tar.gz
kolla-abe305634a9dd5a8a0ba06df8f349617a745ea31.zip
letsencrypt: Add ACME dns-01 challenge support
This adds support for the ACME dns-01 challenge while keeping http-01 the default. A new --challenge option (default http-01) selects the challenge type. When dns-01 is selected, --dns-provider names any DNS provider compiled into the bundled lego binary, and an optional --dns-additional-opts passthrough forwards extra lego DNS flags (such as --dns.resolvers or --dns.propagation-disable-ans) for environments where the authoritative name server propagation check cannot be performed. Provider credentials are supplied via --env-file: lego DNS providers read credentials only from environment variables, so the wrapper exports the file's contents before lego runs. The file is parsed like docker --env-file: literal KEY=VALUE pairs split on the first '=', comments and blank lines ignored, values taken verbatim and never executed as shell. Selecting dns-01 without --dns-provider fails fast. Co-Authored-By: Premysl Kouril <premysl.kouril@ultimum.io> Assisted-By: Claude Code Change-Id: I61dc5e3f399f6eec02475c7b90e0f41c85c6093c Signed-off-by: Timon Schnell <timon.schnell@btc-it-services.com> Signed-off-by: Jan Horstmann <horstmann@osism.tech>
Diffstat (limited to 'kolla/docker')
-rwxr-xr-xkolla/docker/letsencrypt/letsencrypt-lego/letsencrypt-certificates.sh69
1 files changed, 62 insertions, 7 deletions
diff --git a/kolla/docker/letsencrypt/letsencrypt-lego/letsencrypt-certificates.sh b/kolla/docker/letsencrypt/letsencrypt-lego/letsencrypt-certificates.sh
index 83bdede87..4c06688e1 100755
--- a/kolla/docker/letsencrypt/letsencrypt-lego/letsencrypt-certificates.sh
+++ b/kolla/docker/letsencrypt/letsencrypt-lego/letsencrypt-certificates.sh
@@ -24,6 +24,9 @@ function obtain_or_renew_certificate {
local hmac="${9}"
local key_id="${10}"
local letsencrypt_key_type="${11:-}"
+ local challenge_type="${12:-http-01}"
+ local dns_provider="${13:-}"
+ local dns_additional_opts="${14:-}"
certificate_domain_opts=$(echo ${certificate_fqdns} | sed -r -e 's/^/,/g' -e 's/,/--domains=/g' -e 's/--/ --/g')
certificate_fqdn=$(echo ${certificate_fqdns} | awk -F ',' '{print $1}')
@@ -68,17 +71,24 @@ function obtain_or_renew_certificate {
fi
fi
- log_info "[${certificate_fqdn} - cron] Obtaining certificate for domains ${certificate_fqdns}."
+ if [ "${challenge_type}" = "http-01" ]; then
+ challenge_opts="--http --http.webroot /etc/letsencrypt/http-01 --http.port ${listen_port}"
+ elif [ "${challenge_type}" = "dns-01" ]; then
+ challenge_opts="--dns ${dns_provider} ${dns_additional_opts}"
+ else
+ log_error "Unsupported challenge type: ${challenge_type}."
+ exit 1
+ fi
+
+ log_info "[${certificate_fqdn} - cron] Obtaining certificate for domains ${certificate_fqdns} using ${challenge_type} challenge."
mapfile -t cmd_output < <(/opt/lego --email="${mail}" \
$( [ -n "${letsencrypt_key_type}" ] && echo "--key-type ${letsencrypt_key_type}" ) \
${certificate_domain_opts} \
--server "${acme_url}" \
--path "/etc/letsencrypt/lego/${certificate_type}/" \
- --http.webroot "/etc/letsencrypt/http-01" \
- --http.port ${listen_port} \
+ ${challenge_opts} \
--cert.timeout ${valid_days} \
--accept-tos \
- --http \
${eab_opts} \
--pem ${lego_action} \
--${lego_action}-hook="/usr/bin/sync-and-update-certificate --${certificate_type} --fqdn ${certificate_fqdn} --haproxies-ssh ${letsencrypt_ssh_port}" 2>&1)
@@ -117,10 +127,14 @@ EXTERNAL_SET="false"
EXTERNAL_ACCOUNT_BINDING="false"
HMAC="NONE"
KEY_ID="NONE"
+CHALLENGE_TYPE="http-01"
+DNS_PROVIDER=""
+DNS_ADDITIONAL_OPTS=""
+DNS_ENV_FILE=""
LOG_FILE="/var/log/kolla/letsencrypt/lesencrypt-lego.log"
-VALID_ARGS=$(getopt -o ief:p:d:m:a:v:h:k: --long internal,external,fqdns:,port:,days:,mail:,acme:,vips:,haproxies-ssh:,eab,kid:,hmac:,key-type: -- "$@")
+VALID_ARGS=$(getopt -o ief:p:d:m:a:v:h:k: --long internal,external,fqdns:,port:,days:,mail:,acme:,vips:,haproxies-ssh:,eab,kid:,hmac:,key-type:,challenge:,dns-provider:,dns-additional-opts:,env-file: -- "$@")
if [[ $? -ne 0 ]]; then
exit 1;
fi
@@ -182,12 +196,53 @@ while [ : ]; do
LETSENCRYPT_KEY_TYPE="${2}"
shift 2
;;
+ --challenge)
+ CHALLENGE_TYPE="${2}"
+ shift 2
+ ;;
+ --dns-provider)
+ DNS_PROVIDER="${2}"
+ shift 2
+ ;;
+ --dns-additional-opts)
+ DNS_ADDITIONAL_OPTS="${2}"
+ shift 2
+ ;;
+ --env-file)
+ DNS_ENV_FILE="${2}"
+ shift 2
+ ;;
--) shift;
break
;;
esac
done
+if [ "${CHALLENGE_TYPE}" = "dns-01" ]; then
+ if [ "${DNS_PROVIDER}" = "" ]; then
+ log_error "dns-01 challenge requires --dns-provider."
+ exit 1
+ fi
+ # lego DNS providers take their credentials from environment variables,
+ # so the env file must be exported into this process before lego runs.
+ # The file is parsed like docker --env-file: literal KEY=VALUE pairs,
+ # split on the first '=', values taken verbatim. It is deliberately not
+ # sourced, so values containing spaces or shell metacharacters survive
+ # and nothing in the file is ever executed.
+ if [ "${DNS_ENV_FILE}" != "" ]; then
+ if [ ! -r "${DNS_ENV_FILE}" ]; then
+ log_error "DNS provider environment file ${DNS_ENV_FILE} is not readable."
+ exit 1
+ fi
+ while IFS='=' read -r env_key env_value || [ -n "${env_key}" ]; do
+ case "${env_key}" in
+ ''|\#*) continue ;;
+ esac
+ export "${env_key}=${env_value}"
+ done < "${DNS_ENV_FILE}"
+ fi
+fi
+
FQDN=$(echo "${FQDNS}" | awk -F ',' '{print $1}')
if [ "${INTERNAL_SET}" = "true" ] || [ "${EXTERNAL_SET}" = "true" ]; then
@@ -209,12 +264,12 @@ if [ "${INTERNAL_SET}" = "true" ] || [ "${EXTERNAL_SET}" = "true" ]; then
log_info "[${FQDN} - cron] This Letsencrypt-lego host is active..."
if [ "${LETSENCRYPT_INTERNAL_FQDNS}" != "" ]; then
log_info "[${FQDN} - cron] Processing domains ${LETSENCRYPT_INTERNAL_FQDNS}"
- obtain_or_renew_certificate ${LETSENCRYPT_INTERNAL_FQDNS} internal ${PORT} ${DAYS} ${ACME} ${MAIL} ${LETSENCRYPT_SSH_PORT} ${EXTERNAL_ACCOUNT_BINDING} ${HMAC} ${KEY_ID} ${LETSENCRYPT_KEY_TYPE}
+ obtain_or_renew_certificate "${LETSENCRYPT_INTERNAL_FQDNS}" "internal" "${PORT}" "${DAYS}" "${ACME}" "${MAIL}" "${LETSENCRYPT_SSH_PORT}" "${EXTERNAL_ACCOUNT_BINDING}" "${HMAC}" "${KEY_ID}" "${LETSENCRYPT_KEY_TYPE}" "${CHALLENGE_TYPE}" "${DNS_PROVIDER}" "${DNS_ADDITIONAL_OPTS}"
fi
if [ "${LETSENCRYPT_EXTERNAL_FQDNS}" != "" ]; then
log_info "[${FQDN} - cron] Processing domains ${LETSENCRYPT_EXTERNAL_FQDNS}"
- obtain_or_renew_certificate ${LETSENCRYPT_EXTERNAL_FQDNS} external ${PORT} ${DAYS} ${ACME} ${MAIL} ${LETSENCRYPT_SSH_PORT} ${EXTERNAL_ACCOUNT_BINDING} ${HMAC} ${KEY_ID} ${LETSENCRYPT_KEY_TYPE}
+ obtain_or_renew_certificate "${LETSENCRYPT_EXTERNAL_FQDNS}" "external" "${PORT}" "${DAYS}" "${ACME}" "${MAIL}" "${LETSENCRYPT_SSH_PORT}" "${EXTERNAL_ACCOUNT_BINDING}" "${HMAC}" "${KEY_ID}" "${LETSENCRYPT_KEY_TYPE}" "${CHALLENGE_TYPE}" "${DNS_PROVIDER}" "${DNS_ADDITIONAL_OPTS}"
fi
else
log_info "[${FQDN} - cron] This Letsencrypt-lego host is passive, nothing to do..."