#!/bin/bash #FQDN=mail.odamus.com FQDN="$HOSTNAME"."$DOMAIN" ACME_PATH=/etc/letsencrypt/acme ACME_FILE="$ACME_PATH"/acme.json SSL_MOUNT_PATH=/var/mail/ssl ACME_DUMP="$SSL_MOUNT_PATH"/acme_dump.log SELFSIGNED_PATH="$SSL_MOUNT_PATH"/selfsigned CERT_TEMP_PATH=/tmp/ssl LETS_ENCRYPT_LIVE_PATH=/etc/letsencrypt/live/"$FQDN" LIVE_CERT_PATH=/ssl RENEWED_CERTIFICATE=false _normalize_certs() { SSL_DIR="$1" FULLCHAIN="$SSL_DIR"/fullchain.pem CAFILE="$SSL_DIR"/chain.pem CERTFILE="$SSL_DIR"/cert.pem KEYFILE="$SSL_DIR"/privkey.pem # When using https://github.com/jwilder/nginx-proxy there is only key.pem if [ -e "$SSL_DIR"/key.pem ]; then mv -f "$SSL_DIR"/key.pem "$KEYFILE" fi if [ ! -e "$KEYFILE" ]; then echo "[ERROR] No keyfile found in $SSL_DIR !" exit 1 fi if [ "$RENEWED_CERTIFICATE" = true ] || [ ! -e "$CAFILE" ] || [ ! -e "$CERTFILE" ]; then if [ -e "$FULLCHAIN" ]; then # Extract cert.pem and chain.pem from fullchain.pem # Used for containous/traefik and jwilder/nginx-proxy awk -v path="$SSL_DIR" 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > path"/cert" c ".pem"}' < "$FULLCHAIN" mv "$SSL_DIR"/cert1.pem "$CERTFILE" mv "$SSL_DIR"/cert2.pem "$CAFILE" fi fi if [ ! -e "$FULLCHAIN" ]; then cp "$CERTFILE" "$FULLCHAIN" fi } if [ "$1" = "watch" ]; then echo "[INFO] Checking for watchable SSL certificates" if [ -f "$ACME_FILE" ]; then exec watcher.py "$ACME_PATH" elif [ -d "$LETS_ENCRYPT_LIVE_PATH" ]; then exec watcher.py "$LETS_ENCRYPT_LIVE_PATH" else echo "[INFO] No watchable SSL certificate mounts - disabling SSL watcher" fi elif [ "$1" = "update_certs" ]; then mkdir -p "$SSL_MOUNT_PATH" mkdir -p "$CERT_TEMP_PATH" mkdir -p "$LIVE_CERT_PATH" rm -rf "$CERT_TEMP_PATH/*" NORMALIZED_CERT_PATH="$CERT_TEMP_PATH"/normalized mkdir -p "$NORMALIZED_CERT_PATH" if [ -f "$ACME_FILE" ]; then echo "[INFO] Search for SSL certificates generated by Traefik" if [ ! "$2" = "-n" ]; then while [ ! -s "$ACME_FILE" ]; do sleep 5 echo "[INFO] ..." done # Wait for acme.json full filling sleep 10 fi if jq -e -r '.PrivateKey' "$ACME_FILE" >/dev/null ; then echo "[INFO] acme.json found with ACME v1 format, dumping into pem files" | tee -a "$ACME_DUMP" dumpcerts.acme.v1.sh "$ACME_FILE" "$CERT_TEMP_PATH" >> "$ACME_DUMP" 2>&1 elif jq -e -r '.Account.PrivateKey' "$ACME_FILE" >/dev/null ; then echo "[INFO] acme.json found with ACME v2 format, dumping into pem files" | tee -a "$ACME_DUMP" dumpcerts.acme.v2.sh "$ACME_FILE" "$CERT_TEMP_PATH" >> "$ACME_DUMP" 2>&1 if [ -e "$CERT_TEMP_PATH"/certs/"*.${DOMAIN}.crt" ] && [ -e "$CERT_TEMP_PATH"/private/"*.${DOMAIN}.key" ]; then echo "[INFO] Let's encrypt wildcard certificate found" | tee -a "$ACME_DUMP" mv -f "$CERT_TEMP_PATH"/certs/"*.${DOMAIN}.crt" "$CERT_TEMP_PATH"/certs/"$FQDN".crt mv -f "$CERT_TEMP_PATH"/private/"*.${DOMAIN}.key" "$CERT_TEMP_PATH"/private/"$FQDN".key fi elif jq -e -r '.[].Account.PrivateKey' "$ACME_FILE" >/dev/null ; then echo "[INFO] acme.json found with Traefik v2 format, dumping into pem files" | tee -a "$ACME_DUMP" dumpcerts.traefik.v2.sh "$ACME_FILE" "$CERT_TEMP_PATH" >> "$ACME_DUMP" 2>&1 if [ -e "$CERT_TEMP_PATH"/certs/"*.${DOMAIN}.crt" ] && [ -e "$CERT_TEMP_PATH"/private/"*.${DOMAIN}.key" ]; then echo "[INFO] Let's encrypt wildcard certificate found" | tee -a "$ACME_DUMP" mv -f "$CERT_TEMP_PATH"/certs/"*.${DOMAIN}.crt" "$CERT_TEMP_PATH"/certs/"$FQDN".crt mv -f "$CERT_TEMP_PATH"/private/"*.${DOMAIN}.key" "$CERT_TEMP_PATH"/private/"$FQDN".key fi else echo "[ERROR] acme.json found but with an unknown format" >> "$ACME_DUMP" fi if [ -e "$CERT_TEMP_PATH"/certs/"$FQDN".crt ] && [ -e "$CERT_TEMP_PATH"/private/"$FQDN".key ]; then DOMAIN_NAME="$FQDN" elif [ -e "$CERT_TEMP_PATH"/certs/"$DOMAIN".crt ] && [ -e "$CERT_TEMP_PATH"/private/"$DOMAIN".key ]; then DOMAIN_NAME="$DOMAIN" else echo "[ERROR] The certificate for ${FQDN} or the private key was not found !" echo "[INFO] Don't forget to add a new traefik frontend rule to generate a certificate for ${FQDN} subdomain" echo "[INFO] Look /mnt/docker/traefik/acme/dump.log and 'docker logs traefik' for more information" exit 1 fi mv -f "$CERT_TEMP_PATH"/certs/"$DOMAIN_NAME".crt "$NORMALIZED_CERT_PATH"/fullchain.pem mv -f "$CERT_TEMP_PATH"/private/"$DOMAIN_NAME".key "$NORMALIZED_CERT_PATH"/privkey.pem rm -rf "$ACME_DUMP" RENEWED_CERTIFICATE=true else echo "[INFO] Traefik SSL certificates not used" if [ -d "$LETS_ENCRYPT_LIVE_PATH" ]; then echo "[INFO] Let's encrypt live directory found" echo "[INFO] Using $LETS_ENCRYPT_LIVE_PATH folder" cp -RLT "$LETS_ENCRYPT_LIVE_PATH/." "$NORMALIZED_CERT_PATH" else echo "[INFO] No Let's encrypt live directory found" echo "[INFO] Using "$SELFSIGNED_PATH"/ folder" CERTFILE="$SELFSIGNED_PATH"/cert.pem KEYFILE="$SELFSIGNED_PATH"/privkey.pem if [ ! -e "$CERTFILE" ] || [ ! -e "$KEYFILE" ]; then echo "[INFO] No SSL certificates found, generating a new selfsigned certificate" mkdir -p /var/mail/ssl/selfsigned/ openssl req -new -newkey rsa:4096 -days 3658 -sha256 -nodes -x509 \ -subj "/C=FR/ST=France/L=Paris/O=Mailserver certificate/OU=Mail/CN=*.${DOMAIN}/emailAddress=postmaster@${DOMAIN}" \ -keyout "$KEYFILE" \ -out "$CERTFILE" fi cp -RT "$SELFSIGNED_PATH/." "$NORMALIZED_CERT_PATH" fi fi _normalize_certs "$NORMALIZED_CERT_PATH" [ $? -ne 0 ] && exit 1 # Compare Old and New key if cmp --silent "$NORMALIZED_CERT_PATH"/privkey.pem "$LIVE_CERT_PATH"/privkey.pem; then echo "[INFO] Live Certificates match" rm -rf "$CERT_TEMP_PATH" exit 1 fi rm -rf "$LIVE_CERT_PATH/*" cp -RT "$NORMALIZED_CERT_PATH/." "$LIVE_CERT_PATH" elif [ "$1" = "reload" ]; then echo "[INFO] Updating SSL certificates and reloading" if "$0" update_certs -n; then postfix reload dovecot reload echo "[INFO] Reloaded Postfix + Dovecot" fi else echo "[ERROR] Unrecognized command '$1'" exit 1 fi