mirror of
https://github.com/GrapheneOS/infrastructure.git
synced 2024-12-22 21:49:22 -05:00
update certbot-ocsp-fetcher
This commit is contained in:
parent
462bdc8599
commit
5f339efb2d
@ -10,50 +10,94 @@ set \
|
|||||||
IFS=$'\n\t'
|
IFS=$'\n\t'
|
||||||
shopt -s inherit_errexit
|
shopt -s inherit_errexit
|
||||||
|
|
||||||
|
determine_colored_output() {
|
||||||
|
declare -gl COLORED_STDOUT COLORED_STDERR
|
||||||
|
readonly GREEN='\033[0;32m'
|
||||||
|
readonly RED='\033[0;31m'
|
||||||
|
readonly COLOR_DEFAULT='\033[0m'
|
||||||
|
|
||||||
|
if [[ -v NO_COLOR || ${TERM-} == dumb ]]; then
|
||||||
|
COLORED_STDOUT=false COLORED_STDERR=false
|
||||||
|
else
|
||||||
|
[[ -t 1 ]] || COLORED_STDOUT=false
|
||||||
|
[[ -t 2 ]] || COLORED_STDERR=false
|
||||||
|
fi
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
exit_with_error() {
|
exit_with_error() {
|
||||||
echo "${@}" >&2
|
local error_prefix=error:$'\t\t'
|
||||||
|
|
||||||
|
[[ ${COLORED_STDERR-} != false ]] &&
|
||||||
|
local -r COLORED_ERROR_MSG=${RED}${error_prefix}${*}${COLOR_DEFAULT}
|
||||||
|
|
||||||
|
# We will have closed file descriptor 2 unless verbosity was requested, so we
|
||||||
|
# will try to use FD5 (the FD that stderr was likely redirected to), and
|
||||||
|
# fallback to FD2 if FD5 wasn't opened yet.
|
||||||
|
if [[ -f /dev/fd/5 ]]; then
|
||||||
|
exec >&5
|
||||||
|
else
|
||||||
|
exec >&2
|
||||||
|
fi
|
||||||
|
printf '%b\n' "${COLORED_ERROR_MSG:-${error_prefix}${@}}"
|
||||||
|
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
check_for_dependencies() {
|
check_for_dependencies() {
|
||||||
if ((BASH_VERSINFO[0] == 4 && \
|
if ((BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] < 3 || BASH_VERSINFO[0] < 4)); then
|
||||||
BASH_VERSINFO[1] < 3 || \
|
exit_with_error "${0##*/} requires Bash 4.3+."
|
||||||
BASH_VERSINFO[0] < 4)); then
|
|
||||||
exit_with_error \
|
|
||||||
error:$'\t\t'"${0##*/} requires Bash 4.3+."
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! { command -v openssl >&- &&
|
if ! { command -v openssl >&- &&
|
||||||
[[ $(openssl version) =~ ^OpenSSL\ ([[:digit:]]+)\.([[:digit:]]+) ]] &&
|
[[ $(openssl version || true) =~ ^OpenSSL\ ([[:digit:]]+)\.([[:digit:]]+) ]] &&
|
||||||
((BASH_REMATCH[1] == 1 && \
|
((BASH_REMATCH[1] == 1 && BASH_REMATCH[2] >= 1 || BASH_REMATCH[1] > 1)); }; then
|
||||||
BASH_REMATCH[2] >= 1 || \
|
|
||||||
BASH_REMATCH[1] > 1)); }; then
|
|
||||||
# shellcheck disable=2016
|
# shellcheck disable=2016
|
||||||
exit_with_error \
|
exit_with_error \
|
||||||
error:$'\t\t'"${0##*/} requires OpenSSL 1.1.0+," \
|
"${0##*/} requires OpenSSL 1.1.0+," \
|
||||||
'but it is not available on $PATH.'
|
'but it is not available on $PATH.'
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
parse_cli_arguments() {
|
parse_cli_options() {
|
||||||
local -r usage=(
|
local -r cli_options="
|
||||||
"USAGE: ${0}"
|
Usage: ${0} [-c/--certbot-dir DIRECTORY] [-f/--force-update] \\
|
||||||
"[-c/--certbot-dir DIRECTORY]"
|
[-h/--help] [-l/--no-color] [-n/--cert-name NAME[,NAME...] \\
|
||||||
"[-f/--force-update]"
|
[-u/--ocsp-responder URL]] [-o/--output-dir DIRECTORY] \\
|
||||||
"[-h/--help]"
|
[-q/--quiet|-v/--verbose] [-w/--no-reload-webserver]
|
||||||
"[-n/--cert-name NAME[,NAME...] [-u/--ocsp-responder URL]]"
|
"
|
||||||
"[-o/--output-dir DIRECTORY]"
|
|
||||||
"[-q/--quiet]"
|
print_option_error() {
|
||||||
"[-v/--verbose]"
|
local reason=${1} option=${2}
|
||||||
"[-w/--no-reload-webserver]"
|
shift 2
|
||||||
)
|
local option_error="${option}: "
|
||||||
|
|
||||||
|
case ${reason} in
|
||||||
|
--conflict)
|
||||||
|
local second_option=${1}
|
||||||
|
shift
|
||||||
|
option_error+="This option cannot be combined with the option ${second_option}."
|
||||||
|
;;
|
||||||
|
--duplicate)
|
||||||
|
option_error+="This option cannot be specified multiple times."
|
||||||
|
;;
|
||||||
|
--unknown)
|
||||||
|
option_error+="Invalid option."
|
||||||
|
;;
|
||||||
|
--value)
|
||||||
|
option_error+="This option requires a value."
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
exit_with_error "${option_error}" "${cli_options}"
|
||||||
|
}
|
||||||
|
|
||||||
declare -gl ERROR_ENCOUNTERED
|
declare -gl ERROR_ENCOUNTERED
|
||||||
|
|
||||||
declare -gi VERBOSITY=1
|
declare -gi VERBOSITY=${VERBOSITY:-1}
|
||||||
local -r verbosity_error=(
|
|
||||||
"error: -q/--quiet cannot be specified in conjunction with -v/--verbose."
|
|
||||||
)
|
|
||||||
|
|
||||||
while ((${#} > 0)); do
|
while ((${#} > 0)); do
|
||||||
local parameter=${1}
|
local parameter=${1}
|
||||||
@ -64,17 +108,17 @@ parse_cli_arguments() {
|
|||||||
;;
|
;;
|
||||||
-c | --certbot-dir | --certbot-dir=?*)
|
-c | --certbot-dir | --certbot-dir=?*)
|
||||||
if [[ -v CERTBOT_DIR ]]; then
|
if [[ -v CERTBOT_DIR ]]; then
|
||||||
exit_with_error "${usage[@]}"
|
print_option_error --duplicate "${parameter}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ${parameter} =~ --certbot-dir=(.+) ]]; then
|
if [[ ${parameter} =~ --certbot-dir=(.+) ]]; then
|
||||||
CERTBOT_DIR=${BASH_REMATCH[1]}
|
CERTBOT_DIR=${BASH_REMATCH[1]}
|
||||||
else
|
else
|
||||||
if [[ -n ${2:-} ]]; then
|
if [[ -n ${2-} ]]; then
|
||||||
CERTBOT_DIR=${2}
|
CERTBOT_DIR=${2}
|
||||||
shift
|
shift
|
||||||
else
|
else
|
||||||
exit_with_error "${usage[@]}"
|
print_option_error --value "${parameter}"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -95,19 +139,59 @@ parse_cli_arguments() {
|
|||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
-h | --help)
|
-h | --help)
|
||||||
echo >&2 "${usage[@]}"
|
{
|
||||||
|
printf '%s\n' certbot-ocsp-fetcher
|
||||||
|
printf '%s\n' "${cli_options}"
|
||||||
|
local absolute_tool_path
|
||||||
|
absolute_tool_path=$(realpath --no-symlinks -- "${0}")
|
||||||
|
readonly absolute_tool_path
|
||||||
|
cat <<EOSTRING
|
||||||
|
|
||||||
|
certbot-ocsp-fetcher helps you setup OCSP stapling in nginx. The tool primes
|
||||||
|
nginx's OCSP cache to work around nginx's flawed OCSP stapling implementation.
|
||||||
|
The tool does this by fetching and saving OCSP responses for TLS certificates
|
||||||
|
issued with Certbot.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
1. Fetch OCSP responses for all certificates managed by Certbot, and save
|
||||||
|
them in the current working directory. This should usually be run on a
|
||||||
|
schedule, e.g. as a cronjob or systemd timer.
|
||||||
|
|
||||||
|
$ ${0}
|
||||||
|
|
||||||
|
2. Add the path(s) to the resulting OCSP response(s) as the value of the
|
||||||
|
ssl_stapling_file directive in the corresponding vhosts in Nginx. Don't
|
||||||
|
forget to reload Nginx afterwards.
|
||||||
|
|
||||||
|
3. Re-issue all certificates managed by Certbot, to add the OCSP Must-Staple
|
||||||
|
flag to the certs and automatically run certbot-ocsp-fetcher during renewals:
|
||||||
|
|
||||||
|
$ certbot renew --deploy-hook ${absolute_tool_path} --force-renewal --must-staple
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
See the online README for an explanation of all the CLI options:
|
||||||
|
https://github.com/tomwassenberg/certbot-ocsp-fetcher/blob/main/README.md
|
||||||
|
EOSTRING
|
||||||
|
} >&2
|
||||||
exit
|
exit
|
||||||
;;
|
;;
|
||||||
|
-l | --no-color)
|
||||||
|
readonly COLORED_STDOUT=false COLORED_STDERR=false
|
||||||
|
;;
|
||||||
-n | --cert-name | --cert-name=?*)
|
-n | --cert-name | --cert-name=?*)
|
||||||
if [[ ${parameter} =~ --cert-name=(.+) ]]; then
|
if [[ ${parameter} =~ --cert-name=(.+) ]]; then
|
||||||
local cert_lineages_value=${BASH_REMATCH[1]}
|
local cert_lineages_value=${BASH_REMATCH[1]}
|
||||||
shift
|
shift
|
||||||
else
|
else
|
||||||
if [[ -n ${2:-} ]]; then
|
if [[ -n ${2-} ]]; then
|
||||||
local cert_lineages_value=${2}
|
local cert_lineages_value=${2}
|
||||||
shift 2
|
shift 2
|
||||||
else
|
else
|
||||||
exit_with_error "${usage[@]}"
|
print_option_error --value "${parameter}"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -117,15 +201,15 @@ parse_cli_arguments() {
|
|||||||
declare -Ag CERT_LINEAGES
|
declare -Ag CERT_LINEAGES
|
||||||
# Check if a hardcoded OCSP responder was specified for this set of
|
# Check if a hardcoded OCSP responder was specified for this set of
|
||||||
# lineages.
|
# lineages.
|
||||||
case ${1:-} in
|
case ${1-} in
|
||||||
-u | --ocsp-responder)
|
-u | --ocsp-responder)
|
||||||
if [[ -n ${2:-} ]]; then
|
if [[ -n ${2-} ]]; then
|
||||||
for lineage_name in ${cert_lineages_value}; do
|
for lineage_name in ${cert_lineages_value}; do
|
||||||
CERT_LINEAGES["${lineage_name}"]=${2}
|
CERT_LINEAGES["${lineage_name}"]=${2}
|
||||||
done
|
done
|
||||||
shift
|
shift
|
||||||
else
|
else
|
||||||
exit_with_error "${usage[@]}"
|
print_option_error --value "${parameter}"
|
||||||
fi
|
fi
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
@ -149,17 +233,17 @@ parse_cli_arguments() {
|
|||||||
;;
|
;;
|
||||||
-o | --output-dir | --output-dir=?*)
|
-o | --output-dir | --output-dir=?*)
|
||||||
if [[ -v OUTPUT_DIR ]]; then
|
if [[ -v OUTPUT_DIR ]]; then
|
||||||
exit_with_error "${usage[@]}"
|
print_option_error --duplicate "${parameter}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ${parameter} =~ --output-dir=(.+) ]]; then
|
if [[ ${parameter} =~ --output-dir=(.+) ]]; then
|
||||||
OUTPUT_DIR=${BASH_REMATCH[1]}
|
OUTPUT_DIR=${BASH_REMATCH[1]}
|
||||||
else
|
else
|
||||||
if [[ -n ${2:-} ]]; then
|
if [[ -n ${2-} ]]; then
|
||||||
OUTPUT_DIR=${2}
|
OUTPUT_DIR=${2}
|
||||||
shift
|
shift
|
||||||
else
|
else
|
||||||
exit_with_error "${usage[@]}"
|
print_option_error --value "${parameter}"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -175,7 +259,7 @@ parse_cli_arguments() {
|
|||||||
;;
|
;;
|
||||||
-q | --quiet)
|
-q | --quiet)
|
||||||
if ((VERBOSITY != 1)); then
|
if ((VERBOSITY != 1)); then
|
||||||
exit_with_error "${verbosity_error[@]}"
|
print_option_error --conflict "${parameter}" -v/--verbose
|
||||||
else
|
else
|
||||||
readonly VERBOSITY=0
|
readonly VERBOSITY=0
|
||||||
shift
|
shift
|
||||||
@ -183,7 +267,7 @@ parse_cli_arguments() {
|
|||||||
;;
|
;;
|
||||||
-v | --verbose)
|
-v | --verbose)
|
||||||
if ((VERBOSITY == 0)); then
|
if ((VERBOSITY == 0)); then
|
||||||
exit_with_error "${verbosity_error[@]}"
|
print_option_error --conflict "${parameter}" -q/--quiet
|
||||||
else
|
else
|
||||||
VERBOSITY+=1
|
VERBOSITY+=1
|
||||||
shift
|
shift
|
||||||
@ -196,11 +280,19 @@ parse_cli_arguments() {
|
|||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
exit_with_error "${usage[@]}"
|
print_option_error --unknown "${parameter}"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# Respect the common "DEBUG" environment variable if set, unless the --quiet
|
||||||
|
# or --verbose flag has been passed as well.
|
||||||
|
if ((${DEBUG:-0} >= 1)) && ((VERBOSITY == 1)); then
|
||||||
|
# We set VERBOSITY to 0 in case of --quiet, so use the value of $DEBUG
|
||||||
|
# incremented with 1 to match it with $VERBOSITY.
|
||||||
|
VERBOSITY=$((DEBUG + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
# When not parsed, the stdout and/or stderr output of all external commands
|
# When not parsed, the stdout and/or stderr output of all external commands
|
||||||
# we call in the script is redirected to file descriptor 3. Depending on the
|
# we call in the script is redirected to file descriptor 3. Depending on the
|
||||||
# desired verbosity, we redirect this file descriptor to either stderr or to
|
# desired verbosity, we redirect this file descriptor to either stderr or to
|
||||||
@ -210,6 +302,13 @@ parse_cli_arguments() {
|
|||||||
else
|
else
|
||||||
exec 3>/dev/null
|
exec 3>/dev/null
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# First copy file descriptor 2 to a new FD, so stderr can still be used
|
||||||
|
# (unconditionally) in the exit_with_error function.
|
||||||
|
exec 5>&2
|
||||||
|
if ((VERBOSITY < 1)); then
|
||||||
|
exec 2>/dev/null
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Set output directory if necessary and check if it's writeable
|
# Set output directory if necessary and check if it's writeable
|
||||||
@ -223,12 +322,13 @@ prepare_output_dir() {
|
|||||||
-- "${OUTPUT_DIR}" || true
|
-- "${OUTPUT_DIR}" || true
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
readonly OUTPUT_DIR=.
|
# Use $CACHE_DIRECTORY if set (e.g. when run as a systemd service),
|
||||||
|
# otherwise the working directory
|
||||||
|
readonly OUTPUT_DIR=${CACHE_DIRECTORY:-.}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ! -w ${OUTPUT_DIR} ]]; then
|
if [[ ! -w ${OUTPUT_DIR} ]]; then
|
||||||
exit_with_error \
|
exit_with_error "no write access to output directory (\"${OUTPUT_DIR}\")"
|
||||||
error:$'\t\t'"no write access to output directory (\"${OUTPUT_DIR}\")"
|
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -255,26 +355,26 @@ start_in_correct_mode() {
|
|||||||
# Run in "check one or all certificate lineage(s) managed by Certbot" mode
|
# Run in "check one or all certificate lineage(s) managed by Certbot" mode
|
||||||
# $1 - Path to temporary output directory
|
# $1 - Path to temporary output directory
|
||||||
run_standalone() {
|
run_standalone() {
|
||||||
|
printf >&2 '%s\n\n' "Running in stand-alone mode..."
|
||||||
|
|
||||||
readonly CERTBOT_DIR=${CERTBOT_DIR:-/etc/letsencrypt}
|
readonly CERTBOT_DIR=${CERTBOT_DIR:-/etc/letsencrypt}
|
||||||
|
|
||||||
if [[ ! -r ${CERTBOT_DIR} || (-d ${CERTBOT_DIR}/live && ! -r ${CERTBOT_DIR}/live) ]]; then
|
if [[ ! -r ${CERTBOT_DIR} || (-d ${CERTBOT_DIR}/live && ! -r ${CERTBOT_DIR}/live) ]]; then
|
||||||
exit_with_error \
|
exit_with_error "can't access ${CERTBOT_DIR}/live"
|
||||||
error:$'\t\t'"can't access ${CERTBOT_DIR}/live"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check specific lineage if passed on CLI,
|
# Check specific lineage if passed on CLI,
|
||||||
# or otherwise all lineages in Certbot's dir
|
# or otherwise all lineages in Certbot's dir
|
||||||
if [[ -v CERT_LINEAGES[@] ]]; then
|
if [[ -n ${!CERT_LINEAGES[*]} ]]; then
|
||||||
for lineage_name in "${!CERT_LINEAGES[@]}"; do
|
for lineage_name in "${!CERT_LINEAGES[@]}"; do
|
||||||
if [[ -r ${CERTBOT_DIR}/live/${lineage_name} ]]; then
|
if [[ -r ${CERTBOT_DIR}/live/${lineage_name} ]]; then
|
||||||
fetch_ocsp_response \
|
fetch_ocsp_response \
|
||||||
"--standalone" \
|
--standalone \
|
||||||
"${temp_output_dir}" \
|
"${temp_output_dir}" \
|
||||||
"${lineage_name}" \
|
"${lineage_name}" \
|
||||||
"${CERT_LINEAGES["${lineage_name}"]}"
|
"${CERT_LINEAGES["${lineage_name}"]}"
|
||||||
else
|
else
|
||||||
exit_with_error \
|
exit_with_error "can't access ${CERTBOT_DIR}/live/${lineage_name}"
|
||||||
"error:"$'\t\t'"can't access ${CERTBOT_DIR}/live/${lineage_name}"
|
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
else
|
else
|
||||||
@ -287,7 +387,7 @@ run_standalone() {
|
|||||||
[[ -d ${lineage_dir} ]] || continue
|
[[ -d ${lineage_dir} ]] || continue
|
||||||
|
|
||||||
fetch_ocsp_response \
|
fetch_ocsp_response \
|
||||||
"--standalone" "${temp_output_dir}" "${lineage_dir##*/}"
|
--standalone "${temp_output_dir}" "${lineage_dir##*/}"
|
||||||
done
|
done
|
||||||
unset lineage_dir
|
unset lineage_dir
|
||||||
fi
|
fi
|
||||||
@ -296,11 +396,13 @@ run_standalone() {
|
|||||||
# Run in deploy-hook mode, only processing the passed lineage
|
# Run in deploy-hook mode, only processing the passed lineage
|
||||||
# $1 - Path to temporary output directory
|
# $1 - Path to temporary output directory
|
||||||
run_as_deploy_hook() {
|
run_as_deploy_hook() {
|
||||||
|
printf >&2 '%s\n\n' "Running as a deploy hook of Certbot..."
|
||||||
|
|
||||||
if [[ -v CERTBOT_DIR ]]; then
|
if [[ -v CERTBOT_DIR ]]; then
|
||||||
# The directory is already inferred from the environment variable that
|
# The directory is already inferred from the environment variable that
|
||||||
# Certbot passes
|
# Certbot passes
|
||||||
exit_with_error \
|
exit_with_error \
|
||||||
error:$'\t\t'"-c/--certbot-dir cannot be passed" \
|
"-c/--certbot-dir cannot be passed" \
|
||||||
"when run as Certbot hook"
|
"when run as Certbot hook"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -308,15 +410,14 @@ run_as_deploy_hook() {
|
|||||||
# When run as deploy hook the behavior of this flag is used by default.
|
# When run as deploy hook the behavior of this flag is used by default.
|
||||||
# Therefore passing this flag would not have any effect.
|
# Therefore passing this flag would not have any effect.
|
||||||
exit_with_error \
|
exit_with_error \
|
||||||
error:$'\t\t'"-f/--force-update cannot be passed" \
|
"-f/--force-update cannot be passed" \
|
||||||
"when run as Certbot hook"
|
"when run as Certbot hook"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -v CERT_LINEAGES[@] ]]; then
|
if [[ -n ${!CERT_LINEAGES[*]} ]]; then
|
||||||
# The certificate lineage is already inferred from the environment
|
# The certificate lineage is already inferred from the environment
|
||||||
# variable that Certbot passes
|
# variable that Certbot passes
|
||||||
exit_with_error \
|
exit_with_error "-n/--cert-name cannot be passed when run as Certbot hook"
|
||||||
error:$'\t\t'"-n/--cert-name cannot be passed when run as Certbot hook"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
fetch_ocsp_response \
|
fetch_ocsp_response \
|
||||||
@ -349,14 +450,23 @@ check_for_existing_ocsp_staple_file() {
|
|||||||
local -r next_update=${BASH_REMATCH[1]}
|
local -r next_update=${BASH_REMATCH[1]}
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
[[ -n ${this_update:-} && -n ${next_update:-} ]] || return 1
|
[[ -n ${this_update-} && -n ${next_update-} ]] || return 1
|
||||||
|
|
||||||
# Only continue fetching OCSP response if existing response expires within
|
# Only continue fetching OCSP response if existing response expires within
|
||||||
# half of its lifetime.
|
# half of its lifetime.
|
||||||
local -ri response_lifetime_in_seconds=$((\
|
{
|
||||||
$(date +%s --date "${next_update}") - $(date +%s --date "${this_update}")))
|
# The command substitutions here don't respect `set -o errexit`, but in
|
||||||
(($(date +%s) < \
|
# case any of them fail, the total command still fails unless both
|
||||||
$(date +%s --date "${this_update}") + response_lifetime_in_seconds / 2)) || return 1
|
# substitutions print an integer. This seems very unlikely to occur, so
|
||||||
|
# let's ignore this.
|
||||||
|
# shellcheck disable=2312
|
||||||
|
local -ri response_lifetime_in_seconds=$(($(date +%s --date "${next_update}") - $(date +%s --date "${this_update}")))
|
||||||
|
|
||||||
|
# `set -o errexit` isn't respected here either, but we default to renewing
|
||||||
|
# the OCSP response, so this is fine.
|
||||||
|
# shellcheck disable=2312
|
||||||
|
(($(date +%s) < $(date +%s --date "${this_update}") + response_lifetime_in_seconds / 2)) || return 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Generate file used by ssl_stapling_file in nginx config of websites
|
# Generate file used by ssl_stapling_file in nginx config of websites
|
||||||
@ -367,11 +477,27 @@ check_for_existing_ocsp_staple_file() {
|
|||||||
fetch_ocsp_response() {
|
fetch_ocsp_response() {
|
||||||
local -r temp_output_dir=${2}
|
local -r temp_output_dir=${2}
|
||||||
local -r lineage_name=${3}
|
local -r lineage_name=${3}
|
||||||
|
|
||||||
|
# This validation should be revisited once
|
||||||
|
# https://github.com/certbot/certbot/issues/6127 is fixed.
|
||||||
|
if [[ ${lineage_name} =~ ($'\n')|($'\t') ]]; then
|
||||||
|
ERROR_ENCOUNTERED=true
|
||||||
|
exit_with_error \
|
||||||
|
"Unsupported characters encountered in the following" \
|
||||||
|
"lineage name: ${lineage_name}$'\n\n'" \
|
||||||
|
"Lineage names with embedded tabs or newlines are not supported," \
|
||||||
|
"because Certbot (as of version 1.18.0) does not have well-defined" \
|
||||||
|
'behavior on handling any "unconventional" lineage names.'
|
||||||
|
fi
|
||||||
|
|
||||||
case ${1} in
|
case ${1} in
|
||||||
--standalone)
|
--standalone)
|
||||||
local -r lineage_dir=${CERTBOT_DIR}/live/${lineage_name}
|
local -r lineage_dir=${CERTBOT_DIR}/live/${lineage_name}
|
||||||
|
|
||||||
if [[ ${FORCE_UPDATE:-} != true ]] &&
|
# `set -o errexit` is not respected here, but in case of failure we still
|
||||||
|
# err on the safe side by renewing the OCSP staple file.
|
||||||
|
# shellcheck disable=2310
|
||||||
|
if [[ ${FORCE_UPDATE-} != true ]] &&
|
||||||
check_for_existing_ocsp_staple_file; then
|
check_for_existing_ocsp_staple_file; then
|
||||||
lineages_processed["${lineage_name}"]="not updated"$'\t'"valid staple file on disk"
|
lineages_processed["${lineage_name}"]="not updated"$'\t'"valid staple file on disk"
|
||||||
return
|
return
|
||||||
@ -398,7 +524,7 @@ fetch_ocsp_response() {
|
|||||||
set -e
|
set -e
|
||||||
if ((cert_expiry_rc != 0)); then
|
if ((cert_expiry_rc != 0)); then
|
||||||
ERROR_ENCOUNTERED=true
|
ERROR_ENCOUNTERED=true
|
||||||
lineages_processed["${lineage_name}"]="not updated"
|
lineages_processed["${lineage_name}"]="failed to update"
|
||||||
if [[ ${cert_expiry_output} == "Certificate will expire" ]]; then
|
if [[ ${cert_expiry_output} == "Certificate will expire" ]]; then
|
||||||
lineages_processed["${lineage_name}"]+=$'\t'"leaf certificate expired"
|
lineages_processed["${lineage_name}"]+=$'\t'"leaf certificate expired"
|
||||||
fi
|
fi
|
||||||
@ -429,13 +555,13 @@ fetch_ocsp_response() {
|
|||||||
-respout "${temp_output_dir}/${lineage_name}.der" 2>&3)
|
-respout "${temp_output_dir}/${lineage_name}.der" 2>&3)
|
||||||
local -ir ocsp_call_rc=${?}
|
local -ir ocsp_call_rc=${?}
|
||||||
set -e
|
set -e
|
||||||
readonly ocsp_call_output=${ocsp_call_output#${lineage_dir}/cert.pem: }
|
readonly ocsp_call_output=${ocsp_call_output#"${lineage_dir}"/cert.pem: }
|
||||||
local -r cert_status=${ocsp_call_output%%$'\n'*}
|
local -r cert_status=${ocsp_call_output%%$'\n'*}
|
||||||
|
|
||||||
if [[ ${ocsp_call_rc} != 0 || ${cert_status} != good ]]; then
|
if [[ ${ocsp_call_rc} != 0 || ${cert_status} != good ]]; then
|
||||||
ERROR_ENCOUNTERED=true
|
ERROR_ENCOUNTERED=true
|
||||||
|
|
||||||
lineages_processed["${lineage_name}"]="not updated"
|
lineages_processed["${lineage_name}"]="failed to update"
|
||||||
if ((VERBOSITY >= 2)); then
|
if ((VERBOSITY >= 2)); then
|
||||||
lineages_processed["${lineage_name}"]+=$'\t'"${ocsp_call_output//[[:space:]]/ }"
|
lineages_processed["${lineage_name}"]+=$'\t'"${ocsp_call_output//[[:space:]]/ }"
|
||||||
else
|
else
|
||||||
@ -455,44 +581,80 @@ fetch_ocsp_response() {
|
|||||||
print_and_handle_result() {
|
print_and_handle_result() {
|
||||||
local -r header=LINEAGE$'\t'RESULT$'\t'REASON
|
local -r header=LINEAGE$'\t'RESULT$'\t'REASON
|
||||||
|
|
||||||
|
local lineages_processed_marked_up
|
||||||
for lineage_name in "${!lineages_processed[@]}"; do
|
for lineage_name in "${!lineages_processed[@]}"; do
|
||||||
local lineages_processed_formatted+=$'\n'"${lineage_name}"$'\t'"${lineages_processed["${lineage_name}"]}"
|
lineages_processed_marked_up+=$'\n'"${lineage_name}"$'\t'
|
||||||
|
if [[ ${COLORED_STDOUT-} != false ]]; then
|
||||||
|
if [[ ${lineages_processed["${lineage_name}"]} =~ ^updated ]]; then
|
||||||
|
lineages_processed_marked_up+=${GREEN}
|
||||||
|
elif [[ ${lineages_processed["${lineage_name}"]} =~ ^"failed to update" ]]; then
|
||||||
|
lineages_processed_marked_up+=${RED}
|
||||||
|
fi
|
||||||
|
lineages_processed_marked_up+=${lineages_processed["${lineage_name}"]}${COLOR_DEFAULT}
|
||||||
|
else
|
||||||
|
lineages_processed_marked_up+=${lineages_processed["${lineage_name}"]}
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
unset lineage_name
|
unset lineage_name
|
||||||
lineages_processed_formatted=$(sort <<<"${lineages_processed_formatted:-}")
|
lineages_processed_marked_up=$(sort <<<"${lineages_processed_marked_up-}")
|
||||||
readonly lineages_processed_formatted
|
readonly lineages_processed_marked_up
|
||||||
|
|
||||||
if [[ ${RELOAD_WEBSERVER:-} != false ]]; then
|
if [[ ${RELOAD_WEBSERVER-} != false ]]; then
|
||||||
reload_webserver
|
reload_webserver
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local -r output=${header}${lineages_processed_formatted:-}${nginx_status-}
|
local output=${header}${lineages_processed_marked_up-}${nginx_status-}
|
||||||
|
|
||||||
if ((VERBOSITY >= 1)); then
|
if ((VERBOSITY >= 1)); then
|
||||||
if command -v column >&-; then
|
local output_table
|
||||||
column -ts$'\t' <<<"${output}"
|
|
||||||
else
|
|
||||||
# shellcheck disable=2016
|
# shellcheck disable=2016
|
||||||
echo >&2 \
|
output_table=$(column \
|
||||||
'Install the BSD utility `column` for properly formatted output.'$'\n'
|
--output-separator $'\t' \
|
||||||
echo "${output}"
|
--separator $'\t' \
|
||||||
|
--table \
|
||||||
|
<<<"${output}" \
|
||||||
|
2>/dev/null) ||
|
||||||
|
output_table=$(column -s$'\t' -t <<<"${output}" 2>/dev/null) ||
|
||||||
|
local -r column_error=($'\n'
|
||||||
|
'Install the BSD utility `column` for properly formatted output.'
|
||||||
|
'If the version of `column` supports the `--output-separator` flag,'
|
||||||
|
'the output will be formatted as TSV.'
|
||||||
|
$'\n'
|
||||||
|
)
|
||||||
|
readonly output=${output_table:-${output}}
|
||||||
|
unset output_table
|
||||||
|
|
||||||
|
# Extract header to direct it to stderr
|
||||||
|
printf '%s\n' "${output%%$'\n'*}" >&2
|
||||||
|
# Remove header before printing everything else to stdout
|
||||||
|
[[ -n ${!lineages_processed[*]} ]] && printf '%b\n' "${output#*$'\n'}"
|
||||||
|
|
||||||
|
if [[ ${COLORED_STDERR-} != false ]]; then
|
||||||
|
printf %b "${RED}${column_error[*]-}${COLOR_DEFAULT}" >&2
|
||||||
|
else
|
||||||
|
printf %b "${column_error[*]-}" >&2
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
[[ ${ERROR_ENCOUNTERED:-} != true ]]
|
[[ ${ERROR_ENCOUNTERED-} != true ]]
|
||||||
}
|
}
|
||||||
|
|
||||||
reload_webserver() {
|
reload_webserver() {
|
||||||
for lineage_name in "${!lineages_processed[@]}"; do
|
for lineage_name in "${!lineages_processed[@]}"; do
|
||||||
if [[ ${lineages_processed["${lineage_name}"]} == updated ]]; then
|
if [[ ${lineages_processed["${lineage_name}"]} == updated ]]; then
|
||||||
|
local nginx_status
|
||||||
if nginx -s reload >&3 2>&1; then
|
if nginx -s reload >&3 2>&1; then
|
||||||
|
[[ ${COLORED_STDERR-} != false ]] && nginx_status=${GREEN}
|
||||||
# The last line includes a leading space, to workaround the lack of the
|
# The last line includes a leading space, to workaround the lack of the
|
||||||
# `-n` flag in later versions of `column`.
|
# `-n` flag in later versions of `column`.
|
||||||
local -r nginx_status=$'\n\n \t'"nginx reloaded"
|
nginx_status+=$'\n\n \t'"nginx reloaded"
|
||||||
else
|
else
|
||||||
ERROR_ENCOUNTERED=true
|
ERROR_ENCOUNTERED=true
|
||||||
local -r nginx_status=$'\n\n \t'"nginx not reloaded"$'\t'"unable to reload nginx service, try manually"
|
[[ ${COLORED_STDERR-} != false ]] && nginx_status=${RED}
|
||||||
|
nginx_status=$'\n\n \t'"nginx not reloaded"$'\t'"unable to reload nginx service, try manually"
|
||||||
fi
|
fi
|
||||||
|
[[ ${COLORED_STDERR-} != false ]] &&
|
||||||
|
readonly nginx_status+=${COLOR_DEFAULT}
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
@ -502,7 +664,9 @@ reload_webserver() {
|
|||||||
main() {
|
main() {
|
||||||
check_for_dependencies
|
check_for_dependencies
|
||||||
|
|
||||||
parse_cli_arguments "${@}"
|
determine_colored_output
|
||||||
|
|
||||||
|
parse_cli_options "${@}"
|
||||||
|
|
||||||
prepare_output_dir
|
prepare_output_dir
|
||||||
|
|
||||||
|
@ -4,10 +4,54 @@ Description=Fetch OCSP responses for all certificates issued with Certbot
|
|||||||
[Service]
|
[Service]
|
||||||
Type=oneshot
|
Type=oneshot
|
||||||
|
|
||||||
# When systemd v244+ is available, this should be uncommented to enable retries
|
|
||||||
# on failure.
|
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
|
|
||||||
|
CacheDirectory=%N
|
||||||
|
|
||||||
User=root
|
User=root
|
||||||
Group=root
|
Group=root
|
||||||
ExecStart=/usr/local/bin/certbot-ocsp-fetcher -o /etc/nginx/ocsp-cache
|
ExecStart=%N --no-reload-webserver
|
||||||
|
ExecStartPost=systemctl reload nginx.service
|
||||||
|
|
||||||
|
RestartSec=5
|
||||||
|
PrivateDevices=true
|
||||||
|
PrivateTmp=yes
|
||||||
|
PrivateUsers=yes
|
||||||
|
PrivateIPC=true
|
||||||
|
|
||||||
|
NoNewPrivileges=true
|
||||||
|
LockPersonality=true
|
||||||
|
|
||||||
|
CapabilityBoundingSet=
|
||||||
|
ProtectHome=yes
|
||||||
|
ProtectControlGroups=true
|
||||||
|
ProtectKernelTunables=true
|
||||||
|
ProtectKernelModules=true
|
||||||
|
ProtectKernelLogs=true
|
||||||
|
ProtectClock=true
|
||||||
|
ProtectProc=invisible
|
||||||
|
ProcSubset=pid
|
||||||
|
ProtectHostname=true
|
||||||
|
RemoveIPC=true
|
||||||
|
|
||||||
|
RestrictAddressFamilies=AF_INET6 AF_INET AF_UNIX
|
||||||
|
MemoryDenyWriteExecute=true
|
||||||
|
RestrictRealtime=true
|
||||||
|
RestrictNamespaces=true
|
||||||
|
RestrictSUIDSGID=true
|
||||||
|
|
||||||
|
DevicePolicy=strict
|
||||||
|
DeviceAllow=/dev/random r
|
||||||
|
DeviceAllow=/dev/urandom r
|
||||||
|
DeviceAllow=/dev/stdin r
|
||||||
|
DeviceAllow=/dev/stdout r
|
||||||
|
DeviceAllow=/dev/null w
|
||||||
|
|
||||||
|
ProtectSystem=strict
|
||||||
|
InaccessiblePaths=/root/
|
||||||
|
ReadOnlyPaths=/etc/letsencrypt
|
||||||
|
UMask=0077
|
||||||
|
|
||||||
|
SystemCallArchitectures=native
|
||||||
|
SystemCallFilter=@system-service
|
||||||
|
SystemCallFilter=~@clock @debug @module @mount @reboot @swap @resources @cpu-emulation @raw-io @obsolete @keyring @privileged
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
[Unit]
|
[Unit]
|
||||||
Description=Nightly run certbot-ocsp-fetcher
|
Description=Nightly run %N
|
||||||
|
|
||||||
[Timer]
|
[Timer]
|
||||||
OnCalendar=*-*-* 01:00:00
|
OnCalendar=*-*-* 01:00:00
|
||||||
|
Loading…
Reference in New Issue
Block a user