#!/bin/bash #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%# # PainlessLE – A wrapper script for Certbot [Thomas Lange ] # #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%# # # # Easily get an X.509 certificate from the Let's Encrypt Certificate Authority # # without the need of having a dedicated web server installed. With PainlessLE # # you'll manage your certificate and private key files by yourself. Instead of # # Certbot, you're responsible for the periodic renewal of the certificates. # # # # 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. # # # #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%# #=============================================================================== # Define ACME endpoint address and BEFORE/AFTER commands #=============================================================================== LETSENCRYPT_ENDPOINT="https://acme-v02.api.letsencrypt.org/directory" #LETSENCRYPT_ENDPOINT="https://acme-staging-v02.api.letsencrypt.org/directory" #LETSENCRYPT_COMMAND_BEFORE="systemctl stop apache2" #LETSENCRYPT_COMMAND_AFTER="systemctl start apache2" #=============================================================================== # 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) OPT_CONFIDENTIAL="$OPTARG" ;; I) OPT_INTERMEDIATE="$OPTARG" ;; C) OPT_CERTIFICATE_ONLY="$OPTARG" ;; F) OPT_CERTIFICATE_FULL="$OPTARG" ;; esac done; shift $((OPTIND-1)) #=============================================================================== # Set positional argument variables #=============================================================================== TARGET_DIR="$1" DNS_DOMAIN="${@:2}" #=============================================================================== # Check if required positional arguments are missing #=============================================================================== [ -z "${TARGET_DIR}" ] && echo "$0: Missing argument: TARGET_DIR" >&2 [ -z "${DNS_DOMAIN}" ] && echo "$0: Missing argument: DNS_DOMAIN" >&2 [ -z "${TARGET_DIR}" ] || [ -z "${DNS_DOMAIN}" ] && exit 1 #=============================================================================== # Define filename variables #=============================================================================== OPENSSLCONF="/etc/ssl/openssl.cnf" REQUESTFILE="$(mktemp /tmp/painless-le.XXXXXX.csr)" CONFIDENTIAL="${TARGET_DIR%/}/${OPT_CONFIDENTIAL:-confidential.pem}" INTERMEDIATE="${TARGET_DIR%/}/${OPT_INTERMEDIATE:-intermediate.pem}" CERTIFICATE_ONLY="${TARGET_DIR%/}/${OPT_CERTIFICATE_ONLY:-certificate_only.pem}" CERTIFICATE_FULL="${TARGET_DIR%/}/${OPT_CERTIFICATE_FULL:-certificate_full.pem}" #=============================================================================== # Ensure the Certificate-Signing-Request file is deleted on exit #=============================================================================== trap 'rm ${REQUESTFILE}' EXIT #=============================================================================== # Create Certificate-Signing-Request #=============================================================================== 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 "/" #=============================================================================== # Check if Certificate-Signing-Request creation failed #=============================================================================== if [ $? != 0 ]; then echo "$0: Certificate-Signing-Request (CSR) could not be created!" >&2 exit 1 fi #=============================================================================== # Run defined "BEFORE" command #=============================================================================== if [ ! -z "${LETSENCRYPT_COMMAND_BEFORE}" ]; then eval $LETSENCRYPT_COMMAND_BEFORE fi #=============================================================================== # Run Certbot to 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}.$$" #=============================================================================== # Check if Certbot failed to obtain a certificate #=============================================================================== if [ $? != 0 ]; then echo "$0: Certbot could not successfully accomplish the ACME challenge." >&2 exit 1 fi #=============================================================================== # Replace old certificate files (if any) with the newly obtained ones #=============================================================================== for file in "${INTERMEDIATE}" "${CERTIFICATE_ONLY}" "${CERTIFICATE_FULL}"; do if [ -f "${file}.$$" ]; then mv "${file}.$$" "${file}" fi done #=============================================================================== # Inherit permissions of private key to new certificate files #=============================================================================== for command in "chmod" "chown"; do $command --reference "${CONFIDENTIAL}" \ "${INTERMEDIATE}" "${CERTIFICATE_ONLY}" "${CERTIFICATE_FULL}" done #=============================================================================== # Run defined "AFTER" command #=============================================================================== if [ ! -z "${LETSENCRYPT_COMMAND_AFTER}" ]; then eval $LETSENCRYPT_COMMAND_AFTER fi