Skip to content

Commit

Permalink
Merge branch 'v2x'
Browse files Browse the repository at this point in the history
  • Loading branch information
Adrien Ferrand committed Mar 27, 2018
2 parents 9069f7d + af17282 commit 0200ba0
Show file tree
Hide file tree
Showing 6 changed files with 41 additions and 19 deletions.
5 changes: 3 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ LABEL maintainer="Adrien Ferrand <[email protected]>"
ENV PATH /scripts:$PATH

# Versioning
ENV LEXICON_VERSION 2.1.19
ENV CERTBOT_VERSION 0.21.1
ENV LEXICON_VERSION 2.2.1
ENV CERTBOT_VERSION 0.22.2

# Let's Encrypt configuration
ENV LETSENCRYPT_STAGING false
ENV LETSENCRYPT_USER_MAIL [email protected]
ENV LETSENCRYPT_ACME_V1 false

# Lexicon configuration
ENV LEXICON_PROVIDER cloudflare
Expand Down
20 changes: 17 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# adferrand/letsencrypt-dns
![](https://img.shields.io/badge/tags-latest-lightgrey.svg) [![](https://images.microbadger.com/badges/version/adferrand/letsencrypt-dns:1.5.1.svg) ![](https://images.microbadger.com/badges/image/adferrand/letsencrypt-dns:1.5.1.svg)](https://microbadger.com/images/adferrand/letsencrypt-dns:1.5.1)
![](https://img.shields.io/badge/tags-latest-lightgrey.svg) [![](https://images.microbadger.com/badges/version/adferrand/letsencrypt-dns:2.0.0.svg) ![](https://images.microbadger.com/badges/image/adferrand/letsencrypt-dns:2.0.0.svg)](https://microbadger.com/images/adferrand/letsencrypt-dns:2.0.0)

* [Container functionalities](#container-functionalities)
* [Why use this Docker](#why-use-this-docker-)
Expand All @@ -17,6 +17,7 @@
* [Restart containers when a certificate is renewed](#restart-containers-when-a-certificate-is-renewed)
* [Call a reload command on containers when a certificate is renewed](#call-a-reload-command-on-containers-when-a-certificate-is-renewed)
* [Miscellaneous and testing](#miscellaneous-and-testing)
* [Using ACME v1 servers](#using-acme-v1-servers)
* [Activate staging ACME servers](#activating-staging-acme-servers)
* [Auto-export certificates in PFX format](#auto-export-certificates-in-pfx-format)
* [Sleep time](#sleep-time)
Expand All @@ -26,7 +27,7 @@

This Docker is designed to manage [Let's Encrypt](https://letsencrypt.org) SSL certificates based on [DNS challenges](https://tools.ietf.org/html/draft-ietf-acme-acme-01#page-44).

* Let's Encrypt certificates generation by [Certbot](https://github.com/certbot/certbot) using DNS challenges,
* Let's Encrypt wildcard and regular certificates generation by [Certbot](https://github.com/certbot/certbot) using DNS challenges,
* Automated renewal of almost expired certificates using Cron Certbot task,
* Standardized API throuh [Lexicon](https://github.com/AnalogJ/lexicon) library to insert the DNS challenge with various DNS providers,
* Centralized configuration file to maintain several certificates,
Expand All @@ -44,11 +45,14 @@ So far so good, but you may fall in one of the following categories:

1. You are in a firewalled network, and your HTTP/80 and HTTPS/443 ports are not opened to the outside world.
2. You want to secure non-Web services (like LDAP, IMAP, POP, *etc.*) were the HTTPS protocol is of no use.
3. You want to generate a wildcard certificate, valid for any sub-domain of a given domain.

For the first case, ACME servers need to be able to access your website through HTTP (for HTTP challenges) or HTTPS (for TLS challenges) in order to validate the certificate. With a firewall these two challenges - which are widely used in HTTP proxy approaches - will not be usable: you need to ask a DNS challenge. Please note that traefik embed DNS challenges, but only for few DNS providers.

For the second case, there is no website to use TLS or HTTP challenges, and you should ask a DNS challenge. Of course you can create a "fake" website to validate the domain, and reuse the certificate on the "real" service. But it is a workaround, and you have to implement a logic to propagate the certificate, including during its renewal. Indeed, most of the non-Web services will need to be restarted each time the certificate is renewed.

For the last case, the use of a DNS challenge is mandatory. Then the problems concerning certificates propagation which have been discussed in the second case will also occur.

The solution is a dedicated and specialized Docker service which handles the creation/renewal of Let's Encrypt certificates, and ensure their propagation in the relevant Docker services. It is the purpose of this container.

## Preparation of the container
Expand All @@ -67,24 +71,28 @@ Let's take an example. Our domain is `example.com`, and we want:
- a certificate for `smtp.example.com`
- a certificate for `imap.example.com` + `pop.example.com`
- a certificate for `ldap.example.com`
- a wildcard certificate for any sub-domain of `example.com` and for `example.com` itself

Then the `domains.conf` will look like this:

```
smtp.example.com
imap.example.com pop.example.com
ldap.example.com
*.example.com example.com
```

You need also to provide the mail which will be used to register your account on Let's Encrypt. Set the environment variable `LETSENCRYPT_USER_MAIL (default: [email protected])` in the container for this purpose.

_NB: For a wildcard certificate, specifying a sub-domain already covered by the wildcard will raise an error during Certbot certificate generation (eg. `test.example.com` cannot be put on the same line than `*.example.com`)._

### Configuring DNS provider and authentication to DNS API

When using a DNS challenge, a TXT entry must be inserted in the DNS zone which manage the certificate domain. This TXT entry must contain a unique hash calculated by Certbot, and the ACME servers will check it before delivering the certificate.

This container will do the hard work for you, thanks to the association between [Certbot](https://certbot.eff.org/) and [Lexicon](https://github.com/AnalogJ/lexicon): DNS provider API will be called automatically to insert the TXT record when needed. All you have to do is to define for Lexicon the DNS provider to use, and the API access key.

Following DNS provider are supported: AWS Route53, Cloudflare, ClouDNS, CloudXNS, DigitalOcean, DNSimple, DnsMadeEasy, DNSPark, DNSPod, EasyDNS, Gandi, Glesys, GoDaddy, Linode, LuaDNS, Memset, Namecheap, Namesilo, NS1, OVH, PointHQ, PowerDNS, Rackspace, Rage4, SoftLayer, Transip, Yandex, Vultr, Zonomi.
Following DNS provider are supported: AuroraDNS, AWS Route53, Cloudflare, ClouDNS, CloudXNS, DigitalOcean, DNSimple, DnsMadeEasy, DNSPark, DNSPod, EasyDNS, Gandi, Gehirn Infrastructure Service, Glesys, GoDaddy, Linode, LuaDNS, Memset, Namecheap, Namesilo, NS1, OnApp, OVH, PointHQ, PowerDNS, Rackspace, Rage4, Sakura Cloud, SoftLayer, Transip, Yandex, Vultr, Zonomi.

The DNS provider is choosen by setting an environment variable passed to the container: `LEXICON_PROVIDER (default: cloudflare)`.

Expand Down Expand Up @@ -250,6 +258,12 @@ _(Limitations on invokable commands) The option `autocmd-container` is intended

## Miscellaneous and testing

### Using ACME v1 servers

Starting to version 2.0.0, this container uses the ACME v2 servers (production & staging) to allow wildcard certificates generation. If for any reason you want to continue to use old ACME v1 servers, you can set the environment variable `LETSENCRYPT_ACME_V1 (default: false)` to `true`. In this case, ACME v1 servers will be used to any certificate generation, but wildcard certificates will not be supported.

_NB: During a certificate renewal, the server (and authentication) used for the certificate generation will be reused, independently of the `LETSENCRYPT_ACME_V1` environment variable value. If you want to change the server used for a particular certificate, you will need first to revoke it by removing the relevant entry from `domains.txt` file before recreating it._

### Activating staging ACME servers

During development it is not advised to generate certificates againt production ACME servers, as one could reach easily the weekly limit of Let's Encrypt and could not generate certificates for a certain period of time. Staging ACME servers does not have this limit. To use them, set the environment variable `LETSENCRYPT_STAGING (default: false)` to `true`.
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.5.1
2.0.0
4 changes: 2 additions & 2 deletions files/deploy-hook.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ if [ "$PFX_EXPORT" = "true" ]; then
fi

# Synchronize mode and user/group for new certificate files
find $RENEWED_LINEAGE ${RENEWED_LINEAGE/live/archive} -type d -exec chmod "$CERTS_DIRS_MODE" {} +
find $RENEWED_LINEAGE ${RENEWED_LINEAGE/live/archive} -type f -exec chmod "$CERTS_FILES_MODE" {} +
find $RENEWED_LINEAGE ${RENEWED_LINEAGE/live/archive} -type d -exec chmod "$CERTS_DIRS_MODE" {} +
find $RENEWED_LINEAGE ${RENEWED_LINEAGE/live/archive} -type f -exec chmod "$CERTS_FILES_MODE" {} +
chown -R $CERTS_USER_OWNER:$CERTS_GROUP_OWNER $RENEWED_LINEAGE ${RENEWED_LINEAGE/live/archive}
5 changes: 1 addition & 4 deletions files/run.sh
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
#!/bin/sh

# Ensure domain.conf exists
touch /etc/letsencrypt/domains.conf

# Ensure certs folders exist, and with correct permissions
mkdir -p /etc/letsencrypt/live /etc/letsencrypt/archive
if [ "$CERTS_DIR_WORLD_READABLE" = "true" ]; then
Expand All @@ -15,7 +12,7 @@ fi

# Synchronize certs files mode and user/group permissions
find /etc/letsencrypt/live /etc/letsencrypt/archive -type d -exec chmod "$CERTS_DIRS_MODE" {} +
find /etc/letsencrypt/live /etc/letsencrypt/archive -type f -exec chmod "$CERTS_FILES_MODE" {} +
find /etc/letsencrypt/live /etc/letsencrypt/archive -type f -exec chmod "$CERTS_FILES_MODE" {} +
chown -R $CERTS_USER_OWNER:$CERTS_GROUP_OWNER /etc/letsencrypt/live /etc/letsencrypt/archive

# Load crontab
Expand Down
24 changes: 17 additions & 7 deletions files/watch-domains.sh
Original file line number Diff line number Diff line change
@@ -1,21 +1,31 @@
#!/bin/sh

staging_cmd=""
if [ "$LETSENCRYPT_STAGING" = true ]; then
staging_cmd="--staging"
if [ "$LETSENCRYPT_ACME_V1" = true ]; then
server_cmd="--server https://acme-v01.api.letsencrypt.org/directory"
if [ "$LETSENCRYPT_STAGING" = true ]; then
server_cmd="--server https://acme-staging.api.letsencrypt.org/directory"
fi
else
server_cmd="--server https://acme-v02.api.letsencrypt.org/directory"
if [ "$LETSENCRYPT_STAGING" = true ]; then
server_cmd="--server https://acme-staging-v02.api.letsencrypt.org/directory"
fi
fi

current_hash=
while true; do
# Ensure domain.conf exists
touch /etc/letsencrypt/domains.conf

# Calculate the new domains.conf file hash
new_hash=`md5sum /etc/letsencrypt/domains.conf | awk '{ print $1 }'`
if [ "$current_hash" != "$new_hash" ]; then
# Clean all autorestart/autocmd containers instances
rm -f /etc/supervisord.d/*_autorestart-containers
rm -f /etc/supervisord.d/*_autocmd-containers
rm -f /etc/supervisord.d/*_autocmd-containers

echo "#### Registering Let's Encrypt account if needed ####"
certbot register -n --agree-tos -m $LETSENCRYPT_USER_MAIL $staging_cmd
certbot register -n --agree-tos -m $LETSENCRYPT_USER_MAIL $server_cmd

echo "#### Creating missing certificates if needed (~1min for each) ####"
while read -r entry; do
Expand All @@ -42,7 +52,7 @@ while true; do
--manual-public-ip-logging-ok \
--expand \
--deploy-hook deploy-hook.sh \
$staging_cmd \
$server_cmd \
$domains_cmd

if [ "$autorestart_config" != "" ]; then
Expand Down Expand Up @@ -78,7 +88,7 @@ while true; do

if [ "$remove_domain" = true ]; then
echo ">>> Removing the certificate $domain"
certbot revoke -n $staging_cmd --cert-path /etc/letsencrypt/live/$domain/cert.pem
certbot revoke -n $server_cmd --cert-path /etc/letsencrypt/live/$domain/cert.pem
fi
done

Expand Down

0 comments on commit 0200ba0

Please sign in to comment.