Postfix / Dovecot SNI for Letsencrypt certificates via cron script (auto)

Discussion in 'Tips/Tricks/Mods' started by Gino Morilla, Apr 27, 2021.

  1. Gino Morilla

    Gino Morilla New Member

    SCRIPT UPDATED TO RELOAD SERVICES IF ANY CERTIFICATE IS RENEWED OR MODIFIED!!
    SCRIPT UPDATED TO INCLUDE ISPCONFIG SAVED CERTIFICATES IF LETSENCRYPT IS NOT CONFIGURED!!


    Hi all,
    Thanks to the developers of this great panel.
    I attach a simple bash script which:
    • Read every Letsencrypt certificate currently configured/installed at /etc/letsencrypt/live directly.
    • If there is not a Letsencrypt certificate for the domain, it will try to configure those saved from Ispconfig.
    • Build up the dovecot SNI configuration
    • Build up the postfix SNI configuration
    • Insert / update the SNI configurations to include every LE/Ispconfig certificated domain
    • Only reload services if necessary
    • Almost no CPU/IO, so you could schedulle via cron to run every 1/5 minutes if you will...
    • Currently being used in my servers without problems
    Cons:
    • No pure-ftpd SNI yet
    If your hostname has its own LE certificate, it will be included in the SNI configuration, as every other domain.

    Just give thanks if you like it.

    Name it as you like... I usually run it from "/root/scripts/sni-dovecot-postfix.sh"

    Code:
    #!/bin/bash
    
    
    if [ $(grep -E "^indexed = " /etc/postfix/main.cf | wc -l) -eq 0 ]; then
           echo 'indexed = ${default_database_type}:${config_directory}/' >> /etc/postfix/main.cf
    fi
    if [ $(grep -E "^tls_server_sni_maps = " /etc/postfix/main.cf | wc -l) -eq 0 ]; then
           echo 'tls_server_sni_maps = ${indexed}sni' >> /etc/postfix/main.cf
    fi
    [[ ! -f /etc/dovecot/conf.d/99-ispconfig-custom-config.conf ]] && touch /etc/dovecot/conf.d/99-ispconfig-custom-config.conf
    
    if [ $(grep -E "^\!include_try /etc/dovecot/sni.conf" /etc/dovecot/conf.d/99-ispconfig-custom-config.conf | wc -l) -eq 0 ] \
    && [ $(grep -E "^\!include_try /etc/dovecot/sni.conf" /etc/dovecot/dovecot.conf | wc -l) -eq 0 ]
    then
           echo '!include_try /etc/dovecot/sni.conf' >> /etc/dovecot/conf.d/99-ispconfig-custom-config.conf
    fi
    
    letsencrypt="/etc/letsencrypt/live"
    dovecotsni="/etc/dovecot/sni.conf"
    postfixchains="/etc/postfix/sni-chains"
    postfixsni="/etc/postfix/sni"
    reload="false"
    
    [ ! -d "${postfixchains}" ] && mkdir ${postfixchains}
    [ ! -f "${dovecotsni}" ] && touch ${dovecotsni}
    [ ! -f "${postfixsni}" ] && touch ${postfixsni}
    [ -f "${dovecotsni}.new" ] && rm -f "${dovecotsni}.new"
    [ -f "${postfixsni}.new" ] && rm -f "${postfixsni}.new"
    
    echo "# Generated automatically by /root/scripts/sni-dovecot-postfix.sh" >> ${dovecotsni}.new
    
    for domain in $(echo -e "$(find /var/www/ -maxdepth 1 -mindepth 1 -type l | awk -F"/var/www/" '{print $2}')\n$(find ${letsencrypt}/ -maxdepth 1 -mindepth 1 -type d -exec ba
    sename {} \; | sort)" | sort | uniq)
    do
    
           # Check for LE certificate
           if [ -e ${letsencrypt}/${domain}/fullchain.pem ]; then
                   cert="${letsencrypt}/${domain}/fullchain.pem"
                   key="${letsencrypt}/${domain}/privkey.pem"
                   generate="Letsencrypt"
           # Check for ISPCONFIG installed certificate
           elif [ -e /var/www/${domain}/ssl/${domain}.crt ]; then
                   cert="/var/www/${domain}/ssl/${domain}.crt"
                   key="/var/www/${domain}/ssl/${domain}.key"
                   generate="Ispconfig"
           # There is no certificate anywhere for this domain
           else
                   generate="false"
           fi
    
           if [ "${generate}" == "Letsencrypt" ] || [ "${generate}" == "Ispconfig" ]; then
    
                   echo "Processing ${domain} SNI (detected ${generate} certificate)"
    
                   # Dovecot SNI
                   echo "local_name \"*.${domain} ${domain}\" {" >> ${dovecotsni}.new
                   echo "    ssl_cert = <${cert}"  >> ${dovecotsni}.new
                   echo "    ssl_key = <${key}"  >> ${dovecotsni}.new
                   echo "}"  >> ${dovecotsni}.new
    
    
                   # Postfix SNI Chains
                   if [ -e ${postfixchains}/${domain}.pem ]; then
                           awk '{print $0}' ${key} ${cert} > ${postfixchains}/${domain}.pem.new
                           if [ $(diff ${postfixchains}/${domain}.pem.new ${postfixchains}/${domain}.pem | wc -l) -eq 0 ]; then
                                   rm -f ${postfixchains}/${domain}.pem.new
                           else
                                   mv ${postfixchains}/${domain}.pem.new ${postfixchains}/${domain}.pem
                                   echo "(${domain} certificate has been modified)"
                                   reload="true"
                           fi
                   else
                           # Need to do it in two lines because ispconfig key file does not end with \r\n
                           awk '{print $0}' ${key} ${cert} > ${postfixchains}/${domain}.pem
                           reload="true"
                   fi
                   chmod 600 ${postfixchains}/${domain}.pem
                   echo "${domain} ${postfixchains}/${domain}.pem" >> ${postfixsni}.new
                   echo ".${domain} ${postfixchains}/${domain}.pem" >> ${postfixsni}.new
    
           fi
    
    done
    
    
    if [ $(diff ${dovecotsni}.new ${dovecotsni} | wc -l) -gt 0 ] || [ ${reload} == "true" ]; then
           echo ""
           echo "Detected changes in Dovecot SNI: executing reload"
           cat ${dovecotsni}.new > ${dovecotsni}
           systemctl reload dovecot.service
    fi
    
    if [ $(diff ${postfixsni}.new ${postfixsni} | wc -l) -gt 0 ] || [ ${reload} == "true" ]; then
           echo ""
           echo "Detected changes in Postfix SNI: executing postmap and reload"
           cat ${postfixsni}.new > ${postfixsni}
           /usr/sbin/postmap -F /etc/postfix/sni
           systemctl reload postfix
    fi
    
    [ -f "${dovecotsni}.new" ] && rm -f "${dovecotsni}.new"
    [ -f "${postfixsni}.new" ] && rm -f "${postfixsni}.new"
    
     
    Last edited: May 10, 2021
    gb78, Nicram, Mr.X123 and 6 others like this.
  2. Th0m

    Th0m ISPConfig Developer Staff Member ISPConfig Developer

  3. nhybgtvfr

    nhybgtvfr Well-Known Member HowtoForge Supporter

    i've mentioned a few times on here, that pure-ftpd supports sni now, and so should be do-able (at least manually for now) on the latest debian and ubuntu. support should be included from pure-ftpd 1.0.48 onwards, but the debian/ubuntu package pure-ftpd-common doesn't actually include the pure-certd binary. https://answers.launchpad.net/ubuntu/+source/pure-ftpd/+question/696585

    pure-ftpd sni configuration - relevant section is custom certificate handlers
    https://download.pureftpd.org/pub/pure-ftpd/doc/README.TLS
     
    ahrasis and Gino Morilla like this.
  4. Gino Morilla

    Gino Morilla New Member

    Note: modified script so it may reload if any certificate has been renewed or modified somehow.
     
  5. Gino Morilla

    Gino Morilla New Member

    Note: now it will include ispconfig saved certificates for a domain, in case LE do not exist for it.
     
  6. Th0m

    Th0m ISPConfig Developer Staff Member ISPConfig Developer

    Maybe it would be nice to create a GitHub repo for the script as long as you're updating it, so changes can be tracked easily?
     
    Nicram and Gino Morilla like this.
  7. Cyb3rblade

    Cyb3rblade New Member HowtoForge Supporter

    Do I have to create an alias-domain for all my mail-domains to the host-certificate?

    eg.
    host = host.abc.test
    domain1 = aaa.bbb
    domain2 = xxx.zzz
    isp-config mail-domain1 = (mail.)aaa.bbb
    isp-config mail-domain2 = (mail.)xxx.zzz

    so... do I have to add "mail.aaa.bbb" and "mail.xxx.zzz" to the LE-Cert for "host.abc.test"
     
  8. Taleman

    Taleman Well-Known Member HowtoForge Supporter

    Depends.
    I do not do that. My setup has one e-mail domain for all users, and that domain has certificate.
    If you must have separate e-mail domain for each customer and use only one e-mail server for all of them, then adding all of the domains to the one certificate is needed if your customers want to avoid warnings. Let's Encrypt allows up to 100 alias certificates, so if you have more customers than that then it does not scale. This method is, in my opinion, worse in other ways too, so I do not use it.
     
    Cyb3rblade likes this.
  9. nhybgtvfr

    nhybgtvfr Well-Known Member HowtoForge Supporter

    it shouldn't be needed. the sni configuration files should provide the details for each domain and the corresponding certificates and their location.*

    looking at the provided script, it looks like it's designed for a server where the website and email services are provided by the same server.
    looks like running a multi-server setup with a dedicated mailserver would be a lot more complicated (manual configuration?)

    * i haven't actually tried setting up postfix/dovecot sni yet. but everything i've seen and read indicates every domain has it's own certificate. (not sure about email aliasdomains. they may need to be on the same certificate they as the domain they are aliasing to)
     
    Cyb3rblade likes this.
  10. nhybgtvfr

    nhybgtvfr Well-Known Member HowtoForge Supporter

    as an update to this, it looks like with the release of pure-ftp 1.0.50, the pure-ftpd-common package will finally include the pure-certd binary.
    doesn't look like the 1.0.50 release has been backported into the 20.04 repo, hopefully with the release of ubuntu 22.04 in a few months, inclusion of sni support for both postfix/dovecot and pure-ftpd in ispconfig will finally become a viable option.
     

Share This Page