Big rewrite so that we can enable sites as we get HTTPS certs
This commit is contained in:
parent
e8f4c77ed7
commit
9f4becca7e
14
Dockerfile
14
Dockerfile
|
@ -1,18 +1,22 @@
|
|||
FROM python:2
|
||||
FROM nginx
|
||||
MAINTAINER Elliot Saba <staticfloat@gmail.com>
|
||||
|
||||
VOLUME /etc/letsencrypt
|
||||
EXPOSE 80
|
||||
EXPOSE 443
|
||||
|
||||
RUN apt update && apt install -y cron
|
||||
RUN pip install certbot
|
||||
RUN mkdir /scripts
|
||||
RUN apt update && apt install -y cron python python-dev python-pip libffi-dev libssl-dev
|
||||
RUN pip install -U cffi certbot
|
||||
|
||||
# Copy in cron job and scripts for certbot
|
||||
COPY ./crontab /etc/cron.d/certbot
|
||||
RUN crontab /etc/cron.d/certbot
|
||||
|
||||
COPY ./scripts/ /scripts
|
||||
RUN chmod +x /scripts/*.sh
|
||||
|
||||
# Copy in default nginx configuration
|
||||
RUN rm -f /etc/nginx/conf.d/*
|
||||
COPY nginx_conf.d/ /etc/nginx/conf.d/
|
||||
|
||||
ENTRYPOINT []
|
||||
CMD ["/bin/bash", "/scripts/entrypoint.sh"]
|
||||
|
|
1
Makefile
1
Makefile
|
@ -2,6 +2,7 @@ all: build
|
|||
|
||||
build: Makefile Dockerfile
|
||||
docker build --squash -t staticfloat/docker-certbot-cron .
|
||||
echo "Done! Use docker run staticfloat/docker-certbot-cron to run"
|
||||
|
||||
push:
|
||||
docker push staticfloat/docker-certbot-cron
|
||||
|
|
62
README.md
62
README.md
|
@ -1,57 +1,5 @@
|
|||
# docker-certbot-cron
|
||||
Create and automatically renew website SSL certificates using the letsencrypt free certificate authority, and its client *certbot*. Define the environment variables `DOMAINS` (space-separated list of fully-qualified domain names) and `EMAIL` (your letsencrypt registration email) to automatically run `certbot` to renew/fetch your SSL certificates in the background. Configure `nginx` to pass off the ACME validation challenge, and you'll have zero-downtime, 100% automatic SSL certificates for all your Docker containers!
|
||||
|
||||
# ACME Validation challenge
|
||||
|
||||
To authenticate the certificates, the you need to pass the ACME validation challenge. This requires requests made on port 80 to your.domain.com/.well-known/ to be forwarded to this container.
|
||||
|
||||
The recommended way to use this image is to set up your reverse proxy to automatically forward requests for the ACME validation challenges to this container.
|
||||
|
||||
## Nginx example
|
||||
|
||||
If you use nginx as a reverse proxy, you can add the following to your configuration file in order to pass the ACME challenge.
|
||||
|
||||
``` nginx
|
||||
server {
|
||||
listen 80;
|
||||
location '/.well-known/acme-challenge' {
|
||||
default_type "text/plain";
|
||||
# Note: this works with docker-compose only if the service name is `certbot`,
|
||||
# and the `nginx` service `depends_on` the `certbot` service!
|
||||
proxy_pass http://certbot:80;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## `docker-compose` example
|
||||
|
||||
To use this container with `docker-compose`, put something like the following into your configuration:
|
||||
```yml
|
||||
version '2'
|
||||
services:
|
||||
...
|
||||
certbot:
|
||||
image: staticfloat/docker-certbot-cron
|
||||
container_name: certbot
|
||||
volumes:
|
||||
- certbot_etc_letsencrypt:/etc/letsencrypt
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- DOMAINS="foo.bar.com baz.bar.com"
|
||||
- EMAIL=email@domain.com
|
||||
...
|
||||
nginx:
|
||||
...
|
||||
depends_on:
|
||||
- certbot
|
||||
volumes:
|
||||
- certbot_etc_letsencrypt:/etc/letsencrypt:ro
|
||||
...
|
||||
volumes:
|
||||
certbot_etc_letsencrypt:
|
||||
external: true
|
||||
```
|
||||
I personally like having my certificates stored in an external volume so that if I ever accidentally run `docker-compose down` I don't have to re-issue myself the certificates.
|
||||
Create and automatically renew website SSL certificates using the letsencrypt free certificate authority, and its client *certbot*.
|
||||
|
||||
# More information
|
||||
|
||||
|
@ -59,10 +7,16 @@ Find out more about letsencrypt: https://letsencrypt.org
|
|||
|
||||
Certbot github: https://github.com/certbot/certbot
|
||||
|
||||
This repository was originally forked from `@henridwyer`, many thanks to him for the good idea. I've basically taken his approach and made it less flexible/simpler for my own use cases, so if you want this repository to do something a particular way, make sure [his repo](https://github.com/henridwyer/docker-letsencrypt-cron) doesn't already do it.
|
||||
This repository was originally forked from `@henridwyer`, many thanks to him for the good idea. I've rewritten about 90% of this repository, so it bears almost no resemblance to the original. This repository is _much_ more opinionated about the structure of your webservers/code, however it is easier to use as long as all of your webservers follow that pattern.
|
||||
|
||||
# Changelog
|
||||
|
||||
### 0.7
|
||||
- Complete rewrite, build this image on top of the `nginx` image, and run `cron` alongside `nginx` so that we can have nginx configs dynamically enabled as we get SSL certificates.
|
||||
|
||||
### 0.6
|
||||
- Add `nginx_auto_enable.sh` script to `/etc/letsencrypt/` so that users can bring nginx up before SSL certs are actually available.
|
||||
|
||||
### 0.5
|
||||
- Change the name to `docker-certbot-cron`, update documentation, strip out even more stuff I don't care about.
|
||||
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
server {
|
||||
# Listen on plain old HTTP
|
||||
listen 80;
|
||||
|
||||
# Pass this particular URL off to certbot, to authenticate HTTPS certificates
|
||||
location '/.well-known/acme-challenge' {
|
||||
default_type "text/plain";
|
||||
proxy_pass http://localhost:80;
|
||||
}
|
||||
|
||||
# Everything else gets shunted over to HTTPS
|
||||
location / {
|
||||
return 301 https://$http_host$request_uri;
|
||||
}
|
||||
}
|
|
@ -1,6 +1,23 @@
|
|||
#!/bin/bash
|
||||
#!/bin/sh
|
||||
|
||||
# When we get killed, kill all our children
|
||||
trap "exit" INT TERM
|
||||
trap "kill 0" EXIT
|
||||
/scripts/run_certbot.sh && cron -f &
|
||||
|
||||
# Source in util.sh so we can have our nice tools
|
||||
. $(cd $(dirname $0); pwd)/util.sh
|
||||
|
||||
# Immediately run auto_enable_configs so that nginx is in a runnable state
|
||||
auto_enable_configs
|
||||
|
||||
# Start up nginx, save PID so we can reload config inside of run_certbot.sh
|
||||
nginx -g "daemon off;" &
|
||||
export NGINX_PID=$!
|
||||
|
||||
# Next, run certbot to request all the ssl certs we can find
|
||||
/scripts/run_certbot.sh
|
||||
|
||||
# Run `cron -f &` so that it's a background job owned by bash and then `wait`.
|
||||
# This allows SIGINT (e.g. CTRL-C) to kill cron gracefully, due to our `trap`.
|
||||
cron -f &
|
||||
wait
|
||||
|
|
|
@ -1,48 +1,30 @@
|
|||
error() {
|
||||
(set +x; tput -Tscreen bold
|
||||
tput -Tscreen setaf 1
|
||||
echo $*
|
||||
tput -Tscreen sgr0) >&2
|
||||
}
|
||||
#!/bin/sh
|
||||
|
||||
if [ -z "$DOMAINS" ]; then
|
||||
error "DOMAINS environment variable undefined; certbot will do nothing"
|
||||
# Source in util.sh so we can have our nice tools
|
||||
. $(cd $(dirname $0); pwd)/util.sh
|
||||
|
||||
# We require an email to register the ssl certificate for
|
||||
if [ -z "$CERTBOT_EMAIL" ]; then
|
||||
error "CERTBOT_EMAIL environment variable undefined; certbot will do nothing"
|
||||
exit 1
|
||||
fi
|
||||
if [ -z "$EMAIL" ]; then
|
||||
error "EMAIL environment variable undefined; certbot will do nothing"
|
||||
exit 1
|
||||
fi
|
||||
echo "Running certbot for domains $DOMAINS for user $EMAIL..."
|
||||
|
||||
get_certificate() {
|
||||
# Gets the certificate for the domain(s) CERT_DOMAINS (a comma separated list)
|
||||
# The certificate will be named after the first domain in the list
|
||||
# To work, the following variables must be set:
|
||||
# - CERT_DOMAINS : comma separated list of domains
|
||||
# - EMAIL
|
||||
|
||||
local d=${CERT_DOMAINS//,*/} # read first domain
|
||||
echo "Getting certificate for $CERT_DOMAINS"
|
||||
certbot certonly --agree-tos --keep -n --text --email $EMAIL --server \
|
||||
https://acme-v01.api.letsencrypt.org/directory -d $CERT_DOMAINS \
|
||||
--standalone --standalone-supported-challenges http-01 --debug
|
||||
ec=$?
|
||||
echo "certbot exit code $ec"
|
||||
if [ $ec -eq 0 ]; then
|
||||
error "Certificates for $CERT_DOMAINS can be found in /etc/letsencrypt/live/$d"
|
||||
else
|
||||
error "Cerbot failed for $CERT_DOMAINS. Check the logs for details."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
exit_code=0
|
||||
set -x
|
||||
for d in $DOMAINS; do
|
||||
CERT_DOMAINS=$d
|
||||
if ! get_certificate; then
|
||||
# Loop over every domain we can find
|
||||
for domain in $(parse_domains); do
|
||||
if ! get_certificate $domain $CERTBOT_EMAIL; then
|
||||
error "Cerbot failed for $domain. Check the logs for details."
|
||||
exit_code=1
|
||||
fi
|
||||
done
|
||||
|
||||
# After trying to get all our certificates, auto enable any configs that we
|
||||
# did indeed get certificates for
|
||||
auto_enable_configs
|
||||
|
||||
# Finally, tell nginx to reload the configs
|
||||
kill -HUP $NGINX_PID
|
||||
|
||||
set +x
|
||||
exit $exit_code
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
#!/bin/sh
|
||||
|
||||
# Helper function to output error messages to STDERR, with red text
|
||||
error() {
|
||||
(set +x; tput -Tscreen bold
|
||||
tput -Tscreen setaf 1
|
||||
echo $*
|
||||
tput -Tscreen sgr0) >&2
|
||||
}
|
||||
|
||||
# Helper function that sifts through /etc/nginx/conf.d/, looking for lines that
|
||||
# contain ssl_certificate_key, and try to find domain names in them. We accept
|
||||
# a very restricted set of keys: Each key must map to a set of concrete domains
|
||||
# (no wildcards) and each keyfile will be stored at the default location of
|
||||
# /etc/letsencrypt/live/<primary_domain_name>/privkey.pem
|
||||
parse_domains() {
|
||||
# For each configuration file in /etc/nginx/conf.d/*.conf*
|
||||
for conf_file in /etc/nginx/conf.d/*.conf*; do
|
||||
sed -n -e 's/^\s*ssl_certificate_key\s*\/etc/letsencrypt/live/(.*\)/privkey.pem;/\1/p' $conf_file | tr '\n' ','
|
||||
done
|
||||
}
|
||||
|
||||
# Given a config file path, spit out all the ssl_certificate_key file paths
|
||||
parse_keyfiles() {
|
||||
sed -n -e 's/^\s*ssl_certificate_key\s*\(.*\);/\1/p' "$1"
|
||||
}
|
||||
|
||||
# Given a config file path, return 0 if all keyfiles exist (or there are no
|
||||
# keyfiles), return 1 otherwise
|
||||
keyfiles_exist() {
|
||||
for keyfile in $(parse_keyfiles $1); do
|
||||
if [ ! -f $keyfile ]; then
|
||||
echo "Couldn't find keyfile $keyfile for $1"
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
return 0
|
||||
}
|
||||
|
||||
# Helper function that sifts through /etc/nginx/conf.d/, looking for configs
|
||||
# that don't have their keyfiles yet, and disabling them through renaming
|
||||
auto_enable_configs() {
|
||||
for conf_file in /etc/nginx/conf.d/*.conf*; do
|
||||
if ! keyfiles_exist $conf_file; then
|
||||
if [ $conf_file == *.nokey ]; then
|
||||
echo "Found all the keyfiles for $conf_file, enabling..."
|
||||
mv $conf_file ${conf_file%.*}
|
||||
fi
|
||||
else
|
||||
if [ $conf_file == *.conf ]; then
|
||||
echo "Keyfile(s) missing for $conf_file, disabling..."
|
||||
mv $conf_file $conf_file.nokey
|
||||
fi
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# Helper function to ask certbot for the given domain(s). Must have defined the
|
||||
# EMAIL environment variable, to register the proper support email address.
|
||||
get_certificate() {
|
||||
echo "Getting certificate for domain $1 on behalf of user $2"
|
||||
return certbot certonly --agree-tos --keep -n --text --email $2 --server \
|
||||
https://acme-v01.api.letsencrypt.org/directory -d $1 \
|
||||
--standalone --standalone-supported-challenges http-01 --debug
|
||||
}
|
Loading…
Reference in New Issue