certs_helper 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. #!/bin/bash
  2. #FQDN=mail.odamus.com
  3. FQDN="$HOSTNAME"."$DOMAIN"
  4. ACME_PATH=/etc/letsencrypt/acme
  5. ACME_FILE="$ACME_PATH"/acme.json
  6. SSL_MOUNT_PATH=/var/mail/ssl
  7. ACME_DUMP="$SSL_MOUNT_PATH"/acme_dump.log
  8. SELFSIGNED_PATH="$SSL_MOUNT_PATH"/selfsigned
  9. CERT_TEMP_PATH=/tmp/ssl
  10. LETS_ENCRYPT_LIVE_PATH=/etc/letsencrypt/live/"$FQDN"
  11. LIVE_CERT_PATH=/ssl
  12. RENEWED_CERTIFICATE=false
  13. _normalize_certs() {
  14. SSL_DIR="$1"
  15. FULLCHAIN="$SSL_DIR"/fullchain.pem
  16. CAFILE="$SSL_DIR"/chain.pem
  17. CERTFILE="$SSL_DIR"/cert.pem
  18. KEYFILE="$SSL_DIR"/privkey.pem
  19. # When using https://github.com/jwilder/nginx-proxy there is only key.pem
  20. if [ -e "$SSL_DIR"/key.pem ]; then
  21. mv -f "$SSL_DIR"/key.pem "$KEYFILE"
  22. fi
  23. if [ ! -e "$KEYFILE" ]; then
  24. echo "[ERROR] No keyfile found in $SSL_DIR !"
  25. exit 1
  26. fi
  27. if [ "$RENEWED_CERTIFICATE" = true ] || [ ! -e "$CAFILE" ] || [ ! -e "$CERTFILE" ]; then
  28. if [ -e "$FULLCHAIN" ]; then
  29. # Extract cert.pem and chain.pem from fullchain.pem
  30. # Used for containous/traefik and jwilder/nginx-proxy
  31. awk -v path="$SSL_DIR" 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > path"/cert" c ".pem"}' < "$FULLCHAIN"
  32. mv "$SSL_DIR"/cert1.pem "$CERTFILE"
  33. mv "$SSL_DIR"/cert2.pem "$CAFILE"
  34. fi
  35. fi
  36. if [ ! -e "$FULLCHAIN" ]; then
  37. cp "$CERTFILE" "$FULLCHAIN"
  38. fi
  39. }
  40. if [ "$1" = "watch" ]; then
  41. echo "[INFO] Checking for watchable SSL certificates"
  42. if [ -f "$ACME_FILE" ]; then
  43. exec watcher.py "$ACME_PATH"
  44. elif [ -d "$LETS_ENCRYPT_LIVE_PATH" ]; then
  45. exec watcher.py "$LETS_ENCRYPT_LIVE_PATH"
  46. else
  47. echo "[INFO] No watchable SSL certificate mounts - disabling SSL watcher"
  48. fi
  49. elif [ "$1" = "update_certs" ]; then
  50. mkdir -p "$SSL_MOUNT_PATH"
  51. mkdir -p "$CERT_TEMP_PATH"
  52. mkdir -p "$LIVE_CERT_PATH"
  53. rm -rf "$CERT_TEMP_PATH/*"
  54. NORMALIZED_CERT_PATH="$CERT_TEMP_PATH"/normalized
  55. mkdir -p "$NORMALIZED_CERT_PATH"
  56. if [ -f "$ACME_FILE" ]; then
  57. echo "[INFO] Search for SSL certificates generated by Traefik"
  58. if [ ! "$2" = "-n" ]; then
  59. while [ ! -s "$ACME_FILE" ]; do
  60. sleep 5
  61. echo "[INFO] ..."
  62. done
  63. # Wait for acme.json full filling
  64. sleep 10
  65. fi
  66. if jq -e -r '.PrivateKey' "$ACME_FILE" >/dev/null ; then
  67. echo "[INFO] acme.json found with ACME v1 format, dumping into pem files" | tee -a "$ACME_DUMP"
  68. dumpcerts.acme.v1.sh "$ACME_FILE" "$CERT_TEMP_PATH" >> "$ACME_DUMP" 2>&1
  69. elif jq -e -r '.Account.PrivateKey' "$ACME_FILE" >/dev/null ; then
  70. echo "[INFO] acme.json found with ACME v2 format, dumping into pem files" | tee -a "$ACME_DUMP"
  71. dumpcerts.acme.v2.sh "$ACME_FILE" "$CERT_TEMP_PATH" >> "$ACME_DUMP" 2>&1
  72. if [ -e "$CERT_TEMP_PATH"/certs/"*.${DOMAIN}.crt" ] && [ -e "$CERT_TEMP_PATH"/private/"*.${DOMAIN}.key" ]; then
  73. echo "[INFO] Let's encrypt wildcard certificate found" | tee -a "$ACME_DUMP"
  74. mv -f "$CERT_TEMP_PATH"/certs/"*.${DOMAIN}.crt" "$CERT_TEMP_PATH"/certs/"$FQDN".crt
  75. mv -f "$CERT_TEMP_PATH"/private/"*.${DOMAIN}.key" "$CERT_TEMP_PATH"/private/"$FQDN".key
  76. fi
  77. elif jq -e -r '.[].Account.PrivateKey' "$ACME_FILE" >/dev/null ; then
  78. echo "[INFO] acme.json found with Traefik v2 format, dumping into pem files" | tee -a "$ACME_DUMP"
  79. dumpcerts.traefik.v2.sh "$ACME_FILE" "$CERT_TEMP_PATH" >> "$ACME_DUMP" 2>&1
  80. if [ -e "$CERT_TEMP_PATH"/certs/"*.${DOMAIN}.crt" ] && [ -e "$CERT_TEMP_PATH"/private/"*.${DOMAIN}.key" ]; then
  81. echo "[INFO] Let's encrypt wildcard certificate found" | tee -a "$ACME_DUMP"
  82. mv -f "$CERT_TEMP_PATH"/certs/"*.${DOMAIN}.crt" "$CERT_TEMP_PATH"/certs/"$FQDN".crt
  83. mv -f "$CERT_TEMP_PATH"/private/"*.${DOMAIN}.key" "$CERT_TEMP_PATH"/private/"$FQDN".key
  84. fi
  85. else
  86. echo "[ERROR] acme.json found but with an unknown format" >> "$ACME_DUMP"
  87. fi
  88. if [ -e "$CERT_TEMP_PATH"/certs/"$FQDN".crt ] && [ -e "$CERT_TEMP_PATH"/private/"$FQDN".key ]; then
  89. DOMAIN_NAME="$FQDN"
  90. elif [ -e "$CERT_TEMP_PATH"/certs/"$DOMAIN".crt ] && [ -e "$CERT_TEMP_PATH"/private/"$DOMAIN".key ]; then
  91. DOMAIN_NAME="$DOMAIN"
  92. else
  93. echo "[ERROR] The certificate for ${FQDN} or the private key was not found !"
  94. echo "[INFO] Don't forget to add a new traefik frontend rule to generate a certificate for ${FQDN} subdomain"
  95. echo "[INFO] Look /mnt/docker/traefik/acme/dump.log and 'docker logs traefik' for more information"
  96. exit 1
  97. fi
  98. mv -f "$CERT_TEMP_PATH"/certs/"$DOMAIN_NAME".crt "$NORMALIZED_CERT_PATH"/fullchain.pem
  99. mv -f "$CERT_TEMP_PATH"/private/"$DOMAIN_NAME".key "$NORMALIZED_CERT_PATH"/privkey.pem
  100. rm -rf "$ACME_DUMP"
  101. RENEWED_CERTIFICATE=true
  102. else
  103. echo "[INFO] Traefik SSL certificates not used"
  104. if [ -d "$LETS_ENCRYPT_LIVE_PATH" ]; then
  105. echo "[INFO] Let's encrypt live directory found"
  106. echo "[INFO] Using $LETS_ENCRYPT_LIVE_PATH folder"
  107. cp -RLT "$LETS_ENCRYPT_LIVE_PATH/." "$NORMALIZED_CERT_PATH"
  108. else
  109. echo "[INFO] No Let's encrypt live directory found"
  110. echo "[INFO] Using "$SELFSIGNED_PATH"/ folder"
  111. CERTFILE="$SELFSIGNED_PATH"/cert.pem
  112. KEYFILE="$SELFSIGNED_PATH"/privkey.pem
  113. if [ ! -e "$CERTFILE" ] || [ ! -e "$KEYFILE" ]; then
  114. echo "[INFO] No SSL certificates found, generating a new selfsigned certificate"
  115. mkdir -p /var/mail/ssl/selfsigned/
  116. openssl req -new -newkey rsa:4096 -days 3658 -sha256 -nodes -x509 \
  117. -subj "/C=FR/ST=France/L=Paris/O=Mailserver certificate/OU=Mail/CN=*.${DOMAIN}/emailAddress=postmaster@${DOMAIN}" \
  118. -keyout "$KEYFILE" \
  119. -out "$CERTFILE"
  120. fi
  121. cp -RT "$SELFSIGNED_PATH/." "$NORMALIZED_CERT_PATH"
  122. fi
  123. fi
  124. _normalize_certs "$NORMALIZED_CERT_PATH"
  125. [ $? -ne 0 ] && exit 1
  126. # Compare Old and New key
  127. if cmp --silent "$NORMALIZED_CERT_PATH"/privkey.pem "$LIVE_CERT_PATH"/privkey.pem; then
  128. echo "[INFO] Live Certificates match"
  129. rm -rf "$CERT_TEMP_PATH"
  130. exit 1
  131. fi
  132. rm -rf "$LIVE_CERT_PATH/*"
  133. cp -RT "$NORMALIZED_CERT_PATH/." "$LIVE_CERT_PATH"
  134. elif [ "$1" = "reload" ]; then
  135. echo "[INFO] Updating SSL certificates and reloading"
  136. if "$0" update_certs -n; then
  137. postfix reload
  138. dovecot reload
  139. echo "[INFO] Reloaded Postfix + Dovecot"
  140. fi
  141. else
  142. echo "[ERROR] Unrecognized command '$1'"
  143. exit 1
  144. fi