Skip to content

Commit

Permalink
Added support for AWS Route53 DNS challenges
Browse files Browse the repository at this point in the history
  • Loading branch information
richturner committed Apr 29, 2024
1 parent b37d07f commit edcc6ae
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 28 deletions.
13 changes: 7 additions & 6 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@ ENV CERT_DIR /deployment/certs
ENV LE_DIR /deployment/letsencrypt
ENV CHROOT_DIR /etc/haproxy/webroot

# Install certbot and Lexicon DNS plugin
# Install certbot and Route53 DNS plugin
RUN apk update \
&& apk add --no-cache certbot py3-dns-lexicon py3-dnspython inotify-tools tar curl openssl \
&& rm -f /var/cache/apk/*
&& apk add --no-cache certbot py-pip inotify-tools tar curl openssl \
&& rm -f /var/cache/apk/* \
&& pip install certbot-dns-route53 --break-system-packages

# Add ACME LUA plugin
ADD acme-plugin.tar.gz /etc/haproxy/lua/
Expand All @@ -38,9 +39,9 @@ RUN mkdir -p ${CHROOT_DIR} \
&& mkdir -p ${LE_DIR} && chown haproxy:haproxy ${LE_DIR} \
&& mkdir -p /etc/letsencrypt \
&& mkdir -p /var/lib/letsencrypt \
&& touch /etc/periodic/daily/certbot-renew \
&& printf "#!/bin/sh\ncertbot renew --deploy-hook \"/entrypoint.sh sync-haproxy\"\n" > /etc/periodic/daily/certbot-renew \
&& chmod +x /etc/periodic/daily/certbot-renew \
&& touch /etc/periodic/daily/cert-renew \
&& printf "#!/bin/sh\n/entrypoint.sh auto-renew\n" > /etc/periodic/daily/cert-renew \
&& chmod +x /etc/periodic/daily/cert-renew \
&& chown -R haproxy:haproxy /etc/letsencrypt \
&& chown -R haproxy:haproxy /etc/haproxy \
&& chown -R haproxy:haproxy /var/lib/letsencrypt \
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# HAProxy docker image
[![Docker Image](https://github.com/openremote/proxy/actions/workflows/proxy.yml/badge.svg)](https://github.com/openremote/proxy/actions/workflows/proxy.yml)

HAProxy docker image with Lets Encrypt SSL auto renewal using certbot.
HAProxy docker image with Lets Encrypt SSL auto renewal using certbot with built in support for wildcard certificates using AWS Route53.

## Paths
* `/deployment/letsencrypt` - Certbot config directory where generated certificates are stored
Expand All @@ -22,6 +22,8 @@ requested (this is a multi-value alternative to DOMAINNAME)
* `KEYCLOAK_HOST` - Hostname of the Keycloak server (default: `keycloak`)
* `KEYCLOAK_PORT` - Web server port of Keycloak server (default `8080`)
* `LOGFILE` - Location of log file for entrypoint script to write to in addition to stdout (default `none`)
* `AWS_ROUTE53_ROLE` - AWS Route53 Role ARN to be assumed when trying to generate wildcard certificates using Route53 DNS zone, specifically for cross account updates (default `none`)
* `LE_EXTRA_ARGS` - Can be used to add additional arguments to the certbot command (default `none`)

## Custom certificate format
Any custom certificate volume mapped into `/etc/haproxy/certs` should be in PEM format and must include the full certificate chain and the private key, i.e.:
Expand Down
3 changes: 0 additions & 3 deletions cli.ini
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,3 @@ rsa-key-size = 4096

agree-tos = true
no-eff-email = true

authenticator = webroot
webroot-path = /etc/haproxy/webroot
76 changes: 58 additions & 18 deletions entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
IP_REGEX='(^([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}$)|(^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$)|(^[^\.]+$)'

# Configure letsencrypt
LE_EXTRA_ARGS=""
if [ -n "${LE_EMAIL}" ]; then
LE_EXTRA_ARGS="${LE_EXTRA_ARGS} --email ${LE_EMAIL}"
else
Expand All @@ -14,7 +13,7 @@ if [ -n "${LE_RSA_KEY_SIZE}" ]; then
LE_EXTRA_ARGS="${LE_EXTRA_ARGS} --rsa-key-size ${LE_RSA_KEY_SIZE}"
fi

LE_CMD="certbot certonly --logs-dir - -w ${CHROOT_DIR} ${LE_EXTRA_ARGS}"
LE_CMD="certbot certonly -n --logs-dir - -w ${CHROOT_DIR} ${LE_EXTRA_ARGS}"

# Configure haproxy
HAPROXY_CMD="haproxy -W -db -f ${HAPROXY_CONFIG} ${HAPROXY_USER_PARAMS}"
Expand Down Expand Up @@ -70,14 +69,26 @@ run_proxy() {
log_info "LUA_PATH: ${LUA_PATH}"
log_info "CERT_DIR: ${CERT_DIR}"
log_info "LE_DIR: ${LE_DIR}"
log_info "LE_CMD: ${LE_CMD}"
log_info "AWS_ROUTE53_ROLE: ${AWS_ROUTE53_ROLE}"

if check_proxy; then

log_info "Starting crond"
crond

if [ -n "${AWS_ROUTE53_ROLE}" ]; then
log_info "Creating AWS CLI config file"
mkdir ~/.aws/config
rm -f ~/.aws/config 2> /dev/null
echo "[default]" >> ~/.aws/config
echo "role_arn = ${AWS_ROUTE53_ROLE}" >> ~/.aws/config
echo "credential_source = Ec2InstanceMetadata" >> ~/.aws/config
echo "" >> ~/.aws/config
fi

cert_init&

log_info "Starting monitoring process"
monitor&

Expand Down Expand Up @@ -129,7 +140,11 @@ add() {
fi

DOMAIN="${1}"
RENEWED_LINEAGE="${LE_DIR}/live/${DOMAIN}"
FNAME="${DOMAIN}"
if [[ ${DOMAIN:0:2} == "*." ]]; then
FNAME="_${DOMAIN:1}"
fi
RENEWED_LINEAGE="${LE_DIR}/live/${FNAME}"
DOMAIN_FOLDER=$RENEWED_LINEAGE

# Basic invalid DOMAIN check
Expand All @@ -153,7 +168,15 @@ add() {
fi
done

eval "$LE_CMD $DOMAIN_ARGS"
# For wildcard domains we use route 53 DNS plugin
if [[ ${DOMAIN:0:2} == "*." ]]; then
log_info "Wildcard domain cert requested, using route53 plugin: ${DOMAIN}"
CMD="${LE_CMD} --dns-route53 --cert-name _${DOMAIN:1}"
else
CMD="${LE_CMD} --webroot"
fi

eval "$CMD $DOMAIN_ARGS"
ret=$?

if [ $ret -ne 0 ]; then
Expand Down Expand Up @@ -181,7 +204,11 @@ renew() {
fi

DOMAIN="${1}"
DOMAIN_FOLDER="${LE_DIR}/live/${DOMAIN}"
FNAME="${DOMAIN}"
if [[ ${DOMAIN:0:2} == "*." ]]; then
FNAME="_${DOMAIN:1}"
fi
DOMAIN_FOLDER="${LE_DIR}/live/${FNAME}"

if [ ! -d "${DOMAIN_FOLDER}" ]; then
log_error "Domain ${DOMAIN} does not exist! Cannot renew it."
Expand All @@ -197,7 +224,7 @@ renew() {
fi
done

eval "$LE_CMD --force-renewal --deploy-hook \"/entrypoint.sh sync-haproxy\" --expand $DOMAIN_ARGS"
eval "$LE_CMD --force-renewal --deploy-hook \"/entrypoint.sh sync-haproxy\" --expand $DOMAIN_ARGS --cert-name $FNAME"

LE_RESULT=$?

Expand Down Expand Up @@ -226,6 +253,10 @@ print_pin() {
fi

DOMAIN="${1}"
FNAME="${DOMAIN}"
if [[ ${DOMAIN:0:2} == "*." ]]; then
FNAME="_${DOMAIN:1}"
fi
DOMAIN_FOLDER="${LE_DIR}/live/${DOMAIN}"

if [ ! -d "${DOMAIN_FOLDER}" ]; then
Expand All @@ -252,9 +283,13 @@ remove() {
fi

DOMAIN=$1
DOMAIN_LIVE_FOLDER="${LE_DIR}/live/${DOMAIN}"
DOMAIN_ARCHIVE_FOLDER="${LE_DIR}/archive/${DOMAIN}"
DOMAIN_RENEWAL_CONFIG="${LE_DIR}/renewal/${DOMAIN}.conf"
FNAME="${DOMAIN}"
if [[ ${DOMAIN:0:2} == "*." ]]; then
FNAME="_${DOMAIN:1}"
fi
DOMAIN_LIVE_FOLDER="${LE_DIR}/live/${FNAME}"
DOMAIN_ARCHIVE_FOLDER="${LE_DIR}/archive/${FNAME}"
DOMAIN_RENEWAL_CONFIG="${LE_DIR}/renewal/${FNAME}.conf"

log_info "Removing domain \"${DOMAIN}\"..."

Expand All @@ -263,11 +298,8 @@ remove() {
return 5
fi

rm -rf "${DOMAIN_LIVE_FOLDER}" || die "Failed to remove domain live directory ${DOMAIN_FOLDER}"
rm -rf "${DOMAIN_ARCHIVE_FOLDER}" || die "Failed to remove domain archive directory ${DOMAIN_ARCHIVE_FOLDER}"
rm -f "${DOMAIN_RENEWAL_CONFIG}" || die "Failed to remove domain renewal config ${DOMAIN_RENEWAL_CONFIG}"
rm -f "${CERT_DIR}/${DOMAIN}" 2>/dev/null

certbot revoke -n --cert-name ${FNAME}
certbot delete -n --cert-name ${FNAME}
log_info "Removed domain \"${DOMAIN}\"..."
}

Expand Down Expand Up @@ -299,14 +331,18 @@ cert_init() {
i=0
for DOMAIN in $DOMAINNAMES; do
i=$((i+1))
if [ ! -d "${LE_DIR}/live/${DOMAIN}" ]; then
FNAME="${DOMAIN}"
if [[ ${DOMAIN:0:2} == "*." ]]; then
FNAME="_${DOMAIN:1}"
fi
if [ ! -d "${LE_DIR}/live/${FNAME}" ]; then
log_info "Initialising certificate for '${DOMAIN}'..."
rm -rf "${LE_DIR}/live/${DOMAIN}" 2>/dev/null
rm -rf "${LE_DIR}/live/${FNAME}" 2>/dev/null
add "${DOMAIN}"
fi
if [ $i -eq 1 ]; then
log_info "Symlinking first domain to built in cert directory to take precedence over self signed cert"
ln -sfT ${CERT_DIR}/${DOMAIN} /etc/haproxy/certs/00-cert
ln -sfT ${CERT_DIR}/${FNAME} /etc/haproxy/certs/00-cert
fi
done
IFS=$IFS_OLD
Expand All @@ -321,10 +357,14 @@ cert_init() {
continue
fi
CERT=$(basename $d)
if [[ ${CERT:0:2} == "_." ]]; then
CERT="*${CERT:1}"
fi
if [[ "$DOMAINNAMES" != "$CERT"* ]] && [[ "$DOMAINNAMES" != *",$CERT"* ]]; then
log_info "Removing obsolete certificate for '$CERT'"
remove "$CERT"
else
CERT=$(basename $d)
RENEWED_LINEAGE="$LE_DIR/live/$CERT"
sync_haproxy
fi
Expand Down

0 comments on commit edcc6ae

Please sign in to comment.