#!/bin/bash #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%# # PainlessLE – A wrapper script for Certbot [Thomas Lange ] # #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%# # # # Easily get an X.509 certificate from the Let's Encrypt Certificate Authority # # for a bunch of hostnames without having an HTTP server installed. The script # # assumes that you have an existing private key stored within your desired # # install directory (with the filename which is defined in "${CONFIDENTIAL}"). # # # # USAGE: # # painless-le [OPTIONS] TARGET_DIR DNS_DOMAIN [DNS_DOMAIN ...] # # # # TARGET_DIR: Path to the target directory for the certificate files. # # DNS_DOMAIN: One or more DNS hostnames to include in the certficate. # # # # OPTION [-K]: Filename of the existing private key in target directory. # # OPTION [-I]: Filename for the intermediate certificate in target directory. # # OPTION [-C]: Filename for the standalone certificate in target directory. # # OPTION [-F]: Filename for the certificate+intermediate in target directory. # # # #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%# #=============================================================================== # Normalize command-line arguments with GNU getopt #=============================================================================== set -- $(getopt -uo K:I:C:F: -- "$@") #=============================================================================== # Parse command-line options with getopts #=============================================================================== while getopts :K:I:C:F: option do case $option in K) ARGUMENT_CONFIDENTIAL="$OPTARG" ;; I) ARGUMENT_INTERMEDIATE="$OPTARG" ;; C) ARGUMENT_CERTIFICATE_ONLY="$OPTARG" ;; F) ARGUMENT_CERTIFICATE_FULL="$OPTARG" ;; esac done; shift $((OPTIND-1)) #=============================================================================== # Set positional argument variables #=============================================================================== TARGET_DIR="$1" DNS_DOMAIN="${@:2}" #=============================================================================== # Check if required positional arguments are given #=============================================================================== [ -z "${TARGET_DIR}" ] && echo "$0: Missing argument: TARGET_DIR" >&2 [ -z "${DNS_DOMAIN}" ] && echo "$0: Missing argument: DNS_DOMAIN" >&2 #=============================================================================== # Exit script if required positional argument is missing #=============================================================================== [ -z "${TARGET_DIR}" ] || [ -z "${DNS_DOMAIN}" ] && exit 1 #=============================================================================== # Define the ACME endpoint address #=============================================================================== LETSENCRYPT_ENDPOINT="https://acme-v02.api.letsencrypt.org/directory" #LETSENCRYPT_ENDPOINT="https://acme-staging-v02.api.letsencrypt.org/directory" #=============================================================================== # Define commands who are executed BEFORE and AFTER the ACME challenge #=============================================================================== #LETSENCRYPT_COMMAND_BEFORE="systemctl stop apache2" #LETSENCRYPT_COMMAND_AFTER="systemctl start apache2" #=============================================================================== # Define required paths #=============================================================================== OPENSSLCONF="/etc/ssl/openssl.cnf" REQUESTFILE=`mktemp /tmp/painless-le.XXXXXXXXXX.csr` CONFIDENTIAL="${TARGET_DIR%/}/${ARGUMENT_CONFIDENTIAL:-confidential.pem}" INTERMEDIATE="${TARGET_DIR%/}/${ARGUMENT_INTERMEDIATE:-intermediate.pem}" CERTIFICATE_ONLY="${TARGET_DIR%/}/${ARGUMENT_CERTIFICATE_ONLY:-certificate_only.pem}" CERTIFICATE_FULL="${TARGET_DIR%/}/${ARGUMENT_CERTIFICATE_FULL:-certificate_full.pem}" #=============================================================================== # Delete Certificate-Signing-Request (CSR) file on exit #=============================================================================== trap 'rm ${REQUESTFILE}' EXIT #=============================================================================== # Generate Certificate-Signing-Request (CSR) #=============================================================================== openssl req -config <(cat "${OPENSSLCONF}" <(printf "[SAN]\nsubjectAltName=DNS:`echo ${DNS_DOMAIN} | sed "s/ /,DNS:/g"`")) \ -new -sha256 -key "${CONFIDENTIAL}" -out "${REQUESTFILE}" -outform der -reqexts SAN -subj "/" #=============================================================================== # Checking if Certificate-Signing-Request (CSR) was successfully created #=============================================================================== if [ $? != 0 ]; then echo "$0: Certificate-Signing-Request (CSR) could not be created!" >&2 exit 1 fi #=============================================================================== # Execute defined command BEFORE the ACME challenge is started #=============================================================================== [ ! -z "${LETSENCRYPT_COMMAND_BEFORE}" ] && eval $LETSENCRYPT_COMMAND_BEFORE #=============================================================================== # Execute Let's Encrypt and accomplish the ACME challenge to get the certificate #=============================================================================== certbot certonly --authenticator standalone --server "${LETSENCRYPT_ENDPOINT}" --csr "${REQUESTFILE}" \ --cert-path "${CERTIFICATE_ONLY}.$$" --fullchain-path "${CERTIFICATE_FULL}.$$" --chain-path "${INTERMEDIATE}.$$" #=============================================================================== # Checking if Certbot has successfully accomplished the ACME challenge #=============================================================================== if [ $? != 0 ]; then echo "$0: Certbot could not successfully accomplish the ACME challenge." >&2 exit 1 fi #=============================================================================== # Replace previous certificates with the new obtained certificate files #=============================================================================== [ -f "${INTERMEDIATE}.$$" ] && mv "${INTERMEDIATE}.$$" "${INTERMEDIATE}" [ -f "${CERTIFICATE_ONLY}.$$" ] && mv "${CERTIFICATE_ONLY}.$$" "${CERTIFICATE_ONLY}" [ -f "${CERTIFICATE_FULL}.$$" ] && mv "${CERTIFICATE_FULL}.$$" "${CERTIFICATE_FULL}" #=============================================================================== # Adjust the UNIX permissions with owner and group for the new created files #=============================================================================== chmod --reference "${CONFIDENTIAL}" "${INTERMEDIATE}" "${CERTIFICATE_ONLY}" "${CERTIFICATE_FULL}" chown --reference "${CONFIDENTIAL}" "${INTERMEDIATE}" "${CERTIFICATE_ONLY}" "${CERTIFICATE_FULL}" #=============================================================================== # Execute defined command AFTER the ACME challenge is completed #=============================================================================== [ ! -z "${LETSENCRYPT_COMMAND_AFTER}" ] && eval $LETSENCRYPT_COMMAND_AFTER