git subrepo clone https://github.com/mailcow/mailcow-dockerized.git mailcow/src/mailcow-dockerized
subrepo: subdir: "mailcow/src/mailcow-dockerized"
merged: "a832becb"
upstream: origin: "https://github.com/mailcow/mailcow-dockerized.git"
branch: "master"
commit: "a832becb"
git-subrepo: version: "0.4.3"
origin: "???"
commit: "???"
Change-Id: If5be2d621a211e164c9b6577adaa7884449f16b5
diff --git a/mailcow/src/mailcow-dockerized/data/Dockerfiles/acme/obtain-certificate.sh b/mailcow/src/mailcow-dockerized/data/Dockerfiles/acme/obtain-certificate.sh
new file mode 100644
index 0000000..8264a2c
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/Dockerfiles/acme/obtain-certificate.sh
@@ -0,0 +1,130 @@
+#!/bin/bash
+
+# Return values / exit codes
+# 0 = cert created successfully
+# 1 = cert renewed successfully
+# 2 = cert not due for renewal
+# * = errors
+
+
+source /srv/functions.sh
+
+CERT_DOMAINS=(${DOMAINS[@]})
+CERT_DOMAIN=${CERT_DOMAINS[0]}
+ACME_BASE=/var/lib/acme
+
+TYPE=${1}
+PREFIX=""
+# only support rsa certificates for now
+if [[ "${TYPE}" != "rsa" ]]; then
+ log_f "Unknown certificate type '${TYPE}' requested"
+ exit 5
+fi
+DOMAINS_FILE=${ACME_BASE}/${CERT_DOMAIN}/domains
+CERT=${ACME_BASE}/${CERT_DOMAIN}/${PREFIX}cert.pem
+SHARED_KEY=${ACME_BASE}/acme/${PREFIX}key.pem # must already exist
+KEY=${ACME_BASE}/${CERT_DOMAIN}/${PREFIX}key.pem
+CSR=${ACME_BASE}/${CERT_DOMAIN}/${PREFIX}acme.csr
+
+if [[ -z ${CERT_DOMAINS[*]} ]]; then
+ log_f "Missing CERT_DOMAINS to obtain a certificate"
+ exit 3
+fi
+
+if [[ "${LE_STAGING}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
+ if [[ ! -z "${DIRECTORY_URL}" ]]; then
+ log_f "Cannot use DIRECTORY_URL with LE_STAGING=y - ignoring DIRECTORY_URL"
+ fi
+ log_f "Using Let's Encrypt staging servers"
+ DIRECTORY_URL='--directory-url https://acme-staging-v02.api.letsencrypt.org/directory'
+elif [[ ! -z "${DIRECTORY_URL}" ]]; then
+ log_f "Using custom directory URL ${DIRECTORY_URL}"
+ DIRECTORY_URL="--directory-url ${DIRECTORY_URL}"
+fi
+
+if [[ -f ${DOMAINS_FILE} && "$(cat ${DOMAINS_FILE})" == "${CERT_DOMAINS[*]}" ]]; then
+ if [[ ! -f ${CERT} || ! -f "${KEY}" || -f "${ACME_BASE}/force_renew" ]]; then
+ log_f "Certificate ${CERT} doesn't exist yet or forced renewal - start obtaining"
+ # Certificate exists and did not change but could be due for renewal (30 days)
+ elif ! openssl x509 -checkend 2592000 -noout -in ${CERT} > /dev/null; then
+ log_f "Certificate ${CERT} is due for renewal (< 30 days) - start renewing"
+ else
+ log_f "Certificate ${CERT} validation done, neither changed nor due for renewal."
+ exit 2
+ fi
+else
+ log_f "Certificate ${CERT} missing or changed domains '${CERT_DOMAINS[*]}' - start obtaining"
+fi
+
+
+# Make backup
+if [[ -f ${CERT} ]]; then
+ DATE=$(date +%Y-%m-%d_%H_%M_%S)
+ BACKUP_DIR=${ACME_BASE}/backups/${CERT_DOMAIN}/${PREFIX}${DATE}
+ log_f "Creating backups in ${BACKUP_DIR} ..."
+ mkdir -p ${BACKUP_DIR}/
+ [[ -f ${DOMAINS_FILE} ]] && cp ${DOMAINS_FILE} ${BACKUP_DIR}/
+ [[ -f ${CERT} ]] && cp ${CERT} ${BACKUP_DIR}/
+ [[ -f ${KEY} ]] && cp ${KEY} ${BACKUP_DIR}/
+ [[ -f ${CSR} ]] && cp ${CSR} ${BACKUP_DIR}/
+fi
+
+mkdir -p ${ACME_BASE}/${CERT_DOMAIN}
+if [[ ! -f ${KEY} ]]; then
+ log_f "Copying shared private key for this certificate..."
+ cp ${SHARED_KEY} ${KEY}
+ chmod 600 ${KEY}
+fi
+
+# Generating CSR
+printf "[SAN]\nsubjectAltName=" > /tmp/_SAN
+printf "DNS:%s," "${CERT_DOMAINS[@]}" >> /tmp/_SAN
+sed -i '$s/,$//' /tmp/_SAN
+openssl req -new -sha256 -key ${KEY} -subj "/" -reqexts SAN -config <(cat /etc/ssl/openssl.cnf /tmp/_SAN) > ${CSR}
+
+# acme-tiny writes info to stderr and ceritifcate to stdout
+# The redirects will do the following:
+# - redirect stdout to temp certificate file
+# - redirect acme-tiny stderr to stdout (logs to variable ACME_RESPONSE)
+# - tee stderr to get live output and log to dockerd
+
+log_f "Checking resolver..."
+until dig letsencrypt.org +time=3 +tries=1 @unbound > /dev/null; do
+ sleep 2
+done
+log_f "Resolver OK"
+
+ACME_RESPONSE=$(acme-tiny ${DIRECTORY_URL} \
+ --account-key ${ACME_BASE}/acme/account.pem \
+ --disable-check \
+ --csr ${CSR} \
+ --acme-dir /var/www/acme/ 2>&1 > /tmp/_cert.pem | tee /dev/fd/5; exit ${PIPESTATUS[0]})
+SUCCESS="$?"
+ACME_RESPONSE_B64=$(echo "${ACME_RESPONSE}" | openssl enc -e -A -base64)
+log_f "${ACME_RESPONSE_B64}" redis_only b64
+case "$SUCCESS" in
+ 0) # cert requested
+ log_f "Deploying certificate ${CERT}..."
+ # Deploy the new certificate and key
+ # Moving temp cert to {domain} folder
+ if verify_hash_match /tmp/_cert.pem ${KEY}; then
+ RETURN=0 # certificate created
+ if [[ -f ${CERT} ]]; then
+ RETURN=1 # certificate renewed
+ fi
+ mv -f /tmp/_cert.pem ${CERT}
+ echo -n ${CERT_DOMAINS[*]} > ${DOMAINS_FILE}
+ rm /var/www/acme/* 2> /dev/null
+ log_f "Certificate successfully obtained"
+ exit ${RETURN}
+ else
+ log_f "Certificate was successfully requested, but key and certificate have non-matching hashes, ignoring certificate"
+ exit 4
+ fi
+ ;;
+ *) # non-zero is non-fun
+ log_f "Failed to obtain certificate ${CERT} for domains '${CERT_DOMAINS[*]}'"
+ redis-cli -h redis SET ACME_FAIL_TIME "$(date +%s)"
+ exit 100${SUCCESS}
+ ;;
+esac