2017-06-23 00:49:05 +02:00
#!/usr/bin/env bash
2017-04-23 11:59:10 -07:00
# Copyright (c) 2017 Brian 'redbeard' Harrington <redbeard@dead-city.org>
#
# dumpcerts.sh - A simple utility to explode a Traefik acme.json file into a
# directory of certificates and a private key
#
# Usage - dumpcerts.sh /etc/traefik/acme.json /etc/ssl/
#
2017-09-07 03:02:03 -07:00
# Dependencies -
2017-04-23 11:59:10 -07:00
# util-linux
# openssl
# jq
# The MIT License (MIT)
2017-09-07 03:02:03 -07:00
#
2017-04-23 11:59:10 -07:00
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
2017-09-07 03:02:03 -07:00
#
2017-04-23 11:59:10 -07:00
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
2017-09-07 03:02:03 -07:00
#
2017-04-23 11:59:10 -07:00
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# Exit codes:
# 1 - A component is missing or could not be read
# 2 - There was a problem reading acme.json
# 4 - The destination certificate directory does not exist
# 8 - Missing private key
2017-06-23 00:49:05 +02:00
set -o errexit
set -o pipefail
set -o nounset
USAGE = " $( basename " $0 " ) <path to acme> <destination cert directory> "
2017-04-23 11:59:10 -07:00
# Allow us to exit on a missing jq binary
2017-06-23 00:49:05 +02:00
exit_jq( ) {
echo "
You must have the binary 'jq' to use this.
jq is available at: https://stedolan.github.io/jq/download/
${ USAGE } " >&2
2017-04-23 11:59:10 -07:00
exit 1
}
2017-06-23 00:49:05 +02:00
bad_acme( ) {
echo "
There was a problem parsing your acme.json file.
2017-04-23 11:59:10 -07:00
2017-06-23 00:49:05 +02:00
${ USAGE } " >&2
2017-04-23 11:59:10 -07:00
exit 2
}
2017-06-23 00:49:05 +02:00
if [ $# -ne 2 ] ; then
echo "
Insufficient number of parameters.
2017-04-23 11:59:10 -07:00
2017-06-23 00:49:05 +02:00
${ USAGE } " >&2
2017-04-23 11:59:10 -07:00
exit 1
2017-06-23 00:49:05 +02:00
fi
readonly acmefile = " ${ 1 } "
readonly certdir = " ${ 2 %/ } "
if [ ! -r " ${ acmefile } " ] ; then
echo "
There was a problem reading from '${acmefile}'
We need to read this file to explode the JSON bundle... exiting.
${ USAGE } " >&2
exit 2
2017-09-07 03:02:03 -07:00
fi
2017-04-23 11:59:10 -07:00
if [ ! -d " ${ certdir } " ] ; then
2017-06-23 00:49:05 +02:00
echo "
Path ${ certdir } does not seem to be a directory
We need a directory in which to explode the JSON bundle... exiting.
${ USAGE } " >&2
2017-04-23 11:59:10 -07:00
exit 4
2017-09-07 03:02:03 -07:00
fi
2017-04-23 11:59:10 -07:00
2017-06-23 00:49:05 +02:00
jq = $( command -v jq) || exit_jq
2017-04-23 11:59:10 -07:00
2017-06-23 00:49:05 +02:00
priv = $( ${ jq } -e -r '.PrivateKey' " ${ acmefile } " ) || bad_acme
2017-04-23 11:59:10 -07:00
if [ ! -n " ${ priv } " ] ; then
2017-06-23 00:49:05 +02:00
echo "
There didn' t seem to be a private key in ${ acmefile } .
Please ensure that there is a key in this file and try again." >&2
2017-04-23 11:59:10 -07:00
exit 8
fi
# If they do not exist, create the needed subdirectories for our assets
# and place each in a variable for later use, normalizing the path
mkdir -p " ${ certdir } " /{ certs,private}
2017-06-23 00:49:05 +02:00
pdir = " ${ certdir } /private/ "
cdir = " ${ certdir } /certs/ "
2017-04-23 11:59:10 -07:00
# Save the existing umask, change the default mode to 600, then
# after writing the private key switch it back to the default
oldumask = $( umask )
umask 177
2017-06-23 00:49:05 +02:00
trap 'umask ${oldumask}' EXIT
# traefik stores the private key in stripped base64 format but the certificates
# bundled as a base64 object without stripping headers. This normalizes the
# headers and formatting.
2017-04-23 11:59:10 -07:00
#
# In testing this out it was a balance between the following mechanisms:
# gawk:
# echo ${priv} | awk 'BEGIN {print "-----BEGIN RSA PRIVATE KEY-----"}
# {gsub(/.{64}/,"&\n")}1
# END {print "-----END RSA PRIVATE KEY-----"}' > "${pdir}/letsencrypt.key"
#
# openssl:
# echo -e "-----BEGIN RSA PRIVATE KEY-----\n${priv}\n-----END RSA PRIVATE KEY-----" \
2017-09-07 03:02:03 -07:00
# | openssl rsa -inform pem -out "${pdir}/letsencrypt.key"
2017-04-23 11:59:10 -07:00
#
# and sed:
# echo "-----BEGIN RSA PRIVATE KEY-----" > "${pdir}/letsencrypt.key"
# echo ${priv} | sed 's/(.{64})/\1\n/g' >> "${pdir}/letsencrypt.key"
# echo "-----END RSA PRIVATE KEY-----" > "${pdir}/letsencrypt.key"
#
# In the end, openssl was chosen because most users will need this script
# *because* of openssl combined with the fact that it will refuse to write the
# key if it does not parse out correctly. The other mechanisms were left as
# comments so that the user can choose the mechanism most appropriate to them.
echo -e " -----BEGIN RSA PRIVATE KEY-----\n ${ priv } \n-----END RSA PRIVATE KEY----- " \
2017-06-23 00:49:05 +02:00
| openssl rsa -inform pem -out " ${ pdir } /letsencrypt.key "
2017-04-23 11:59:10 -07:00
# Process the certificates for each of the domains in acme.json
for domain in $( jq -r '.DomainsCertificate.Certs[].Certificate.Domain' acme.json) ; do
2017-09-07 03:02:03 -07:00
# Traefik stores a cert bundle for each domain. Within this cert
2017-04-23 11:59:10 -07:00
# bundle there is both proper the certificate and the Let's Encrypt CA
echo " Extracting cert bundle for ${ domain } "
2017-06-23 00:49:05 +02:00
cert = $( jq -e -r --arg domain " $domain " ' .DomainsCertificate.Certs[ ] .Certificate |
select ( .Domain = = $domain ) | .Certificate' ${ acmefile } ) || bad_acme
echo " ${ cert } " | base64 --decode > " ${ cdir } / ${ domain } .pem "
2017-04-23 11:59:10 -07:00
done