Dehydrated updates for Devuan.
This commit is contained in:
parent
2365db7351
commit
67a9bdd9eb
8 changed files with 455 additions and 361 deletions
|
|
@ -1,136 +1,189 @@
|
|||
#!/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.
|
||||
# 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="\"Server: ${HOSTNAME%%.*}\" <noreply@slackware.uk>"
|
||||
# The syslog facility and tag to use. Comment for no sysloging.
|
||||
SYSLOG_FACILITY="local1"
|
||||
SYSLOG_TAG="dehydrated-hooks"
|
||||
# Where from/to to send emails. Comment for no emailing.
|
||||
EMAIL_FROM="\"Server: ${HOSTNAME%%.*}\" <noreply@slackware.uk.net>"
|
||||
EMAIL_TO=("Systems' Administrator <sysadmin@slackware.uk>")
|
||||
|
||||
# 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.
|
||||
# Write a message to syslog and/or send via email.
|
||||
notify() {
|
||||
local LOG_PREFIX="${LOG_PREFIX:-Certificate renewal} $1" PRIORITY
|
||||
# Parameters:
|
||||
# $1 Log entry type (currently one of: error, warning or info).
|
||||
# $2... The text of the log entry.
|
||||
|
||||
local PREFIX PRIORITY
|
||||
|
||||
[[ -z "$1" ]] && return 1
|
||||
|
||||
# Select the syslog priority level.
|
||||
case "$1" in
|
||||
'error') PRIORITY="err" ;;
|
||||
'warning') PRIORITY="warn" ;;
|
||||
*) PRIORITY="info" ;;
|
||||
'error')
|
||||
PREFIX="${LOG_PREFIX:-Certificate renewal} $1"
|
||||
PRIORITY="err"
|
||||
shift
|
||||
;;
|
||||
'info')
|
||||
PREFIX="${LOG_PREFIX:-Certificate renewal} $1"
|
||||
PRIORITY="info"
|
||||
shift
|
||||
;;
|
||||
'warning')
|
||||
PREFIX="${LOG_PREFIX:-Certificate renewal} $1"
|
||||
PRIORITY="warn"
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
LOG_PREFIX="Dehydrated hooks' coding"
|
||||
notify "error" "Invalid or no log entry severity specified - using 'error'"
|
||||
PREFIX="${LOG_PREFIX:-Certificate renewal} error"
|
||||
PRIORITY="err"
|
||||
;;
|
||||
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
|
||||
# Log the message to syslog.
|
||||
[[ -n "$SYSLOG_FACILITY" ]] && [[ -n "$SYSLOG_TAG" ]] && {
|
||||
if [[ "$SYSTEM_ID" == "alpine" ]]; then
|
||||
# BusyBox logger on Alpine's is missing the --id option.
|
||||
printf "%s\\n" "$PREFIX:" "$@" "EOL" | logger -p "$SYSLOG_FACILITY.$PRIORITY" -t "$SYSLOG_TAG" >/dev/null 2>&1
|
||||
else
|
||||
printf "%s\\n" "$PREFIX:" "$@" "EOL" | logger --id="$$" -p "$SYSLOG_FACILITY.$PRIORITY" -t "$SYSLOG_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
|
||||
[[ -n "$EMAIL_FROM" ]] && [[ -n "${EMAIL_TO[*]}" ]] && {
|
||||
printf "%s\\n" "$@" | mail -r "$EMAIL_FROM" -s "$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 SANITY="$1"
|
||||
# Parameters:
|
||||
# $1 Whether to sanity check configuration. '1' == sanity check.
|
||||
|
||||
# Select the service configuration based on the distribution.
|
||||
# RCFILE_<service> is required for any service.
|
||||
# Either DAEMON_<service> or PIDFILE_<service>, or both is required for any service.
|
||||
if [[ "$SYSTEM_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 [[ "$SYSTEM_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 [[ "$SYSTEM_ID" == "alpine" ]]; then
|
||||
# HTTP daemon selection.
|
||||
local LOG_PREFIX="Dehydrated hooks' configuration" SERVICE
|
||||
declare -g -A SERVICE_CTL SERVICE_DAEMON SERVICE_PIDFILE
|
||||
|
||||
# Select the service configurations based on the distribution.
|
||||
# SERVICE_CTL['<service>'] is required, and either SERVICE_DAEMON['<service>'] or SERVICE_PIDFILE['<service>'], or both is required for any service.
|
||||
# An 'httpd' service must be defined for certificate renewal to work.
|
||||
if [[ "$SYSTEM_ID" == "alpine" ]]; then
|
||||
# HTTP daemon.
|
||||
if [[ -x "/etc/init.d/apache2" ]]; then
|
||||
RCFILE_HTTPD="/etc/init.d/apache2"
|
||||
DAEMON_HTTPD="httpd"
|
||||
PIDFILE_HTTPD="/run/apache2/httpd.pid"
|
||||
SERVICE_CTL['httpd']="/etc/init.d/apache2"
|
||||
SERVICE_DAEMON['httpd']="httpd"
|
||||
SERVICE_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"
|
||||
SERVICE_CTL['httpd']="/etc/init.d/thttpd"
|
||||
SERVICE_DAEMON['httpd']="thttpd"
|
||||
SERVICE_PIDFILE['httpd']="/run/thttpd.pid"
|
||||
fi
|
||||
# Samba daemon selection.
|
||||
if [[ -x "/etc/init.d/samba" ]]; then
|
||||
# FIXME:
|
||||
# RCFILE_SAMBA="/etc/init.d/samba"
|
||||
DAEMON_SAMBA="samba"
|
||||
PIDFILE_SAMBA="/run/samba.pid"
|
||||
SERVICE_CTL['samba']="/etc/init.d/samba"
|
||||
SERVICE_DAEMON['samba']="samba"
|
||||
SERVICE_PIDFILE['samba']="/run/samba.pid"
|
||||
fi
|
||||
elif [[ "$SYSTEM_ID" =~ ^(debian|devuan)$ ]]; then
|
||||
# HTTP daemon.
|
||||
if [[ -x "/usr/sbin/apachectl" ]]; then
|
||||
SERVICE_CTL['httpd']="/usr/sbin/apachectl"
|
||||
SERVICE_DAEMON['httpd']="apache2"
|
||||
SERVICE_PIDFILE['httpd']="/run/apache2/apache2.pid"
|
||||
fi
|
||||
# Samba daemon.
|
||||
if [[ -x "/etc/init.d/samba" ]]; then
|
||||
SERVICE_CTL['samba']="/etc/init.d/samba"
|
||||
SERVICE_DAEMON['samba']="samba"
|
||||
SERVICE_PIDFILE['samba']="/run/samba/samba.pid"
|
||||
fi
|
||||
elif [[ "$SYSTEM_ID" == "slackware" ]]; then
|
||||
# HTTP daemon.
|
||||
if [[ -x "/etc/rc.d/rc.httpd" ]]; then
|
||||
SERVICE_CTL['httpd']="/etc/rc.d/rc.httpd"
|
||||
SERVICE_DAEMON['httpd']="httpd"
|
||||
SERVICE_PIDFILE['httpd']="/run/httpd.pid"
|
||||
elif [[ -x "/etc/rc.d/rc.thttpd" ]]; then
|
||||
SERVICE_CTL['httpd']="/etc/rc.d/rc.thttpd"
|
||||
SERVICE_DAEMON['httpd']="thttpd"
|
||||
SERVICE_PIDFILE['httpd']="/run/thttpd.pid"
|
||||
fi
|
||||
# FTP daemon.
|
||||
if [[ -x "/etc/rc.d/rc.proftpd" ]]; then
|
||||
SERVICE_CTL['ftpd']="/etc/rc.d/rc.proftpd"
|
||||
SERVICE_DAEMON['ftpd']="proftpd"
|
||||
SERVICE_PIDFILE['ftpd']="/run/proftpd.pid"
|
||||
fi
|
||||
# SMTP daemon.
|
||||
if [[ -x "/etc/rc.d/rc.exim" ]]; then
|
||||
SERVICE_CTL['smtpd']="/etc/rc.d/rc.exim"
|
||||
SERVICE_DAEMON['smtpd']="exim"
|
||||
SERVICE_PIDFILE['smtpd']="/run/exim.pid"
|
||||
fi
|
||||
elif [[ "$SYSTEM_ID" == "void" ]]; then
|
||||
# HTTP daemon selection.
|
||||
# Note: thttpd on Void doesn't have a directly callable rc script, so can't be supported.
|
||||
if [[ -x "/usr/sbin/apachectl" ]]; then
|
||||
SERVICE_CTL['httpd']="/usr/sbin/apachectl"
|
||||
SERVICE_DAEMON['httpd']="httpd"
|
||||
SERVICE_PIDFILE['httpd']="/run/httpd/httpd.pid"
|
||||
fi
|
||||
else
|
||||
notify "error" "'$SYSTEM_ID' is not a supported platform for hooks"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Sanity check settings.
|
||||
((SANITY == 1)) && {
|
||||
[[ -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}" ]] && notify "error" "'$RCFILE' is set, but neither '$DAEMON' nor '$PIDFILE' is set - at least one setting is required -- aborting" && ERR=1
|
||||
(( $1 == 1 )) && {
|
||||
local IFS=$'\n'
|
||||
for SERVICE in $(printf "%s\\n" "${!SERVICE_CTL[@]}" "${!SERVICE_DAEMON[@]}" "${!SERVICE_PIDFILE[@]}" | sort | uniq); do
|
||||
if [[ -z "${SERVICE_CTL[\"$SERVICE\"]}" ]]; then
|
||||
if [[ "$SERVICE" == "httpd" ]]; then
|
||||
notify "warning" "'SERVICE_CTL' is not set for service 'httpd' - HTTP based validation, if used, not possible" "Comment all 'httpd' SERVICE_* settings to silence this warning"
|
||||
else
|
||||
notify "warning" "'SERVICE_CTL' is not set for service '$SERVICE'"
|
||||
fi
|
||||
unset "SERVICE_CTL['$SERVICE']" "SERVICE_DAEMON['$SERVICE']" "SERVICE_PIDFILE['$SERVICE']"
|
||||
continue
|
||||
else
|
||||
[[ -z "${SERVICE_DAEMON[\"$SERVICE\"]}" ]] && [[ -z "${SERVICE_PIDFILE[\"$SERVICE\"]}" ]] && {
|
||||
notify "warning" "neither 'SERVICE_DAEMON' nor 'SERVICE_PIDFILE' is set for service '$SERVICE' - at least one setting is required"
|
||||
unset "SERVICE_CTL['$SERVICE']" "SERVICE_DAEMON['$SERVICE']" "SERVICE_PIDFILE['$SERVICE']"
|
||||
continue
|
||||
}
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
((ERR == 1)) && return 1
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Called once for every domain that needs to be validated, including any alternative names listed.
|
||||
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.
|
||||
# $1 (DOMAIN) The domain name (CN or subject alternative name) being validated.
|
||||
# $2 (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}.
|
||||
# $3 (TOKEN_VALUE) The token value that needs to be served for validation.
|
||||
# For HTTP validation it is the value that is expected be found in the $TOKEN_FILENAME file.
|
||||
# For DNS validation, this is what you want to put in the _acme-challenge TXT record.
|
||||
|
||||
local DOMAIN="$1" TOKEN_FILENAME="$2" TOKEN_VALUE="$3"
|
||||
|
||||
# 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
|
||||
|
|
@ -139,11 +192,18 @@ deploy_challenge() {
|
|||
}
|
||||
|
||||
|
||||
# Called after attempting to validate each domain, whether or not validation was successful.
|
||||
# Can be used to delete files or DNS records that are no longer needed.
|
||||
clean_challenge() {
|
||||
local DOMAIN="$1" TOKEN_FILENAME="$2" TOKEN_VALUE="$3"
|
||||
# Parameters:
|
||||
# $1 (DOMAIN) The domain name (CN or subject alternative name) being validated.
|
||||
# $2 (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}.
|
||||
# $3 (TOKEN_VALUE) The token value that needs to be served for validation.
|
||||
# For HTTP validation it is the value that is expected be found in the $TOKEN_FILENAME file.
|
||||
# For DNS validation, this is what you want to put in the _acme-challenge TXT record.
|
||||
|
||||
# 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.
|
||||
local DOMAIN="$1" TOKEN_FILENAME="$2" TOKEN_VALUE="$3"
|
||||
|
||||
# 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
|
||||
|
|
@ -152,38 +212,37 @@ clean_challenge() {
|
|||
}
|
||||
|
||||
|
||||
# Called after the certificates have been created but before they are symlinked.
|
||||
# This allows the 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.
|
||||
sync_cert() {
|
||||
# Parameters:
|
||||
# $1 (KEYFILE) The path of the file containing the private key.
|
||||
# $2 (CERTFILE) The path of the file containing the signed certificate.
|
||||
# $3 (FULLCHAINFILE) The path of the file containing the full certificate chain.
|
||||
# $4 (CHAINFILE) The path of the file containing the intermediate certificate(s).
|
||||
# $5 (REQUESTFILE) The path of the file containing the certificate signing request.
|
||||
|
||||
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"
|
||||
sync "$KEYFILE" "$CERTFILE" "$FULLCHAINFILE" "$CHAINFILE" "$REQUESTFILE"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
# Called once for each certificate that has been produced.
|
||||
# Here you can copy your new certificates to service-specific locations and reload the service.
|
||||
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.
|
||||
# $1 (DOMAIN) The primary domain name, i.e. the certificate common name (CN).
|
||||
# $2 (KEYFILE) The path of the file containing the private key.
|
||||
# $3 (CERTFILE) The path of the file containing the signed certificate.
|
||||
# $4 (FULLCHAINFILE) The path of the file containing the full certificate chain.
|
||||
# $5 (CHAINFILE) The path of the file containing the intermediate certificate(s).
|
||||
# $6 (TIMESTAMP) Timestamp when the specified certificate was created.
|
||||
|
||||
local DOMAIN="$1" KEYFILE="$2" CERTFILE="$3" FULLCHAINFILE="$4" CHAINFILE="$5" TIMESTAMP="$6"
|
||||
|
||||
local FILE LOG_PREFIX="Certificate deployment"
|
||||
|
||||
|
|
@ -198,9 +257,10 @@ deploy_cert() {
|
|||
}
|
||||
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.
|
||||
# Only copy the certificate if it differs from the new one.
|
||||
cmp "$CERTFILE" "$CERTSDIR/${DOMAIN}_cert.pem" >/dev/null 2>&1 || {
|
||||
# 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" || {
|
||||
|
|
@ -211,7 +271,7 @@ deploy_cert() {
|
|||
}
|
||||
|
||||
# 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 || {
|
||||
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
|
||||
|
|
@ -225,15 +285,15 @@ deploy_cert() {
|
|||
}
|
||||
|
||||
|
||||
# Called once for each updated OCSP stapling file that has been produced.
|
||||
# Here you can copy your new OCSP stapling files to service-specific locations and reload the service.
|
||||
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.
|
||||
# $1 (DOMAIN) The primary domain name, i.e. the certificate common name (CN).
|
||||
# $2 (OCSPFILE) The path of the OCSP stapling file.
|
||||
# $3 (TIMESTAMP) Timestamp when the specified OCSP stapling file was created.
|
||||
|
||||
local DOMAIN="$1" OCSPFILE="$2" TIMESTAMP="$3"
|
||||
|
||||
# Simple example: Copy file to nginx config
|
||||
# cp "$OCSPFILE" /etc/nginx/ssl/; chown -R nginx: /etc/nginx/ssl
|
||||
|
|
@ -243,28 +303,28 @@ deploy_ocsp() {
|
|||
}
|
||||
|
||||
|
||||
# Called once for each certificate that is still valid and therefore wasn't reissued.
|
||||
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).
|
||||
# $1 (DOMAIN) The primary domain name, i.e. the certificate common name (CN).
|
||||
# $2 (KEYFILE) The path of the file containing the private key.
|
||||
# $3 (CERTFILE) The path of the file containing the signed certificate.
|
||||
# $4 (FULLCHAINFILE) The path of the file containing the full certificate chain.
|
||||
# $5 (CHAINFILE) The path of the file containing the intermediate certificate(s).
|
||||
|
||||
local DOMAIN="$1" KEYFILE="$2" CERTFILE="$3" FULLCHAINFILE="$4" CHAINFILE="$5"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
# Called if the challenge response has failed, so domain owners can be aware and act accordingly.
|
||||
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
|
||||
# $1 (DOMAIN) The primary domain name, i.e. the certificate common name (CN).
|
||||
# $2 (RESPONSE) The response that the verification server returned
|
||||
|
||||
local DOMAIN="$1" RESPONSE="$2"
|
||||
|
||||
# Notify the sysadmin.
|
||||
notify "error" "Validation of '$DOMAIN' failed:" "$RESPONSE"
|
||||
|
|
@ -273,154 +333,174 @@ invalid_challenge() {
|
|||
}
|
||||
|
||||
|
||||
# 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.
|
||||
request_failure() {
|
||||
# Parameters:
|
||||
# $1 (STATUSCODE) The HTML status code that originated the error.
|
||||
# $2 (REASON) The specified reason for the error.
|
||||
# $3 (REQTYPE) The kind of request that was made (GET, POST...)
|
||||
|
||||
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"
|
||||
notify "error" "HTTP '$REQTYPE' request failed for '$DOMAIN' with code '$STATUSCODE'" "Reason: $REASON" "Headers:" "$HEADERS"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
# 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.
|
||||
generate_csr() {
|
||||
# Parameters:
|
||||
# $1 (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.
|
||||
# $2 (CERTDIR) Certificate output directory for this particular certificate.
|
||||
# Can be used for storing additional files.
|
||||
# $3 (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.
|
||||
|
||||
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
|
||||
# [[ -e "$CERTDIR/pre-generated.csr" ]] && cat "$CERTDIR/pre-generated.csr"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
# Called before the cron command to do some initial tasks (e.g. starting a webserver).
|
||||
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 (with sanity check)
|
||||
services 1 || return 1
|
||||
# The 'root' user is required.
|
||||
(( $(id -u) != 0 )) && {
|
||||
notify "error" "Must be running as 'root' -- aborting"
|
||||
return 1
|
||||
}
|
||||
|
||||
# Read services configuration (with sanity checks).
|
||||
services "1"
|
||||
|
||||
# Make sure the certificates directory exists.
|
||||
[[ -n "$CERTSDIR" ]] && {
|
||||
umask 022
|
||||
# shellcheck disable=SC2174
|
||||
mkdir -p -m 0755 "$CERTSDIR" 2>/dev/null || {
|
||||
notify "error" "Failed to create certificate storage directory -- aborting"
|
||||
notify "error" "Failed to create certificate storage directory '$CERTSDIR' -- aborting"
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
# If an HTTP daemon rc script is available and the service is not already running, start it.
|
||||
[[ -n "$RCFILE_HTTPD" ]] && {
|
||||
pgrep -c ${PIDFILE_HTTPD:+-F "$PIDFILE_HTTPD"} "$DAEMON_HTTPD" >/dev/null 2>&1 || {
|
||||
"$RCFILE_HTTPD" start >/dev/null 2>&1
|
||||
# If an HTTP daemon is not already running, start it.
|
||||
[[ -n "${SERVICE_CTL['httpd']}" ]] && {
|
||||
pgrep -c ${SERVICE_PIDFILE['httpd']:+-F "${SERVICE_PIDFILE['httpd']}"} "${SERVICE_DAEMON['httpd']}" >/dev/null 2>&1 || {
|
||||
"${SERVICE_CTL['httpd']}" "start" >/dev/null 2>&1
|
||||
sleep 5
|
||||
if pgrep -c ${PIDFILE_HTTPD:+-F "$PIDFILE_HTTPD"} "$DAEMON_HTTPD" >/dev/null 2>&1; then
|
||||
if pgrep -c ${SERVICE_PIDFILE['httpd']:+-F "${SERVICE_PIDFILE['httpd']}"} "${SERVICE_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"
|
||||
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"
|
||||
notify "error" "Failed to start HTTP daemon for nonce validation -- aborting"
|
||||
return 1
|
||||
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
|
||||
# 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 --dport 80 -j ACCEPT && ip6tables -I dehydrated 1 -p tcp --dport 80 -j ACCEPT; } >/dev/null 2>&1 || {
|
||||
notify "warning" "Failed to insert firewall rules to allow nonce validation"
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
# Called at the end of the cron command and can be used to do some final (cleanup or other) tasks.
|
||||
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.
|
||||
# $1 (ERROR) Contains error message if dehydrated exits with error.
|
||||
|
||||
local DAEMON ERR=0 LOG_PREFIX="Dehydrated shutdown" PIDFILE RCFILE TIMEOUT=30
|
||||
local ERROR="$1"
|
||||
local ERR=0 LOG_PREFIX="Dehydrated shutdown" SERVICE TIMEOUT=30
|
||||
|
||||
# Read services configuration (without sanity check - this was already done at startup)
|
||||
services 0 || return 1
|
||||
services 0
|
||||
|
||||
# Delete firewall rules that was added to allow HTTP traffic.
|
||||
iptables -C INPUT -j dehydrated >/dev/null 2>&1 && iptables -D INPUT -j dehydrated >/dev/null 2>&1
|
||||
ip6tables -C INPUT -j dehydrated >/dev/null 2>&1 && ip6tables -D INPUT -j dehydrated >/dev/null 2>&1
|
||||
iptables -F dehydrated >/dev/null 2>&1
|
||||
ip6tables -F dehydrated >/dev/null 2>&1
|
||||
iptables -X dehydrated >/dev/null 2>&1
|
||||
ip6tables -X dehydrated >/dev/null 2>&1
|
||||
|
||||
# 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_}"
|
||||
[[ -e "/run/dehydrated-reload-marker" ]] && {
|
||||
local IFS=$'\n'
|
||||
for SERVICE in $(printf "%s\\n" "${!SERVICE_CTL[@]}"); do
|
||||
# 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"
|
||||
[[ "$SERVICE" == "httpd" ]] && [[ -e "/run/dehydrated-http-daemon-stop-marker" ]] && continue
|
||||
|
||||
# Restart the services.
|
||||
"${SERVICE_CTL[\"$SERVICE\"]}" "restart" >/dev/null 2>&1 || {
|
||||
notify "error" "Controlled restart of service '$SERVICE' failed -- check server"
|
||||
ERR=1
|
||||
continue
|
||||
}
|
||||
sleep "$TIMEOUT"
|
||||
pgrep -c ${PIDFILE:+-F "${!PIDFILE}"} "${!DAEMON}" >/dev/null 2>&1 || {
|
||||
notify "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"
|
||||
pgrep -c ${SERVICE_PIDFILE["$SERVICE"]:+-F "${SERVICE_PIDFILE[\"$SERVICE\"]}"} "${SERVICE_DAEMON[\"$SERVICE\"]}" >/dev/null 2>&1 || {
|
||||
notify "warning" "Service '$SERVICE' exited unexpectedly - trying to start again"
|
||||
"${SERVICE_CTL[\"$SERVICE\"]}" "start" >/dev/null 2>&1 || {
|
||||
notify "error" "Controlled start of service '$SERVICE' failed -- check server"
|
||||
ERR=1
|
||||
continue
|
||||
}
|
||||
sleep "$TIMEOUT"
|
||||
pgrep -c ${SERVICE_PIDFILE["$SERVICE"]:+-F "${SERVICE_PIDFILE[\"$SERVICE\"]}"} "${SERVICE_DAEMON[\"$SERVICE\"]}" >/dev/null 2>&1 || {
|
||||
notify "error" "Multiple failures (re)starting service '$SERVICE' -- check server"
|
||||
ERR=1
|
||||
continue
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
(( ERR == 0 )) && {
|
||||
rm -f "/run/dehydrated-reload-marker" 2>/dev/null || notify "warning" "Failed to remove services reload marker -- check server"
|
||||
}
|
||||
|
||||
# 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"; }
|
||||
# If an HTTP daemon is configured, process the shutdown of it.
|
||||
[[ -n "${SERVICE_CTL['httpd']}" ]] && {
|
||||
# Delete firewall rules that were added to allow HTTP traffic.
|
||||
{ iptables -C INPUT -j dehydrated && iptables -D INPUT -j dehydrated; } >/dev/null 2>&1
|
||||
{ ip6tables -C INPUT -j dehydrated && ip6tables -D INPUT -j dehydrated; } >/dev/null 2>&1
|
||||
iptables -F dehydrated >/dev/null 2>&1
|
||||
ip6tables -F dehydrated >/dev/null 2>&1
|
||||
iptables -X dehydrated >/dev/null 2>&1
|
||||
ip6tables -X dehydrated >/dev/null 2>&1
|
||||
|
||||
# If an HTTP daemon was started in startup_hook(), stop it now.
|
||||
ERR=0
|
||||
[[ -e "/run/dehydrated-http-daemon-stop-marker" ]] && {
|
||||
pgrep -c ${SERVICE_PIDFILE['httpd']:+-F "${SERVICE_PIDFILE['httpd']}"} "${SERVICE_DAEMON['httpd']}" >/dev/null 2>&1 && {
|
||||
"${SERVICE_CTL['httpd']}" "stop" >/dev/null 2>&1 || notify "error" "Controlled stop service of 'httpd' failed -- check server"
|
||||
sleep "$TIMEOUT"
|
||||
pgrep -c ${SERVICE_PIDFILE['httpd']:+-F "${SERVICE_PIDFILE['httpd']}"} "${SERVICE_DAEMON['httpd']}" >/dev/null 2>&1 && {
|
||||
pkill -TERM ${SERVICE_PIDFILE['httpd']:+-F "${SERVICE_PIDFILE['httpd']}"} "${SERVICE_DAEMON['httpd']}" >/dev/null 2>&1 || notify "warning" "Failed to SIGTERM service 'httpd' -- check server"
|
||||
sleep "$TIMEOUT"
|
||||
pgrep -c ${SERVICE_PIDFILE['httpd']:+-F "${SERVICE_PIDFILE['httpd']}"} "${SERVICE_DAEMON['httpd']}" >/dev/null 2>&1 && {
|
||||
pkill -KILL ${SERVICE_PIDFILE['httpd']:+-F "${SERVICE_PIDFILE['httpd']}"} "${SERVICE_DAEMON['httpd']}" >/dev/null 2>&1 || notify "warning" "Failed to SIGKILL service 'httpd' -- check server"
|
||||
sleep 5
|
||||
}
|
||||
}
|
||||
pgrep -c ${SERVICE_PIDFILE['httpd']:+-F "${SERVICE_PIDFILE['httpd']}"} "${SERVICE_DAEMON['httpd']}" >/dev/null 2>&1 && notify "error" "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
|
||||
}
|
||||
|
|
@ -433,4 +513,4 @@ if declare -pF "$HANDLER" >/dev/null 2>&1; then
|
|||
exit "$?"
|
||||
else
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue