blob: 8264a2cb810ce8ad19b20b72b15521c1bb7641fb [file] [log] [blame]
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01001#!/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
10source /srv/functions.sh
11
12CERT_DOMAINS=(${DOMAINS[@]})
13CERT_DOMAIN=${CERT_DOMAINS[0]}
14ACME_BASE=/var/lib/acme
15
16TYPE=${1}
17PREFIX=""
18# only support rsa certificates for now
19if [[ "${TYPE}" != "rsa" ]]; then
20 log_f "Unknown certificate type '${TYPE}' requested"
21 exit 5
22fi
23DOMAINS_FILE=${ACME_BASE}/${CERT_DOMAIN}/domains
24CERT=${ACME_BASE}/${CERT_DOMAIN}/${PREFIX}cert.pem
25SHARED_KEY=${ACME_BASE}/acme/${PREFIX}key.pem # must already exist
26KEY=${ACME_BASE}/${CERT_DOMAIN}/${PREFIX}key.pem
27CSR=${ACME_BASE}/${CERT_DOMAIN}/${PREFIX}acme.csr
28
29if [[ -z ${CERT_DOMAINS[*]} ]]; then
30 log_f "Missing CERT_DOMAINS to obtain a certificate"
31 exit 3
32fi
33
34if [[ "${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'
40elif [[ ! -z "${DIRECTORY_URL}" ]]; then
41 log_f "Using custom directory URL ${DIRECTORY_URL}"
42 DIRECTORY_URL="--directory-url ${DIRECTORY_URL}"
43fi
44
45if [[ -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
55else
56 log_f "Certificate ${CERT} missing or changed domains '${CERT_DOMAINS[*]}' - start obtaining"
57fi
58
59
60# Make backup
61if [[ -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}/
70fi
71
72mkdir -p ${ACME_BASE}/${CERT_DOMAIN}
73if [[ ! -f ${KEY} ]]; then
74 log_f "Copying shared private key for this certificate..."
75 cp ${SHARED_KEY} ${KEY}
76 chmod 600 ${KEY}
77fi
78
79# Generating CSR
80printf "[SAN]\nsubjectAltName=" > /tmp/_SAN
81printf "DNS:%s," "${CERT_DOMAINS[@]}" >> /tmp/_SAN
82sed -i '$s/,$//' /tmp/_SAN
83openssl req -new -sha256 -key ${KEY} -subj "/" -reqexts SAN -config <(cat /etc/ssl/openssl.cnf /tmp/_SAN) > ${CSR}
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
91log_f "Checking resolver..."
92until dig letsencrypt.org +time=3 +tries=1 @unbound > /dev/null; do
93 sleep 2
94done
95log_f "Resolver OK"
96
97ACME_RESPONSE=$(acme-tiny ${DIRECTORY_URL} \
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]})
102SUCCESS="$?"
103ACME_RESPONSE_B64=$(echo "${ACME_RESPONSE}" | openssl enc -e -A -base64)
104log_f "${ACME_RESPONSE_B64}" redis_only b64
105case "$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 ;;
130esac