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