From e903be8aedb23c51def64f208f31b45ff6602de0 Mon Sep 17 00:00:00 2001 From: Matthew Saunders Brown Date: Sat, 27 Mar 2021 16:15:03 -0700 Subject: [PATCH] initial commit --- README.md | 4 + bin/letsencrypt-certonly.sh | 90 +++++++++++++++++++ bin/letsencrypt-del.sh | 80 +++++++++++++++++ etc/apache2/conf-available/certbot.conf | 4 + etc/letsencrypt/cli.ini | 11 +++ .../renewal-hooks/deploy/cp-to-etc-ssl.sh | 33 +++++++ .../post/sync-certs-to-etc-ssl.sh | 75 ++++++++++++++++ 7 files changed, 297 insertions(+) create mode 100644 README.md create mode 100644 bin/letsencrypt-certonly.sh create mode 100644 bin/letsencrypt-del.sh create mode 100644 etc/apache2/conf-available/certbot.conf create mode 100644 etc/letsencrypt/cli.ini create mode 100644 etc/letsencrypt/renewal-hooks/deploy/cp-to-etc-ssl.sh create mode 100644 etc/letsencrypt/renewal-hooks/post/sync-certs-to-etc-ssl.sh diff --git a/README.md b/README.md new file mode 100644 index 0000000..24696e7 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +# letsencrypt-tools + +etc/letsencrypt/cli.ini + email = hostmaster@example.com diff --git a/bin/letsencrypt-certonly.sh b/bin/letsencrypt-certonly.sh new file mode 100644 index 0000000..9b48e9c --- /dev/null +++ b/bin/letsencrypt-certonly.sh @@ -0,0 +1,90 @@ +#!/bin/bash + +# must be root +if [ "$USER" != "root" ]; then + exec sudo $0 +fi + +help() +{ + thisfilename=$(basename -- "$0") + echo "$thisfilename" + echo "Create a Let's Encrypt certificate." + echo "" + echo "Usage: $thisfilename domain [OPTIONS]" + echo "" + echo " -h Print this help." + echo " -n Dry Run - don't create cert, just echo command to run." + exit +} + +# check for and set domain +if [ -n "$1" ]; then + if [ $1 == "-h" ]; then + help + else + domain=$1 + shift + # basic but good enough domain name regex validation + if [[ ! $domain =~ ^(([a-zA-Z](-?[a-zA-Z0-9])*)\.)+[a-zA-Z]{2,}$ ]] ; then + echo "ERROR: Invalid domain name: $1" + exit 1 + fi + fi +else + help +fi + +# set any options that were passed +while getopts "hn" opt; do + case "${opt}" in + h ) + help + exit;; + n ) + dryrun=true + ;; + \? ) + echo "Invalid option: $OPTARG" 1>&2 + exit;; + : ) + echo "Invalid option: $OPTARG requires an argument" 1>&2 + exit;; + esac +done + +# set vars +command="certbot certonly --cert-name $domain" +dnscheck=false +ips=(`ip -4 -o addr show | awk '{ print $4 }' | cut -d / -f 1`) + +# check dns for domain +dns=`host -t A $domain|grep 'has address'|awk '{ print $4 }'` +if [[ " ${ips[@]} " =~ " ${dns} " ]]; then + command="$command -d $domain" + dnscheck=true +fi + +# check dns for www subdomain +dns=`host -t A www.$domain|grep 'has address'|awk '{ print $4 }'` +if [[ " ${ips[@]} " =~ " ${dns} " ]]; then + command="$command -d www.$domain" + dnscheck=true +fi + +# copy above www subdomain section and modify as desired to +# automatically check for and add additional subdomains to cert + +# check if any of the dns lookups passed +if [[ "$dnscheck" = "false" ]]; then + echo "All dns checks failed, can't create cert." + exit 1 +fi + +# run (or display) command +if [[ "$dryrun" = "true" ]]; then + echo "Run this command to create cert:" + echo "$command" +else + $command +fi diff --git a/bin/letsencrypt-del.sh b/bin/letsencrypt-del.sh new file mode 100644 index 0000000..df7a61b --- /dev/null +++ b/bin/letsencrypt-del.sh @@ -0,0 +1,80 @@ +#!/bin/bash + +help() +{ + thisfilename=$(basename -- "$0") + echo "Delete an existing Let's Encrypt certificate." + echo "" + echo "Usage: $thisfilename cert-name(domain) [OPTIONS]" + echo "" + echo " -h Print this help." + echo " -r Revoke cert from Let's Encrypt before deleting files." + exit +} + +# check for and set domain +if [ -n "$1" ]; then + if [ $1 == "-h" ]; then + help + else + domain=$1 + shift + # basic but good enough domain name regex validation + if [[ ! $domain =~ ^(([a-zA-Z](-?[a-zA-Z0-9])*)\.)+[a-zA-Z]{2,}$ ]] ; then + echo "ERROR: Invalid domain name: $1" + exit 1 + fi + fi +else + help +fi + +# set any options that were passed +while getopts "hr" opt; do + case "${opt}" in + h ) + help + exit;; + r ) + revoke=true + ;; + \? ) + echo "Invalid option: $OPTARG" 1>&2 + exit;; + : ) + echo "Invalid option: $OPTARG requires an argument" 1>&2 + exit;; + esac +done + + +# start by checking if the renewal config exits +if test -f "/etc/letsencrypt/renewal/$domain.conf"; then + + if [[ "$revoke" = "true" ]]; then + certbot revoke --cert-path /etc/letsencrypt/live/$domain/fullchain.pem + fi + + if test -f "/etc/letsencrypt/renewal/$domain.conf"; then + rm "/etc/letsencrypt/renewal/$domain.conf" + fi + + if test -d "/etc/letsencrypt/live/$domain"; then + rm -r "/etc/letsencrypt/live/$domain" + fi + + if test -d "/etc/letsencrypt/archive/$domain"; then + rm -r "/etc/letsencrypt/archive/$domain" + fi + + if test -f "/etc/ssl/letsencrypt/$domain.pem"; then + rm "/etc/ssl/letsencrypt/$domain.pem"; + fi + + if test -h "/etc/ssl/letsencrypt/mail.$domain.pem"; then + rm "/etc/ssl/letsencrypt/mail.$domain.pem"; + fi + +else + echo "Did not find cert for $domain." +fi diff --git a/etc/apache2/conf-available/certbot.conf b/etc/apache2/conf-available/certbot.conf new file mode 100644 index 0000000..24595f6 --- /dev/null +++ b/etc/apache2/conf-available/certbot.conf @@ -0,0 +1,4 @@ +# ACME (Automatic Certificate Management Environment) + + ProxyPass "http://127.0.0.1:18080/.well-known/acme-challenge/" + diff --git a/etc/letsencrypt/cli.ini b/etc/letsencrypt/cli.ini new file mode 100644 index 0000000..9e8f324 --- /dev/null +++ b/etc/letsencrypt/cli.ini @@ -0,0 +1,11 @@ +# Because we are using logrotate for greater flexibility, disable the +# internal certbot logrotation. +max-log-backups = 0 +email = hostmaster@example.com +agree-tos = True +allow-subset-of-names = True +expand = True +keep-until-expiring = True +non-interactive = True +standalone = True +http-01-port=18080 diff --git a/etc/letsencrypt/renewal-hooks/deploy/cp-to-etc-ssl.sh b/etc/letsencrypt/renewal-hooks/deploy/cp-to-etc-ssl.sh new file mode 100644 index 0000000..11cb6a7 --- /dev/null +++ b/etc/letsencrypt/renewal-hooks/deploy/cp-to-etc-ssl.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +# script is run once per domain after successful renewal with these vars available: +# $RENEWED_LINEAGE=/etc/letsencrypt/live/example.com +# $RENEWED_DOMAINS="example.com www.example.com" + +# makes sure vars were passed and LE cert exits +if [ ! -n "$RENEWED_LINEAGE" ]; then + exit 1 +fi + +if [ ! -f "$RENEWED_LINEAGE/fullchain.pem" ]; then + exit 1 +fi + +if [ ! -d "/etc/ssl/letsencrypt" ]; then + install --owner=root --group=ssl-cert --mode=750 --directory /etc/ssl/letsencrypt +fi + +DOMAIN=`basename $RENEWED_LINEAGE` +PEM="/etc/ssl/letsencrypt/$DOMAIN.pem" + +# If the file doesn't already exist first create empty file with correct ownership and +# permissions. Thus the copied cert is *never* world readable, not even for an instant. +if [ ! -f "$PEM" ]; then + install --owner=root --group=ssl-cert --mode=640 /dev/null $PEM +fi + +cat $RENEWED_LINEAGE/fullchain.pem > $PEM +cat $RENEWED_LINEAGE/privkey.pem >> $PEM +# set perms & ownership again just for good measure, should be redundant +chmod 640 $PEM +chown root:ssl-cert $PEM diff --git a/etc/letsencrypt/renewal-hooks/post/sync-certs-to-etc-ssl.sh b/etc/letsencrypt/renewal-hooks/post/sync-certs-to-etc-ssl.sh new file mode 100644 index 0000000..fd6d27a --- /dev/null +++ b/etc/letsencrypt/renewal-hooks/post/sync-certs-to-etc-ssl.sh @@ -0,0 +1,75 @@ +#!/bin/bash + +# sync-certs-to-etc-ssl.sh +# +# Takes all Let's Encrypt certs & keys and concats them in +# to pem files for use by apache, dovecot, exim, haproxy, etc. +# +# Install this script in to /etc/letsencrypt/renewal-hooks/post/ +# to have it run automatically after attempting to obtain/renew certificates. +# +# Alternatively you can put the script in a different location and then +# run sync-certs-to-etc-ssl.sh manually after creating or renewing certs, +# or specificy the path to the script with the --post-hook cerbot command option +# to have it automatically run when attempting to obtain/renew certificates. + +# make dir if it doesn't already exist +if [[ ! -e /etc/ssl/letsencrypt/ ]]; then + install --owner=root --group=ssl-cert --mode=750 --directory /etc/ssl/letsencrypt +fi + +# check that Let's Encrpyt archive dir exists before proceeding +if [ ! -d "/etc/letsencrypt/archive" ]; then + exit +fi + +# Get list of Let's Encrpyt certs +# Check the "archive" dir instead of "live" as "live" +# has a README file that we don't want in our array. +cd /etc/letsencrypt/archive/ +lecerts=(*) +# get list of certs in the SSL dir +cd /etc/ssl/letsencrypt/ +sslcerts=(*) + +# First cycle thru /etc/ssl/letsencrypt/ and remove any pem +# files that don't have a cert in /etc/ssl/letsencrypt/ +# (removes certs that have been deleted from letsencrypt). +for sslcert in "${!sslcerts[@]}" +do + # set cert variable + cert=${sslcerts[$sslcert]} + # remove .pem from end of $cert + cert=$(basename $cert .pem) + if [[ ! " ${lecerts[@]} " =~ " $cert " ]]; then + rm /etc/ssl/letsencrypt/${sslcerts[$sslcert]} + fi +done + +# add / update pem files in /etc/ssl/letsencrypt/ +for lecert in "${!lecerts[@]}" +do + # set cert variable + cert=${lecerts[$lecert]} + if [ -f "/etc/ssl/letsencrypt/$cert.pem" ]; then + # /etc/ssl/letsencrypt/ pem file already exists + # get modified times and only upate if newer + LECERTTIME=`date +%s -r /etc/letsencrypt/live/$cert/fullchain.pem` + SSLCERTTIME=`date +%s -r /etc/ssl/letsencrypt/$cert.pem` + if [[ $LECERTTIME -gt $SSLCERTTIME ]]; then + # make sure perms are correct, should be redundant + chmod 640 /etc/ssl/letsencrypt/$cert.pem + chown root:ssl-cert /etc/ssl/letsencrypt/$cert.pem + # replace existing cert with new data + cat /etc/letsencrypt/live/$cert/fullchain.pem > /etc/ssl/letsencrypt/$cert.pem + cat /etc/letsencrypt/live/$cert/privkey.pem >> /etc/ssl/letsencrypt/$cert.pem + fi + else + # /etc/ssl/letsencrypt/ pem file does not exists. First create + # empty file with correct ownership and permissions. Thus the + # copied cert is *never* world readable, not even for an instant. + install --owner=root --group=ssl-cert --mode=640 /dev/null /etc/ssl/letsencrypt/$cert.pem + cat /etc/letsencrypt/live/$cert/fullchain.pem > /etc/ssl/letsencrypt/$cert.pem + cat /etc/letsencrypt/live/$cert/privkey.pem >> /etc/ssl/letsencrypt/$cert.pem + fi +done