#!/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. # # # # [-K name]: Filename of the existing private key in target directory. # # [-I name]: Filename for the intermediate certificate in target directory. # # [-C name]: Filename for the standalone certificate in target directory. # # [-F name]: Filename for the certificate+intermediate in target directory. # # [--server URL]: Specify a custom URL to an ACME endpoint. # # [--staging]: Use a staging server to obtain an invalid test certificate. # # # #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%# #=============================================================================== # Normalize command-line argument string #=============================================================================== eval set -- "$(getopt -o K:I:C:F: -l server:,staging -- "$@")" #=============================================================================== # Parse command-line options #=============================================================================== while true; do case "$1" in -K) OPT_CONFIDENTIAL="$2"; shift 2;; -I) OPT_INTERMEDIATE="$2"; shift 2;; -C) OPT_CERTIFICATE_ONLY="$2"; shift 2;; -F) OPT_CERTIFICATE_FULL="$2"; shift 2;; --server) OPT_SERVER="$2"; shift 2;; --staging) OPT_STAGING=1; shift;; --) shift; break;; esac done #=============================================================================== # 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 #=============================================================================== 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 #=============================================================================== # Assemble OpenSSL configuration for CSR generation #=============================================================================== SUBJECT_ALT_NAME="DNS:$(echo ${DNS_DOMAIN} | sed "s/ /,DNS:/g")" OPENSSL_CONFIG="[req] distinguished_name = req_distinguished_name [req_distinguished_name] [SAN] subjectAltName=${SUBJECT_ALT_NAME}" #=============================================================================== # Create Certificate-Signing-Request #=============================================================================== openssl req -config <(echo "$OPENSSL_CONFIG") -new -sha256 -reqexts SAN \ -subj "/" -key "${CONFIDENTIAL}" -out "${REQUESTFILE}" #=============================================================================== # 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 Certbot to obtain the certificate #=============================================================================== CERTBOT_OPTIONS=( "--csr" "${REQUESTFILE}" "--cert-path" "${CERTIFICATE_ONLY}.$$" "--chain-path" "${INTERMEDIATE}.$$" "--fullchain-path" "${CERTIFICATE_FULL}.$$" ) [ ! -z "$OPT_STAGING" ] && \ CERTBOT_OPTIONS+=("--staging") [ ! -z "$OPT_SERVER" ] && [ -z "$OPT_STAGING" ] && \ CERTBOT_OPTIONS+=("--server" "${OPT_SERVER}") certbot certonly --authenticator standalone "${CERTBOT_OPTIONS[@]}" #=============================================================================== # 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