mirror of
https://github.com/dehydrated-io/dehydrated.git
synced 2026-01-14 15:43:35 +01:00
Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1dbbc64ce9 | ||
|
|
12877bb238 | ||
|
|
ad43e250b2 | ||
|
|
8e9e5ef9c7 | ||
|
|
a7deeaedbc | ||
|
|
3d95f18000 | ||
|
|
ce9eb300e2 | ||
|
|
9cfcd66f15 | ||
|
|
73bb54a4b2 | ||
|
|
3a71a7ad94 | ||
|
|
0290338853 | ||
|
|
fcca67b53c | ||
|
|
cf9e6a33fd | ||
|
|
bec154f070 | ||
|
|
0141d86267 | ||
|
|
a86a176805 | ||
|
|
200cd68e7e | ||
|
|
e973cb2d8a | ||
|
|
7c438c484f | ||
|
|
a94f451014 | ||
|
|
a615a55ad6 | ||
|
|
f6d82e2715 | ||
|
|
1a1cb94a61 | ||
|
|
5ab8c3806d | ||
|
|
4ea5081640 | ||
|
|
4fd777e87e | ||
|
|
e3ef43c816 | ||
|
|
67b111a7b0 | ||
|
|
fa68ad8b23 | ||
|
|
5c4adf6baa | ||
|
|
35bfea55b6 | ||
|
|
ea84199863 | ||
|
|
6091ba4bc2 |
27
CHANGELOG
27
CHANGELOG
@@ -2,11 +2,38 @@
|
|||||||
This file contains a log of major changes in dehydrated
|
This file contains a log of major changes in dehydrated
|
||||||
|
|
||||||
## [x.x.x] - xxxx-xx-xx
|
## [x.x.x] - xxxx-xx-xx
|
||||||
|
## Added
|
||||||
|
- Added a configuration parameter to allow for timeouts during domain validation processing (`VALIDATION_TIMEOUT`, defaults to 0 = no timeout)
|
||||||
|
|
||||||
|
## 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`)
|
||||||
|
|
||||||
|
## Fixed
|
||||||
|
- Changed behaviour of `openssl req` stdin handling to fix compatibility with OpenSSL version 3.2+
|
||||||
|
|
||||||
|
## [0.7.1] - 2022-10-31
|
||||||
## Changed
|
## Changed
|
||||||
- `--force` no longer forces domain name revalidation by default, a new argument `--force-validation` has been added for that
|
- `--force` no longer forces domain name revalidation by default, a new argument `--force-validation` has been added for that
|
||||||
- Added support for EC secp521r1 algorithm (works with e.g. zerossl)
|
- Added support for EC secp521r1 algorithm (works with e.g. zerossl)
|
||||||
- `EC PARAMETERS` are no longer written to privkey.pem (didn't seem necessary and was causing issues with various software)
|
- `EC PARAMETERS` are no longer written to privkey.pem (didn't seem necessary and was causing issues with various software)
|
||||||
|
|
||||||
|
## Fixed
|
||||||
|
- Requests resulting in `badNonce` errors are now automatically retried (fixes operation with LE staging servers)
|
||||||
|
- Deprecated `egrep` usage has been removed
|
||||||
|
|
||||||
## Added
|
## Added
|
||||||
- Implemented EC for account keys
|
- Implemented EC for account keys
|
||||||
- Domain list now also read from domains.txt.d subdirectory (behaviour might change, see docs)
|
- Domain list now also read from domains.txt.d subdirectory (behaviour might change, see docs)
|
||||||
|
|||||||
11
README.md
11
README.md
@@ -71,7 +71,7 @@ Parameters:
|
|||||||
--ca url/preset Use specified CA URL or preset
|
--ca url/preset Use specified CA URL or preset
|
||||||
--alias certalias Use specified name for certificate directory (and per-certificate config) instead of the primary domain (only used if --domain is specified)
|
--alias certalias Use specified name for certificate directory (and per-certificate config) instead of the primary domain (only used if --domain is specified)
|
||||||
--keep-going (-g) Keep going after encountering an error while creating/renewing multiple certificates in cron mode
|
--keep-going (-g) Keep going after encountering an error while creating/renewing multiple certificates in cron mode
|
||||||
--force (-x) Force renew of certificate even if it is longer valid than value in RENEW_DAYS
|
--force (-x) Force certificate renewal even if it is not due to expire within RENEW_DAYS
|
||||||
--force-validation Force revalidation of domain names (used in combination with --force)
|
--force-validation Force revalidation of domain names (used in combination with --force)
|
||||||
--no-lock (-n) Don't use lockfile (potentially dangerous!)
|
--no-lock (-n) Don't use lockfile (potentially dangerous!)
|
||||||
--lock-suffix example.com Suffix lockfile name with a string (useful for with -d)
|
--lock-suffix example.com Suffix lockfile name with a string (useful for with -d)
|
||||||
@@ -85,4 +85,13 @@ Parameters:
|
|||||||
--alpn alpn-certs/directory Output alpn verification certificates into the specified directory
|
--alpn alpn-certs/directory Output alpn verification certificates into the specified directory
|
||||||
--challenge (-t) http-01|dns-01|tls-alpn-01 Which challenge should be used? Currently http-01, dns-01, and tls-alpn-01 are supported
|
--challenge (-t) http-01|dns-01|tls-alpn-01 Which challenge should be used? Currently http-01, dns-01, and tls-alpn-01 are supported
|
||||||
--algo (-a) rsa|prime256v1|secp384r1 Which public key algorithm should be used? Supported: rsa, prime256v1 and secp384r1
|
--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
|
||||||
|
|
||||||
|
Dehydrated has an official IRC-channel `#dehydrated` on libera.chat that can be used for general discussion and suggestions.
|
||||||
|
|
||||||
|
The channel can also be accessed with Matrix using the official libera.chat bridge at `#dehydrated:libera.chat`.
|
||||||
|
|||||||
249
dehydrated
249
dehydrated
@@ -17,7 +17,7 @@ umask 077 # paranoid umask, we're creating private keys
|
|||||||
exec 3>&-
|
exec 3>&-
|
||||||
exec 4>&-
|
exec 4>&-
|
||||||
|
|
||||||
VERSION="0.7.1"
|
VERSION="0.7.3"
|
||||||
|
|
||||||
# Find directory in which this script is stored by traversing all symbolic links
|
# Find directory in which this script is stored by traversing all symbolic links
|
||||||
SOURCE="${0}"
|
SOURCE="${0}"
|
||||||
@@ -143,7 +143,7 @@ jsonsh() {
|
|||||||
|
|
||||||
# Force zsh to expand $A into multiple words
|
# Force zsh to expand $A into multiple words
|
||||||
local is_wordsplit_disabled
|
local is_wordsplit_disabled
|
||||||
is_wordsplit_disabled="$(unsetopt 2>/dev/null | grep -c '^shwordsplit$')"
|
is_wordsplit_disabled="$(unsetopt 2>/dev/null | grep -c '^shwordsplit$' || true)"
|
||||||
if [ "${is_wordsplit_disabled}" != "0" ]; then setopt shwordsplit; fi
|
if [ "${is_wordsplit_disabled}" != "0" ]; then setopt shwordsplit; fi
|
||||||
$GREP "$STRING|$NUMBER|$KEYWORD|$SPACE|." | grep -Ev "^$SPACE$"
|
$GREP "$STRING|$NUMBER|$KEYWORD|$SPACE|." | grep -Ev "^$SPACE$"
|
||||||
if [ "${is_wordsplit_disabled}" != "0" ]; then unsetopt shwordsplit; fi
|
if [ "${is_wordsplit_disabled}" != "0" ]; then unsetopt shwordsplit; fi
|
||||||
@@ -217,7 +217,7 @@ jsonsh() {
|
|||||||
'[') parse_array "$jpath" ;;
|
'[') parse_array "$jpath" ;;
|
||||||
# At this point, the only valid single-character tokens are digits.
|
# At this point, the only valid single-character tokens are digits.
|
||||||
''|[!0-9]) throw "EXPECTED value GOT ${token:-EOF}" ;;
|
''|[!0-9]) throw "EXPECTED value GOT ${token:-EOF}" ;;
|
||||||
*) value="${token/\\\///}"
|
*) value="${token//\\\///}"
|
||||||
# replace solidus ("\/") in json strings with normalized value: "/"
|
# replace solidus ("\/") in json strings with normalized value: "/"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
@@ -260,7 +260,7 @@ _mktemp() {
|
|||||||
# Check for script dependencies
|
# Check for script dependencies
|
||||||
check_dependencies() {
|
check_dependencies() {
|
||||||
# look for required binaries
|
# look for required binaries
|
||||||
for binary in grep mktemp diff sed awk curl cut; do
|
for binary in grep mktemp diff sed awk curl cut head tail hexdump; do
|
||||||
bin_path="$(command -v "${binary}" 2>/dev/null)" || _exiterr "This script requires ${binary}."
|
bin_path="$(command -v "${binary}" 2>/dev/null)" || _exiterr "This script requires ${binary}."
|
||||||
[[ -x "${bin_path}" ]] || _exiterr "${binary} found in PATH but it's not executable"
|
[[ -x "${bin_path}" ]] || _exiterr "${binary} found in PATH but it's not executable"
|
||||||
done
|
done
|
||||||
@@ -291,6 +291,10 @@ store_configvars() {
|
|||||||
__OPENSSL_CNF="${OPENSSL_CNF}"
|
__OPENSSL_CNF="${OPENSSL_CNF}"
|
||||||
__RENEW_DAYS="${RENEW_DAYS}"
|
__RENEW_DAYS="${RENEW_DAYS}"
|
||||||
__IP_VERSION="${IP_VERSION}"
|
__IP_VERSION="${IP_VERSION}"
|
||||||
|
__ACME_PROFILE="${ACME_PROFILE}"
|
||||||
|
__ORDER_TIMEOUT=${ORDER_TIMEOUT}
|
||||||
|
__VALIDATION_TIMEOUT=${VALIDATION_TIMEOUT}
|
||||||
|
__KEEP_GOING=${KEEP_GOING}
|
||||||
}
|
}
|
||||||
|
|
||||||
reset_configvars() {
|
reset_configvars() {
|
||||||
@@ -309,6 +313,10 @@ reset_configvars() {
|
|||||||
OPENSSL_CNF="${__OPENSSL_CNF}"
|
OPENSSL_CNF="${__OPENSSL_CNF}"
|
||||||
RENEW_DAYS="${__RENEW_DAYS}"
|
RENEW_DAYS="${__RENEW_DAYS}"
|
||||||
IP_VERSION="${__IP_VERSION}"
|
IP_VERSION="${__IP_VERSION}"
|
||||||
|
ACME_PROFILE="${__ACME_PROFILE}"
|
||||||
|
ORDER_TIMEOUT=${__ORDER_TIMEOUT}
|
||||||
|
VALIDATION_TIMEOUT=${__VALIDATION_TIMEOUT}
|
||||||
|
KEEP_GOING="${__KEEP_GOING}"
|
||||||
}
|
}
|
||||||
|
|
||||||
hookscript_bricker_hook() {
|
hookscript_bricker_hook() {
|
||||||
@@ -322,11 +330,13 @@ hookscript_bricker_hook() {
|
|||||||
# verify configuration values
|
# verify configuration values
|
||||||
verify_config() {
|
verify_config() {
|
||||||
[[ "${CHALLENGETYPE}" == "http-01" || "${CHALLENGETYPE}" == "dns-01" || "${CHALLENGETYPE}" == "tls-alpn-01" ]] || _exiterr "Unknown challenge type ${CHALLENGETYPE}... cannot continue."
|
[[ "${CHALLENGETYPE}" == "http-01" || "${CHALLENGETYPE}" == "dns-01" || "${CHALLENGETYPE}" == "tls-alpn-01" ]] || _exiterr "Unknown challenge type ${CHALLENGETYPE}... cannot continue."
|
||||||
if [[ "${CHALLENGETYPE}" = "dns-01" ]] && [[ -z "${HOOK}" ]]; then
|
if [[ "${COMMAND:-}" =~ sign_domains|sign_csr ]]; then
|
||||||
_exiterr "Challenge type dns-01 needs a hook script for deployment... cannot continue."
|
if [[ "${CHALLENGETYPE}" = "dns-01" ]] && [[ -z "${HOOK}" ]]; then
|
||||||
fi
|
_exiterr "Challenge type dns-01 needs a hook script for deployment... cannot continue."
|
||||||
if [[ "${CHALLENGETYPE}" = "http-01" && ! -d "${WELLKNOWN}" && ! "${COMMAND:-}" = "register" ]]; then
|
fi
|
||||||
_exiterr "WELLKNOWN directory doesn't exist, please create ${WELLKNOWN} and set appropriate permissions."
|
if [[ "${CHALLENGETYPE}" = "http-01" ]] && [[ ! -d "${WELLKNOWN}" ]]; then
|
||||||
|
_exiterr "WELLKNOWN directory doesn't exist, please create ${WELLKNOWN} and set appropriate permissions."
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
[[ "${KEY_ALGO}" == "rsa" || "${KEY_ALGO}" == "prime256v1" || "${KEY_ALGO}" == "secp384r1" || "${KEY_ALGO}" == "secp521r1" ]] || _exiterr "Unknown public key algorithm ${KEY_ALGO}... cannot continue."
|
[[ "${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
|
if [[ -n "${IP_VERSION}" ]]; then
|
||||||
@@ -334,6 +344,8 @@ verify_config() {
|
|||||||
fi
|
fi
|
||||||
[[ "${API}" == "auto" || "${API}" == "1" || "${API}" == "2" ]] || _exiterr "Unsupported API version defined in config: ${API}"
|
[[ "${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"
|
[[ "${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
|
# Setup default config values, search for and load configuration files
|
||||||
@@ -355,6 +367,8 @@ load_config() {
|
|||||||
CA_LETSENCRYPT_TEST="https://acme-staging-v02.api.letsencrypt.org/directory"
|
CA_LETSENCRYPT_TEST="https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||||
CA_BUYPASS="https://api.buypass.com/acme/directory"
|
CA_BUYPASS="https://api.buypass.com/acme/directory"
|
||||||
CA_BUYPASS_TEST="https://api.test4.buypass.no/acme/directory"
|
CA_BUYPASS_TEST="https://api.test4.buypass.no/acme/directory"
|
||||||
|
CA_GOOGLE="https://dv.acme-v02.api.pki.goog/directory"
|
||||||
|
CA_GOOGLE_TEST="https://dv.acme-v02.test-api.pki.goog/directory"
|
||||||
|
|
||||||
# Default values
|
# Default values
|
||||||
CA="letsencrypt"
|
CA="letsencrypt"
|
||||||
@@ -372,7 +386,7 @@ load_config() {
|
|||||||
HOOK=
|
HOOK=
|
||||||
PREFERRED_CHAIN=
|
PREFERRED_CHAIN=
|
||||||
HOOK_CHAIN="no"
|
HOOK_CHAIN="no"
|
||||||
RENEW_DAYS="30"
|
RENEW_DAYS="32"
|
||||||
KEYSIZE="4096"
|
KEYSIZE="4096"
|
||||||
WELLKNOWN=
|
WELLKNOWN=
|
||||||
PRIVATE_KEY_RENEW="yes"
|
PRIVATE_KEY_RENEW="yes"
|
||||||
@@ -388,9 +402,14 @@ load_config() {
|
|||||||
IP_VERSION=
|
IP_VERSION=
|
||||||
CHAINCACHE=
|
CHAINCACHE=
|
||||||
AUTO_CLEANUP="no"
|
AUTO_CLEANUP="no"
|
||||||
|
AUTO_CLEANUP_DELETE="no"
|
||||||
DEHYDRATED_USER=
|
DEHYDRATED_USER=
|
||||||
DEHYDRATED_GROUP=
|
DEHYDRATED_GROUP=
|
||||||
API="auto"
|
API="auto"
|
||||||
|
ACME_PROFILE=""
|
||||||
|
ORDER_TIMEOUT=0
|
||||||
|
VALIDATION_TIMEOUT=0
|
||||||
|
KEEP_GOING="no"
|
||||||
|
|
||||||
if [[ -z "${CONFIG:-}" ]]; then
|
if [[ -z "${CONFIG:-}" ]]; then
|
||||||
echo "#" >&2
|
echo "#" >&2
|
||||||
@@ -481,6 +500,10 @@ load_config() {
|
|||||||
CA="${CA_BUYPASS}"
|
CA="${CA_BUYPASS}"
|
||||||
elif [ "${CA}" = "buypass-test" ]; then
|
elif [ "${CA}" = "buypass-test" ]; then
|
||||||
CA="${CA_BUYPASS_TEST}"
|
CA="${CA_BUYPASS_TEST}"
|
||||||
|
elif [ "${CA}" = "google" ]; then
|
||||||
|
CA="${CA_GOOGLE}"
|
||||||
|
elif [ "${CA}" = "google-test" ]; then
|
||||||
|
CA="${CA_GOOGLE_TEST}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -z "${OLDCA}" ]] && [[ "${CA}" = "https://acme-v02.api.letsencrypt.org/directory" ]]; then
|
if [[ -z "${OLDCA}" ]] && [[ "${CA}" = "https://acme-v02.api.letsencrypt.org/directory" ]]; then
|
||||||
@@ -544,6 +567,10 @@ load_config() {
|
|||||||
[[ -n "${PARAM_KEY_ALGO:-}" ]] && KEY_ALGO="${PARAM_KEY_ALGO}"
|
[[ -n "${PARAM_KEY_ALGO:-}" ]] && KEY_ALGO="${PARAM_KEY_ALGO}"
|
||||||
[[ -n "${PARAM_OCSP_MUST_STAPLE:-}" ]] && OCSP_MUST_STAPLE="${PARAM_OCSP_MUST_STAPLE}"
|
[[ -n "${PARAM_OCSP_MUST_STAPLE:-}" ]] && OCSP_MUST_STAPLE="${PARAM_OCSP_MUST_STAPLE}"
|
||||||
[[ -n "${PARAM_IP_VERSION:-}" ]] && IP_VERSION="${PARAM_IP_VERSION}"
|
[[ -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
|
if [ "${PARAM_FORCE_VALIDATION:-no}" = "yes" ] && [ "${PARAM_FORCE:-no}" = "no" ]; then
|
||||||
_exiterr "Argument --force-validation can only be used in combination with --force (-x)"
|
_exiterr "Argument --force-validation can only be used in combination with --force (-x)"
|
||||||
@@ -587,6 +614,10 @@ init_system() {
|
|||||||
_exiterr "Problem retrieving ACME/CA-URLs, check if your configured CA points to the directory entrypoint."
|
_exiterr "Problem retrieving ACME/CA-URLs, check if your configured CA points to the directory entrypoint."
|
||||||
# Since reg URI is missing from directory we will assume it is the same as CA_NEW_REG without the new part
|
# Since reg URI is missing from directory we will assume it is the same as CA_NEW_REG without the new part
|
||||||
CA_REG=${CA_NEW_REG/new-reg/reg}
|
CA_REG=${CA_NEW_REG/new-reg/reg}
|
||||||
|
|
||||||
|
if [[ -n "${ACME_PROFILE}" ]]; then
|
||||||
|
_exiterr "ACME profiles are not supported in ACME v1."
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
CA_NEW_ORDER="$(printf "%s" "${CA_DIRECTORY}" | get_json_string_value newOrder)" &&
|
CA_NEW_ORDER="$(printf "%s" "${CA_DIRECTORY}" | get_json_string_value newOrder)" &&
|
||||||
CA_NEW_NONCE="$(printf "%s" "${CA_DIRECTORY}" | get_json_string_value newNonce)" &&
|
CA_NEW_NONCE="$(printf "%s" "${CA_DIRECTORY}" | get_json_string_value newNonce)" &&
|
||||||
@@ -595,6 +626,35 @@ init_system() {
|
|||||||
CA_REQUIRES_EAB="$(printf "%s" "${CA_DIRECTORY}" | get_json_bool_value -p '"meta","externalAccountRequired"' || echo false)" &&
|
CA_REQUIRES_EAB="$(printf "%s" "${CA_DIRECTORY}" | get_json_bool_value -p '"meta","externalAccountRequired"' || echo false)" &&
|
||||||
CA_REVOKE_CERT="$(printf "%s" "${CA_DIRECTORY}" | get_json_string_value revokeCert)" ||
|
CA_REVOKE_CERT="$(printf "%s" "${CA_DIRECTORY}" | get_json_string_value revokeCert)" ||
|
||||||
_exiterr "Problem retrieving ACME/CA-URLs, check if your configured CA points to the directory entrypoint."
|
_exiterr "Problem retrieving ACME/CA-URLs, check if your configured CA points to the directory entrypoint."
|
||||||
|
|
||||||
|
# Checking ACME profile
|
||||||
|
if [[ -n "${ACME_PROFILE}" ]]; then
|
||||||
|
# Extract available profiles from CA directory
|
||||||
|
declare -A available_profiles=()
|
||||||
|
while IFS=$'\t' read -r path value; do
|
||||||
|
if [[ "${value}" =~ ^\"([^\"]+)\"$ ]]; then
|
||||||
|
value=${BASH_REMATCH[1]}
|
||||||
|
fi
|
||||||
|
if [[ "${path}" =~ ^\[\"([^\"]+)\"\]$ ]]; then
|
||||||
|
available_profiles[${BASH_REMATCH[1]}]=$value
|
||||||
|
fi
|
||||||
|
done <<< "$(printf "%s" "${CA_DIRECTORY}" | get_json_dict_value -p '"meta","profiles"' 2>/dev/null)"
|
||||||
|
if [[ ${#available_profiles[@]} -eq 0 ]]; then
|
||||||
|
_exiterr "ACME profile not supported by this CA"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if the requested profile is available
|
||||||
|
found_profile="no"
|
||||||
|
for profile in "${!available_profiles[@]}"; do
|
||||||
|
if [[ "${profile}" == "${ACME_PROFILE}" ]]; then
|
||||||
|
found_profile="yes"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [[ "${found_profile}" == "no" ]]; then
|
||||||
|
_exiterr "ACME profile '${ACME_PROFILE}' not found, available profiles:$(for key in "${!available_profiles[@]}"; do printf "\n %s: %s" "${key}" "${available_profiles[$key]}"; done)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Export some environment variables to be used in hook script
|
# Export some environment variables to be used in hook script
|
||||||
@@ -703,6 +763,14 @@ init_system() {
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Google special sauce
|
||||||
|
if [[ "${CA}" = "${CA_GOOGLE}" ]]; then
|
||||||
|
if [[ -z "${CONTACT_EMAIL}" ]] || [[ -z "${EAB_KID:-}" ]] || [[ -z "${EAB_HMAC_KEY:-}" ]]; then
|
||||||
|
echo "Google requires contact email, EAB_KID and EAB_HMAC_KEY to be manually configured (see https://cloud.google.com/certificate-manager/docs/public-ca-tutorial)"
|
||||||
|
FAILED=true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# Check if external account is required
|
# Check if external account is required
|
||||||
if [[ "${FAILED}" = "false" ]]; then
|
if [[ "${FAILED}" = "false" ]]; then
|
||||||
if [[ "${CA_REQUIRES_EAB}" = "true" ]]; then
|
if [[ "${CA_REQUIRES_EAB}" = "true" ]]; then
|
||||||
@@ -839,7 +907,7 @@ hex2bin() {
|
|||||||
|
|
||||||
# Convert binary data to hex string
|
# Convert binary data to hex string
|
||||||
bin2hex() {
|
bin2hex() {
|
||||||
hexdump -e '16/1 "%02x"'
|
hexdump -v -e '/1 "%02x"'
|
||||||
}
|
}
|
||||||
|
|
||||||
# OpenSSL writes to stderr/stdout even when there are no errors. So just
|
# OpenSSL writes to stderr/stdout even when there are no errors. So just
|
||||||
@@ -871,14 +939,14 @@ http_request() {
|
|||||||
set +e
|
set +e
|
||||||
# shellcheck disable=SC2086
|
# shellcheck disable=SC2086
|
||||||
if [[ "${1}" = "head" ]]; then
|
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="${?}"
|
curlret="${?}"
|
||||||
touch "${tempheaders}"
|
touch "${tempheaders}"
|
||||||
elif [[ "${1}" = "get" ]]; then
|
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="${?}"
|
curlret="${?}"
|
||||||
elif [[ "${1}" = "post" ]]; then
|
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="${?}"
|
curlret="${?}"
|
||||||
else
|
else
|
||||||
set -e
|
set -e
|
||||||
@@ -1009,13 +1077,13 @@ signed_request() {
|
|||||||
# Extracts all subject names from a CSR
|
# Extracts all subject names from a CSR
|
||||||
# Outputs either the CN, or the SANs, one per line
|
# Outputs either the CN, or the SANs, one per line
|
||||||
extract_altnames() {
|
extract_altnames() {
|
||||||
csr="${1}" # the CSR itself (not a file)
|
csrfile="${1}" # path to CSR file
|
||||||
|
|
||||||
if ! <<<"${csr}" "${OPENSSL}" req -verify -noout 2>/dev/null; then
|
if ! "${OPENSSL}" req -in "${csrfile}" -verify -noout >/dev/null; then
|
||||||
_exiterr "Certificate signing request isn't valid"
|
_exiterr "Certificate signing request isn't valid"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
reqtext="$( <<<"${csr}" "${OPENSSL}" req -noout -text )"
|
reqtext="$("${OPENSSL}" req -in "${csrfile}" -noout -text)"
|
||||||
if <<<"${reqtext}" grep -q '^[[:space:]]*X509v3 Subject Alternative Name:[[:space:]]*$'; then
|
if <<<"${reqtext}" grep -q '^[[:space:]]*X509v3 Subject Alternative Name:[[:space:]]*$'; then
|
||||||
# SANs used, extract these
|
# SANs used, extract these
|
||||||
altnames="$( <<<"${reqtext}" awk '/X509v3 Subject Alternative Name:/{print;getline;print;}' | tail -n1 )"
|
altnames="$( <<<"${reqtext}" awk '/X509v3 Subject Alternative Name:/{print;getline;print;}' | tail -n1 )"
|
||||||
@@ -1043,7 +1111,7 @@ get_last_cn() {
|
|||||||
|
|
||||||
# Create certificate for domain(s) and outputs it FD 3
|
# Create certificate for domain(s) and outputs it FD 3
|
||||||
sign_csr() {
|
sign_csr() {
|
||||||
csr="${1}" # the CSR itself (not a file)
|
csrfile="${1}" # path to CSR file
|
||||||
|
|
||||||
if { true >&3; } 2>/dev/null; then
|
if { true >&3; } 2>/dev/null; then
|
||||||
: # fd 3 looks OK
|
: # fd 3 looks OK
|
||||||
@@ -1082,7 +1150,12 @@ sign_csr() {
|
|||||||
challenge_identifiers="[${challenge_identifiers%, }]"
|
challenge_identifiers="[${challenge_identifiers%, }]"
|
||||||
|
|
||||||
echo " + Requesting new certificate order from CA..."
|
echo " + Requesting new certificate order from CA..."
|
||||||
order_location="$(signed_request "${CA_NEW_ORDER}" '{"identifiers": '"${challenge_identifiers}"'}' 4>&1 | grep -i ^Location: | cut -d':' -f2- | tr -d ' \t\r\n')"
|
local order_payload='{"identifiers": '"${challenge_identifiers}"
|
||||||
|
if [[ -n "${ACME_PROFILE}" ]]; then
|
||||||
|
order_payload="${order_payload}"',"profile":"'"${ACME_PROFILE}"'"'
|
||||||
|
fi
|
||||||
|
order_payload="${order_payload}"'}'
|
||||||
|
order_location="$(signed_request "${CA_NEW_ORDER}" "${order_payload}" 4>&1 | grep -i ^Location: | cut -d':' -f2- | tr -d ' \t\r\n')"
|
||||||
result="$(signed_request "${order_location}" "" | jsonsh)"
|
result="$(signed_request "${order_location}" "" | jsonsh)"
|
||||||
|
|
||||||
order_authorizations="$(echo "${result}" | get_json_array_values authorizations)"
|
order_authorizations="$(echo "${result}" | get_json_array_values authorizations)"
|
||||||
@@ -1218,8 +1291,14 @@ sign_csr() {
|
|||||||
|
|
||||||
reqstatus="$(echo "${result}" | get_json_string_value status)"
|
reqstatus="$(echo "${result}" | get_json_string_value status)"
|
||||||
|
|
||||||
|
local waited=0
|
||||||
while [[ "${reqstatus}" = "pending" ]] || [[ "${reqstatus}" = "processing" ]]; do
|
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
|
sleep 1
|
||||||
|
waited=$((waited+1))
|
||||||
if [[ "${API}" -eq 2 ]]; then
|
if [[ "${API}" -eq 2 ]]; then
|
||||||
result="$(signed_request "${challenge_uris[${idx}]}" "" | jsonsh)"
|
result="$(signed_request "${challenge_uris[${idx}]}" "" | jsonsh)"
|
||||||
else
|
else
|
||||||
@@ -1268,25 +1347,30 @@ sign_csr() {
|
|||||||
|
|
||||||
# Finally request certificate from the acme-server and store it in cert-${timestamp}.pem and link from cert.pem
|
# Finally request certificate from the acme-server and store it in cert-${timestamp}.pem and link from cert.pem
|
||||||
echo " + Requesting certificate..."
|
echo " + Requesting certificate..."
|
||||||
csr64="$( <<<"${csr}" "${OPENSSL}" req -config "${OPENSSL_CNF}" -outform DER | urlbase64)"
|
csr64="$("${OPENSSL}" req -in "${csrfile}" -config "${OPENSSL_CNF}" -outform DER | urlbase64)"
|
||||||
if [[ ${API} -eq 1 ]]; then
|
if [[ ${API} -eq 1 ]]; then
|
||||||
crt64="$(signed_request "${CA_NEW_CERT}" '{"resource": "new-cert", "csr": "'"${csr64}"'"}' | "${OPENSSL}" base64 -e)"
|
crt64="$(signed_request "${CA_NEW_CERT}" '{"resource": "new-cert", "csr": "'"${csr64}"'"}' | "${OPENSSL}" base64 -e)"
|
||||||
crt="$( printf -- '-----BEGIN CERTIFICATE-----\n%s\n-----END CERTIFICATE-----\n' "${crt64}" )"
|
crt="$( printf -- '-----BEGIN CERTIFICATE-----\n%s\n-----END CERTIFICATE-----\n' "${crt64}" )"
|
||||||
else
|
else
|
||||||
result="$(signed_request "${finalize}" '{"csr": "'"${csr64}"'"}' | jsonsh)"
|
result="$(signed_request "${finalize}" '{"csr": "'"${csr64}"'"}' | jsonsh)"
|
||||||
|
waited=0
|
||||||
while :; do
|
while :; do
|
||||||
orderstatus="$(echo "${result}" | get_json_string_value status)"
|
orderstatus="$(echo "${result}" | get_json_string_value status)"
|
||||||
case "${orderstatus}"
|
case "${orderstatus}"
|
||||||
in
|
in
|
||||||
"processing" | "pending")
|
"processing" | "pending")
|
||||||
|
if [ ${ORDER_TIMEOUT} -gt 0 ] && [ ${waited} -gt ${ORDER_TIMEOUT} ]; then
|
||||||
|
_exiterr "Timed out waiting for processing of order (still ${orderstatus})"
|
||||||
|
fi
|
||||||
echo " + Order is ${orderstatus}..."
|
echo " + Order is ${orderstatus}..."
|
||||||
sleep 2;
|
sleep 2;
|
||||||
|
waited=$((waited+2))
|
||||||
;;
|
;;
|
||||||
"valid")
|
"valid")
|
||||||
break;
|
break;
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
_exiterr "Order in status ${orderstatus}"
|
_exiterr "Order has invalid/unknown status: ${orderstatus}"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
result="$(signed_request "${order_location}" "" | jsonsh)"
|
result="$(signed_request "${order_location}" "" | jsonsh)"
|
||||||
@@ -1487,7 +1571,7 @@ sign_domain() {
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
if [[ "${domain}" =~ ^ip: ]]; then
|
if [[ "${domain}" =~ ^ip: ]]; then
|
||||||
SUBJ="/CN=${domain:3}/"
|
SUBJ="/"
|
||||||
else
|
else
|
||||||
SUBJ="/CN=${domain}/"
|
SUBJ="/CN=${domain}/"
|
||||||
fi
|
fi
|
||||||
@@ -1510,7 +1594,7 @@ sign_domain() {
|
|||||||
|
|
||||||
crt_path="${certdir}/cert-${timestamp}.pem"
|
crt_path="${certdir}/cert-${timestamp}.pem"
|
||||||
# shellcheck disable=SC2086
|
# shellcheck disable=SC2086
|
||||||
sign_csr "$(< "${certdir}/cert-${timestamp}.csr")" ${altnames} 3>"${crt_path}"
|
sign_csr "${certdir}/cert-${timestamp}.csr" ${altnames} 3>"${crt_path}"
|
||||||
|
|
||||||
# Create fullchain.pem
|
# Create fullchain.pem
|
||||||
echo " + Creating fullchain.pem..."
|
echo " + Creating fullchain.pem..."
|
||||||
@@ -1557,6 +1641,42 @@ sign_domain() {
|
|||||||
echo " + Done!"
|
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)
|
# Usage: --version (-v)
|
||||||
# Description: Print version information
|
# Description: Print version information
|
||||||
command_version() {
|
command_version() {
|
||||||
@@ -1775,7 +1895,7 @@ command_sign_domains() {
|
|||||||
# All settings that are allowed here should also be stored and
|
# All settings that are allowed here should also be stored and
|
||||||
# restored in store_configvars() and reset_configvars()
|
# restored in store_configvars() and reset_configvars()
|
||||||
case "${config_var}" in
|
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)
|
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}"
|
echo " + ${config_var} = ${config_value}"
|
||||||
declare -- "${config_var}=${config_value}"
|
declare -- "${config_var}=${config_value}"
|
||||||
;;
|
;;
|
||||||
@@ -1792,16 +1912,18 @@ command_sign_domains() {
|
|||||||
skip="no"
|
skip="no"
|
||||||
|
|
||||||
# Allow for external CSR generation
|
# Allow for external CSR generation
|
||||||
local csr=""
|
local csrfile=""
|
||||||
if [[ -n "${HOOK}" ]]; then
|
if [[ -n "${HOOK}" ]]; then
|
||||||
csr="$("${HOOK}" "generate_csr" "${domain}" "${certdir}" "${domain} ${morenames}")" || _exiterr 'generate_csr hook returned with non-zero exit code'
|
csr="$("${HOOK}" "generate_csr" "${domain}" "${certdir}" "${domain} ${morenames}")" || _exiterr 'generate_csr hook returned with non-zero exit code'
|
||||||
if grep -qE "\-----BEGIN (NEW )?CERTIFICATE REQUEST-----" <<< "${csr}"; then
|
if grep -qE "\-----BEGIN (NEW )?CERTIFICATE REQUEST-----" <<< "${csr}"; then
|
||||||
altnames="$(extract_altnames "${csr}")"
|
csrfile="$(_mktemp)"
|
||||||
|
cat > "${csrfile}" <<< "${csr}"
|
||||||
|
altnames="$(extract_altnames "${csrfile}")"
|
||||||
domain="$(cut -d' ' -f1 <<< "${altnames}")"
|
domain="$(cut -d' ' -f1 <<< "${altnames}")"
|
||||||
morenames="$(cut -s -d' ' -f2- <<< "${altnames}")"
|
morenames="$(cut -s -d' ' -f2- <<< "${altnames}")"
|
||||||
echo " + Using CSR from hook script (real names: ${altnames})"
|
echo " + Using CSR from hook script (real names: ${altnames})"
|
||||||
else
|
else
|
||||||
csr=""
|
csrfile=""
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -1830,7 +1952,7 @@ command_sign_domains() {
|
|||||||
valid="$("${OPENSSL}" x509 -enddate -noout -in "${cert}" | cut -d= -f2- )"
|
valid="$("${OPENSSL}" x509 -enddate -noout -in "${cert}" | cut -d= -f2- )"
|
||||||
|
|
||||||
printf " + Valid till %s " "${valid}"
|
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)) -noout -in "${cert}" 2>&1 | grep -q "will not expire"); then
|
||||||
printf "(Longer than %d days). " "${RENEW_DAYS}"
|
printf "(Longer than %d days). " "${RENEW_DAYS}"
|
||||||
if [[ "${force_renew}" = "yes" ]]; then
|
if [[ "${force_renew}" = "yes" ]]; then
|
||||||
echo "Ignoring because renew was forced!"
|
echo "Ignoring because renew was forced!"
|
||||||
@@ -1851,9 +1973,12 @@ command_sign_domains() {
|
|||||||
# Sign certificate for this domain
|
# Sign certificate for this domain
|
||||||
if [[ ! "${skip}" = "yes" ]]; then
|
if [[ ! "${skip}" = "yes" ]]; then
|
||||||
update_ocsp="yes"
|
update_ocsp="yes"
|
||||||
[[ -z "${csr}" ]] || printf "%s" "${csr}" > "${certdir}/cert-${timestamp}.csr"
|
if [[ -n "${csrfile}" ]]; then
|
||||||
|
cat "${csrfile}" > "${certdir}/cert-${timestamp}.csr"
|
||||||
|
rm "${csrfile}"
|
||||||
|
fi
|
||||||
# shellcheck disable=SC2086
|
# shellcheck disable=SC2086
|
||||||
if [[ "${PARAM_KEEP_GOING:-}" = "yes" ]]; then
|
if [[ "${KEEP_GOING:-}" = "yes" ]]; then
|
||||||
skip_exit_hook=yes
|
skip_exit_hook=yes
|
||||||
sign_domain "${certdir}" "${timestamp}" "${domain}" ${morenames} &
|
sign_domain "${certdir}" "${timestamp}" "${domain}" ${morenames} &
|
||||||
wait $! || exit_with_errorcode=1
|
wait $! || exit_with_errorcode=1
|
||||||
@@ -1864,27 +1989,13 @@ command_sign_domains() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "${OCSP_FETCH}" = "yes" ]]; then
|
if [[ "${OCSP_FETCH}" = "yes" ]]; then
|
||||||
local ocsp_url
|
if [[ "${KEEP_GOING:-}" = "yes" ]]; then
|
||||||
ocsp_url="$(get_ocsp_url "${cert}")"
|
skip_exit_hook=yes
|
||||||
|
update_ocsp_stapling "${certdir}" "${update_ocsp}" "${cert}" "${chain}" &
|
||||||
if [[ ! -e "${certdir}/ocsp.der" ]]; then
|
wait $! || exit_with_errorcode=1
|
||||||
update_ocsp="yes"
|
skip_exit_hook=no
|
||||||
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}"
|
|
||||||
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
|
else
|
||||||
echo " + OCSP stapling file is still valid (skipping update)"
|
update_ocsp_stapling "${certdir}" "${update_ocsp}" "${cert}" "${chain}"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
@@ -1895,8 +2006,8 @@ command_sign_domains() {
|
|||||||
|
|
||||||
[[ -n "${HOOK}" ]] && ("${HOOK}" "exit_hook" || echo 'exit_hook returned with non-zero exit code!' >&2)
|
[[ -n "${HOOK}" ]] && ("${HOOK}" "exit_hook" || echo 'exit_hook returned with non-zero exit code!' >&2)
|
||||||
if [[ "${AUTO_CLEANUP}" == "yes" ]]; then
|
if [[ "${AUTO_CLEANUP}" == "yes" ]]; then
|
||||||
echo "+ Running automatic cleanup"
|
echo " + Running automatic cleanup"
|
||||||
command_cleanup noinit
|
PARAM_CLEANUPDELETE="${AUTO_CLEANUP_DELETE:-no}" command_cleanup noinit | _sed 's/^/ + /g'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
exit "${exit_with_errorcode}"
|
exit "${exit_with_errorcode}"
|
||||||
@@ -1912,19 +2023,18 @@ command_sign_csr() {
|
|||||||
exec 3>&1 1>&2
|
exec 3>&1 1>&2
|
||||||
|
|
||||||
# load csr
|
# load csr
|
||||||
csrfile="${1}"
|
local csrfile="${1}"
|
||||||
if [ ! -r "${csrfile}" ]; then
|
if [ ! -r "${csrfile}" ]; then
|
||||||
_exiterr "Could not read certificate signing request ${csrfile}"
|
_exiterr "Could not read certificate signing request ${csrfile}"
|
||||||
fi
|
fi
|
||||||
csr="$(cat "${csrfile}")"
|
|
||||||
|
|
||||||
# extract names
|
# extract names
|
||||||
altnames="$(extract_altnames "${csr}")"
|
altnames="$(extract_altnames "${csrfile}")"
|
||||||
|
|
||||||
# gen cert
|
# gen cert
|
||||||
certfile="$(_mktemp)"
|
certfile="$(_mktemp)"
|
||||||
# shellcheck disable=SC2086
|
# shellcheck disable=SC2086
|
||||||
sign_csr "${csr}" ${altnames} 3> "${certfile}"
|
sign_csr "${csrfile}" ${altnames} 3> "${certfile}"
|
||||||
|
|
||||||
# print cert
|
# print cert
|
||||||
echo "# CERT #" >&3
|
echo "# CERT #" >&3
|
||||||
@@ -2262,7 +2372,7 @@ main() {
|
|||||||
;;
|
;;
|
||||||
|
|
||||||
# PARAM_Usage: --force (-x)
|
# PARAM_Usage: --force (-x)
|
||||||
# PARAM_Description: Force renew of certificate even if it is longer valid than value in RENEW_DAYS
|
# PARAM_Description: Force certificate renewal even if it is not due to expire within RENEW_DAYS
|
||||||
--force|-x)
|
--force|-x)
|
||||||
PARAM_FORCE="yes"
|
PARAM_FORCE="yes"
|
||||||
;;
|
;;
|
||||||
@@ -2364,6 +2474,31 @@ main() {
|
|||||||
check_parameters "${1:-}"
|
check_parameters "${1:-}"
|
||||||
PARAM_KEY_ALGO="${1}"
|
PARAM_KEY_ALGO="${1}"
|
||||||
;;
|
;;
|
||||||
|
|
||||||
|
# PARAM_Usage: --acme-profile profile_name
|
||||||
|
# PARAM_Description: Use specified ACME profile
|
||||||
|
--acme-profile)
|
||||||
|
shift 1
|
||||||
|
check_parameters "${1:-}"
|
||||||
|
PARAM_ACME_PROFILE="${1}"
|
||||||
|
;;
|
||||||
|
|
||||||
|
# PARAM_Usage: --order-timeout seconds
|
||||||
|
# PARAM_Description: Amount of seconds to wait for processing of order until erroring out
|
||||||
|
--order-timeout)
|
||||||
|
shift 1
|
||||||
|
check_parameters "${1:-}"
|
||||||
|
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 "Unknown parameter detected: ${1}" >&2
|
||||||
echo >&2
|
echo >&2
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
#IP_VERSION=
|
#IP_VERSION=
|
||||||
|
|
||||||
# URL to certificate authority or internal preset
|
# URL to certificate authority or internal preset
|
||||||
# Presets: letsencrypt, letsencrypt-test, zerossl, buypass, buypass-test
|
# Presets: letsencrypt, letsencrypt-test, zerossl, buypass, buypass-test, google, google-test
|
||||||
# default: letsencrypt
|
# default: letsencrypt
|
||||||
#CA="letsencrypt"
|
#CA="letsencrypt"
|
||||||
|
|
||||||
@@ -92,8 +92,8 @@
|
|||||||
# Chain clean_challenge|deploy_challenge arguments together into one hook call per certificate (default: no)
|
# Chain clean_challenge|deploy_challenge arguments together into one hook call per certificate (default: no)
|
||||||
#HOOK_CHAIN="no"
|
#HOOK_CHAIN="no"
|
||||||
|
|
||||||
# Minimum days before expiration to automatically renew certificate (default: 30)
|
# Minimum days before expiration to automatically renew certificate (default: 32)
|
||||||
#RENEW_DAYS="30"
|
#RENEW_DAYS="32"
|
||||||
|
|
||||||
# Regenerate private keys instead of just signing new certificates on renewal (default: yes)
|
# Regenerate private keys instead of just signing new certificates on renewal (default: yes)
|
||||||
#PRIVATE_KEY_RENEW="yes"
|
#PRIVATE_KEY_RENEW="yes"
|
||||||
@@ -125,8 +125,20 @@
|
|||||||
# Automatic cleanup (default: no)
|
# Automatic cleanup (default: no)
|
||||||
#AUTO_CLEANUP="no"
|
#AUTO_CLEANUP="no"
|
||||||
|
|
||||||
|
# Delete files during automatic cleanup instead of moving to archive (default: no)
|
||||||
|
#AUTO_CLEANUP_DELETE="no"
|
||||||
|
|
||||||
# ACME API version (default: auto)
|
# ACME API version (default: auto)
|
||||||
#API=auto
|
#API=auto
|
||||||
|
|
||||||
# Preferred issuer chain (default: <unset> -> uses default chain)
|
# Preferred issuer chain (default: <unset> -> uses default chain)
|
||||||
#PREFERRED_CHAIN=
|
#PREFERRED_CHAIN=
|
||||||
|
|
||||||
|
# Request certificate with specific profile (default: <unset>)
|
||||||
|
#ACME_PROFILE=
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|||||||
@@ -20,13 +20,13 @@ Dehydrated will notify if no account is configured. Run with \fB--register
|
|||||||
|
|
||||||
Next, all domain names must be provided in domains.txt. The format is line
|
Next, all domain names must be provided in domains.txt. The format is line
|
||||||
based: If the file contains two lines "example.com" and "example.net",
|
based: If the file contains two lines "example.com" and "example.net",
|
||||||
Dehydrated will request two certificate, one for "example.com" and the other
|
dehydrated will request two certificate, one for "example.com" and the other
|
||||||
for "example.net". A single line while "example.com example.net" will request a
|
for "example.net". A single line containing "example.com example.net" will request a
|
||||||
single certificate valid for both "example.net" and "example.com" through the \fISubject
|
single certificate valid for both "example.net" and "example.com" through the \fISubject
|
||||||
Alternative Name\fR (SAN) field.
|
Alternative Name\fR (SAN) field.
|
||||||
|
|
||||||
For the next step, one way of verifying domain name ownership needs to be
|
For the next step, one way of verifying domain name ownership needs to be
|
||||||
configured. Dehydrated implements \fIhttp-01\fR and \fIdns-01\fR verification.
|
configured. Dehydrated implements \fIhttp-01\fR and \fIdns-01\fR verification.
|
||||||
|
|
||||||
The \fIhttp-01\fR verification provides proof of ownership by providing a
|
The \fIhttp-01\fR verification provides proof of ownership by providing a
|
||||||
challenge token. In order to do that, the directory referenced in the
|
challenge token. In order to do that, the directory referenced in the
|
||||||
@@ -106,7 +106,7 @@ Keep going after encountering an error while creating/renewing multiple
|
|||||||
certificates in cron mode
|
certificates in cron mode
|
||||||
.TP
|
.TP
|
||||||
.BR \-\-force ", " \-x
|
.BR \-\-force ", " \-x
|
||||||
Force renew of certificate even if it is longer valid than value in RENEW_DAYS
|
Force certificate renewal even if it is not due to expire within RENEW_DAYS
|
||||||
.TP
|
.TP
|
||||||
.BR \-\-no\-lock ", " \-n
|
.BR \-\-no\-lock ", " \-n
|
||||||
Don't use lockfile (potentially dangerous!)
|
Don't use lockfile (potentially dangerous!)
|
||||||
@@ -139,7 +139,7 @@ secp384r1
|
|||||||
The program exits 0 if everything was fine, 1 if an error occurred.
|
The program exits 0 if everything was fine, 1 if an error occurred.
|
||||||
.SH BUGS
|
.SH BUGS
|
||||||
Please report any bugs that you may encounter at the project web site
|
Please report any bugs that you may encounter at the project web site
|
||||||
.UR https://github.com/lukas2511/dehydrated/issues
|
.UR https://github.com/dehydrated-io/dehydrated/issues
|
||||||
.UE .
|
.UE .
|
||||||
.SH AUTHOR
|
.SH AUTHOR
|
||||||
Dehydrated was written by Lukas Schauer. This man page was contributed by
|
Dehydrated was written by Lukas Schauer. This man page was contributed by
|
||||||
@@ -151,5 +151,5 @@ distribution for licensing information.
|
|||||||
.SH SEE ALSO
|
.SH SEE ALSO
|
||||||
Full documentation along with configuration examples are provided in the \fIdocs\fR
|
Full documentation along with configuration examples are provided in the \fIdocs\fR
|
||||||
directory of the distribution, or at
|
directory of the distribution, or at
|
||||||
.UR https://github.com/lukas2511/dehydrated/tree/master/docs
|
.UR https://github.com/dehydrated-io/dehydrated/tree/master/docs
|
||||||
.UE .
|
.UE .
|
||||||
|
|||||||
Reference in New Issue
Block a user