| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170 |
- #!/usr/bin/env bash
- # 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/
- #
- # Dependencies -
- # util-linux
- # openssl
- # jq
- # The MIT License (MIT)
- #
- # 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:
- #
- # The above copyright notice and this permission notice shall be included in
- # all copies or substantial portions of the Software.
- #
- # 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
- set -o errexit
- set -o pipefail
- set -o nounset
- USAGE="$(basename "$0") <path to acme> <destination cert directory>"
- # Platform variations
- case "$(uname)" in
- 'Linux')
- # On Linux, -d should always work. --decode does not work with Alpine's busybox-binary
- CMD_DECODE_BASE64="base64 -d"
- ;;
- *)
- # Max OS-X supports --decode and -D, but --decode may be supported by other platforms as well.
- CMD_DECODE_BASE64="base64 --decode"
- ;;
- esac
- # Allow us to exit on a missing jq binary
- exit_jq() {
- echo "
- You must have the binary 'jq' to use this.
- jq is available at: https://stedolan.github.io/jq/download/
- ${USAGE}" >&2
- exit 1
- }
- bad_acme() {
- echo "
- There was a problem parsing your acme.json file.
- ${USAGE}" >&2
- exit 2
- }
- if [ $# -ne 2 ]; then
- echo "
- Insufficient number of parameters.
- ${USAGE}" >&2
- exit 1
- 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
- fi
- if [ ! -d "${certdir}" ]; then
- echo "
- Path ${certdir} does not seem to be a directory
- We need a directory in which to explode the JSON bundle... exiting.
- ${USAGE}" >&2
- exit 4
- fi
- jq=$(command -v jq) || exit_jq
- priv=$(${jq} -e -r '.[].Account.PrivateKey' "${acmefile}") || bad_acme
- if [ ! -n "${priv}" ]; then
- 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
- 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}
- pdir="${certdir}/private/"
- cdir="${certdir}/certs/"
- # 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
- 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.
- #
- # 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-----" \
- # | openssl rsa -inform pem -out "${pdir}/letsencrypt.key"
- #
- # and sed:
- # echo "-----BEGIN RSA PRIVATE KEY-----" > "${pdir}/letsencrypt.key"
- # echo ${priv} | sed -E 's/(.{64})/\1\n/g' >> "${pdir}/letsencrypt.key"
- # sed -i '$ d' "${pdir}/letsencrypt.key"
- # echo "-----END RSA PRIVATE KEY-----" >> "${pdir}/letsencrypt.key"
- # openssl rsa -noout -in "${pdir}/letsencrypt.key" -check # To check if the key is valid
- # 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-----" \
- | openssl rsa -inform pem -out "${pdir}/letsencrypt.key" >/dev/null
- # Process the certificates for each of the domains in acme.json
- for domain in $(jq -r '.[].Certificates[].domain.main' ${acmefile}); do
- # Traefik stores a cert bundle for each domain. Within this cert
- # bundle there is both proper the certificate and the Let's Encrypt CA
- echo "Extracting cert bundle for ${domain}"
- cert=$(jq -e -r --arg domain "$domain" '.[].Certificates[] |
- select (.domain.main == $domain )| .certificate' ${acmefile}) || bad_acme
- echo "${cert}" | ${CMD_DECODE_BASE64} > "${cdir}/${domain}.crt"
- echo "Extracting private key for ${domain}"
- key=$(jq -e -r --arg domain "$domain" '.[].Certificates[] |
- select (.domain.main == $domain )| .key' ${acmefile}) || bad_acme
- echo "${key}" | ${CMD_DECODE_BASE64} > "${pdir}/${domain}.key"
- done
|