Skip to content

Instantly share code, notes, and snippets.

@Syquel
Created August 6, 2019 20:34
Show Gist options
  • Save Syquel/41e072a7f91abd0f6e08eafc67b65fa0 to your computer and use it in GitHub Desktop.
Save Syquel/41e072a7f91abd0f6e08eafc67b65fa0 to your computer and use it in GitHub Desktop.
A script to setup ECC / ECDSA certificates with Certbot including automatic renewal support.
#!/bin/bash
set -euf -o pipefail
## Prerequisites
# This script assumes lexicon-dns to be installed and configured for your specific DNS provider
# for the Let's Encrypt DNS-challenge.
# Further it is assumed that the lexicon-dns certbot-hook is installed under ${CONF_BASE_PATH}/renewal-hooks/manual/lexicon.sh .
# The lexicon-dns hook for certbot can be downloaded from https://github.com/AnalogJ/lexicon/blob/master/examples/certbot.default.sh .
# Make sure to adjust the variables in the section "Configuration" according to your installation.
## Configuration
ACCOUNT_HASH="CHANGE_ME" # The user-specific Let's Encrypt account hash.
TMP_BASE_PATH="/tmp/certbot-ecdsa" # The base path to be used for temporary files created by this script.
CONF_BASE_PATH="/etc/letsencrypt" # The configuration base path of the certbot installation.
## Helper function declarations
function join_by { local IFS="$1"; shift; echo "$*"; }
# Script start
if [ "$#" -lt 1 ]; then
echo "Domains parameter missing!"
echo "Example usage: $0 \"mydomain.example\" \"*.mydomain.example\""
exit 1
fi
if [ ! -d "${CONF_BASE_PATH}/accounts/acme-v02.api.letsencrypt.org/directory/${ACCOUNT_HASH}" ]; then
echo "The ACCOUNT_HASH is not correctly configured."
exit 4
fi
DOMAINS=(${@})
PRIMARY_DOMAIN="${DOMAINS[0]}"
TMP_PATH="${TMP_BASE_PATH}/$PRIMARY_DOMAIN"
CONF_ARCHIVE_PATH="${CONF_BASE_PATH}/archive/${PRIMARY_DOMAIN}-ecdsa"
CONF_LIVE_PATH="${CONF_BASE_PATH}/live/${PRIMARY_DOMAIN}-ecdsa"
CONF_RENEW_PATH="${CONF_BASE_PATH}/renewal/${PRIMARY_DOMAIN}-ecdsa.conf"
## Directory structure setup
if [ -d "${CONF_LIVE_PATH}" ]; then
echo "Live Path already exists!";
exit 2
fi
if [ -d "${CONF_ARCHIVE_PATH}" ]; then
echo "Archive Path already exists!";
exit 3
fi
if [ -d "${TMP_PATH}" ]; then
rm -r "${TMP_PATH}"
fi
mkdir "${CONF_LIVE_PATH}"
mkdir "${CONF_ARCHIVE_PATH}"
mkdir -p "${TMP_PATH}"
## ECC private key and CSR generation
SUBJECT_ALT=$(join_by , "${DOMAINS[@]/#/DNS:}")
openssl ecparam -genkey -name secp384r1 -out "${CONF_ARCHIVE_PATH}/privkey1.pem"
openssl req -new -key "${CONF_ARCHIVE_PATH}/privkey1.pem" -out "${TMP_PATH}/cert.csr" -sha256 \
-subj "/CN=${PRIMARY_DOMAIN}" -reqexts reqext -extensions reqext \
-config <(printf "[ req ]\nprompt=no\nencrypt_key = no\ndistinguished_name = dname\n[ reqext ]\nsubjectAltName=${SUBJECT_ALT}\n[ dname ]\nCN = ${PRIMARY_DOMAIN}\nemailAddress = admin@${PRIMARY_DOMAIN}")
## Let's Encrypt certificate request
certbot certonly -n --csr "${TMP_PATH}/cert.csr" --manual --preferred-challenges dns \
--manual-auth-hook "${CONF_BASE_PATH}/renewal-hooks/manual/lexicon.sh auth" \
--manual-cleanup-hook "${CONF_BASE_PATH}/renewal-hooks/manual/lexicon.sh cleanup" \
--manual-public-ip-logging-ok \
--cert-path "${CONF_ARCHIVE_PATH}/cert1.pem" --chain-path "${CONF_ARCHIVE_PATH}/chain1.pem" \
--fullchain-path="${CONF_ARCHIVE_PATH}/fullchain1.pem" ${DOMAINS[@]/#/-d }
## Local certificate enablement
ln -s "${CONF_ARCHIVE_PATH}/privkey1.pem" "${CONF_LIVE_PATH}/privkey.pem"
ln -s "${CONF_ARCHIVE_PATH}/cert1.pem" "${CONF_LIVE_PATH}/cert.pem"
ln -s "${CONF_ARCHIVE_PATH}/chain1.pem" "${CONF_LIVE_PATH}/chain.pem"
ln -s "${CONF_ARCHIVE_PATH}/fullchain1.pem" "${CONF_LIVE_PATH}/fullchain.pem"
## Cleanup
rm -r "${TMP_PATH}"
# Automatic renewal enablement
cat <<EOT >> ${CONF_RENEW_PATH}
# renew_before_expiry = 30 days
cert = ${CONF_LIVE_PATH}/cert.pem
privkey = ${CONF_LIVE_PATH}/privkey.pem
chain = ${CONF_LIVE_PATH}/chain.pem
fullchain = ${CONF_LIVE_PATH}/fullchain.pem
version = 0.31.0
archive_dir = ${CONF_ARCHIVE_PATH}
# Options and defaults used in the renewal process
[renewalparams]
account = ${ACCOUNT_HASH}
reuse_key = True
server = https://acme-v02.api.letsencrypt.org/directory
pref_challs = dns-01,
authenticator = manual
manual_auth_hook = ${CONF_BASE_PATH}/renewal-hooks/manual/lexicon.sh auth
manual_cleanup_hook = ${CONF_BASE_PATH}/renewal-hooks/manual/lexicon.sh cleanup
manual_public_ip_logging_ok = True
EOT
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment