Setting CERTBOT_EMAIL broke certificate issuance outright. CERTBOT_OPTS was built as "--email $CERTBOT_EMAIL" and passed quoted, so certbot received the flag and its value as one argument and rejected it as unrecognised; `set -e` then ended the pass, and the loop repeated the same failure every 12 hours while the site served the self-signed placeholder. The arguments are built with `set --` now, which keeps them two words. Installation into the running HAProxy reported success whatever happened. The runtime API answers a refusal in the reply text and still closes cleanly, so socat exits 0 either way; the script sent set, commit and show in a row and inspected none of them. It now matches the replies and stops at the first that is not an acknowledgement, saying plainly that the running HAProxy is still on its previous certificate. It also reaches the API over a unix socket on a volume shared with haproxy rather than TCP 9999, so the private key it carries no longer crosses a network in the clear. Also: - The self-signed placeholder wrote site.key, site.csr and site.crt to the working directory (/opt/certbot) and left the private key there for good. It is built in a subshell under umask 077 on the volume the key belongs on, the CSR step is gone, and `>>` is replaced by a temporary file and an atomic rename — HAProxy reads site.pem at start-up and must never find it partial. - 2-concatenate-cert.sh changed into the live directory without checking, and on failure looked for fullchain.pem in whatever directory the caller was sitting in — the scripts are sourced, so that is a real possibility. It uses absolute paths and reports when there is nothing to concatenate. - The wait for HAProxy watches for the runtime API socket instead of probing TCP 9999, which no longer exists. - compose: /var/lib/letsencrypt is declared a VOLUME by the base image, so leaving it unnamed created a fresh anonymous volume on every container creation. It and the volumes section, which was missing entirely, are added. Build context is the repository root, as the Dockerfile's COPY expects. Verified on testnet2 against a real unix socket: the happy path sends set, commit and show; a refused set stops before commit; a refused commit and an unreachable socket are both reported. The runtime API payload is byte-identical to what the previous echo produced.
65 lines
2.3 KiB
Bash
65 lines
2.3 KiB
Bash
#!/bin/sh
|
|
|
|
set -e
|
|
|
|
if [ ! -f /etc/certificates/site.pem ]; then
|
|
# Self-signed placeholder, so HAProxy can bind :443 before a real certificate
|
|
# exists. Built inside a subshell: this script is sourced, so a bare cd or
|
|
# umask would change the caller's shell too. Relative paths used to resolve
|
|
# against the working directory (/opt/certbot) and left the private key there
|
|
# for good; here they resolve against the volume the key belongs on, and the
|
|
# intermediates are removed before site.pem appears.
|
|
(
|
|
umask 077
|
|
cd /etc/certificates
|
|
openssl req -x509 -newkey rsa:2048 -nodes -days 365 -batch \
|
|
-subj "/CN=${CERTBOT_DOMAIN:-localhost}" \
|
|
-keyout site.key.tmp -out site.crt.tmp
|
|
cat site.key.tmp site.crt.tmp > site.pem.tmp
|
|
rm -f site.key.tmp site.crt.tmp
|
|
# Last and atomic: HAProxy reads site.pem at start-up and must never find
|
|
# it half-written.
|
|
mv site.pem.tmp site.pem
|
|
)
|
|
fi
|
|
|
|
# Wait for haproxy: Let's Encrypt reaches this container's challenge server only
|
|
# through it, so asking for a certificate first would just fail validation. The
|
|
# runtime API socket appearing is the signal — HAProxy creates it once the
|
|
# configuration has parsed and the binds have succeeded. Waiting on the socket
|
|
# rather than a TCP probe also means there is no port left to probe.
|
|
HAPROXY_SOCKET=/var/lib/haproxy/admin.sock
|
|
while [ ! -S "$HAPROXY_SOCKET" ]; do
|
|
echo "Waiting for $HAPROXY_SOCKET..."
|
|
sleep 7
|
|
done
|
|
|
|
if [ -n "$CERTBOT_DOMAIN" ]; then
|
|
|
|
# E-mail for Let's Encrypt expiry notices. Built with set -- so that the flag
|
|
# and its value stay two separate arguments: a single "$CERTBOT_OPTS" word
|
|
# reached certbot as one token ("--email you@example.org") and was rejected
|
|
# as an unknown argument, which failed every issuance with an address set.
|
|
# The entrypoint passes no positional parameters, so there are none to lose.
|
|
if [ -n "$CERTBOT_EMAIL" ]; then
|
|
set -- --email "$CERTBOT_EMAIL"
|
|
else
|
|
set -- --register-unsafely-without-email
|
|
fi
|
|
|
|
# Request certificate
|
|
certbot certonly --standalone \
|
|
--non-interactive --agree-tos --http-01-port=380 \
|
|
"$@" \
|
|
--cert-name "$CERTBOT_DOMAIN" \
|
|
-d "$CERTBOT_DOMAIN"
|
|
|
|
# Concatenate certificates
|
|
. $(dirname $0)/2-concatenate-cert.sh
|
|
|
|
fi
|
|
|
|
# Update certificates in HAProxy
|
|
. $(dirname $0)/3-update-haproxy-cert.sh
|
|
|