--- /dev/null
+#!/bin/bash
+#
+# Dump certificates from the HAProxy stats or master socket to the filesystem
+# Experimental script
+#
+
+set -e
+
+export BASEPATH=${BASEPATH:-/etc/haproxy}/
+export SOCKET=${SOCKET:-/var/run/haproxy-master.sock}
+export DRY_RUN=0
+export DEBUG=
+export VERBOSE=
+export M="@1 "
+
+vecho() {
+
+ [ -n "$VERBOSE" ] && echo "$@"
+ return 0
+}
+
+read_certificate() {
+ name=$1
+ crt_filename=
+ key_filename=
+
+ OFS=$IFS
+ IFS=":"
+
+ while read -r key value; do
+ case "$key" in
+ "Crt filename")
+ crt_filename="${value# }"
+ key_filename="${value# }"
+ ;;
+ "Key filename")
+ key_filename="${value# }"
+ ;;
+ esac
+ done < <(echo "${M}show ssl cert ${name}" | socat "${SOCKET}" -)
+ IFS=$OFS
+
+ if [ -z "$crt_filename" ] || [ -z "$key_filename" ]; then
+ echo "error: can't dump \"$name\", crt/key filename details not found in \"show ssl cert\"" >&2
+ return 1
+ fi
+
+ # handle fields without a crt-base/key-base
+ [ "${crt_filename:0:1}" != "/" ] && crt_filename="${BASEPATH}${crt_filename}"
+ [ "${key_filename:0:1}" != "/" ] && key_filename="${BASEPATH}${key_filename}"
+
+ vecho "name:$name"
+ vecho "crt:$crt_filename"
+ vecho "key:$key_filename"
+
+ export NAME="$name"
+ export CRT_FILENAME="$crt_filename"
+ export KEY_FILENAME="$key_filename"
+
+ return 0
+}
+
+dump_certificate() {
+ name=$1
+ crt_filename=$2
+ key_filename=$3
+
+ tmp="tmp.${RANDOM}"
+ d="old.$(date +%s)"
+
+ if ! touch "${crt_filename}.${tmp}" || ! touch "${key_filename}.${tmp}"; then
+ echo "error: can't dump \"$name\", can't create tmp files" >&2
+ return 1
+ fi
+
+ echo "${M}dump ssl cert ${name}" | socat "${SOCKET}" - | openssl pkey >> "${key_filename}.${tmp}"
+ # use crl2pkcs7 as a way to dump multiple x509, storeutl could be used in modern versions of openssl
+ echo "${M}dump ssl cert ${name}" | socat "${SOCKET}" - | openssl crl2pkcs7 -nocrl -certfile /dev/stdin | openssl pkcs7 -print_certs >> "${crt_filename}.${tmp}"
+
+ if ! cmp -s <(openssl x509 -in "${crt_filename}.${tmp}" -pubkey -noout) <(openssl pkey -in "${key_filename}.${tmp}" -pubout); then
+ echo "Error: Private key \"${key_filename}.${tmp}\" and public key \"${crt_filename}.${tmp}\" don't match" >&2
+ return 1
+ fi
+
+ # move the current certificates to ".old.timestamp"
+ mv "${crt_filename}" "${crt_filename}.${d}"
+ [ "${crt_filename}" != "${key_filename}" ] && mv "${key_filename}" "${key_filename}.${d}"
+
+ mv "${crt_filename}.${tmp}" "${crt_filename}"
+ [ "${crt_filename}" != "${key_filename}" ] && mv "${key_filename}.${tmp}" "${key_filename}"
+
+ return 0
+}
+
+dump_all_certificates() {
+ echo "${M}show ssl cert" | socat "${SOCKET}" - | grep -v '^#' | grep -v '^$' | while read -r line; do
+ export NAME
+ export CRT_FILENAME
+ export KEY_FILENAME
+
+ if read_certificate "$line"; then
+ [ "${DRY_RUN}" = "0" ] && dump_certificate "$NAME" "$CRT_FILENAME" "$KEY_FILENAME"
+ fi
+ done
+}
+
+usage() {
+ echo "Usage:"
+ echo " $0 [options]* [cert]*"
+ echo ""
+ echo " Dump certificates from the HAProxy stats or master socket to the filesystem"
+ echo " Require socat and openssl"
+ echo " EXPERIMENTAL script, backup your files!"
+ echo " The script will move your previous files to FILE.old.unixtimestamp (ex: foo.com.pem.old.1759044998)"
+
+ echo ""
+ echo "Options:"
+ echo " -S, --master-socket <path> Use the master socket at <path> (default: ${SOCKET})"
+ echo " -s, --socket <path> Use the stats socket at <path>"
+ echo " -p, --path <path> Specifiy a base path for relative files (default: ${BASEPATH})"
+ echo " -n, --dry-run Read certificates on the socket but don't dump them"
+ echo " -d, --debug Debug mode, set -x"
+ echo " -v, --verbose Verbose mode"
+ echo " -h, --help This help"
+ echo " -- End of options"
+ echo ""
+ echo "Examples:"
+ echo " $0 -v -p ${BASEPATH} -S ${SOCKET}"
+ echo " $0 -v -p ${BASEPATH} -S ${SOCKET} bar.com.rsa.pem"
+ echo " $0 -v -p ${BASEPATH} -S ${SOCKET} -- foo.com.ecdsa.pem bar.com.rsa.pem"
+}
+
+main() {
+ while [ -n "$1" ]; do
+ case "$1" in
+ -S|--master-socket)
+ SOCKET="$2"
+ M="@1 "
+ shift 2
+ ;;
+ -s|--socket)
+ SOCKET="$2"
+ M=
+ shift 2
+ ;;
+ -p|--path)
+ BASEPATH="$2"
+ shift 2
+ ;;
+ -n|--dry-run)
+ DRY_RUN=1
+ shift
+ ;;
+ -d|--debug)
+ DEBUG=1
+ shift
+ ;;
+ -v|--verbose)
+ VERBOSE=1
+ shift
+ ;;
+ -h|--help)
+ usage "$@"
+ exit 0
+ ;;
+ --)
+ shift
+ break
+ ;;
+ -*)
+ echo "error: Unknown option '$1'" >&2
+ usage "$@"
+ exit 1
+ ;;
+ *)
+ break
+ ;;
+ esac
+ done
+
+ if [ -n "$DEBUG" ]; then
+ set -x
+ fi
+
+
+ if [ -z "$1" ]; then
+ dump_all_certificates
+ else
+ # compute the certificates names at the end of the command
+ while [ -n "$1" ]; do
+ read_certificate "$1"
+ [ "${DRY_RUN}" = "0" ] && dump_certificate "$NAME" "$CRT_FILENAME" "$KEY_FILENAME"
+ shift
+ done
+ fi
+}
+
+main "$@"