Ian Wienand 547a4578bd letsencrypt : don't use staging in the gate
Currently we connect to the LE staging environment with acme.sh during
CI to get the DNS-01 tokens (but we never follow-through and actually
generate the certificate, as we have nowhere to publish the tokens).
We've known for a while that LE staging isn't really meant to be used
by CI like this, and recent instability has made the issue pronounced.

This modifies the driver script to generate fake tokens which work to
ensure all the DNS processing, etc. is happening correctly.

I have put this behind a flag so the letsencrypt job still does this
however.  I think it is worth this job actually calling acme.sh to
validate this path; this shouldn't be required too often.

Change-Id: I7c0b471a0661aa311aaa861fd2a0d47b07e45a72
2021-10-06 15:34:21 +11:00

128 lines
5.0 KiB
Bash

#!/bin/bash
ACME_SH=${ACME_SH:-/opt/acme.sh/acme.sh}
CERT_HOME=${CERT_HOME:-/etc/letsencrypt-certs}
CHALLENGE_ALIAS_DOMAIN=${CHALLENGE_ALIAS_DOMAIN:-acme.opendev.org.}
# Set to !0 to use letsencrypt staging rather than production requests
LETSENCRYPT_STAGING=${LETSENCRYPT_STAGING:-0}
LOG_FILE=${LOG_FILE:-/var/log/acme.sh/acme.sh.log}
SERVER=""
if [[ ${LETSENCRYPT_STAGING} != 0 ]]; then
# TODO acme.sh doesn't let us specify staging and also set the server.
# If --staging is passed then the built in default is used. Can/should
# we change this to --server letsencrypt_test?
SERVER="--staging"
#SERVER="--server letsencrypt_test"
else
SERVER="--server letsencrypt"
fi
# Ensure we don't write out files as world-readable
umask 027
echo -e "\n--- start --- ${1} --- $(date -u '+%Y-%m-%dT%k:%M:%S%z') ---" >> ${LOG_FILE}
if [[ ${1} == "issue" ]]; then
# Take output like:
# [Thu Feb 14 13:44:37 AEDT 2019] Domain: '_acme-challenge.test.opendev.org'
# [Thu Feb 14 13:44:37 AEDT 2019] TXT value: 'QjkChGcuqD7rl0jN8FNWkWNAISX1Zry_vE-9RxWF2pE'
#
# and turn it into:
#
# _acme-challenge.test.opendev.org:QjkChGcuqD7rl0jN8FNWkWNAISX1Zry_vE-9RxWF2pE
#
# Ansible then parses this back to a dict.
shift;
for arg in "$@"; do
$ACME_SH ${SERVER} \
--cert-home ${CERT_HOME} \
--no-color \
--yes-I-know-dns-manual-mode-enough-go-ahead-please \
--issue \
--dns \
--challenge-alias ${CHALLENGE_ALIAS_DOMAIN} \
$arg 2>&1 | tee -a ${LOG_FILE} | \
egrep 'Domain:|TXT value:' | cut -d"'" -f2 | paste -d':' - -
# shell magic ^ is
# - extract everything between ' '
# - stick every two lines together, separated by a :
done
elif [[ ${1} == "issue-selfsign" ]]; then
shift;
for arg in "$@"; do
# looks like
# "-d foo01.com -d foo.com " "-d bar01.com -d bar.com"
arr=(${arg})
len=${#arr[@]}
for (( i=0; i<$len; i++ )); do
if [[ $((i%2)) -eq 0 ]]; then
continue # this should be a "-d"
else
# The ACME protocol hashes "stuff" and the TXT record
# is ultimately a sha256 encoded into base64url
# (RFC8555); emulate that here.
base64url=$(echo -n ${arr[$i]}-${RANDOM} | \
openssl dgst -binary -sha256 | \
openssl base64 | sed 's/+/-/g; s,/,_,g; s/=//g')
echo "${arr[$i]}:${base64url}"
fi
done
done
elif [[ ${1} == "renew" ]]; then
shift;
for arg in "$@"; do
# NOTE(ianw) 2020-02-28 : we only set force here because of a
# bug/misfeature in acme.sh dns manual-mode where it does not
# notice that the renewal is required when we update domain
# names in a cert
# (https://github.com/acmesh-official/acme.sh/issues/2763).
# This is safe (i.e. will not explode our quota limits by
# constantly renewing) because Ansible only calls this path
# when TXT records have been installed for this certificate;
# i.e. we will never run this renewal unless it is actually
# required.
$ACME_SH ${SERVER} \
--cert-home ${CERT_HOME} \
--no-color \
--yes-I-know-dns-manual-mode-enough-go-ahead-please \
--force \
--renew \
$arg 2>&1 | tee -a ${LOG_FILE}
done
elif [[ ${1} == "selfsign" ]]; then
# For testing, simulate the key generation
shift;
for arg in "$@"; do
# TODO(ianw): Set SAN names from the other "-d" arguments?;
# it's a pita to parse.
{
read -r -a domain_array <<< "$arg"
domain=${domain_array[1]}
mkdir -p ${CERT_HOME}/${domain}
cd ${CERT_HOME}/${domain}
echo "Creating certs in ${CERT_HOME}/${domain}"
# Create key for domain
openssl genrsa -out ${domain}.key 2048
# openssl makes this 0600; match the permissions acme.sh
# makes it with for general sanity
chmod 0640 ${domain}.key
# Generate a fake CA key
openssl genrsa -out ca.key 2048
# Create fake CA root certificate
openssl req -x509 -new -nodes -key ca.key -sha256 -days 1024 -subj "/C=US/ST=CA/O=opendev" -out ca.cer
# Create localhost certificate signing request
openssl req -sha256 -new -key ${domain}.key -out ${domain}.csr -subj '/CN=localhost'
# Create localhost certificate signed by fake CA
openssl x509 -req -CA ca.cer -CAkey ca.key -CAcreateserial \
-sha256 -days 365 -in ${domain}.csr -out ${domain}.cer
cp ${domain}.cer fullchain.cer
} 2>&1 | tee -a ${LOG_FILE}
done
else
echo "Unknown driver arg: $1"
exit 1
fi
echo "--- end --- $(date -u '+%Y-%m-%dT%k:%M:%S%z') ---" >> ${LOG_FILE}