Initial commit
This commit is contained in:
parent
4888ebc238
commit
e052241f80
2 changed files with 506 additions and 0 deletions
499
acme-tool
Executable file
499
acme-tool
Executable file
|
@ -0,0 +1,499 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Internal Initialization
|
||||||
|
|
||||||
|
if [[ -d "${PWD}/.acme.sh" ]]; then
|
||||||
|
export LE_WORKING_DIR="${PWD}/.acme.sh"
|
||||||
|
export AWS_CONFIG_FILE="${PWD}/.acme.sh/aws/config"
|
||||||
|
export AWS_SHARED_CREDENTIALS_FILE="${PWD}/.acme.sh/aws/credentials"
|
||||||
|
elif [[ -d "${HOME}/.acme.sh" ]]; then
|
||||||
|
export LE_WORKING_DIR="${HOME}/.acme.sh"
|
||||||
|
export AWS_CONFIG_FILE="${HOME}/.acme.sh/aws/config"
|
||||||
|
export AWS_SHARED_CREDENTIALS_FILE="${HOME}/.acme.sh/aws/credentials"
|
||||||
|
elif [[ -d "/etc/acme" ]]; then
|
||||||
|
export LE_WORKING_DIR="/etc/acme"
|
||||||
|
export AWS_CONFIG_FILE="/etc/acme/aws/config"
|
||||||
|
export AWS_SHARED_CREDENTIALS_FILE="/etc/acme/aws/credentials"
|
||||||
|
else
|
||||||
|
if [[ "${1,,}" != "install" ]]; then
|
||||||
|
echo "ERROR: Cannot find acme.sh working directory in:"
|
||||||
|
echo " \"${PWD}/.acme.sh\""
|
||||||
|
echo " \"${HOME}/.acme.sh\""
|
||||||
|
echo " \"/etc/acme\""
|
||||||
|
echo "Either one of these directories need to exist to act on behalve of acme.sh's"
|
||||||
|
echo "home directory for configuration, hooks, and certificates."
|
||||||
|
exit 99
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
script_name=$(readlink -e $0)
|
||||||
|
script_dir=$(dirname "$script_name")
|
||||||
|
|
||||||
|
if [[ -r "${LE_WORKING_DIR}/acme-tool.conf" ]]; then
|
||||||
|
. "${LE_WORKING_DIR}/acme-tool.conf"
|
||||||
|
else
|
||||||
|
echo "ACME-Tool Configuration not found in '${LE_WORKING_DIR}/acme-tool.conf'"
|
||||||
|
exit 99
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -d "${LE_WORKING_DIR}/domains" ]]; then
|
||||||
|
mkdir "${LE_WORKING_DIR}/domains"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -d "${PWD}/hooks/pre.d" || -d "${PWD}/hooks/post.d" ]]; then
|
||||||
|
hook_dir="${PWD}/hooks"
|
||||||
|
elif [[ -d "${LE_WORKING_DIR}/hooks/pre.d" || -d "${LE_WORKING_DIR}/hooks/post.d" ]]; then
|
||||||
|
hook_dir="${LE_WORKING_DIR}/hooks"
|
||||||
|
elif [[ -d "/etc/acme/hooks/pre.d" || -d "/etc/acme/hooks/post.d" ]]; then
|
||||||
|
hook_dir="/etc/acme/hooks"
|
||||||
|
else
|
||||||
|
if [[ "${1,,}" != "install" ]]; then
|
||||||
|
echo "Hook dir does not exist in at least one of the following paths:"
|
||||||
|
echo " \"${PWD}/hooks\""
|
||||||
|
echo " \"${LE_WORKING_DIR}/hooks\""
|
||||||
|
echo " \"/etc/acme/hooks\""
|
||||||
|
echo "Either of these directories need to contain one or both of pre.d and post.d"
|
||||||
|
echo "directories for hooks to run."
|
||||||
|
exit 99
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# Functions
|
||||||
|
|
||||||
|
trim() {
|
||||||
|
local extglobWasOff=1
|
||||||
|
local var=$1
|
||||||
|
|
||||||
|
shopt extglob >/dev/null && extglobWasOff=0
|
||||||
|
(( extglobWasOff )) && shopt -s extglob
|
||||||
|
var=${var##+([[:space:]])}
|
||||||
|
var=${var%%+([[:space:]])}
|
||||||
|
(( extglobWasOff )) && shopt -u extglob
|
||||||
|
echo -n "$var"
|
||||||
|
}
|
||||||
|
|
||||||
|
run-parts() {
|
||||||
|
# Ignore *~ and *, scripts
|
||||||
|
for i in $(LC_ALL=C; echo ${1%/}/*[^~,]) ; do
|
||||||
|
[[ -d $i ]] && continue
|
||||||
|
# Don't run *.{rpmsave,rpmorig,rpmnew,swp,cfsaved} scripts
|
||||||
|
[[ "${i%.cfsaved}" != "${i}" ]] && continue
|
||||||
|
[[ "${i%.rpmsave}" != "${i}" ]] && continue
|
||||||
|
[[ "${i%.rpmorig}" != "${i}" ]] && continue
|
||||||
|
[[ "${i%.rpmnew}" != "${i}" ]] && continue
|
||||||
|
[[ "${i%.swp}" != "${i}" ]] && continue
|
||||||
|
[[ "${i%,v}" != "${i}" ]] && continue
|
||||||
|
|
||||||
|
# jobs.deny prevents specific files from being executed
|
||||||
|
# jobs.allow prohibits all non-named jobs from being run.
|
||||||
|
# can be used in conjunction but there's no reason to do so.
|
||||||
|
if [[ -r "${1}/jobs.deny" ]]; then
|
||||||
|
grep -q "^$(basename $i)$" "${1}/jobs.deny" && continue
|
||||||
|
fi
|
||||||
|
if [[ -r "${1}/jobs.allow" ]]; then
|
||||||
|
grep -q "^$(basename $i)$" "${1}/jobs.allow" || continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -e $i ]]; then
|
||||||
|
if [[ -r "${1}/whitelist" ]]; then
|
||||||
|
grep -q "^$(basename $i)$" "${1}/whitelist" && continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -x "$i" ]]; then
|
||||||
|
# run executable files
|
||||||
|
echo "$i"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
run-hook() {
|
||||||
|
local hook=$1
|
||||||
|
local errors=0
|
||||||
|
shift
|
||||||
|
|
||||||
|
if [[ ! -d "${hook_dir}/${hook}" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
while read s
|
||||||
|
do
|
||||||
|
"$s" $*
|
||||||
|
err=$?
|
||||||
|
|
||||||
|
if [[ $err -ne 0 ]]; then
|
||||||
|
let errors++
|
||||||
|
fi
|
||||||
|
done < <(run-parts "${hook_dir}/${hook}")
|
||||||
|
|
||||||
|
return $errors
|
||||||
|
}
|
||||||
|
|
||||||
|
get_arg_domains() {
|
||||||
|
local domain=$1
|
||||||
|
|
||||||
|
if [[ ! -f "${LE_WORKING_DIR}/domains/${domain}" ]]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
while read d; do
|
||||||
|
d=$(trim "$d")
|
||||||
|
if [[ ${d:0:1} == '#' ]] || [[ -z "$d" ]]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
echo -n " -d $d"
|
||||||
|
done < "${LE_WORKING_DIR}/domains/${domain}"
|
||||||
|
}
|
||||||
|
|
||||||
|
get_conf_domains() {
|
||||||
|
local di
|
||||||
|
local dn
|
||||||
|
|
||||||
|
while read di;
|
||||||
|
do
|
||||||
|
echo "$di"
|
||||||
|
done < <(find "${LE_WORKING_DIR}/domains/" -mindepth 1 -maxdepth 1 -type f -exec basename "{}" \; | sort)
|
||||||
|
}
|
||||||
|
|
||||||
|
get_acme_domains() {
|
||||||
|
local di
|
||||||
|
|
||||||
|
while read di;
|
||||||
|
do
|
||||||
|
echo "$di"
|
||||||
|
done < <(find "${LE_WORKING_DIR}/" -mindepth 1 -maxdepth 1 -type d -name '*.*' -exec basename "{}" \; | sed -e 's/_ecc//' | uniq | sort)
|
||||||
|
}
|
||||||
|
|
||||||
|
get_acme_domains_types() {
|
||||||
|
local domain=$1
|
||||||
|
|
||||||
|
if [[ -d "${LE_WORKING_DIR}/${domain}" ]]; then
|
||||||
|
echo -n "(rsa)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -d "${LE_WORKING_DIR}/${domain}_ecc" ]]; then
|
||||||
|
echo -n "(ecc)"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
check_domains_file() {
|
||||||
|
local domain=$1
|
||||||
|
if [[ -f "${LE_WORKING_DIR}/domains/${domain}" ]]; then
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
is_certs_different() {
|
||||||
|
local domain=$1
|
||||||
|
local file_domains
|
||||||
|
local cert_domains
|
||||||
|
|
||||||
|
if check_domains_file "$domain"; then
|
||||||
|
if [[ ! -f "${LE_WORKING_DIR}/${domain}/${domain}.cer" ]] && \
|
||||||
|
[[ ! -f "${LE_WORKING_DIR}/${domain}_ecc/${domain}.cer" ]]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "ERROR: '${LE_WORKING_DIR}/domains/${domain}' does not exist."
|
||||||
|
exit 3
|
||||||
|
fi
|
||||||
|
|
||||||
|
cert_domains=$(
|
||||||
|
openssl x509 -in certs/"${domain}"/"${domain}".cer -noout -text | awk '
|
||||||
|
/X509v3 Subject Alternative Name/ {
|
||||||
|
getline
|
||||||
|
gsub(/ /, "", $0)
|
||||||
|
gsub(/DNS:/, "", $0)
|
||||||
|
gsub(/IPAddress:/, "", $0)
|
||||||
|
gsub(",", "\n")
|
||||||
|
print
|
||||||
|
}' | sort | tr -d '\n')
|
||||||
|
file_domains=$(sort < "${LE_WORKING_DIR}/domains/${domain}" | tr -d '\n')
|
||||||
|
|
||||||
|
if [[ "$cert_domains" == "$file_domains" ]]; then
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
issue_certs() {
|
||||||
|
local domain=$1
|
||||||
|
shift
|
||||||
|
local args=$*
|
||||||
|
|
||||||
|
if [[ -d "${LE_WORKING_DIR}/${domain}" ]]; then
|
||||||
|
echo "Running Lets Encrypt on $domain for RSA${keysize_rsa}"
|
||||||
|
"$LE_WORKING_DIR"/acme.sh \
|
||||||
|
--issue --dns dns_aws --keylength $keysize_rsa \
|
||||||
|
--post-hook "$script_name hook sync.d" \
|
||||||
|
$(get_arg_domains "$domain") $args
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -d "${LE_WORKING_DIR}/${domain}_ecc" ]]; then
|
||||||
|
echo "Running Lets Encrypt on $domain for EC${keysize_ecc}"
|
||||||
|
"$LE_WORKING_DIR"/acme.sh \
|
||||||
|
--issue --dns dns_aws --keylength ec-$keysize_ecc \
|
||||||
|
--post-hook "$script_name hook sync.d" \
|
||||||
|
$(get_arg_domains "$domain") $args
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
cron_certs() {
|
||||||
|
"${LE_WORKING_DIR}"/acme.sh --cron --home ${LE_WORKING_DIR} --renew-hook "${script_name} sync upload"
|
||||||
|
}
|
||||||
|
|
||||||
|
create_certs() {
|
||||||
|
local domain=$1
|
||||||
|
shift
|
||||||
|
local types=$*
|
||||||
|
|
||||||
|
for t in $types; do
|
||||||
|
case ${t,,} in
|
||||||
|
rsa)
|
||||||
|
if [[ ! -d "${LE_WORKING_DIR}/${domain}" ]]; then
|
||||||
|
mkdir "${LE_WORKING_DIR}/${domain}"
|
||||||
|
else
|
||||||
|
if [[ -f "${LE_WORKING_DIR}/${domain}/${domain}.cer" ]]; then
|
||||||
|
echo "ERROR: '$domain' already initialized"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
ec|ecc)
|
||||||
|
if [[ ! -d "${LE_WORKING_DIR}/${domain}_ecc" ]]; then
|
||||||
|
mkdir "${LE_WORKING_DIR}/${domain}_ecc"
|
||||||
|
else
|
||||||
|
if [[ -f "${LE_WORKING_DIR}/${domain}_ecc/${domain}.cer" ]]; then
|
||||||
|
echo "ERROR: '$domain' already initialized"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
issue_certs $domain
|
||||||
|
}
|
||||||
|
|
||||||
|
s3_upload() {
|
||||||
|
local domain
|
||||||
|
local dompart
|
||||||
|
local errors=0
|
||||||
|
local totalerrors=0
|
||||||
|
|
||||||
|
for domain in $(get_acme_domains); do
|
||||||
|
errors=0
|
||||||
|
|
||||||
|
for dompart in "${domain}" "${domain}_ecc"; do
|
||||||
|
if [[ -d "${LE_WORKING_DIR}/${dompart}" ]]; then
|
||||||
|
run-hook pre.d "$domain"
|
||||||
|
aws s3 sync "${LE_WORKING_DIR}/${dompart}/" "${s3_bucket}${s3_folder}${dompart}/"
|
||||||
|
if [[ $? -ne 0 ]]; then
|
||||||
|
let errors++
|
||||||
|
fi
|
||||||
|
run-hook post.d "$domain"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ $errors -eq 0 ]]; then
|
||||||
|
run-hook deploy.d "$domain"
|
||||||
|
fi
|
||||||
|
totalerrors=$((totalerrors+errors))
|
||||||
|
done
|
||||||
|
return $totalerrors
|
||||||
|
}
|
||||||
|
|
||||||
|
s3_check() {
|
||||||
|
local domain
|
||||||
|
local dompart
|
||||||
|
local status=1
|
||||||
|
|
||||||
|
for domain in $(get_acme_domains); do
|
||||||
|
for dompart in "$domain" "${domain}_ecc"; do
|
||||||
|
if [[ -d "${LE_WORKING_DIR}/${dompart}" ]]; then
|
||||||
|
aws --dryrun s3 sync "${s3_bucket}${s3_folder}${dompart}/" "${LE_WORKING_DIR}/${dompart}/" | grep download &>/dev/null
|
||||||
|
if [[ $? -eq 0 ]]; then
|
||||||
|
status=0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
done
|
||||||
|
return $status
|
||||||
|
}
|
||||||
|
|
||||||
|
s3_show() {
|
||||||
|
local domain
|
||||||
|
local dompart
|
||||||
|
|
||||||
|
for domain in $(get_acme_domains); do
|
||||||
|
for dompart in "$domain" "${domain}_ecc"; do
|
||||||
|
if [[ -d "${LE_WORKING_DIR}/${dompart}" ]]; then
|
||||||
|
aws --dryrun s3 sync "${s3_bucket}${s3_folder}${dompart}/" "${LE_WORKING_DIR}/${dompart}/" | sed -e "s|.* to .*\/\(${dompart}.*\)$|\1|"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
s3_download() {
|
||||||
|
local domain
|
||||||
|
local dompart
|
||||||
|
local errors=0
|
||||||
|
local totalerrors=0
|
||||||
|
|
||||||
|
for domain in $(get_acme_domains); do
|
||||||
|
errors=0
|
||||||
|
|
||||||
|
for dompart in "${domain}" "${domain}_ecc"; do
|
||||||
|
if [[ -d "${LE_WORKING_DIR}/${dompart}" ]]; then
|
||||||
|
aws s3 sync "${s3_bucket}${s3_folder}${dompart}/" "${LE_WORKING_DIR}/${dompart}/"
|
||||||
|
if [[ $? -ne 0 ]]; then
|
||||||
|
let errors++
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ $errors -eq 0 ]]; then
|
||||||
|
run-hook deploy.d "$domain"
|
||||||
|
fi
|
||||||
|
totalerrors=$((totalerrors+errors))
|
||||||
|
done
|
||||||
|
return $totalerrors
|
||||||
|
}
|
||||||
|
|
||||||
|
install_system() {
|
||||||
|
local acme_basedir
|
||||||
|
|
||||||
|
if [[ "$USER" == "root" ]]; then
|
||||||
|
acme_basedir="/etc/acme"
|
||||||
|
else
|
||||||
|
acme_basedir="${HOME}/.acme.sh"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "${acme_basedir}/domains"
|
||||||
|
mkdir -p "${acme_basedir}/hooks/pre.d"
|
||||||
|
mkdir -p "${acme_basedir}/hooks/post.d"
|
||||||
|
mkdir -p "${acme_basedir}/hooks/sync.d"
|
||||||
|
mkdir -p "${acme_basedir}/hooks/deploy.d"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Main
|
||||||
|
|
||||||
|
case ${1,,} in
|
||||||
|
install)
|
||||||
|
install_system
|
||||||
|
;;
|
||||||
|
init)
|
||||||
|
if [[ -n "$2" ]] && [[ -n "$3" ]]; then
|
||||||
|
do=${2,,}
|
||||||
|
shift 2
|
||||||
|
if [[ -n "$EDITOR" ]]; then
|
||||||
|
$EDITOR "${LE_WORKING_DIR}/domains/${do}"
|
||||||
|
fi
|
||||||
|
create_certs $do $*
|
||||||
|
else
|
||||||
|
echo "ERROR: init requires a domain and type"
|
||||||
|
exit 6
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
issue|update)
|
||||||
|
for d in $(get_conf_domains); do
|
||||||
|
if is_certs_different $d; then
|
||||||
|
echo "$d: Match"
|
||||||
|
else
|
||||||
|
echo "$d: Changes"
|
||||||
|
issue_certs "$d" --force
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
;;
|
||||||
|
list)
|
||||||
|
echo "Configured Domains:"
|
||||||
|
get_conf_domains
|
||||||
|
echo
|
||||||
|
echo "ACME Domains:"
|
||||||
|
for d in $(get_acme_domains); do
|
||||||
|
echo "$d $(get_acme_domains_types "$d")"
|
||||||
|
done
|
||||||
|
;;
|
||||||
|
sync)
|
||||||
|
case "${2,,}" in
|
||||||
|
upload)
|
||||||
|
echo "Synchronizing Let's Encrypt Certificates (upload)"
|
||||||
|
s3_upload
|
||||||
|
err=$?
|
||||||
|
if [[ $err -ne 0 ]]; then
|
||||||
|
echo "Upload(s) failed: $err"
|
||||||
|
fi
|
||||||
|
echo "Done"
|
||||||
|
;;
|
||||||
|
check|"")
|
||||||
|
echo "Checking for new Let's Encrypt Certificates"
|
||||||
|
if s3_check; then
|
||||||
|
echo "There are new certificates available"
|
||||||
|
else
|
||||||
|
echo "No new certificates available"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
show)
|
||||||
|
echo "Listing updated Let's Encrypt Certificates:"
|
||||||
|
s3_show
|
||||||
|
;;
|
||||||
|
down|download)
|
||||||
|
echo "Synchronizing Let's Encrypt Certificates (download)"
|
||||||
|
if s3_check; then
|
||||||
|
s3_download
|
||||||
|
err=$?
|
||||||
|
if [[ $err -ne 0 ]]; then
|
||||||
|
echo "Download(s) failed: $err"
|
||||||
|
fi
|
||||||
|
echo "Done"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "ERROR: Unknown sync command. Available sync commands:"
|
||||||
|
echo " check, show, download, upload"
|
||||||
|
exit 9
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
edit)
|
||||||
|
if [[ -z "$EDITOR" ]]; then
|
||||||
|
echo "ERROR: EDITOR environment not set"
|
||||||
|
exit 3
|
||||||
|
fi
|
||||||
|
|
||||||
|
for d in $(get_conf_domains); do
|
||||||
|
if [[ "$d" == "${2,,}" ]]; then
|
||||||
|
echo "Editing domain: $d"
|
||||||
|
$EDITOR "${LE_WORKING_DIR}/domains/${d}"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
;;
|
||||||
|
hook)
|
||||||
|
if [[ -n "$2" ]]; then
|
||||||
|
shift 2
|
||||||
|
|
||||||
|
case ${2,,} in
|
||||||
|
pre.d) run-hook pre.d $*;;
|
||||||
|
post.d) run-hook post.d $*;;
|
||||||
|
sync.d) run-hook sync.d $*;;
|
||||||
|
deploy.d) run-hook deploy.d $*;;
|
||||||
|
*) echo "ERROR: Unknown hook \"${2,,}\". Available hooks:"
|
||||||
|
echo " pre.d Before running issue/renew/sync"
|
||||||
|
echo " post.d After running issue/renew/sync"
|
||||||
|
echo " sync.d After running issue/renew"
|
||||||
|
echo " deploy.d After successfully running issue/renew/sync"
|
||||||
|
error 6
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
cron)
|
||||||
|
cron_certs
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown command '$1'. Available commands are: init, issue, list, sync, cron"
|
||||||
|
;;
|
||||||
|
esac
|
7
acme-tool.conf.sample
Normal file
7
acme-tool.conf.sample
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# Configuration
|
||||||
|
|
||||||
|
keysize_rsa=4096
|
||||||
|
keysize_ecc=256
|
||||||
|
s3_bucket=s3://linux-help-certs/
|
||||||
|
s3_folder=
|
||||||
|
|
Loading…
Reference in a new issue