mirror of
https://github.com/dehydrated-io/dehydrated.git
synced 2026-04-24 09:18:28 +02:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7ea8aaab5c | ||
|
|
6f5c9dba64 | ||
|
|
4a340caf29 | ||
|
|
2e6933464e | ||
|
|
1dbbc64ce9 | ||
|
|
12877bb238 | ||
|
|
ad43e250b2 | ||
|
|
8e9e5ef9c7 | ||
|
|
a7deeaedbc | ||
|
|
3d95f18000 | ||
|
|
ce9eb300e2 | ||
|
|
9cfcd66f15 | ||
|
|
73bb54a4b2 | ||
|
|
3a71a7ad94 | ||
|
|
0290338853 |
16
CHANGELOG
16
CHANGELOG
@@ -1,11 +1,27 @@
|
||||
# Change Log
|
||||
This file contains a log of major changes in dehydrated
|
||||
|
||||
## [x.x.x] - xxxx-xx-xx
|
||||
## Fixed
|
||||
- Various bugfixes around IP certificate orders
|
||||
- Implement workaround for OpenSSL regression which broke the time-based validity check
|
||||
|
||||
## Added
|
||||
- Added a configuration parameter to allow for timeouts during domain validation processing (`VALIDATION_TIMEOUT`, defaults to 0 = no timeout)
|
||||
- Added documentation for IP certificates
|
||||
|
||||
## Changed
|
||||
- Only validate existance of wellknown directory or hook script when actually needed
|
||||
- Also allow setting `KEEP_GOING` in config file instead of relying on cli arguments
|
||||
- Allow skipping over OCSP stapling errors, indicate that some CAs no longer support OCSP
|
||||
- Throw error with information about OCSP deprecation if certificate doesn't indicate OCSP support
|
||||
|
||||
## [0.7.2] - 2025-05-18
|
||||
## Added
|
||||
- Implemented support for certificate profile selection
|
||||
- Added a configuration parameter to allow for timeouts during order processing (`ORDER_TIMEOUT`, defaults to 0 = no timeout)
|
||||
- Allowed for automatic deletion of old files (`AUTO_CLEANUP_DELETE`, disabled by default)
|
||||
- Added CA presets for Google Trust Services (prod: google, test: google-test)
|
||||
|
||||
## Changed
|
||||
- Renew certificates with 32 days remaining (instead of 30) to avoid issues with monthly cronjobs (`RENEW_DAYS=32`)
|
||||
|
||||
@@ -87,6 +87,7 @@ Parameters:
|
||||
--algo (-a) rsa|prime256v1|secp384r1 Which public key algorithm should be used? Supported: rsa, prime256v1 and secp384r1
|
||||
--acme-profile profile_name Use specified ACME profile
|
||||
--order-timeout seconds Amount of seconds to wait for processing of order until erroring out
|
||||
--validation-timeout seconds Amount of seconds to wait for processing of domain validations until erroring out
|
||||
```
|
||||
|
||||
## Chat
|
||||
|
||||
152
dehydrated
152
dehydrated
@@ -17,7 +17,7 @@ umask 077 # paranoid umask, we're creating private keys
|
||||
exec 3>&-
|
||||
exec 4>&-
|
||||
|
||||
VERSION="0.7.2"
|
||||
VERSION="0.7.3"
|
||||
|
||||
# Find directory in which this script is stored by traversing all symbolic links
|
||||
SOURCE="${0}"
|
||||
@@ -252,6 +252,43 @@ ip_to_ptr() {
|
||||
fi
|
||||
}
|
||||
|
||||
# IPv6 conversion helpers
|
||||
ipv6_expand() {
|
||||
# expand double colons until 8 segments exist
|
||||
# replace remaining double colon with single colon
|
||||
# pad all segments to 4 characters with leading zeros
|
||||
_sed \
|
||||
-e ':addsegs; /^([^:]*:){0,7}[^:]*$/{ s/::/:0000::/g; t addsegs; }' \
|
||||
-e 's/::/:/' \
|
||||
-e ':padsegs; s/(:|^)([^:]{0,3})(:|$)/\10\2\3/g; t padsegs;'
|
||||
}
|
||||
|
||||
ipv6_shorten() {
|
||||
# remove leading zeros from all segments
|
||||
# find the longest matching run of zeros and replace with double colons (this could be prettier..)
|
||||
_sed \
|
||||
-e ':unpadsegs;/(^|:)0/{s/(^|:)0([^:])/\1\2/g;t unpadsegs;}' \
|
||||
-e '/(^|:)(0(:|$)){8}/{ s/(^|:)(0(:|$)){8}/::/; t end; }' \
|
||||
-e '/(^|:)(0(:|$)){7}/{ s/(^|:)(0(:|$)){7}/::/; t end; }' \
|
||||
-e '/(^|:)(0(:|$)){6}/{ s/(^|:)(0(:|$)){6}/::/; t end; }' \
|
||||
-e '/(^|:)(0(:|$)){5}/{ s/(^|:)(0(:|$)){5}/::/; t end; }' \
|
||||
-e '/(^|:)(0(:|$)){4}/{ s/(^|:)(0(:|$)){4}/::/; t end; }' \
|
||||
-e '/(^|:)(0(:|$)){3}/{ s/(^|:)(0(:|$)){3}/::/; t end; }' \
|
||||
-e '/(^|:)(0(:|$)){2}/{ s/(^|:)(0(:|$)){2}/::/; t end; }' \
|
||||
-e ':end'
|
||||
}
|
||||
|
||||
ipv6_normalize() {
|
||||
for domain in $(cat); do
|
||||
if [[ "${domain}" =~ : ]]; then
|
||||
ipv6_expand <<< "${domain}" | ipv6_shorten
|
||||
else
|
||||
printf "%s" "${domain}"
|
||||
fi
|
||||
printf " "
|
||||
done | sed -e 's/ $//'
|
||||
}
|
||||
|
||||
# Create (identifiable) temporary files
|
||||
_mktemp() {
|
||||
mktemp "${TMPDIR:-/tmp}/dehydrated-XXXXXX"
|
||||
@@ -293,6 +330,8 @@ store_configvars() {
|
||||
__IP_VERSION="${IP_VERSION}"
|
||||
__ACME_PROFILE="${ACME_PROFILE}"
|
||||
__ORDER_TIMEOUT=${ORDER_TIMEOUT}
|
||||
__VALIDATION_TIMEOUT=${VALIDATION_TIMEOUT}
|
||||
__KEEP_GOING=${KEEP_GOING}
|
||||
}
|
||||
|
||||
reset_configvars() {
|
||||
@@ -313,6 +352,8 @@ reset_configvars() {
|
||||
IP_VERSION="${__IP_VERSION}"
|
||||
ACME_PROFILE="${__ACME_PROFILE}"
|
||||
ORDER_TIMEOUT=${__ORDER_TIMEOUT}
|
||||
VALIDATION_TIMEOUT=${__VALIDATION_TIMEOUT}
|
||||
KEEP_GOING="${__KEEP_GOING}"
|
||||
}
|
||||
|
||||
hookscript_bricker_hook() {
|
||||
@@ -326,12 +367,14 @@ hookscript_bricker_hook() {
|
||||
# verify configuration values
|
||||
verify_config() {
|
||||
[[ "${CHALLENGETYPE}" == "http-01" || "${CHALLENGETYPE}" == "dns-01" || "${CHALLENGETYPE}" == "tls-alpn-01" ]] || _exiterr "Unknown challenge type ${CHALLENGETYPE}... cannot continue."
|
||||
if [[ "${COMMAND:-}" =~ sign_domains|sign_csr ]]; then
|
||||
if [[ "${CHALLENGETYPE}" = "dns-01" ]] && [[ -z "${HOOK}" ]]; then
|
||||
_exiterr "Challenge type dns-01 needs a hook script for deployment... cannot continue."
|
||||
fi
|
||||
if [[ "${CHALLENGETYPE}" = "http-01" && ! -d "${WELLKNOWN}" && ! "${COMMAND:-}" = "register" ]]; then
|
||||
if [[ "${CHALLENGETYPE}" = "http-01" ]] && [[ ! -d "${WELLKNOWN}" ]]; then
|
||||
_exiterr "WELLKNOWN directory doesn't exist, please create ${WELLKNOWN} and set appropriate permissions."
|
||||
fi
|
||||
fi
|
||||
[[ "${KEY_ALGO}" == "rsa" || "${KEY_ALGO}" == "prime256v1" || "${KEY_ALGO}" == "secp384r1" || "${KEY_ALGO}" == "secp521r1" ]] || _exiterr "Unknown public key algorithm ${KEY_ALGO}... cannot continue."
|
||||
if [[ -n "${IP_VERSION}" ]]; then
|
||||
[[ "${IP_VERSION}" = "4" || "${IP_VERSION}" = "6" ]] || _exiterr "Unknown IP version ${IP_VERSION}... cannot continue."
|
||||
@@ -339,6 +382,7 @@ verify_config() {
|
||||
[[ "${API}" == "auto" || "${API}" == "1" || "${API}" == "2" ]] || _exiterr "Unsupported API version defined in config: ${API}"
|
||||
[[ "${OCSP_DAYS}" =~ ^[0-9]+$ ]] || _exiterr "OCSP_DAYS must be a number"
|
||||
[[ "${ORDER_TIMEOUT}" =~ ^[0-9]+$ ]] || _exiterr "ORDER_TIMEOUT must be a number"
|
||||
[[ "${VALIDATION_TIMEOUT}" =~ ^[0-9]+$ ]] || _exiterr "VALIDATION_TIMEOUT must be a number"
|
||||
}
|
||||
|
||||
# Setup default config values, search for and load configuration files
|
||||
@@ -401,6 +445,8 @@ load_config() {
|
||||
API="auto"
|
||||
ACME_PROFILE=""
|
||||
ORDER_TIMEOUT=0
|
||||
VALIDATION_TIMEOUT=0
|
||||
KEEP_GOING="no"
|
||||
|
||||
if [[ -z "${CONFIG:-}" ]]; then
|
||||
echo "#" >&2
|
||||
@@ -560,6 +606,8 @@ load_config() {
|
||||
[[ -n "${PARAM_IP_VERSION:-}" ]] && IP_VERSION="${PARAM_IP_VERSION}"
|
||||
[[ -n "${PARAM_ACME_PROFILE:-}" ]] && ACME_PROFILE="${PARAM_ACME_PROFILE}"
|
||||
[[ -n "${PARAM_ORDER_TIMEOUT:-}" ]] && ORDER_TIMEOUT="${PARAM_ORDER_TIMEOUT}"
|
||||
[[ -n "${PARAM_VALIDATION_TIMEOUT:-}" ]] && VALIDATION_TIMEOUT="${PARAM_VALIDATION_TIMEOUT}"
|
||||
[[ -n "${PARAM_KEEP_GOING:-}" ]] && KEEP_GOING="${PARAM_KEEP_GOING}"
|
||||
|
||||
if [ "${PARAM_FORCE_VALIDATION:-no}" = "yes" ] && [ "${PARAM_FORCE:-no}" = "no" ]; then
|
||||
_exiterr "Argument --force-validation can only be used in combination with --force (-x)"
|
||||
@@ -928,14 +976,14 @@ http_request() {
|
||||
set +e
|
||||
# shellcheck disable=SC2086
|
||||
if [[ "${1}" = "head" ]]; then
|
||||
statuscode="$(curl ${ip_version:-} ${CURL_OPTS} -A "dehydrated/${VERSION} curl/${CURL_VERSION}" -s -w "%{http_code}" -o "${tempcont}" "${2}" -I)"
|
||||
statuscode="$(curl ${ip_version:-} ${CURL_OPTS} -A "dehydrated/${VERSION} curl/${CURL_VERSION}" -s -w "%{http_code}" -o "${tempcont}" -H 'Cache-Control: no-cache' "${2}" -I)"
|
||||
curlret="${?}"
|
||||
touch "${tempheaders}"
|
||||
elif [[ "${1}" = "get" ]]; then
|
||||
statuscode="$(curl ${ip_version:-} ${CURL_OPTS} -A "dehydrated/${VERSION} curl/${CURL_VERSION}" -L -s -w "%{http_code}" -o "${tempcont}" -D "${tempheaders}" "${2}")"
|
||||
statuscode="$(curl ${ip_version:-} ${CURL_OPTS} -A "dehydrated/${VERSION} curl/${CURL_VERSION}" -L -s -w "%{http_code}" -o "${tempcont}" -D "${tempheaders}" -H 'Cache-Control: no-cache' "${2}")"
|
||||
curlret="${?}"
|
||||
elif [[ "${1}" = "post" ]]; then
|
||||
statuscode="$(curl ${ip_version:-} ${CURL_OPTS} -A "dehydrated/${VERSION} curl/${CURL_VERSION}" -s -w "%{http_code}" -o "${tempcont}" "${2}" -D "${tempheaders}" -H 'Content-Type: application/jose+json' -d "${3}")"
|
||||
statuscode="$(curl ${ip_version:-} ${CURL_OPTS} -A "dehydrated/${VERSION} curl/${CURL_VERSION}" -s -w "%{http_code}" -o "${tempcont}" "${2}" -D "${tempheaders}" -H 'Cache-Control: no-cache' -H 'Content-Type: application/jose+json' -d "${3}")"
|
||||
curlret="${?}"
|
||||
else
|
||||
set -e
|
||||
@@ -1131,7 +1179,11 @@ sign_csr() {
|
||||
local challenge_identifiers=""
|
||||
for altname in ${altnames}; do
|
||||
if [[ "${altname}" =~ ^ip: ]]; then
|
||||
challenge_identifiers+="$(printf '{"type": "ip", "value": "%s"}, ' "${altname:3}")"
|
||||
ip="${altname:3}"
|
||||
if [[ "${ip}" =~ : ]]; then
|
||||
ip="$(ipv6_normalize <<< "${ip}")"
|
||||
fi
|
||||
challenge_identifiers+="$(printf '{"type": "ip", "value": "%s"}, ' "${ip}")"
|
||||
else
|
||||
challenge_identifiers+="$(printf '{"type": "dns", "value": "%s"}, ' "${altname}")"
|
||||
fi
|
||||
@@ -1280,8 +1332,14 @@ sign_csr() {
|
||||
|
||||
reqstatus="$(echo "${result}" | get_json_string_value status)"
|
||||
|
||||
local waited=0
|
||||
while [[ "${reqstatus}" = "pending" ]] || [[ "${reqstatus}" = "processing" ]]; do
|
||||
if [ ${VALIDATION_TIMEOUT} -gt 0 ] && [ ${waited} -gt ${VALIDATION_TIMEOUT} ]; then
|
||||
_exiterr "Timed out waiting for processing of domain validation (still ${reqstatus})"
|
||||
fi
|
||||
echo " + Validation is ${reqstatus}..."
|
||||
sleep 1
|
||||
waited=$((waited+1))
|
||||
if [[ "${API}" -eq 2 ]]; then
|
||||
result="$(signed_request "${challenge_uris[${idx}]}" "" | jsonsh)"
|
||||
else
|
||||
@@ -1554,7 +1612,7 @@ sign_domain() {
|
||||
fi
|
||||
done
|
||||
if [[ "${domain}" =~ ^ip: ]]; then
|
||||
SUBJ="/CN=${domain:3}/"
|
||||
SUBJ="/"
|
||||
else
|
||||
SUBJ="/CN=${domain}/"
|
||||
fi
|
||||
@@ -1624,6 +1682,42 @@ sign_domain() {
|
||||
echo " + Done!"
|
||||
}
|
||||
|
||||
# Update OCSP stapling file
|
||||
update_ocsp_stapling() {
|
||||
local certdir="${1}"
|
||||
local update_ocsp="${2}"
|
||||
local cert="${3}"
|
||||
local chain="${4}"
|
||||
|
||||
local ocsp_url="$(get_ocsp_url "${cert}")"
|
||||
|
||||
if [[ -z "${ocsp_url}" ]]; then
|
||||
echo " ! ERROR: OCSP stapling requested but no OCSP url found in certificate." >&2
|
||||
echo " ! Keep in mind that some CAs ended support for OCSP: https://letsencrypt.org/2024/12/05/ending-ocsp/" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ ! -e "${certdir}/ocsp.der" ]]; then
|
||||
update_ocsp="yes"
|
||||
elif ! ("${OPENSSL}" ocsp -no_nonce -issuer "${chain}" -verify_other "${chain}" -cert "${cert}" -respin "${certdir}/ocsp.der" -status_age $((OCSP_DAYS*24*3600)) 2>&1 | grep -q "${cert}: good"); then
|
||||
update_ocsp="yes"
|
||||
fi
|
||||
|
||||
if [[ "${update_ocsp}" = "yes" ]]; then
|
||||
echo " + Updating OCSP stapling file"
|
||||
ocsp_timestamp="$(date +%s)"
|
||||
if grep -qE "^(openssl (0|(1\.0))\.)|(libressl (1|2|3)\.)" <<< "$(${OPENSSL} version | awk '{print tolower($0)}')"; then
|
||||
ocsp_log="$("${OPENSSL}" ocsp -no_nonce -issuer "${chain}" -verify_other "${chain}" -cert "${cert}" -respout "${certdir}/ocsp-${ocsp_timestamp}.der" -url "${ocsp_url}" -header "HOST" "$(echo "${ocsp_url}" | _sed -e 's/^http(s?):\/\///' -e 's/\/.*$//g')" 2>&1)" || _exiterr "Fetching of OCSP information failed. Please note that some CAs (e.g. LetsEncrypt) do no longer support OCSP. Error message: ${ocsp_log}"
|
||||
else
|
||||
ocsp_log="$("${OPENSSL}" ocsp -no_nonce -issuer "${chain}" -verify_other "${chain}" -cert "${cert}" -respout "${certdir}/ocsp-${ocsp_timestamp}.der" -url "${ocsp_url}" 2>&1)" || _exiterr "Fetching of OCSP information failed. Please note that some CAs (e.g. LetsEncrypt) do no longer support OCSP. Error message: ${ocsp_log}"
|
||||
fi
|
||||
ln -sf "ocsp-${ocsp_timestamp}.der" "${certdir}/ocsp.der"
|
||||
[[ -n "${HOOK}" ]] && (altnames="${domain} ${morenames}" "${HOOK}" "deploy_ocsp" "${domain}" "${certdir}/ocsp.der" "${ocsp_timestamp}" || _exiterr 'deploy_ocsp hook returned with non-zero exit code')
|
||||
else
|
||||
echo " + OCSP stapling file is still valid (skipping update)"
|
||||
fi
|
||||
}
|
||||
|
||||
# Usage: --version (-v)
|
||||
# Description: Print version information
|
||||
command_version() {
|
||||
@@ -1842,7 +1936,7 @@ command_sign_domains() {
|
||||
# All settings that are allowed here should also be stored and
|
||||
# restored in store_configvars() and reset_configvars()
|
||||
case "${config_var}" in
|
||||
KEY_ALGO|OCSP_MUST_STAPLE|OCSP_FETCH|OCSP_DAYS|PRIVATE_KEY_RENEW|PRIVATE_KEY_ROLLOVER|KEYSIZE|CHALLENGETYPE|HOOK|PREFERRED_CHAIN|WELLKNOWN|HOOK_CHAIN|OPENSSL_CNF|RENEW_DAYS|ACME_PROFILE|ORDER_TIMEOUT)
|
||||
KEY_ALGO|OCSP_MUST_STAPLE|OCSP_FETCH|OCSP_DAYS|PRIVATE_KEY_RENEW|PRIVATE_KEY_ROLLOVER|KEYSIZE|CHALLENGETYPE|HOOK|PREFERRED_CHAIN|WELLKNOWN|HOOK_CHAIN|OPENSSL_CNF|RENEW_DAYS|ACME_PROFILE|ORDER_TIMEOUT|VALIDATION_TIMEOUT|KEEP_GOING)
|
||||
echo " + ${config_var} = ${config_value}"
|
||||
declare -- "${config_var}=${config_value}"
|
||||
;;
|
||||
@@ -1878,8 +1972,8 @@ command_sign_domains() {
|
||||
if [[ -e "${cert}" && "${force_renew}" = "no" ]]; then
|
||||
printf " + Checking domain name(s) of existing cert..."
|
||||
|
||||
certnames="$("${OPENSSL}" x509 -in "${cert}" -text -noout | grep -E '(DNS|IP( Address*)):' | _sed 's/(DNS|IP( Address)*)://g' | tr -d ' ' | tr ',' '\n' | sort -u | tr '\n' ' ' | _sed 's/ $//')"
|
||||
givennames="$(echo "${domain}" "${morenames}"| tr ' ' '\n' | sort -u | tr '\n' ' ' | _sed 's/ip://g' | _sed 's/ $//' | _sed 's/^ //')"
|
||||
certnames="$("${OPENSSL}" x509 -in "${cert}" -text -noout | grep -E '(DNS|IP( Address*)):' | _sed 's/(DNS|IP( Address)*)://g' | tr -d ' ' | tr ',' '\n' | sort -u | tr '\n' ' ' | _sed 's/ $//' | awk '{print tolower($0)}' | ipv6_normalize)"
|
||||
givennames="$(echo "${domain}" "${morenames}"| tr ' ' '\n' | sort -u | tr '\n' ' ' | _sed 's/ip://g' | _sed 's/ $//' | _sed 's/^ //' | ipv6_normalize)"
|
||||
|
||||
if [[ "${certnames}" = "${givennames}" ]]; then
|
||||
echo " unchanged."
|
||||
@@ -1899,7 +1993,7 @@ command_sign_domains() {
|
||||
valid="$("${OPENSSL}" x509 -enddate -noout -in "${cert}" | cut -d= -f2- )"
|
||||
|
||||
printf " + Valid till %s " "${valid}"
|
||||
if ("${OPENSSL}" x509 -checkend $((RENEW_DAYS * 86400)) -noout -in "${cert}" > /dev/null 2>&1); then
|
||||
if ("${OPENSSL}" x509 -checkend $((RENEW_DAYS * 86400)) -in "${cert}" 2>&1 | grep -q "will not expire"); then
|
||||
printf "(Longer than %d days). " "${RENEW_DAYS}"
|
||||
if [[ "${force_renew}" = "yes" ]]; then
|
||||
echo "Ignoring because renew was forced!"
|
||||
@@ -1925,7 +2019,7 @@ command_sign_domains() {
|
||||
rm "${csrfile}"
|
||||
fi
|
||||
# shellcheck disable=SC2086
|
||||
if [[ "${PARAM_KEEP_GOING:-}" = "yes" ]]; then
|
||||
if [[ "${KEEP_GOING:-}" = "yes" ]]; then
|
||||
skip_exit_hook=yes
|
||||
sign_domain "${certdir}" "${timestamp}" "${domain}" ${morenames} &
|
||||
wait $! || exit_with_errorcode=1
|
||||
@@ -1936,27 +2030,13 @@ command_sign_domains() {
|
||||
fi
|
||||
|
||||
if [[ "${OCSP_FETCH}" = "yes" ]]; then
|
||||
local ocsp_url
|
||||
ocsp_url="$(get_ocsp_url "${cert}")"
|
||||
|
||||
if [[ ! -e "${certdir}/ocsp.der" ]]; then
|
||||
update_ocsp="yes"
|
||||
elif ! ("${OPENSSL}" ocsp -no_nonce -issuer "${chain}" -verify_other "${chain}" -cert "${cert}" -respin "${certdir}/ocsp.der" -status_age $((OCSP_DAYS*24*3600)) 2>&1 | grep -q "${cert}: good"); then
|
||||
update_ocsp="yes"
|
||||
fi
|
||||
|
||||
if [[ "${update_ocsp}" = "yes" ]]; then
|
||||
echo " + Updating OCSP stapling file"
|
||||
ocsp_timestamp="$(date +%s)"
|
||||
if grep -qE "^(openssl (0|(1\.0))\.)|(libressl (1|2|3)\.)" <<< "$(${OPENSSL} version | awk '{print tolower($0)}')"; then
|
||||
ocsp_log="$("${OPENSSL}" ocsp -no_nonce -issuer "${chain}" -verify_other "${chain}" -cert "${cert}" -respout "${certdir}/ocsp-${ocsp_timestamp}.der" -url "${ocsp_url}" -header "HOST" "$(echo "${ocsp_url}" | _sed -e 's/^http(s?):\/\///' -e 's/\/.*$//g')" 2>&1)" || _exiterr "Error while fetching OCSP information: ${ocsp_log}"
|
||||
if [[ "${KEEP_GOING:-}" = "yes" ]]; then
|
||||
skip_exit_hook=yes
|
||||
update_ocsp_stapling "${certdir}" "${update_ocsp}" "${cert}" "${chain}" &
|
||||
wait $! || exit_with_errorcode=1
|
||||
skip_exit_hook=no
|
||||
else
|
||||
ocsp_log="$("${OPENSSL}" ocsp -no_nonce -issuer "${chain}" -verify_other "${chain}" -cert "${cert}" -respout "${certdir}/ocsp-${ocsp_timestamp}.der" -url "${ocsp_url}" 2>&1)" || _exiterr "Error while fetching OCSP information: ${ocsp_log}"
|
||||
fi
|
||||
ln -sf "ocsp-${ocsp_timestamp}.der" "${certdir}/ocsp.der"
|
||||
[[ -n "${HOOK}" ]] && (altnames="${domain} ${morenames}" "${HOOK}" "deploy_ocsp" "${domain}" "${certdir}/ocsp.der" "${ocsp_timestamp}" || _exiterr 'deploy_ocsp hook returned with non-zero exit code')
|
||||
else
|
||||
echo " + OCSP stapling file is still valid (skipping update)"
|
||||
update_ocsp_stapling "${certdir}" "${update_ocsp}" "${cert}" "${chain}"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
@@ -2452,6 +2532,14 @@ main() {
|
||||
PARAM_ORDER_TIMEOUT=${1}
|
||||
;;
|
||||
|
||||
# PARAM_Usage: --validation-timeout seconds
|
||||
# PARAM_Description: Amount of seconds to wait for processing of domain validations until erroring out
|
||||
--validation-timeout)
|
||||
shift 1
|
||||
check_parameters "${1:-}"
|
||||
PARAM_VALIDATION_TIMEOUT=${1}
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Unknown parameter detected: ${1}" >&2
|
||||
echo >&2
|
||||
|
||||
@@ -139,3 +139,6 @@
|
||||
|
||||
# Amount of seconds to wait for processing of order until erroring out (default: 0 => no timeout)
|
||||
#ORDER_TIMEOUT=0
|
||||
|
||||
# Skip over errors during certificate orders and updating of OCSP stapling information (default: no)
|
||||
#KEEP_GOING=no
|
||||
|
||||
97
docs/ip-certificates.md
Normal file
97
docs/ip-certificates.md
Normal file
@@ -0,0 +1,97 @@
|
||||
## IP Certificates
|
||||
|
||||
In addition to issuing certificates for domain names, the ACME protocol also supports certificates
|
||||
for IP addresses. Dehydrated has included support for IP identifiers for quite some time, but this
|
||||
feature only became practically useful once Let’s Encrypt made IP certificate issuance publicly
|
||||
available.
|
||||
|
||||
IP certificates can be helpful in scenarios where a service is accessed directly via an address
|
||||
rather than a hostname, for example in internal networks, appliances, temporary systems, or
|
||||
environments without reliable DNS.
|
||||
|
||||
### Limitations and requirements
|
||||
|
||||
Currently, there are a few important constraints to be aware of:
|
||||
|
||||
- Validation is only possible using http-01 challenges. This means you must have a web server publicly reachable on the IP address you want to certify.
|
||||
- Let's Encrypt only issues IP certificates via the shortlived ACME profile. Certificates issued through this profile are currently valid for 7 days.
|
||||
|
||||
Because of the short lifetime, it’s important to renew these certificates frequently and adjust
|
||||
any automated jobs accordingly.
|
||||
|
||||
### Preparing an IP certificate in dehydrated
|
||||
|
||||
For convenience, create the certificate directory and a per-certificate configuration file in advance.
|
||||
|
||||
Example for an IPv6 address:
|
||||
|
||||
```bash
|
||||
ip="2001:0db8:0:3::1337"
|
||||
```
|
||||
|
||||
Or for IPv4:
|
||||
|
||||
```bash
|
||||
ip="224.13.37.42"
|
||||
```
|
||||
|
||||
Then set up the certificate directory and configuration and add the ip to domains.txt:
|
||||
|
||||
```bash
|
||||
# Create certificate directory
|
||||
mkdir -p "certs/ip:${ip}"
|
||||
|
||||
# Use the shortlived ACME profile for this certificate
|
||||
echo "ACME_PROFILE=shortlived" >> "certs/ip:${ip}/config"
|
||||
|
||||
# Renew this certificate every 4 days
|
||||
echo "RENEW_DAYS=4" >> "certs/ip:${ip}/config"
|
||||
|
||||
# Add IP to domains.txt
|
||||
echo ip:${ip} >> domains.txt
|
||||
```
|
||||
|
||||
Keep in mind that you also can use aliases for better readability in your directory structure.
|
||||
See the `domains.txt` documentation for more information.
|
||||
|
||||
### Requesting the certificate
|
||||
|
||||
Once the directory and configuration are in place, you can request and renew the certificate as usual:
|
||||
|
||||
```bash
|
||||
dehydrated -c
|
||||
```
|
||||
|
||||
Dehydrated will automatically include the IP identifier and use the configured ACME profile.
|
||||
|
||||
### Renewal considerations
|
||||
|
||||
Since short-lived certificates expire after one week, make sure that:
|
||||
|
||||
- Your renewal job runs frequently enough (for example daily or every few days)
|
||||
- Monitoring or alerting accounts for the much shorter validity period
|
||||
- Failing to renew in time will result in expired certificates much sooner than with standard domain certificates.
|
||||
|
||||
### IPv6 address normalization
|
||||
|
||||
To ensure compatibility with Let's Encrypt's seemingly somewhat non-standard handling of IP identifiers,
|
||||
dehydrated internally normalizes IPv6 addresses before using them as certificate names.
|
||||
|
||||
This process first expands and reformats IPv6 notation into a consistent representation, eliminating
|
||||
shorthand forms such as :: compression. Afterwards it re-shortens the IPv6 address in a way that is
|
||||
accepted by Let's Encrypt. Doing so guarantees that:
|
||||
|
||||
- IPv6 addresses are compatible with Let's Encrypt
|
||||
- Matching of existing and configured identifiers works, without dependency on special formatting in domains.txt
|
||||
|
||||
This happens internally and should be invisible to most users, but if you are running this against
|
||||
a custom ACME server you might want to be aware of this behaviour.
|
||||
|
||||
Example formatting:
|
||||
|
||||
- Original IPv6 address: `2001:db8:0:3:0:0:0:1337` (not accepted by Let's Encrypt)
|
||||
- Fully expanded IPv6 address: `2001:0db8:0000:0003:0000:0000:0000:1337` (also not accepted)
|
||||
- Re-shortened IPv6 address: `2001:db8:0:3::1337` (gets accepted)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user