diff --git a/.gitattributesdb b/.gitattributesdb index e6090a0..09a291d 100644 --- a/.gitattributesdb +++ b/.gitattributesdb @@ -7,7 +7,7 @@ LmdpdGhvb2tzL3Bvc3QtbWVyZ2U= 1757519106 1757519106 root:root 0755 - - LmdpdGhvb2tzL3ByZS1jb21taXQ= 1757519106 1757519106 root:root 0755 - - LmdpdGlnbm9yZQ== 1757789404 1757593248 root:root 0644 - - LmdpdG1vZHVsZXM= 1757607701 1757607701 root:root 0644 - - -ZXRjLy5naXRpZ25vcmU= 1757611781 1757611781 root:root 0644 - - +ZXRjLy5naXRpZ25vcmU= 1757874149 1757611781 root:root 0644 - - ZXRjL2FwYWNoZTIvLmdpdGlnbm9yZQ== 1757775950 1757775932 root:root 0644 - - ZXRjL2FwYWNoZTIvaHR0cGQuY29uZg== 1757785734 1757785514 root:root 0644 - - ZXRjL2FwYWNoZTIvc2l0ZXMuZC9jb3JlLnNsYWNrd2FyZS51ay5uZXQuY29uZg== 1757786703 1757785113 root:root 0644 - - @@ -18,7 +18,16 @@ ZXRjL2NvbmYuZC9zYW1iYQ== 1757592912 1757592912 root:root 0644 - - ZXRjL2NvbmYuZC9zc2hk 1757593051 1757593051 root:root 0644 - - ZXRjL2NvbmYuZC90ZXJyYWZvcm0taHR0cC1iYWNrZW5k 1757771663 1757595391 root:root 0644 - - ZXRjL2Nyb250YWJzL3Jvb3Q= 1757593504 1757593504 root:root 0600 - - -ZXRjL2dyb3Vw 1757761113 1757594224 root:root 0644 - - +ZXRjL2RlaHlkcmF0ZWQvYWNjb3VudHMvLmdpdGlnbm9yZQ== 1757873230 1757873230 root:root 0644 - - +ZXRjL2RlaHlkcmF0ZWQvYWNjb3VudHMvYUhSMGNITTZMeTloWTIxbExYWXdNaTVoY0drdWJHVjBjMlZ1WTNKNWNIUXViM0puTDJScGNtVmpkRzl5ZVFvLnRhci5ncGc= 1757873275 1757873275 root:root 0644 - - +ZXRjL2RlaHlkcmF0ZWQvYXJjaGl2ZS8uZ2l0aWdub3Jl 1757874259 1757873451 root:root 0644 - - +ZXRjL2RlaHlkcmF0ZWQvY2VydHMvLmdpdGlnbm9yZQ== 1757874303 1757873537 root:root 0644 - - +ZXRjL2RlaHlkcmF0ZWQvY29uZmln 1757863188 1757862077 root:root 0644 - - +ZXRjL2RlaHlkcmF0ZWQvZG9tYWlucw== 1757862328 1757862077 root:root 0644 - - +ZXRjL2RlaHlkcmF0ZWQvZG9tYWlucy5kL19leGFtcGxlXw== 1757863238 1757862077 root:root 0644 - - +ZXRjL2RlaHlkcmF0ZWQvZG9tYWlucy5kL2NvcmUuc2xhY2t3YXJlLnVrLm5ldA== 1757863250 1757863250 root:root 0644 - - +ZXRjL2RlaHlkcmF0ZWQvaG9va3MvZGVmYXVsdA== 1758032719 1757862077 root:root 0755 - - +ZXRjL2dyb3Vw 1757873802 1757869538 root:root 0644 - - ZXRjL2hvc3RuYW1l 1757594311 1757594311 root:root 0644 - - ZXRjL2hvc3Rz 1757594362 1757594362 root:root 0644 - - ZXRjL2lwdGFibGVzL3J1bGVzLXNhdmU= 1757789154 1757789154 root:root 0600 - - @@ -27,11 +36,11 @@ ZXRjL2xvY2FsLmQvLmdpdGlnbm9yZQ== 1757595481 1757595481 root:root 0644 - - ZXRjL2xvY2FsLmQvdGVycmFmb3JtLWh0dHAtYmFja2VuZC5zdGFydA== 1757595926 1757595926 root:root 0755 - - ZXRjL25ldHdvcmsvLmdpdGlnbm9yZQ== 1757596572 1757596572 root:root 0644 - - ZXRjL25ldHdvcmsvaW50ZXJmYWNlcw== 1757759982 1757596330 root:root 0644 - - -ZXRjL3Bhc3N3ZA== 1757771794 1757594202 root:root 0644 - - +ZXRjL3Bhc3N3ZA== 1757873724 1757869538 root:root 0644 - - ZXRjL3BlcmlvZGljL2RhaWx5L2Nyb25qb2ItZGVoeWRyYXRlZA== 1757708520 1757708520 root:root 0777 - - ZXRjL3BlcmlvZGljL2RhaWx5L2Nyb25qb2ItdXBkYXRlLXBhY2thZ2VzLWxpc3Q= 1757708520 1757708520 root:root 0777 - - ZXRjL3BlcmlvZGljL2RhaWx5L2Nyb25qb2Itd2Fybi1naXQtc3RhdHVz 1757708520 1757708520 root:root 0777 - - -ZXRjL3BrZ2xpc3Q= 1757609913 1757609913 root:root 0644 - - +ZXRjL3BrZ2xpc3Q= 1757955745 1757609913 root:root 0644 - - ZXRjL3Jlc29sdi5jb25m 1757611605 1757611605 root:root 0644 - - ZXRjL3J1bmxldmVscy9ib290Ly5naXRpZ25vcmU= 1757769666 1757598667 root:root 0644 - - ZXRjL3J1bmxldmVscy9ib290L3JzeXNsb2c= 1757708520 1757708520 root:root 0777 - - @@ -80,8 +89,8 @@ cm9vdC8uc3NoL2F1dGhvcml6ZWRfa2V5cw== 1757587611 1757587611 root:root 0644 - - c3J2L2RlaHlkcmF0ZWQvLmdpdGtlZXBkaXI= 1757776960 1757776960 root:root 0644 - - ZXRjL2RvYXMuY29uZg== 1728635393 1728635393 root:root 0640 - - ZXRjL2RvYXMuZA== 1757595612 1757595612 root:root 0750 - - -ZXRjL3NoYWRvdw== 1757761290 1757702629 root:shadow 0640 - - -ZXRjL3NoYWRvdy0= 1757702585 1757702585 root:shadow 0640 - - +ZXRjL3NoYWRvdw== 1757873748 1757869538 root:shadow 0640 - - +ZXRjL3NoYWRvdy0= 1757761290 1757702629 root:shadow 0640 - - ZXRjL3N1ZG9lcnM= 1753553353 1753553353 root:root 0440 - - ZXRjL3N1ZG9lcnMuZC9kZWZhdWx0cw== 1757599359 1757599359 root:root 0640 - - ZXRjL3N1ZG9lcnMuZC9yb290LWFjY2Vzcw== 1757600157 1757600157 root:root 0640 - - diff --git a/etc/dehydrated/accounts/.gitignore b/etc/dehydrated/accounts/.gitignore new file mode 100644 index 0000000..8e3c016 --- /dev/null +++ b/etc/dehydrated/accounts/.gitignore @@ -0,0 +1,2 @@ +/*/ +/*.tar diff --git a/etc/dehydrated/accounts/aHR0cHM6Ly9hY21lLXYwMi5hcGkubGV0c2VuY3J5cHQub3JnL2RpcmVjdG9yeQo.tar.gpg b/etc/dehydrated/accounts/aHR0cHM6Ly9hY21lLXYwMi5hcGkubGV0c2VuY3J5cHQub3JnL2RpcmVjdG9yeQo.tar.gpg new file mode 100644 index 0000000..5baf3d9 Binary files /dev/null and b/etc/dehydrated/accounts/aHR0cHM6Ly9hY21lLXYwMi5hcGkubGV0c2VuY3J5cHQub3JnL2RpcmVjdG9yeQo.tar.gpg differ diff --git a/etc/dehydrated/archive/.gitignore b/etc/dehydrated/archive/.gitignore new file mode 100644 index 0000000..33662f5 --- /dev/null +++ b/etc/dehydrated/archive/.gitignore @@ -0,0 +1 @@ +/* diff --git a/etc/dehydrated/certs/.gitignore b/etc/dehydrated/certs/.gitignore new file mode 100644 index 0000000..33662f5 --- /dev/null +++ b/etc/dehydrated/certs/.gitignore @@ -0,0 +1 @@ +/* diff --git a/etc/dehydrated/config b/etc/dehydrated/config new file mode 100644 index 0000000..b43b3ad --- /dev/null +++ b/etc/dehydrated/config @@ -0,0 +1,147 @@ +# This is the main config file for dehydrated. +# This file is looked for in the following locations: +# $SCRIPTDIR/config (next to this script) +# /usr/local/etc/dehydrated/config +# /etc/dehydrated/config +# ${PWD}/config (in current working-directory) + +# Which user should dehydrated run as? This will be implictly enforced when running as root. +# Default: +#DEHYDRATED_USER="" + +# Which group should dehydrated run as? This will be implictly enforced when running as root. +# Default: +#DEHYDRATED_GROUP="" + +# Resolve names to addresses of IP version only, for curl. +# Supported values: 4, 6. +# Default: +#IP_VERSION="" + +# Path to certificate authority. +# Default: https://acme-v02.api.letsencrypt.org/directory +#CA="https://acme-v02.api.letsencrypt.org/directory" +# Use staging server for testing: +#CA="https://acme-staging-v02.api.letsencrypt.org/directory" + +# Path to old certificate authority. +# Set this value to your old CA when upgrading from ACMEv1 to ACMEv2 under a different endpoint. +# If dehydrated detects an account-key for the old CA it will automatically reuse that key +# instead of registering a new one. +# Default: https://acme-v01.api.letsencrypt.org/directory +#OLDCA="https://acme-v01.api.letsencrypt.org/directory" + +# Which challenge should be used? +# Supported values: http-01, dns-01, tls-alpn-01. +# Default: http-01 +#CHALLENGETYPE="http-01" + +# Path to a directory containing additional config files. +# This allows overriding the defaults found in the main configuration file. +# Additional config files in this directory must be named with a '.sh' ending. +# Default: +#CONFIG_D="" + +# Base directory for account key, generated certificates and list of domains. +# Default: $SCRIPTDIR +BASEDIR="/etc/dehydrated" + +# File containing the list of domains for which to request certificates. +# Default: $BASEDIR/domains.txt +DOMAINS_TXT="${BASEDIR}/domains" + +# Directory for per-domain configuration files. +# If not set, per-domain configurations are sourced from each certificates output directory. +# Default: +DOMAINS_D="${BASEDIR}/domains.d" + +# Output directory for generated certificates. +# Default: ${BASEDIR}/certs +#CERTDIR="${BASEDIR}/certs" + +# Output directory for alpn verification certificates. +# Default: ${BASEDIR}/alpn-certs +#ALPNCERTDIR="${BASEDIR}/alpn-certs" + +# Directory for account keys and registration information. +# Default: ${BASEDIR}/accounts +#ACCOUNTDIR="${BASEDIR}/accounts" + +# Output directory for challenge-tokens to be served by webserver, or deployed in $HOOK. +# Default: /var/www/dehydrated +WELLKNOWN="/srv/dehydrated" + +# Default keysize for private keys. +# Default: 4096 +#KEYSIZE="4096" + +# Path to openssl config file. +# To try and figure out the system default, leave this unset. +# Default: +#OPENSSL_CNF="" + +# Path to OpenSSL binary. +# Default: openssl +#OPENSSL="openssl" + +# Extra options passed to the curl binary. +# Default: +#CURL_OPTS="" + +# Program or function called at certain stages of processing. +# BASEDIR and WELLKNOWN variables are exported and can be used in an external program. +# Default: +HOOK="${BASEDIR}/hooks/default" + +# Chain clean_challenge|deploy_challenge arguments together into one hook call per certificate? +# Default: no +#HOOK_CHAIN="no" + +# Minimum days before expiration to automatically renew certificate. +# Default: 30 +#RENEW_DAYS="30" + +# Regenerate private keys instead of just signing new certificates on renewal? +# Default: yes +PRIVATE_KEY_RENEW="no" + +# Create an extra private key for rollover? +# Default: no +#PRIVATE_KEY_ROLLOVER="no" + +# Which public key algorithm should be used? +# Supported: rsa, prime256v1, secp384r1. +# Default: rsa +KEY_ALGO="secp384r1" + +# E-mail to use during the registration. +# Default: +CONTACT_EMAIL="sysadmin@slackware.uk" + +# Lockfile location, to prevent concurrent execution. +# Default: $BASEDIR/lock +LOCKFILE="/run/dehydrated.lock" + +# Option to add CSR-flag indicating OCSP stapling to be mandatory. +# Default: no +#OCSP_MUST_STAPLE="no" + +# Fetch OCSP responses. +# Default: no +#OCSP_FETCH="no" + +# OCSP refresh interval, in days. +# Default: 5 +#OCSP_DAYS="5" + +# Issuer chain cache directory. +# Default: $BASEDIR/chains +#CHAINCACHE="${BASEDIR}/chains" + +# Automatic cleanup? +# Default: no +AUTO_CLEANUP="yes" + +# ACME API version. +# Default: auto +#API=auto diff --git a/etc/dehydrated/domains b/etc/dehydrated/domains new file mode 100644 index 0000000..c9d26dd --- /dev/null +++ b/etc/dehydrated/domains @@ -0,0 +1,32 @@ +# Create certificate for 'example.org' with an alternative name of +# 'www.example.org'. It will be stored in the directory ${CERT_DIR}/example.org +#example.org www.example.org + +# Create certificate for 'example.com' with alternative names of +# 'www.example.com' & 'wiki.example.com'. It will be stored in the directory +# ${CERT_DIR}/example.com +#example.com www.example.com wiki.example.com + +# Using the alias 'certalias' create certificate for 'example.net' with +# alternate name 'www.example.net' and store it in the directory +# ${CERTDIR}/certalias +#example.net www.example.net > certalias + +# Using the alias 'service_example_com' create a wildcard certificate for +# '*.service.example.com' and store it in the directory +# ${CERTDIR}/service_example_com +# NOTE: It is NOT a certificate for 'service.example.com' +#*.service.example.com > service_example_com + +# Using the alias 'star_service_example_org' create a wildcard certificate for +# '*.service.example.org' with an alternative name of `service.example.org' +# and store it in the directory ${CERTDIR}/star_service_example_org +# NOTE: It is a certificate for 'service.example.org' +#*.service.example.org service.example.org > star_service_example_org + +# Create a certificate for 'service.example.net' with an alternative name of +# '*.service.example.net' (which is a wildcard domain) and store it in the +# directory ${CERTDIR}/service.example.net +#service.example.net *.service.example.net + +core.slackware.uk.net diff --git a/etc/dehydrated/domains.d/_example_ b/etc/dehydrated/domains.d/_example_ new file mode 100644 index 0000000..941659e --- /dev/null +++ b/etc/dehydrated/domains.d/_example_ @@ -0,0 +1,48 @@ +# The settings in this file can be used to override those in the global config file in /etc/dehydrated + +# Which challenge should be used? +# Supported values: http-01, dns-01, tls-alpn-01. +# Default: http-01 +#CHALLENGETYPE="http-01" + +# Default keysize for private keys. +# Default: 4096 +#KEYSIZE="4096" + +# Program or function called at certain stages of processing. +# BASEDIR and WELLKNOWN variables are exported and can be used in an external program. +# Default: +#HOOK="" + +# Chain clean_challenge|deploy_challenge arguments together into one hook call per certificate? +# Default: no +#HOOK_CHAIN="no" + +# Minimum days before expiration to automatically renew certificate. +# Default: 30 +#RENEW_DAYS="30" + +# Regenerate private keys instead of just signing new certificates on renewal? +# Default: yes +#PRIVATE_KEY_RENEW="yes" + +# Create an extra private key for rollover? +# Default: no +#PRIVATE_KEY_ROLLOVER="no" + +# Which public key algorithm should be used? +# Supported: rsa, prime256v1, secp384r1. +# Default: rsa +#KEY_ALGO="rsa" + +# Option to add CSR-flag indicating OCSP stapling to be mandatory. +# Default: no +#OCSP_MUST_STAPLE="no" + +# Fetch OCSP responses. +# Default: no +#OCSP_FETCH="no" + +# OCSP refresh interval, in days. +# Default: 5 +#OCSP_DAYS="5" diff --git a/etc/dehydrated/domains.d/core.slackware.uk.net b/etc/dehydrated/domains.d/core.slackware.uk.net new file mode 100644 index 0000000..941659e --- /dev/null +++ b/etc/dehydrated/domains.d/core.slackware.uk.net @@ -0,0 +1,48 @@ +# The settings in this file can be used to override those in the global config file in /etc/dehydrated + +# Which challenge should be used? +# Supported values: http-01, dns-01, tls-alpn-01. +# Default: http-01 +#CHALLENGETYPE="http-01" + +# Default keysize for private keys. +# Default: 4096 +#KEYSIZE="4096" + +# Program or function called at certain stages of processing. +# BASEDIR and WELLKNOWN variables are exported and can be used in an external program. +# Default: +#HOOK="" + +# Chain clean_challenge|deploy_challenge arguments together into one hook call per certificate? +# Default: no +#HOOK_CHAIN="no" + +# Minimum days before expiration to automatically renew certificate. +# Default: 30 +#RENEW_DAYS="30" + +# Regenerate private keys instead of just signing new certificates on renewal? +# Default: yes +#PRIVATE_KEY_RENEW="yes" + +# Create an extra private key for rollover? +# Default: no +#PRIVATE_KEY_ROLLOVER="no" + +# Which public key algorithm should be used? +# Supported: rsa, prime256v1, secp384r1. +# Default: rsa +#KEY_ALGO="rsa" + +# Option to add CSR-flag indicating OCSP stapling to be mandatory. +# Default: no +#OCSP_MUST_STAPLE="no" + +# Fetch OCSP responses. +# Default: no +#OCSP_FETCH="no" + +# OCSP refresh interval, in days. +# Default: 5 +#OCSP_DAYS="5" diff --git a/etc/dehydrated/hooks/default b/etc/dehydrated/hooks/default new file mode 100755 index 0000000..9b63c37 --- /dev/null +++ b/etc/dehydrated/hooks/default @@ -0,0 +1,421 @@ +#!/usr/bin/env bash +# This file contains the default hook functions for dehydrated - these functions will be used when there is no overriding certificate specific hooks file. +# All but startup_hook and ext_hook can be overridden by a hooks script on a per certificate basis. +# +# shellcheck disable=SC2034,SC2317 + +# Configuration. +# Where the copies of the current certificates/keys should be placed. Comment for no copying. +CERTSDIR="/etc/certificates" +# The syslog facility and tag to use. +FACILITY="local3" +TAG="dehydrated" +# Where from/to to send emails. +EMAIL_FROM="Systems' Administrator " +EMAIL_TO=("Systems' Administrator ") + +# Get the system ID. +# shellcheck disable=SC2046 +declare SYSTEM_$(grep '^ID=' /etc/os-release 2>/dev/null) + +# Write a message to syslog, and send a copy via email. +notify() { + local LOG_PREFIX="${LOG_PREFIX:-Certificate renewal} $1" PRIORITY + + [[ -z "$1" ]] && return 1 + + # Select the syslog priority level. + case "$1" in + 'error') PRIORITY="err" ;; + 'warning') PRIORITY="warn" ;; + *) PRIORITY="info" ;; + esac + shift + + # Log the message to syslog + if [[ "$ID" == "alpine" ]]; then + # BusyBox logger on Alpine's is missing the --id option. + printf "%s\\n" "$LOG_PREFIX:" "$@" "EOX" | logger -p "$FACILITY.$PRIORITY" -t "$TAG" >/dev/null 2>&1 + else + printf "%s\\n" "$LOG_PREFIX:" "$@" "EOX" | logger --id="$$" -p "$FACILITY.$PRIORITY" -t "$TAG" >/dev/null 2>&1 + fi + + # Email the notification. + printf "%s\\n" "$@" | mail -r "$EMAIL_FROM" -s "$LOG_PREFIX" "${EMAIL_TO[@]}" >/dev/null 2>&1 + + return 0 +} + +# Service configurations (used at startup/shutdown). +services() { + local DAEMON ERR=0 LOG_PREFIX="Dehydrated configuration" PIDFILE RCFILE + + # Select the service configuration based on the distribution. + # RCFILE_ is required for any service. + # Either DAEMON_ or PIDFILE_, or both is required for any service. + if [[ "$ID" == "slackware" ]]; then + # HTTP daemon selection. + if [[ -x "/etc/rc.d/rc.httpd" ]]; then + RCFILE_HTTPD="/etc/rc.d/rc.httpd" + DAEMON_HTTPD="httpd" + PIDFILE_HTTPD="/run/httpd.pid" + elif [[ -x "/etc/rc.d/rc.thttpd" ]]; then + RCFILE_HTTPD="/etc/rc.d/rc.thttpd" + DAEMON_HTTPD="thttpd" + PIDFILE_HTTPD="/run/thttpd.pid" + fi + # FTP daemon selection. + if [[ -x "/etc/rc.d/rc.proftpd" ]]; then + RCFILE_FTPD="/etc/rc.d/rc.proftpd" + DAEMON_FTPD="proftpd" + PIDFILE_FTPD="/run/proftpd.pid" + fi + # SMTP daemon selection. + if [[ -x "/etc/rc.d/rc.exim" ]]; then + RCFILE_SMTPD="/etc/rc.d/rc.exim" + DAEMON_SMTPD="exim" + PIDFILE_SMTPD="/run/exim.pid" + fi + elif [[ "$ID" == "void" ]]; then + # HTTP daemon selection. + # thttpd on Void doesn't have a directly callable rc script, so can't be supported. + if [[ -x "/usr/sbin/apachectl" ]]; then + RCFILE_HTTPD="/usr/sbin/apachectl" + DAEMON_HTTPD="httpd" + PIDFILE_HTTPD="/run/httpd/httpd.pid" + fi + elif [[ "$ID" == "alpine" ]]; then + # HTTP daemon selection. + if [[ -x "/etc/init.d/apache2" ]]; then + RCFILE_HTTPD="/etc/init.d/apache2" + DAEMON_HTTPD="httpd" + PIDFILE_HTTPD="/run/apache2/httpd.pid" + elif [[ -x "/etc/init.d/thttpd" ]]; then + RCFILE_HTTPD="/etc/init.d/thttpd" + DAEMON_HTTPD="thttpd" + PIDFILE_HTTPD="/run/thttpd.pid" + fi + # Samba daemon selection. + if [[ -x "/etc/init.d/samba" ]]; then + SAMBA_RCFILE="/etc/init.d/samba" + SAMBA_SERVICENAME="samba" + SAMBA_PIDFILE="/run/samba.pid" + fi + fi + + # Sanity check settings. + [[ -z "$RCFILE_HTTPD" ]] && notify "warning" "No configuration settings for an HTTP daemon - no start/restart of HTTP daemon is possible -- check configuration" + for RCFILE in "${!RCFILE_@}"; do + DAEMON="DAEMON_${RCFILE#RCFILE_}" + PIDFILE="PIDFILE_${RCFILE#RCFILE_}" + [[ -n "${!RCFILE}" ]] && [[ -z "${!DAEMON}" ]] && [[ -z "${!PIDFILE}" ]] && [[ ! -v "SERVICES_ERROR_REPORTED" ]] && notify "error" "'$RCFILE' is set, but neither '$DAEMON' nor '$PIDFILE' is set - at least one setting is required -- aborting" && ERR=1 + done + + # Don't report configuration errors more than once. + ((ERR == 1)) && SERVICES_ERROR_REPORTED=1 && return 1 + + return 0 +} + +deploy_challenge() { + local DOMAIN="$1" TOKEN_FILENAME="$2" TOKEN_VALUE="$3" + + # This hook is called once for every domain that needs to be + # validated, including any alternative names you may have listed. + # Parameters: + # DOMAIN - The domain name (CN or subject alternative name) being validated. + # TOKEN_FILENAME - The name of the file containing the token to be served for HTTP validation + # Should be served by your web server as /.well-known/acme-challenge/${TOKEN_FILENAME}. + # TOKEN_VALUE - The token value that needs to be served for validation. + # For DNS validation, this is what you want to put in the _acme-challenge TXT record. + # For HTTP validation it is the value that is expected be found in the $TOKEN_FILENAME file. + + # Simple example: Use nsupdate with local named + # printf 'server 127.0.0.1\nupdate add _acme-challenge.%s 300 IN TXT "%s"\nsend\n' "$DOMAIN" "$TOKEN_VALUE" | nsupdate -k /var/run/named/session.key + + return 0 +} + + +clean_challenge() { + local DOMAIN="$1" TOKEN_FILENAME="$2" TOKEN_VALUE="$3" + + # This hook is called after attempting to validate each domain, whether or not validation was successful. Here you can delete files or DNS records that are no longer needed. + # The parameters are the same as for deploy_challenge. + + # Simple example: Use nsupdate with local named + # printf 'server 127.0.0.1\nupdate delete _acme-challenge.%s TXT "%s"\nsend\n' "$DOMAIN" "$TOKEN_VALUE" | nsupdate -k /var/run/named/session.key + + return 0 +} + + +sync_cert() { + local KEYFILE="$1" CERTFILE="$2" FULLCHAINFILE="$3" CHAINFILE="$4" REQUESTFILE="$5" + + # This hook is called after the certificates have been created but before they are symlinked. + # This allows you to sync the files to disk to prevent creating a symlink to empty files on unexpected system crashes. + # This hook is not intended to be used for further processing of certificate files; see deploy_cert for that. + # Parameters: + # KEYFILE - The path of the file containing the private key. + # CERTFILE - The path of the file containing the signed certificate. + # FULLCHAINFILE - The path of the file containing the full certificate chain. + # CHAINFILE - The path of the file containing the intermediate certificate(s). + # REQUESTFILE - The path of the file containing the certificate signing request. + + # Simple example: sync the files before symlinking them + # sync "$KEYFILE" "$CERTFILE" "$FULLCHAINFILE" "$CHAINFILE" "$REQUESTFILE" + + return 0 +} + + +deploy_cert() { + local DOMAIN="$1" KEYFILE="$2" CERTFILE="$3" FULLCHAINFILE="$4" CHAINFILE="$5" TIMESTAMP="$6" + + # This hook is called once for each certificate that has been produced. + # Here you might, for instance, copy your new certificates to service-specific locations and reload the service. + # Parameters: + # DOMAIN - The primary domain name, i.e. the certificate common name (CN). + # KEYFILE - The path of the file containing the private key. + # CERTFILE - The path of the file containing the signed certificate. + # FULLCHAINFILE - The path of the file containing the full certificate chain. + # CHAINFILE - The path of the file containing the intermediate certificate(s). + # TIMESTAMP - Timestamp when the specified certificate was created. + + local FILE LOG_PREFIX="Certificate deployment" + + # Only copy the certificate if there's a CERTSDIR setting. + [[ -n "$CERTSDIR" ]] && { + # If any of the destination files are symlinks, bail out - we don't want to clobber something. + for FILE in "$CERTSDIR/${DOMAIN}_"{cert,key,chain,fullchain}.pem; do + [[ -e "$FILE" ]] && [[ -L "$FILE" ]] && { + notify "error" "Will not copy to symlink '$FILE' during '$DOMAIN' certificate deployment" + # Return 0 so that dehydrated doesn't stop - there may be some more certificates to renew. + return 0 + } + done + + # The first time through this will create the files readable by root only, but better to err on the side of caution. + # Subsequent runs will retain whatever permissions were set by the admin after the first run. + umask 066 + # shellcheck disable=SC2015 + cat "$CERTFILE" >"$CERTSDIR/${DOMAIN}_cert.pem" && cat "$KEYFILE" >"$CERTSDIR/${DOMAIN}_key.pem" && cat "$CHAINFILE" >"$CERTSDIR/${DOMAIN}_chain.pem" && cat "$FULLCHAINFILE" >"$CERTSDIR/${DOMAIN}_fullchain.pem" || { + notify "error" "Failed to copy certificates/key to '$CERTSDIR' during '$DOMAIN' certificate deployment" + # Return 0 so that dehydrated doesn't stop - there may be some more certificates to renew. + return 0 + } + } + + # Set a marker (used in the exit_hook function) to signal that services should be reloaded at the end of deployments. + touch /run/dehydrated-reload-marker || { + notify "warning" "Failed to create reload marker during '$DOMAIN' certificate deployment - reloading services manually may be required -- check server" + # Return 0 so that dehydrated doesn't stop - there may be some more certificates to renew. + return 0 + } + + # Notify the sysadmin of the sucessful renewal. + notify "information" "Sucessful renewal and deployment of certificate/key for '$DOMAIN'" + + return 0 +} + + +deploy_ocsp() { + local DOMAIN="$1" OCSPFILE="$2" TIMESTAMP="$3" + + # This hook is called once for each updated ocsp stapling file that has been produced. + # Here you might, for instance, copy your new ocsp stapling files to service-specific locations and reload the service. + # Parameters: + # DOMAIN - The primary domain name, i.e. the certificate common name (CN). + # OCSPFILE - The path of the ocsp stapling file. + # TIMESTAMP - Timestamp when the specified ocsp stapling file was created. + + # Simple example: Copy file to nginx config + # cp "$OCSPFILE" /etc/nginx/ssl/; chown -R nginx: /etc/nginx/ssl + # systemctl reload nginx + + return 0 +} + + +unchanged_cert() { + local DOMAIN="$1" KEYFILE="$2" CERTFILE="$3" FULLCHAINFILE="$4" CHAINFILE="$5" + + # This hook is called once for each certificate that is still valid and therefore wasn't reissued. + # Parameters: + # DOMAIN - The primary domain name, i.e. the certificate common name (CN). + # KEYFILE - The path of the file containing the private key. + # CERTFILE - The path of the file containing the signed certificate. + # FULLCHAINFILE - The path of the file containing the full certificate chain. + # CHAINFILE - The path of the file containing the intermediate certificate(s). + + return 0 +} + + +invalid_challenge() { + local DOMAIN="$1" RESPONSE="$2" + + # This hook is called if the challenge response has failed, so domain owners can be aware and act accordingly. + # Parameters: + # DOMAIN - The primary domain name, i.e. the certificate common name (CN). + # RESPONSE - The response that the verification server returned + + # Notify the sysadmin. + notify "error" "Validation of '$DOMAIN' failed:" "$RESPONSE" + + return 0 +} + + +request_failure() { + local STATUSCODE="$1" REASON="$2" REQTYPE="$3" HEADERS="$4" + + # This hook is called when an HTTP request fails (e.g., when the ACME server is busy, returns an error, etc). + # It will be called upon any response code that does not start with '2'. Useful to alert admins about problems with requests. + # Parameters: + # STATUSCODE - The HTML status code that originated the error. + # REASON - The specified reason for the error. + # REQTYPE - The kind of request that was made (GET, POST...) + + # Notify the sysadmin. + notify "error" "HTTP $REQTYPE request failed for '$DOMAIN' with code '$STATUSCODE'" "Reason: $REASON" "Headers:" "$HEADERS" + + return 0 +} + + +generate_csr() { + local DOMAIN="$1" CERTDIR="$2" ALTNAMES="$3" + + # This hook is called before any certificate signing operation takes place. + # It can be used to generate or fetch a certificate signing request with external tools. + # The output should be just the cerificate signing request formatted as PEM. + # Parameters: + # DOMAIN - The primary domain as specified in domains.txt. + # This does not need to match with the domains in the CSR, it's basically just the directory name. + # CERTDIR - Certificate output directory for this particular certificate. + # Can be used for storing additional files. + # ALTNAMES - All domain names for the current certificate as specified in domains.txt. + # Again, this doesn't need to match with the CSR, it's just there for convenience. + + # Simple example: Look for pre-generated CSRs + # if [ -e "$CERTDIR/pre-generated.csr" ]; then + # cat "$CERTDIR/pre-generated.csr" + # fi + + return 0 +} + + +startup_hook() { + # This hook is called before the cron command to do some initial tasks (e.g. starting a webserver). + + local LOG_PREFIX="Dehydrated startup" + + # Read services configuration. + services || return 1 + + # If an HTTP daemon rc script is available and the service is not already running, start it. + [[ -n "$RCFILE_HTTPD" ]] && { + if ! pgrep -c ${PIDFILE_HTTPD:+-F "$PIDFILE_HTTPD"} "$DAEMON_HTTPD" >/dev/null 2>&1; then + "$RCFILE_HTTPD" start >/dev/null 2>&1 + sleep 5 + if pgrep -c ${PIDFILE_HTTPD:+-F "$PIDFILE_HTTPD"} "$DAEMON_HTTPD" >/dev/null 2>&1; then + # Set a marker (used in exit_hook()) to signal that the HTTP daemon should be stopped at the end of deployments. + touch /run/dehydrated-http-daemon-stop-marker 2>/dev/null || notify "warning" "Failed to create HTTP daemon stop marker - HTTP daemon will be left running -- check server" + else + notify "error" "Failure of '$RCFILE_HTTPD' to start HTTP daemon -- aborting" + return 1 + fi + else + notify "warning" "'$DAEMON_HTTPD' is already running - will not be shutdown at exit -- check server" + fi + } + + # Add firewall rules to allow HTTP traffic so the nonce can be validated. + { iptables -N dehydrated && ip6tables -N dehydrated && iptables -I INPUT 1 -j dehydrated && ip6tables -I INPUT 1 -j dehydrated && iptables -I dehydrated 1 -p tcp --syn --dport 80 -m conntrack --ctstate NEW -j ACCEPT && ip6tables -I dehydrated 1 -p tcp --syn --dport 80 -m conntrack --ctstate NEW -j ACCEPT; } >/dev/null 2>&1 || { + notify "error" "Failed to insert firewall rules to allow nonce validation -- aborting" + return 1 + } + + return 0 +} + + +exit_hook() { + local ERROR="$1" + + # This hook is called at the end of the cron command and can be used to do some final (cleanup or other) tasks. + # Parameters: + # ERROR - Contains error message if dehydrated exits with error. + + local DAEMON ERR=0 LOG_PREFIX="Dehydrated shutdown" PIDFILE RCFILE TIMEOUT=30 + + # Read services configuration. + services || return 1 + + # Delete firewall rules that was added to allow HTTP traffic. + { iptables -D dehydrated -p tcp --syn --dport 80 -m conntrack --ctstate NEW -j ACCEPT && ip6tables -D dehydrated -p tcp --syn --dport 80 -m conntrack --ctstate NEW -j ACCEPT && iptables -D INPUT -j dehydrated && ip6tables -D INPUT -j dehydrated && iptables -X dehydrated && ip6tables -X dehydrated; } >/dev/null 2>&1 || notify "warning" "Failed to remove firewall rules that were added to allow HTTP traffic -- check server" + + # If the reload marker was set, restart services. + [[ -e /run/dehydrated-reload-marker ]] && { + for RCFILE in "${!RCFILE_@}"; do + DAEMON="DAEMON_${RCFILE#RCFILE_}" + PIDFILE="PIDFILE_${RCFILE#RCFILE_}" + # If the HTTP daemon is going to be shut down, there's no need to restart it. + [[ "$RCFILE" == "RCFILE_HTTPD" ]] && [[ ! -e /run/dehydrated-http-daemon-stop-marker ]] && continue + # Restart the service. + "${!RCFILE}" restart >/dev/null 2>&1 || notify "warning" "Failed to restart service '${!DAEMON}' -- check server" + sleep "$TIMEOUT" + pgrep -c ${PIDFILE:+-F "${!PIDFILE}"} "${!DAEMON}" >/dev/null 2>&1 || { + notice "warning" "Service '${!DAEMON}' exited unexpectedly - trying to start again" + "${!RCFILE}" start >/dev/null 2>&1 || notify "warning" "Failed to start service '${!DAEMON}' -- check server" + sleep "$TIMEOUT" + pgrep -c ${PIDFILE:+-F "${!PIDFILE}"} "${!DAEMON}" >/dev/null 2>&1 || { + notify "warning" "Service '${!DAEMON}' failed to restart correctly -- check server" + ERR=1 + } + } + done + } + + # Remove the reload marker if all services restarted without issue. Keep the marker if any failed. + ((ERR == 0)) && { rm -f /run/dehydrated-reload-marker 2>/dev/null || notify "warning" "Failed to remove services reload marker -- check server"; } + + # If an HTTP daemon was started by dehydrated, stop it now. + ERR=0 + [[ -e /run/dehydrated-http-daemon-stop-marker ]] && { + pgrep -c ${PIDFILE_HTTPD:+-F "$PIDFILE_HTTPD"} "$DAEMON_HTTPD" >/dev/null 2>&1 && { + "$RCFILE_HTTPD" stop >/dev/null 2>&1 || notify "warning" "Failed to gracefully stop service '$DAEMON_HTTPD' -- check server" + sleep "$TIMEOUT" + pgrep -c ${PIDFILE_HTTPD:+-F "$PIDFILE_HTTPD"} "$DAEMON_HTTPD" >/dev/null 2>&1 && { + pkill -TERM ${PIDFILE_HTTPD:+-F "$PIDFILE_HTTPD"} "$DAEMON_HTTPD" >/dev/null 2>&1 || notify "warning" "Failed to -SIGTERM service '$DAEMON_HTTPD' -- check server" + sleep "$TIMEOUT" + pgrep -c ${PIDFILE_HTTPD:+-F "$PIDFILE_HTTPD"} "$DAEMON_HTTPD" >/dev/null 2>&1 && { + pkill -KILL ${PIDFILE_HTTPD:+-F "$PIDFILE_HTTPD"} "$DAEMON_HTTPD" >/dev/null 2>&1 || notify "warning" "Failed to -SIGKILL service '$DAEMON_HTTPD' -- check server" + sleep 5 + } + } + pgrep -c ${PIDFILE_HTTPD:+-F "$PIDFILE_HTTPD"} "$DAEMON_HTTPD" >/dev/null 2>&1 && notify "warning" "Failed to stop HTTP daemon that dehydrated started" && ERR=1 + } + } + + # If the HTTP daemon was stopped correctly, remove the stop marker. + ((ERR == 0)) && { rm -f /run/dehydrated-http-daemon-stop-marker 2>/dev/null || notify "warning" "Failed to remove HTTP daemon stop marker -- check server"; } + + return 0 +} + +# Run the correct function. +HANDLER="$1" +shift +if declare -pF "$HANDLER" >/dev/null 2>&1; then + "$HANDLER" "$@" + exit "$?" +else + LOG_PREFIX="Dehydrated configuration" + notify "error" "Hook script called with undefined function name '$HANDLER' -- check configuration" + exit 1 +fi