Skip to content
This repository has been archived by the owner on Mar 26, 2021. It is now read-only.

Commit

Permalink
Merge pull request #1 from forMetris/master
Browse files Browse the repository at this point in the history
Add Consul support
  • Loading branch information
SvenDowideit authored Mar 25, 2019
2 parents 1ce58f0 + 8cc5a26 commit 2239607
Show file tree
Hide file tree
Showing 5 changed files with 201 additions and 27 deletions.
10 changes: 9 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
FROM ciscocloud/consul-cli:0.3.1 AS consul-cli



FROM alpine

RUN apk --no-cache add inotify-tools jq openssl util-linux bash
COPY --from=consul-cli /bin/consul-cli /bin/consul-cli
RUN wget https://raw.githubusercontent.com/containous/traefik/master/contrib/scripts/dumpcerts.sh -O dumpcerts.sh
RUN mkdir -p /traefik/ssl/

COPY run.sh /
ENV CERTDUMPER_MODE=default
ENV CERTDUMPER_CONSUL_PREFIX=traefik

COPY *.sh /
ENTRYPOINT ["/run.sh"]
42 changes: 38 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
# traefik-certdumper
dump the Lets Encrypt certs that Traefik stores in acme.json - to .crt, .key and .pem
Dump the Lets Encrypt certs that Traefik stores in acme.json or in Consul - to .crt, .key and .pem.

pretty much to solve https://github.com/containous/traefik/issues/2418 using @flesser's
Pretty much to solve https://github.com/containous/traefik/issues/2418 using @flesser's
compose-file script and https://github.com/containous/traefik/blob/master/contrib/scripts/dumpcerts.sh
with a few little mods
with a few little mods.

I use Docker Swarm, so my additional compose file service is:
## Default mode: acme.json
This is the default mode. In this case, you just have to mount `acme.json` on `/traefik/acme.json`.
The certs will be dumped to `/traefik/ssl`.

Here is a Docker Swarm stack configuration example:

```
# Watch acme.json and dump certificates to files
Expand All @@ -18,3 +22,33 @@ I use Docker Swarm, so my additional compose file service is:
mode: replicated
replicas: 1
```

## Consul mode
When you run multiple Traefik instances, it usually uses a KV store to store Lets Encrypt certs, instead of `acme.json`.
Consul is currently the [recommanded KV store](https://docs.traefik.io/user-guide/cluster/).

To enable this mode, you have to:
* set the environment variable `CERTDUMPER_MODE` to `consul`
* set the environment variable `CONSUL_HTTP_ADDR` (`host:port`)
* override the environment variable `CERTDUMPER_CONSUL_PREFIX` if needed (defaults to `traefik`)

Here is a Docker Swarm stack configuration example:

```
# Watch acme configuration from Consul and dump certificates to files
certdumper:
image: svendowideit/traefik-certdumper:latest
environment:
- CERTDUMPER_MODE=consul
- CERTDUMPER_CONSUL_ADDR=traefik_consul:8500
volumes:
- traefikcerts:/traefik/ssl
networks:
- traefik
deploy:
mode: replicated
replicas: 1
restart_policy:
condition: on-failure
delay: 60s
```
35 changes: 13 additions & 22 deletions run.sh
Original file line number Diff line number Diff line change
@@ -1,26 +1,17 @@
#!/bin/bash
#!/bin/sh
set -e

function dump() {
bash dumpcerts.sh /traefik/acme.json /traefik/ssl/
ln -f /traefik/ssl/certs/* /traefik/ssl/
ln -f /traefik/ssl/private/* /traefik/ssl/
for crt_file in $(ls /traefik/ssl/certs/*); do
pem_file=$(echo $crt_file | sed 's/certs/pem/g' | sed 's/.crt/-public.pem/g')
echo "openssl x509 -inform PEM -in $crt_file > $pem_file"
openssl x509 -inform PEM -in $crt_file > $pem_file
done
for key_file in $(ls /traefik/ssl/private/*); do
pem_file=$(echo $key_file | sed 's/private/pem/g' | sed 's/.key/-private.pem/g')
echo "openssl rsa -in $key_file -text > $pem_file"
openssl rsa -in $key_file -text > $pem_file
done
###
# log()
#
function log() {
echo "[$(date)] $@"
}

mkdir -p /traefik/ssl/pem/
# run once on start to make sure we have any old certs
dump
log "Starting dumper into $CERTDUMPER_MODE mode"

while true; do
inotifywait -e modify /traefik/acme.json
dump
done
if [ $CERTDUMPER_MODE == "consul" ]; then
exec /run_consul.sh
else
exec /run_default.sh
fi
114 changes: 114 additions & 0 deletions run_consul.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
#!/bin/bash
# Extracts certs and keys from Traefik ACME Consul configuration
# Author: @thomvaill
#
# Special thanks to @camilb (https://github.com/containous/traefik/issues/3847#issuecomment-425386416) for the Consul commands
#
set -e

###
# dump_consul_acme_json($json)
# string $json: JSON ACME configuration from Consul
#
# This function reproduces the behavior of https://github.com/containous/traefik/blob/master/contrib/scripts/dumpcerts.sh
# It extracts the private key and cert for each domain to /traefik/ssl/{certs,private}
#
function dump_consul_acme_json() {
json=$1

cert_urls=$(echo $json | jq -r '.DomainsCertificate.Certs[].Certificate.CertURL')
for cert_url in $cert_urls; do
log "Dumping $cert_url..."

domain=$(echo $json | jq -r --arg cert_url "$cert_url" '.DomainsCertificate.Certs[] | select (.Certificate.CertURL == $cert_url) | .Certificate.Domain')
log "-> main domain: $domain"

log "Extracting cert bundle..."
cert=$(echo $json | jq -r --arg cert_url "$cert_url" '.DomainsCertificate.Certs[] | select (.Certificate.CertURL == $cert_url) | .Certificate.Certificate')
echo $cert | base64 -d > /traefik/ssl/certs/$domain.crt

log "Extracting private key..."
key=$(echo $json | jq -r --arg cert_url "$cert_url" '.DomainsCertificate.Certs[] | select (.Certificate.CertURL == $cert_url) | .Certificate.PrivateKey')
echo $key | base64 -d > /traefik/ssl/private/$domain.key
done
}

###
# convert_to_pem()
#
# Same function as dump() from run_default.sh, to have the same behavior between the 2 modes:
# - copy .crt and .key files to /traefik/ssl root
# - generate .pem files into /traefik/ssl/pem
#
function convert_to_pem() {
ln -f /traefik/ssl/certs/* /traefik/ssl/
ln -f /traefik/ssl/private/* /traefik/ssl/
for crt_file in $(ls /traefik/ssl/certs/*); do
pem_file=$(echo $crt_file | sed 's/certs/pem/g' | sed 's/.crt/-public.pem/g')
echo "openssl x509 -inform PEM -in $crt_file > $pem_file"
openssl x509 -inform PEM -in $crt_file > $pem_file
done
for key_file in $(ls /traefik/ssl/private/*); do
pem_file=$(echo $key_file | sed 's/private/pem/g' | sed 's/.key/-private.pem/g')
echo "openssl rsa -in $key_file -text > $pem_file"
openssl rsa -in $key_file -text > $pem_file
done
}

###
# log()
#
function log() {
echo "[$(date)] $@"
}




mkdir -p "/traefik/ssl/"{certs,private}
mkdir -p /traefik/ssl/pem/

if [ -z $CERTDUMPER_CONSUL_ADDR ]; then
log "Please set CERTDUMPER_CONSUL_ADDR environment variable!"
exit 1
fi

# Test consul key existence by retreiving its ModifyIndex
# We will use this index later for watching changes
acme_modify_index=$(consul-cli kv read \
--consul=$CERTDUMPER_CONSUL_ADDR \
--format=text \
--fields=ModifyIndex \
$CERTDUMPER_CONSUL_PREFIX/acme/account/object)

while true; do
if [ -z $acme_modify_index ]; then
log "No entry found in $CERTDUMPER_CONSUL_ADDR/$CERTDUMPER_CONSUL_PREFIX/acme/account/object"
exit 1
fi

# Decompress consul value
# We have to fetch it again because bash does not handle binary variables :(
log "Retreiving $CERTDUMPER_CONSUL_ADDR/$CERTDUMPER_CONSUL_PREFIX/acme/account/object..."
acme_json=$(consul-cli kv read \
--consul=$CERTDUMPER_CONSUL_ADDR \
--format=text \
--fields=Value \
$CERTDUMPER_CONSUL_PREFIX/acme/account/object | gzip -dc)

# Dump certs
log "Dumping certs..."
dump_consul_acme_json $acme_json
convert_to_pem
log "Done"

# Wait for a value change
log "Waiting for an update of $CERTDUMPER_CONSUL_ADDR/$CERTDUMPER_CONSUL_PREFIX/acme/account/object (ModifyIndex $acme_modify_index)..."
acme_modify_index=$(consul-cli kv watch \
--consul=$CERTDUMPER_CONSUL_ADDR \
--wait-index=$acme_modify_index \
--format=text \
--fields=ModifyIndex \
$CERTDUMPER_CONSUL_PREFIX/acme/account/object)
log "Value has been updated, dumping again..."
done
27 changes: 27 additions & 0 deletions run_default.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/bin/bash
set -e

function dump() {
bash dumpcerts.sh /traefik/acme.json /traefik/ssl/
ln -f /traefik/ssl/certs/* /traefik/ssl/
ln -f /traefik/ssl/private/* /traefik/ssl/
for crt_file in $(ls /traefik/ssl/certs/*); do
pem_file=$(echo $crt_file | sed 's/certs/pem/g' | sed 's/.crt/-public.pem/g')
echo "openssl x509 -inform PEM -in $crt_file > $pem_file"
openssl x509 -inform PEM -in $crt_file > $pem_file
done
for key_file in $(ls /traefik/ssl/private/*); do
pem_file=$(echo $key_file | sed 's/private/pem/g' | sed 's/.key/-private.pem/g')
echo "openssl rsa -in $key_file -text > $pem_file"
openssl rsa -in $key_file -text > $pem_file
done
}

mkdir -p /traefik/ssl/pem/
# run once on start to make sure we have any old certs
dump

while true; do
inotifywait -e modify /traefik/acme.json
dump
done

0 comments on commit 2239607

Please sign in to comment.