diff --git a/Dockerfile b/Dockerfile index d93a8d9..238b4e2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,3 +2,24 @@ ARG ALPINE_VERSION=3.13.6 ARG BASE_IMAGE=sutty/monit FROM ${BASE_IMAGE}:${ALPINE_VERSION} MAINTAINER "f " + +# Install requisites +RUN apk add --no-cache certbot jq wget + +# Run certbot +COPY ./monit.conf /etc/monit.d/certbot.conf + +# Install certbot's script +COPY ./certbot.sh /usr/local/bin/certbot +RUN chmod +x /usr/local/bin/certbot + +# Add ssl group +RUN addgroup -S -g 777 ssl + +RUN apk add --no-cache openssh-client rsync +RUN install -dm 2750 -o root -g root /root/.ssh +COPY ./ssh_config /root/.ssh/config + +# Access to certificates and challenges +VOLUME /etc/letsencrypt +VOLUME /var/lib/letsencrypt diff --git a/certbot.sh b/certbot.sh new file mode 100644 index 0000000..6f1b905 --- /dev/null +++ b/certbot.sh @@ -0,0 +1,64 @@ +#!/bin/sh + +case $1 in + # Renew certificates, trust in certbot's algorithms + renew) /usr/bin/certbot renew --quiet --agree-tos ;; + bootstrap) + for site in ${SUTTY} api.${SUTTY}; do + test -d "/etc/letsencrypt/live/${site}" && exit 0 + + # Get the certificate for the domain, the webserver will need + # access to this directory + /usr/bin/certbot certonly --email "certbot@${SUTTY}" \ + --webroot \ + --agree-tos \ + --webroot-path /var/lib/letsencrypt \ + -d "${site}" + + cd /etc/letsencrypt/live + ln -s ${SUTTY} default + done ;; + # Generate certificates + *) + # Save headers here + headers=/tmp/headers + # Gets ETag from previous headers + test -f "${headers}" \ + && etag="$(grep "^ Etag: " "${headers}" | cut -d : -f 2)" + + # Get site list from the API and transform to a list. Save headers + # for next run. Use ETag to avoid running when nothing changed + wget --user="${HTTP_BASIC_USER}" --password="${HTTP_BASIC_PASSWORD}" \ + --header="If-None-Match:${etag}" -qSO - \ + "https://api.${SUTTY}/v1/sites.json" \ + 2>"${headers}" \ + | jq --raw-output .[] \ + | while read domain; do + # Skip already existing domains + test -d "/etc/letsencrypt/live/${domain}" && continue + + # Ignore non local domains + nslookup "${domain}" 8.8.8.8 | grep -q "${SUTTY_ADDRESS}" || continue + + # Get the certificate for the domain, the webserver will need + # access to this directory + /usr/bin/certbot certonly --email "certbot@${SUTTY}" \ + --webroot \ + --agree-tos \ + --webroot-path /var/lib/letsencrypt \ + -d "${domain}" + done +esac + +# Fix permissions, users in group ssl have read access +find /etc/letsencrypt -type d | xargs -r chmod 2750 +find /etc/letsencrypt -type f | xargs -r chmod 640 +chgrp -R ssl /etc/letsencrypt + +# Push certificates to nodes, we use SSH as a secure transport +# but this means we're synchronizing from container to host which is +# awkward. A restricted rsync treats / as the remote location for the +# certificates. +for NODE in ${NODES}; do + rsync -avHAX --delete-after /etc/letsencrypt/ ${NODE}:/ +done diff --git a/monit.conf b/monit.conf new file mode 100644 index 0000000..7d1eb88 --- /dev/null +++ b/monit.conf @@ -0,0 +1,17 @@ +# Get first certificate! +check program certbot_bootstrap + with path "/usr/local/bin/certbot bootstrap" + every 1 cycle + if status = 0 then unmonitor + +# Renew certificates once a week +check program certbot_renew + with path "/usr/local/bin/certbot renew" + every "13 5 * * *" + if status != 0 then alert + +# Get missing certificates for every cycle. +check program certbot + with path "/usr/local/bin/certbot" + every 1 cycle + if status != 0 then alert diff --git a/ssh_config b/ssh_config new file mode 100644 index 0000000..fd417ed --- /dev/null +++ b/ssh_config @@ -0,0 +1,6 @@ +Host * + Protocol 2 + IdentityFile /root/certbot + VerifyHostKeyDNS yes + HashKnownHosts yes + StrictHostKeyChecking yes