| Matthias Andreas Benkard | b382b10 | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 1 | #!/bin/bash | 
|  | 2 |  | 
|  | 3 | # Return values / exit codes | 
|  | 4 | # 0 = cert created successfully | 
|  | 5 | # 1 = cert renewed successfully | 
|  | 6 | # 2 = cert not due for renewal | 
|  | 7 | # * = errors | 
|  | 8 |  | 
|  | 9 |  | 
|  | 10 | source /srv/functions.sh | 
|  | 11 |  | 
|  | 12 | CERT_DOMAINS=(${DOMAINS[@]}) | 
|  | 13 | CERT_DOMAIN=${CERT_DOMAINS[0]} | 
|  | 14 | ACME_BASE=/var/lib/acme | 
|  | 15 |  | 
|  | 16 | TYPE=${1} | 
|  | 17 | PREFIX="" | 
|  | 18 | # only support rsa certificates for now | 
|  | 19 | if [[ "${TYPE}" != "rsa" ]]; then | 
|  | 20 | log_f "Unknown certificate type '${TYPE}' requested" | 
|  | 21 | exit 5 | 
|  | 22 | fi | 
|  | 23 | DOMAINS_FILE=${ACME_BASE}/${CERT_DOMAIN}/domains | 
|  | 24 | CERT=${ACME_BASE}/${CERT_DOMAIN}/${PREFIX}cert.pem | 
|  | 25 | SHARED_KEY=${ACME_BASE}/acme/${PREFIX}key.pem  # must already exist | 
|  | 26 | KEY=${ACME_BASE}/${CERT_DOMAIN}/${PREFIX}key.pem | 
|  | 27 | CSR=${ACME_BASE}/${CERT_DOMAIN}/${PREFIX}acme.csr | 
|  | 28 |  | 
|  | 29 | if [[ -z ${CERT_DOMAINS[*]} ]]; then | 
|  | 30 | log_f "Missing CERT_DOMAINS to obtain a certificate" | 
|  | 31 | exit 3 | 
|  | 32 | fi | 
|  | 33 |  | 
|  | 34 | if [[ "${LE_STAGING}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then | 
|  | 35 | if [[ ! -z "${DIRECTORY_URL}" ]]; then | 
|  | 36 | log_f "Cannot use DIRECTORY_URL with LE_STAGING=y - ignoring DIRECTORY_URL" | 
|  | 37 | fi | 
|  | 38 | log_f "Using Let's Encrypt staging servers" | 
|  | 39 | DIRECTORY_URL='--directory-url https://acme-staging-v02.api.letsencrypt.org/directory' | 
|  | 40 | elif [[ ! -z "${DIRECTORY_URL}" ]]; then | 
|  | 41 | log_f "Using custom directory URL ${DIRECTORY_URL}" | 
|  | 42 | DIRECTORY_URL="--directory-url ${DIRECTORY_URL}" | 
|  | 43 | fi | 
|  | 44 |  | 
|  | 45 | if [[ -f ${DOMAINS_FILE} && "$(cat ${DOMAINS_FILE})" ==  "${CERT_DOMAINS[*]}" ]]; then | 
|  | 46 | if [[ ! -f ${CERT} || ! -f "${KEY}" || -f "${ACME_BASE}/force_renew" ]]; then | 
|  | 47 | log_f "Certificate ${CERT} doesn't exist yet or forced renewal - start obtaining" | 
|  | 48 | # Certificate exists and did not change but could be due for renewal (30 days) | 
|  | 49 | elif ! openssl x509 -checkend 2592000 -noout -in ${CERT} > /dev/null; then | 
|  | 50 | log_f "Certificate ${CERT} is due for renewal (< 30 days) - start renewing" | 
|  | 51 | else | 
|  | 52 | log_f "Certificate ${CERT} validation done, neither changed nor due for renewal." | 
|  | 53 | exit 2 | 
|  | 54 | fi | 
|  | 55 | else | 
|  | 56 | log_f "Certificate ${CERT} missing or changed domains '${CERT_DOMAINS[*]}' - start obtaining" | 
|  | 57 | fi | 
|  | 58 |  | 
|  | 59 |  | 
|  | 60 | # Make backup | 
|  | 61 | if [[ -f ${CERT} ]]; then | 
|  | 62 | DATE=$(date +%Y-%m-%d_%H_%M_%S) | 
|  | 63 | BACKUP_DIR=${ACME_BASE}/backups/${CERT_DOMAIN}/${PREFIX}${DATE} | 
|  | 64 | log_f "Creating backups in ${BACKUP_DIR} ..." | 
|  | 65 | mkdir -p ${BACKUP_DIR}/ | 
|  | 66 | [[ -f ${DOMAINS_FILE} ]] && cp ${DOMAINS_FILE} ${BACKUP_DIR}/ | 
|  | 67 | [[ -f ${CERT} ]] && cp ${CERT} ${BACKUP_DIR}/ | 
|  | 68 | [[ -f ${KEY} ]] && cp ${KEY} ${BACKUP_DIR}/ | 
|  | 69 | [[ -f ${CSR} ]] && cp ${CSR} ${BACKUP_DIR}/ | 
|  | 70 | fi | 
|  | 71 |  | 
|  | 72 | mkdir -p ${ACME_BASE}/${CERT_DOMAIN} | 
|  | 73 | if [[ ! -f ${KEY} ]]; then | 
|  | 74 | log_f "Copying shared private key for this certificate..." | 
|  | 75 | cp ${SHARED_KEY} ${KEY} | 
|  | 76 | chmod 600 ${KEY} | 
|  | 77 | fi | 
|  | 78 |  | 
|  | 79 | # Generating CSR | 
|  | 80 | printf "[SAN]\nsubjectAltName=" > /tmp/_SAN | 
|  | 81 | printf "DNS:%s," "${CERT_DOMAINS[@]}" >> /tmp/_SAN | 
|  | 82 | sed -i '$s/,$//' /tmp/_SAN | 
| Matthias Andreas Benkard | 1ba5381 | 2022-12-27 17:32:58 +0100 | [diff] [blame] | 83 | openssl req -new -sha256 -key ${KEY} -subj "/" -reqexts SAN -config <(cat "$(openssl version -d | sed 's/.*"\(.*\)"/\1/g')/openssl.cnf" /tmp/_SAN) > ${CSR} | 
| Matthias Andreas Benkard | b382b10 | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 84 |  | 
|  | 85 | # acme-tiny writes info to stderr and ceritifcate to stdout | 
|  | 86 | # The redirects will do the following: | 
|  | 87 | # - redirect stdout to temp certificate file | 
|  | 88 | # - redirect acme-tiny stderr to stdout (logs to variable ACME_RESPONSE) | 
|  | 89 | # - tee stderr to get live output and log to dockerd | 
|  | 90 |  | 
|  | 91 | log_f "Checking resolver..." | 
|  | 92 | until dig letsencrypt.org +time=3 +tries=1 @unbound > /dev/null; do | 
|  | 93 | sleep 2 | 
|  | 94 | done | 
|  | 95 | log_f "Resolver OK" | 
| Matthias Andreas Benkard | 7b2a3a1 | 2021-08-16 10:57:25 +0200 | [diff] [blame] | 96 | log_f "Using command acme-tiny ${DIRECTORY_URL} ${ACME_CONTACT_PARAMETER} --account-key ${ACME_BASE}/acme/account.pem --disable-check --csr ${CSR} --acme-dir /var/www/acme/" | 
|  | 97 | ACME_RESPONSE=$(acme-tiny ${DIRECTORY_URL} ${ACME_CONTACT_PARAMETER} \ | 
| Matthias Andreas Benkard | b382b10 | 2021-01-02 15:32:21 +0100 | [diff] [blame] | 98 | --account-key ${ACME_BASE}/acme/account.pem \ | 
|  | 99 | --disable-check \ | 
|  | 100 | --csr ${CSR} \ | 
|  | 101 | --acme-dir /var/www/acme/ 2>&1 > /tmp/_cert.pem | tee /dev/fd/5; exit ${PIPESTATUS[0]}) | 
|  | 102 | SUCCESS="$?" | 
|  | 103 | ACME_RESPONSE_B64=$(echo "${ACME_RESPONSE}" | openssl enc -e -A -base64) | 
|  | 104 | log_f "${ACME_RESPONSE_B64}" redis_only b64 | 
|  | 105 | case "$SUCCESS" in | 
|  | 106 | 0) # cert requested | 
|  | 107 | log_f "Deploying certificate ${CERT}..." | 
|  | 108 | # Deploy the new certificate and key | 
|  | 109 | # Moving temp cert to {domain} folder | 
|  | 110 | if verify_hash_match /tmp/_cert.pem ${KEY}; then | 
|  | 111 | RETURN=0  # certificate created | 
|  | 112 | if [[ -f ${CERT} ]]; then | 
|  | 113 | RETURN=1  # certificate renewed | 
|  | 114 | fi | 
|  | 115 | mv -f /tmp/_cert.pem ${CERT} | 
|  | 116 | echo -n ${CERT_DOMAINS[*]} > ${DOMAINS_FILE} | 
|  | 117 | rm /var/www/acme/* 2> /dev/null | 
|  | 118 | log_f "Certificate successfully obtained" | 
|  | 119 | exit ${RETURN} | 
|  | 120 | else | 
|  | 121 | log_f "Certificate was successfully requested, but key and certificate have non-matching hashes, ignoring certificate" | 
|  | 122 | exit 4 | 
|  | 123 | fi | 
|  | 124 | ;; | 
|  | 125 | *) # non-zero is non-fun | 
|  | 126 | log_f "Failed to obtain certificate ${CERT} for domains '${CERT_DOMAINS[*]}'" | 
|  | 127 | redis-cli -h redis SET ACME_FAIL_TIME "$(date +%s)" | 
|  | 128 | exit 100${SUCCESS} | 
|  | 129 | ;; | 
|  | 130 | esac |