From 8369338216b4486e237db3737f1511021b986d66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Christian=20Gr=C3=BCnhage?= Date: Mon, 23 Apr 2018 18:55:55 +0100 Subject: [PATCH] feat: more powerful config generation config generation is now done with jinja2 instead of sed. also added a way to do the dns challenge without fiddling with more than env vars. BREAKING CHANGE: a lot of env vars and volumes changed. --- Dockerfile | 23 ++++-- README.md | 67 ++++++++++++---- dns-01.md | 47 +++++++++++ examples/docker-compose.dns-01.yml | 20 +++++ examples/docker-compose.http-01.yml | 16 ++++ http-01.md | 44 +++++++++++ root/etc/dehydrated/config.j2 | 116 ++++++++++++++++++++++++++++ root/etc/periodic/weekly/dehydrated | 4 +- root/etc/s6.d/dehydrated/run | 13 +++- root/etc/s6.d/setup/run | 58 -------------- zero-config-mode.md | 46 ----------- 11 files changed, 324 insertions(+), 130 deletions(-) create mode 100644 dns-01.md create mode 100644 examples/docker-compose.dns-01.yml create mode 100644 examples/docker-compose.http-01.yml create mode 100644 http-01.md create mode 100644 root/etc/dehydrated/config.j2 delete mode 100755 root/etc/s6.d/setup/run delete mode 100644 zero-config-mode.md diff --git a/Dockerfile b/Dockerfile index 6c6ee7a..da7d2bc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,6 +7,7 @@ RUN apk add --no-cache \ libffi-dev \ build-base \ openssl-dev \ + py2-pip \ && apk add --no-cache \ --virtual .runtime-deps \ openssl \ @@ -16,14 +17,26 @@ RUN apk add --no-cache \ bash \ su-exec \ libxml2-utils \ - && git clone https://github.com/lukas2511/dehydrated /dehydrated \ + py2-pip \ + python3 \ + && mkdir /opt \ + && git clone https://github.com/lukas2511/dehydrated.git /opt/dehydrated \ && pip3 install requests[security] \ && pip3 install dns-lexicon \ + && pip2 install j2cli[yaml] \ && apk del .build-deps +ENV \ + DEHYDRATED_CA="https://acme-staging-v02.api.letsencrypt.org/directory" \ + DEHYDRATED_CHALLENGE="http-01" \ + DEHYDRATED_KEYSIZE="4096" \ + DEHYDRATED_HOOK="" \ + DEHYDRATED_RENEW_DAYS="30" \ + DEHYDRATED_KEY_RENEW="yes" \ + DEHYDRATED_ACCEPT_TERMS="no" \ + DEHYDRATED_EMAIL="user@example.org" \ + DEHYDRATED_GENERATE_CONFIG="yes" + ADD root / -VOLUME /etc/dehydrated -VOLUME /var/www/dehydrated -VOLUME /certs - +VOLUME /data diff --git a/README.md b/README.md index b342281..ac812ca 100644 --- a/README.md +++ b/README.md @@ -5,22 +5,57 @@ This is a docker container that wraps around [dehydrated](https://github.com/lukas2511/dehydrated). ## Usage +We have short tutorials for two different modi operandi: The `dns-01` and `http-01` challenge. +Both are fairly easy to use. The `dns-01` challenge requires less effort if your DNS provider +is supported by [lexicon](https://github.com/AnalogJ/lexicon/#providers), the `http-01` challenge otherwise. -For a short tutorial on how to use this container with zero-configuration to sign certificates -using the HTTP-Challenge, see ["Zero-Config"-Mode"](zero-config-mode.md). +For a short tutorial of getting a certificate with this container and the `dns-01` challenge, +go [here](dns-01.md), for the same using the `http-01` challenge, go [here](http-01.md). -## Environment variables - -The following environment variables can be set to influence the container's behaviour: - -- `$ENDPOINT` which ACME-Endpoint you want to use, supported values: "staging", "production" (default). -- `$CHALLENGE` what type of challenge should be used, supported values: "http-01" (default), "dns-01" - -If the environment variables were not explicitely set, no modification to the configuration file is made - -## Behaviour on startup - -When the container is started, a script is run which looks for the configuration file in the places supported by dehydrated, -and if no configuration file is found, it will copy the [example configuration file](https://github.com/lukas2511/dehydrated/docs/examples/config) -into `/etc/dehydrated/config`. +## Behaviour +By default the container will attempt to generate a config as `/data/config` +with the default values for all the environment variables. +The defaults are explicitly meant to not work. Things you need to change: + - set `DEHYDRATED_ACCEPT_TERMS` to yes, ***after reading letsencrypts ToS*** + - set `DEHYDRATED_EMAIL` to an email address you own + - set `DEHYDRATED_CA` to a production ACME CA, for example letsencrypt's ACME v2 endpoint, "https://acme-v02.api.letsencrypt.org/directory" + - Only do this ***after*** you have tried it with the default staging endpoint + and it worked and you got the certificates you want. If this fails too often, + letsencrypt will block your IP and domain for a week, so do your experiments + on the staging endpoint. +### Advanced configuration +- `DEHYDRATED_CA`: +This controls which ACME endpoint dehydrated contacts. The most common value for +production environments is "https://acme-v02.api.letsencrypt.org/directory", +while you should use "https://acme-staging-v02.api.letsencrypt.org/directory" +for experiments. +- `DEHYDRATED_CHALLENGE`: +You can either put `dns-01` or `http-01` here, depending on how you want letsencrypt +to verify that you are allowed to obtain this certificate. +- `DEHYDRATED_KEYSIZE`: +This defaults to `4096`, but you could also put `2048` or `3072` here, if you want +less secure but slightly faster keys. This only makes sense if your host or your clients +are *very slow*. +- `DEHYDRATED_HOOK`: +If you use the `dns-01` challenge, you need to supply a hook script, +which dehydrated will use to set dns records. The container ships with +lexicon installed and a lexicon hook in `/usr/local/bin/lexicon-hook`. +Apart from the `dns-01` challenge, you can also use hooks to deploy newly created +certificates. For more info see [dehydrated's project page](https://github.com/lukas2511/dehydrated). +- `DEHYDRATED_RENEW_DAYS`: +When dehydrated runs, it will check if any certificates need renewal and renew those. +All certificates which expire in the next `n` days will be renewed, where `n` is the +number you set here. Default is 30 +- `DEHYDRATED_KEY_RENEW`: +Set this to yes to make dehydrated renew keys too when renewing certificates, or to +no to keep the keys. +- `DEHYDRATED_ACCEPT_TERMS`: +For the first run this needs to be set to yes, else dehydrated will not work. +Read the terms of service of letsencrypt before setting this to yes. +- `DEHYDRATED_EMAIL`: +Set your email address here. +- `DEHYDRATED_GENERATE_CONFIG`: +Set to yes by default. If you want to use a config supplied by you, +change this to no and put your own config in `/data/config` +- `UID` and `GID`: You can set the UID and GID of the things run in the docker container here.s diff --git a/dns-01.md b/dns-01.md new file mode 100644 index 0000000..3a6dbf9 --- /dev/null +++ b/dns-01.md @@ -0,0 +1,47 @@ +## Prerequisites + +For using the container with the `dns-01`-challenge, you need on your machine: + +- docker-compose (and docker obviously) + +Download [the docker-compose example](https://git.jcg.re/jcgruenhage/docker-dehydrated/raw/branch/master/examples/docker-compose.dns-01.yml) +as `docker-compose.yml` into an empty folder. Inside that folder, create the folder `data`. + +Now, create a file `data/domains.txt` in which you list the domains you want to create certificates for, +using the following format: + +- each certificate on a new line +- each line can contain a list of (sub)domains, separated by spaces + +For more information on the format, see [the dehydrated documentation](https://github.com/lukas2511/dehydrated/blob/master/docs/domains_txt.md). + +Before running, you need to set a few things in the `docker-compose.yml` file, +as explained [here in the README](https://git.jcg.re/jcgruenhage/docker-dehydrated#behaviour) + +## Configuring DNS API access + +Dehydrated needs to be able to add and remove DNS entries. For this, +the `docker-compose.yml` file contains an example how this works with Cloudflare. +Set your email and token there, and if you use another provider from +[this list](https://github.com/AnalogJ/lexicon/#providers), do the same but replace +cloudflare with your provider everywhere. + +## Using docker-dehydrated + +Inside the folder where you put the `docker-compose.yml` file, run this command: + +```bash +docker-compose up +``` + +This will create the requested certificates, if possible. +It will also check once per week whether the certificates need to be renewed, +and do so if necessary. To start the container in the background, add ` -d` +to the end of the command above. + +Please note that the container will `chown` the folders passed to it, so make sure your webserver can +still serve the contents of `data/wellknown`. You can configure the UID and GID +that the container uses by adding a UID and GID environment variable to the +`docker-compose.yml` file. + +After the challenges have been run, the certificates will be stored in `data/certs`. diff --git a/examples/docker-compose.dns-01.yml b/examples/docker-compose.dns-01.yml new file mode 100644 index 0000000..275de4b --- /dev/null +++ b/examples/docker-compose.dns-01.yml @@ -0,0 +1,20 @@ +version: '2' +services: + dehydrated: + image: docker.jcg.re/dehydrated + restart: unless-stopped + volumes: + - ./data:/data + environment: + - DEHYDRATED_GENERATE_CONFIG="yes" + - DEHYDRATED_CA="https://acme-v02.api.letsencrypt.org/directory" + - DEHYDRATED_CHALLENGE="dns-01" + - DEHYDRATED_KEYSIZE="4096" + - DEHYDRATED_HOOK="/usr/local/bin/lexicon-hook" + - DEHYDRATED_RENEW_DAYS="30" + - DEHYDRATED_KEY_RENEW="yes" + - DEHYDRATED_EMAIL="user@example.org" + - DEHYDRATED_ACCEPT_TERMS=no + - PROVIDER=cloudflare + - LEXICON_CLOUDFLARE_USERNAME=user@example.org + - LEXICON_CLOUDFLARE_TOKEN=abcdefghijklmnopqrstuvwxyz01234567890 diff --git a/examples/docker-compose.http-01.yml b/examples/docker-compose.http-01.yml new file mode 100644 index 0000000..d971420 --- /dev/null +++ b/examples/docker-compose.http-01.yml @@ -0,0 +1,16 @@ +version: '2' +services: + dehydrated: + image: docker.jcg.re/dehydrated + restart: unless-stopped + volumes: + - ./data:/data + environment: + - DEHYDRATED_GENERATE_CONFIG="yes" + - DEHYDRATED_CA="https://acme-v02.api.letsencrypt.org/directory" + - DEHYDRATED_CHALLENGE="http-01" + - DEHYDRATED_KEYSIZE="4096" + - DEHYDRATED_RENEW_DAYS="30" + - DEHYDRATED_KEY_RENEW="yes" + - DEHYDRATED_EMAIL="user@example.org" + - DEHYDRATED_ACCEPT_TERMS=no diff --git a/http-01.md b/http-01.md new file mode 100644 index 0000000..d5e05f7 --- /dev/null +++ b/http-01.md @@ -0,0 +1,44 @@ +## Prerequisites + +These are the things that you need to setup / already have set up in order to +use this container for creating certificates using the `http-01`-challenge: + +- A working internet connection, with port 80 of your machine available publicly +- An http server to serve the `.well-known` folder +- docker-compose + +Download [the docker-compose example](https://git.jcg.re/jcgruenhage/docker-dehydrated/raw/branch/master/examples/docker-compose.http-01.yml) +as `docker-compose.yml` into an empty folder. Inside that folder, create the folder `data/wellknown`. +Configure your Webserver to serve the contents of this folder under `domain/.well-known/acme-challenge` +(for all domains for which you want to create certificates). + +Now, create a file `data/domains.txt` in which you list the domains you want to create certificates for, +using the following format: + +- each certificate on a new line +- each line can contain a list of (sub)domains, separated by spaces + +For more information on the format, see [the dehydrated documentation](https://github.com/lukas2511/dehydrated/blob/master/docs/domains_txt.md). + +Before running, you need to set a few things in the `docker-compose.yml` file, +as explained [here in the README](https://git.jcg.re/jcgruenhage/docker-dehydrated#behaviour) + +## Using docker-dehydrated + +Inside the folder where you put the `docker-compose.yml` file, run this command: + +```bash +docker-compose up +``` + +This will create the requested certificates, if possible. +It will also check once per week whether the certificates need to be renewed, +and do so if necessary. To start the container in the background, add ` -d` +to the end of the command above. + +Please note that the container will `chown` the folders passed to it, so make sure your webserver can +still serve the contents of `data/wellknown`. You can configure the UID and GID +that the container uses by adding a UID and GID environment variable to the +`docker-compose.yml` file. + +After the challenges have been run, the certificates will be stored in `data/certs`. diff --git a/root/etc/dehydrated/config.j2 b/root/etc/dehydrated/config.j2 new file mode 100644 index 0000000..fb3a9d6 --- /dev/null +++ b/root/etc/dehydrated/config.j2 @@ -0,0 +1,116 @@ +######################################################## +# This is the main config file for dehydrated # +# # +# This file is looked for in the following locations: # +# $SCRIPTDIR/config (next to this script) # +# /usr/local/etc/dehydrated/config # +# /etc/dehydrated/config # +# ${PWD}/config (in current working-directory) # +# # +# Default values of this config are in comments # +######################################################## + +# Which user should dehydrated run as? This will be implictly enforced when running as root +#DEHYDRATED_USER= + +# Which group should dehydrated run as? This will be implictly enforced when running as root +#DEHYDRATED_GROUP= + +# Resolve names to addresses of IP version only. (curl) +# supported values: 4, 6 +# default: +#IP_VERSION= + +# Path to certificate authority (default: https://acme-v02.api.letsencrypt.org/directory) +CA={{ DEHYDRATED_CA }} + +# Path to old certificate authority +# Set this value to your old CA value when upgrading from ACMEv1 to ACMEv2 under a different endpoint. +# If dehydrated detects an account-key for the old CA it will automatically reuse that key +# instead of registering a new one. +# default: https://acme-v01.api.letsencrypt.org/directory +#OLDCA="https://acme-v01.api.letsencrypt.org/directory" + +# Which challenge should be used? Currently http-01 and dns-01 are supported +CHALLENGETYPE={{ DEHYDRATED_CHALLENGE }} + +# Path to a directory containing additional config files, allowing to override +# the defaults found in the main configuration file. Additional config files +# in this directory needs to be named with a '.sh' ending. +# default: +#CONFIG_D= + +# Base directory for account key, generated certificates and list of domains (default: $SCRIPTDIR -- uses config directory if undefined) +BASEDIR=/data + +# File containing the list of domains to request certificates for (default: $BASEDIR/domains.txt) +#DOMAINS_TXT="${BASEDIR}/domains.txt" + +# Output directory for generated certificates +#CERTDIR="${BASEDIR}/certs" + +# Directory for account keys and registration information +#ACCOUNTDIR="${BASEDIR}/accounts" + +# Output directory for challenge-tokens to be served by webserver or deployed in HOOK (default: /var/www/dehydrated) +WELLKNOWN=/data/wellknown + +# Default keysize for private keys (default: 4096) +keysize={{ DEHYDRATED_KEYSIZE }} + +# Path to openssl config file (default: - tries to figure out system default) +#OPENSSL_CNF= + +# Path to OpenSSL binary (default: "openssl") +#OPENSSL="openssl" + +# Extra options passed to the curl binary (default: ) +#CURL_OPTS= + +# Program or function called in certain situations +# +# After generating the challenge-response, or after failed challenge (in this case altname is empty) +# Given arguments: clean_challenge|deploy_challenge altname token-filename token-content +# +# After successfully signing certificate +# Given arguments: deploy_cert domain path/to/privkey.pem path/to/cert.pem path/to/fullchain.pem +# +# BASEDIR and WELLKNOWN variables are exported and can be used in an external program +# default: +HOOK={{ DEHYDRATED_HOOK }} + +# Chain clean_challenge|deploy_challenge arguments together into one hook call per certificate (default: no) +#HOOK_CHAIN="no" + +# Minimum days before expiration to automatically renew certificate (default: 30) +RENEW_DAYS={{ DEHYDRATED_RENEW_DAYS }} + +# Regenerate private keys instead of just signing new certificates on renewal (default: yes) +PRIVATE_KEY_RENEW={{ DEHYDRATED_KEY_RENEW }} + +# Create an extra private key for rollover (default: no) +#PRIVATE_KEY_ROLLOVER="no" + +# Which public key algorithm should be used? Supported: rsa, prime256v1 and secp384r1 +#KEY_ALGO=rsa + +# E-mail to use during the registration (default: ) +CONTACT_EMAIL={{ DEHYDRATED_EMAIL }} + +# Lockfile location, to prevent concurrent access (default: $BASEDIR/lock) +#LOCKFILE="${BASEDIR}/lock" + +# Option to add CSR-flag indicating OCSP stapling to be mandatory (default: no) +#OCSP_MUST_STAPLE="no" + +# Fetch OCSP responses (default: no) +#OCSP_FETCH="no" + +# Issuer chain cache directory (default: $BASEDIR/chains) +#CHAINCACHE="${BASEDIR}/chains" + +# Automatic cleanup (default: no) +#AUTO_CLEANUP="no" + +# ACME API version (default: auto) +#API=auto diff --git a/root/etc/periodic/weekly/dehydrated b/root/etc/periodic/weekly/dehydrated index 83adba3..aa6e294 100755 --- a/root/etc/periodic/weekly/dehydrated +++ b/root/etc/periodic/weekly/dehydrated @@ -1,4 +1,4 @@ #!/bin/bash -chown -R ${UID}:${GID} /etc/dehydrated /certs /var/www/dehydrated +chown -R ${UID}:${GID} /data # Run dehydrated -su-exec ${UID}:${GID} /dehydrated/dehydrated --cron --keep-going +su-exec ${UID}:${GID} /opt/dehydrated/dehydrated --config /data/config --cron --keep-going diff --git a/root/etc/s6.d/dehydrated/run b/root/etc/s6.d/dehydrated/run index 388cdf0..5501c9e 100755 --- a/root/etc/s6.d/dehydrated/run +++ b/root/etc/s6.d/dehydrated/run @@ -1,11 +1,18 @@ -#!/bin/sh +#!/bin/bash s6-svc -O /etc/s6.d/dehydrated +# If config generation is turned on, generate a config from the template and current env vars +if [[ "$DEHYDRATED_GENERATE_CONFIG" == "yes" ]]; then + j2 /etc/dehydrated/config.j2 > /data/config +fi + # Set ownership to dehydrated on the relevant folders -chown -R ${UID}:${GID} /etc/dehydrated /certs /var/www/dehydrated +chown -R ${UID}:${GID} /data # Register to the CA -su-exec ${UID}:${GID} /dehydrated/dehydrated --register --accept-terms +if [[ "$DEHYDRATED_ACCEPT_TERMS" == "yes" ]]; then + su-exec ${UID}:${GID} /opt/dehydrated/dehydrated --config /data/config --register --accept-terms +fi # Run the weekly script once /etc/periodic/weekly/dehydrated diff --git a/root/etc/s6.d/setup/run b/root/etc/s6.d/setup/run deleted file mode 100755 index d9ec739..0000000 --- a/root/etc/s6.d/setup/run +++ /dev/null @@ -1,58 +0,0 @@ -#!/bin/bash -s6-svc -O /etc/s6.d/setup - -# Check if and which configuration file exists -CONFIGFILE="none" -for check_config in "/etc/dehydrated" "/usr/local/etc/dehydrated" "${PWD}" "${SCRIPTDIR}"; do - if [[ -f "${check_config}/config" ]]; then - CONFIGFILE="${check_config}/config" - fi -done - -# At this point, if no configuration file exists, copy the example into /etc/dehydrated -if [[ "$CONFIGFILE" == "none" ]]; then - cp /dehydrated/docs/examples/config /etc/dehydrated/config - CONFIGFILE="/etc/dehydrated/config" -fi - -# Determine if the staging endpoint should be used -case "$ENDPOINT" in - "staging") - # If CA=... is commented, uncomment and set it to staging, if it is set to production, set it to staging - sed -ie 's/#CA=.*$/CA="https:\/\/acme-staging.api.letsencrypt.org\/directory"/g' $CONFIGFILE - sed -ie 's/CA=.+acme-v01\.api\..+$/CA="https:\/\/acme-staging.api.letsencrypt.org\/directory"/g' $CONFIGFILE - # Same procedure for CA_TERMS=... - sed -ie 's/#CA_TERMS=.*$/CA_TERMS="https:\/\/acme-staging.api.letsencrypt.org\/terms"/g' $CONFIGFILE - sed -ie 's/CA_TERMS=.+acme-v01\.api\..+$/CA_TERMS="https:\/\/acme-staging.api.letsencrypt.org\/terms"/g' $CONFIGFILE - ;; - "production") - # If CA=... is commented, uncomment and set to production, if it was set to staging, set it to production - sed -ie 's/#CA=.*$/CA="https:\/\/acme-v01.api.letsencrypt.org\/directory"/g' $CONFIGFILE - sed -ie 's/CA=.+acme-staging\.api\..+$/https:\/\/acme-v01.api.letsencrypt.org\/directory"/g' $CONFIGFILE - # Same thing for CA_TERMS=... - sed -ie 's/#CA_TERMS=.*$/CA_TERMS="https:\/\/acme-v01.api.letsencrypt.org\/terms"/g' $CONFIGFILE - sed -ie 's/CA_TERMS=.+acme-staging\.api\..+$/CA_TERMS="https:\/\/acme-v01.api.letsencrypt.org\/terms"/g' $CONFIGFILE - ;; - *) - echo "INFO: No endpoint was specifically set, dehydrated will use its default" - ;; -esac - -# Determine which type of challenge should be used -case "$CHALLENGE" in - "http-01") - # If we have a "fresh" config, uncomment the challengetype-line and set our value - sed -ie 's/#CHALLENGETYPE=.*$/CHALLENGETYPE="http-01"/g' $CONFIGFILE - # If a challengetype is already set, overwrite it - sed -ie 's/CHALLENGETYPE=.+$/CHALLENGETYPE="http-01"/g' $CONFIGFILE - ;; - "dns-01") - # If we have the default config, uncomment the line and set our challengetype - sed -ie 's/#CHALLENGETYPE=.*$/CHALLENGETYPE="dns-01"/g' $CONFIGFILE - # If a challengetype was already set, overwrite it with the new value - sed -ie 's/CHALLENGETYPE=.+$/CHALLENGETYPE="dns-01"/g' $CONFIGFILE - ;; - *) - echo "INFO: No challenge-type was specified, the default from dehydrated will be used" - ;; -esac diff --git a/zero-config-mode.md b/zero-config-mode.md deleted file mode 100644 index caf3c16..0000000 --- a/zero-config-mode.md +++ /dev/null @@ -1,46 +0,0 @@ -# Zero-configuration mode - -This is a tutorial on how to use this container (without needing to configure anything) to create -certificates for a given set of domains (using the HTTP-Challenge). - -## Prerequisites - -These are the things that you need to setup / already have set up in order to use this container -for creating certificates using the HTTP-Challenge: - -- A working internet connection, obviously -- HTTP Webserver to serve the ``.well-known`` which is used for the HTTP-Challenge - -Now create a folder in which dehydrated can push the challenge-data later, in this tutorial it -will be called ``dehydrated-www``. Configure your Webserver to serve the contents of this folder -under ``domain/.well-known/acme-challenge`` (for all domains for which you want to create certificates). - -Next create another folder in which dehydrated will place its configuration, certificates etc., -in this tutorial it will be called ``dehydrated-data``. In this folder, create a file called -``domains.txt`` in which you list the domains you want to create certificates for, using the -following format: - -- each domain on a new line -- subdomains of a domain on the same line as the domain. - -For more information on the format, see [https://github.com/lukas2511/dehydrated/blob/master/docs/domains_txt.md](https://github.com/lukas2511/dehydrated/blob/master/docs/domains_txt.md) - -## Using docker-dehydrated - -Now you can just run the container, and as the default challenge is the HTTP-Challenge, you do -not need to pass environment variables to alter the default behaviour. To run the container, -execute: - -```bash -$ docker run -v ./dehydrated-www:/var/www/dehydrated \ --v ./dehydrated-data:/etc/dehydrated docker.jcg.re/dehydrated -``` - -Please note that on SELinux-Systems, you need to set the "SELinux"-Flag when passing volumes: -``./dehydrated-www:/var/www/dehydrated:z`` (analog for ``dehydrated-data``). - -Also, the container will ``chown`` the folders passed to himself, so make sure your webserver can -still serve the contents of ``dehydrated-www``. - -After the challenges have been run, the certificates will be stored in ``dehydrated-data/certs``, -make sure to back this folder up!