Use certbot standalone to create Lets Encrypt SSL certs for ISPConfig Servers

Discussion in 'Developers' Forum' started by ahrasis, Oct 30, 2018.

  1. ahrasis

    ahrasis Well-Known Member HowtoForge Supporter

    After several thoughts, I think it is best if we check and install UFW if it is not yet installed for the standalone. We then check only for port 80 (http) to decide whether to close it back or otherwise not. Port 443 (https) is actually not needed for standalone mode and thus is dropped. Suggested changes:
    Code:
               // Set webroot path for all ISPConfig server LE certs
               $webroot_path = $conf['ispconfig_install_dir'].'/interface/acme';
               if (!@is_dir($webroot_path)) $webroot_path = '/var/www/html';
               
               // If there is no UFW, we need to install it
               $ufw = explode("\n", shell_exec('which ufw /usr/sbin/ufw'));
               $ufw = reset($ufw);
               if (!is_executable($ufw))
                   exec("apt-get -v &> /dev/null && apt-get -y ufw || yum install epel-release -y; yum install --enablerepo=\"epel\" ufw -y");
                   
               // Check if port 80 http is opened by default
               $try=exec('true &>/dev/null </dev/tcp/127.0.0.1/80 && echo open || echo close');
    
               // Set pre-hook and post hook for standalone non-webserver
               $pre_hook = "--pre-hook \"ufw --force enable && ufw allow http\"";
               if ($try == 'close') $post_hook = " --post-hook \"ufw --force enable && ufw deny http\""; else $post_hook = '';
               $hook = $pre_hook . $post_hook;
    
    I updated my git to reflect the same for further testings.
     
  2. till

    till Super Moderator Staff Member ISPConfig Developer

    That's not a good idea in my opinion for the following reasons:

    - UFW is not available for all supported operating systems.
    - ISPConfig supports UFW and Bastille, so the user can just have bastille installed. Adding UFW will probably destroy the firewall setup and may make the server unreachable.
    - The user may use a completely different firewall or even manual iptables rules, installing UFW will cause issues here too.

    Having a custom pre and post hook script is probably the best option, so users can customize it when needed. if ufw is installed, the script may contain the necessary ufw commands. If UFW is not installed, the script should empty (just bash shebang).
     
    ahrasis likes this.
  3. Jesse Norell

    Jesse Norell Well-Known Member Staff Member Howtoforge Staff

    While tweaking and adding hooks for customization, what is the feasibility of making the command to request/renew configurable (eg. conf file settings) or customizable (eg. hook scripts)?

    Eg. as a use case, I would prefer to use DNS based auth for non-webserver nodes, not standalone mode. That eliminates the local firewall issue as well as network level firewalls. I think DNS based auth will be supported within ISPConfig eventually, but for now I could just setup acme.sh to use the nsupdate plugin if there were hooks to call a custom register/renew script.

    As far as implementing there would of course have to be a way to tell the installer "use my custom register/renew scripts", and define what/where those are. But additionally there would need to be a way for the custom register script to signal to the installer that the certificate is in place and ready. Eg. some DNS based registrations I've setup in the past used more than a 5 minute timeout to allow DNS caches to expire, and you may or may not want the installer to "hang" waiting for that to finish.

    I suppose in the custom scripts case you could expect the script to fully setup the certificate (files or symlinks), and if those don't exist when the register script exits, the installer prints a notice about waiting on certificate request and offers the option to wait longer or maybe exits and can be rerun to continue at a later point. You could define script exit logic so 0 means success/ready to go, and other exit values signal "request in progress" vs. "request failed" .. or just keep it simple, and successful exit plus existence of the cert files is the condition to proceed with the install.
     
  4. ahrasis

    ahrasis Well-Known Member HowtoForge Supporter

    Sorry but I fell asleep afterwards. Considering all the feedbacks received, the suggested changes are as follow:
    Code:
                // Set webroot path for all ISPConfig server LE certs
                $webroot_path = $conf['ispconfig_install_dir'].'/interface/acme';
                if (!@is_dir($webroot_path)) $webroot_path = '/var/www/html';
           
                // Check http port 80 status as it cannot be determined at post hook stage
                $port80_status=exec('true &>/dev/null </dev/tcp/127.0.0.1/80 && echo open || echo close');
           
               // Now set pre-hook (with or without post-hook) for standalone non-webserver
               $pre_hook = "--pre-hook \"letsencrypt_pre_hook.sh\"";
               if ($port80_status == 'close') {
                   $post_hook = " --post-hook \"letsencrypt_post_hook.sh\"";
                   $hook = $pre_hook . $post_hook;
               }
               else
                   $hook = $pre_hook;
           
                // This script is needed earlier to check and open http port 80 or standalone might fail
                chown('/tmp/ispconfig3_install/server/scripts/letsencrypt_pre_hook.sh', 'root');
                chown('/tmp/ispconfig3_install/server/scripts/letsencrypt_post_hook.sh', 'root');
                chmod('/tmp/ispconfig3_install/server/scripts/letsencrypt_pre_hook.sh', 0700);
                chmod('/tmp/ispconfig3_install/server/scripts/letsencrypt_post_hook.sh', 0700);
                // Make executable and temporary symlink letsencrypt pre and post hook script before install
                if(!is_link('/usr/local/bin/letsencrypt_pre_hook.sh'))
                    symlink('/tmp/ispconfig3_install/server/scripts/letsencrypt_pre_hook.sh', '/usr/local/bin/letsencrypt_pre_hook.sh');
                if(!is_link('/usr/local/bin/letsencrypt_post_hook.sh'))
                    symlink('/tmp/ispconfig3_install/server/scripts/letsencrypt_post_hook.sh', '/usr/local/bin/letsencrypt_post_hook.sh');
    [...]
    somewhere under install_ispconfig() function
    [...]
            // Make executable then unlink and symlink letsencrypt pre and post hook scripts
            chown($install_dir.'/server/scripts/letsencrypt_pre_hook.sh', 'root');
            chown($install_dir.'/server/scripts/letsencrypt_post_hook.sh', 'root');
            chmod($install_dir.'/server/scripts/letsencrypt_pre_hook.sh', 0700);
            chmod($install_dir.'/server/scripts/letsencrypt_post_hook.sh', 0700);
            if(is_link('/usr/local/bin/letsencrypt_pre_hook.sh')) unlink('/usr/local/bin/letsencrypt_pre_hook.sh');
            if(is_link('/usr/local/bin/letsencrypt_post_hook.sh')) unlink('/usr/local/bin/letsencrypt_post_hook.sh');
            symlink($install_dir.'/server/scripts/letsencrypt_pre_hook.sh', '/usr/local/bin/letsencrypt_pre_hook.sh');
            symlink($install_dir.'/server/scripts/letsencrypt_post_hook.sh', '/usr/local/bin/letsencrypt_post_hook.sh');
    

    After deeper thought, I really think the default status for http port 80 for the non-webserver must be determined during ISPConfig installation stage and cannot be done later at the post-hook stage, so the above code reflects my thought on it.

    Both letsencrypt_pre_hook.sh and letsencrypt_post_hook.sh are now created as follows:

    letsencrypt_pre_hook.sh
    Code:
    # For RHEL, Centos or derivatives
    if rpm -q ufw; then
        ufw --force enable && ufw allow http
    fi
    # For Debian, Ubuntu or derivatives
    if [ $(dpkg-query -W -f='${Status}' ufw 2>/dev/null | grep -c "ok installed") -eq 1 ]; then
        ufw --force enable && ufw allow http
    fi
    

    letsencrypt_post_hook.sh
    Code:
    # For RHEL, Centos or derivatives
    if rpm -q ufw; then
        ufw --force enable && ufw deny http
    fi
    # For Debian, Ubuntu or derivatives
    if [ $(dpkg-query -W -f='${Status}' ufw 2>/dev/null | grep -c "ok installed") -eq 1 ]; then
        ufw --force enable && ufw deny http
    fi
    
    Please feel free to test and give feedbacks.
     
  5. ahrasis

    ahrasis Well-Known Member HowtoForge Supporter

    I am not so sure about that.

    Any request for ISPConfig SSL during install or update will only check for the server hostname fqdn in Let's Encrypt live directory and if you already manually have the LE SSL certs for the server hostname fqdn in there, it will simply link them to ISPConfig SSL directory and extends them to postfix and pureftpd.

    So you can request for your LE SSL certs your way before install or update, put them in the server hostname fqdn in Let's Encrypt live directory and those will be simply symlinked and use instead of creating a new one using standalone.

    I visited this several times both for ISPConfig install / update as well as for websites under ISPConfig but so far I still couldn't find any good solutions, yet.

    There is, however, no support from ISPConfig for nsupdate which relies on its database instead, but one could try to write a dns plugin that can read from the updated dns file and update ISPConfig dns database.

    And I think relying on script is not the ISPConfig way but one can try to write a script and later convert it to a php code.

    In any event, to achieve what you wish, someone need to attend to it especially to see the whole view and the practicality side of it.
     
  6. ahrasis

    ahrasis Well-Known Member HowtoForge Supporter

    Code in post #36 is updated to include letsencrypt_pre_hook.sh and letsencrypt_post_hook.sh
     
    Last edited: May 31, 2020
    Richard Foley and till like this.
  7. ahrasis

    ahrasis Well-Known Member HowtoForge Supporter

    Code in post #36 is updated again to include letsencrypt_renew_hook.sh. This is to enable ispserver.pem to be re-created upon ISPConfig server LE SSL certs successful renewal, reload / restart all other services and do whatever is needed in relation of the said renewal. I think that part should conclude the final part for securing ISPConfig and all main services under it (except for mysql / mariadb).
     
  8. ahrasis

    ahrasis Well-Known Member HowtoForge Supporter

    Just in case you haven't visited the ISPConfig git recently, there is already a certbot third party dns plugin for ISPConfig created by Matthias Bilger at github which can be installed for those who use certbot and ISPConfig remote dns server.
     
    till likes this.
  9. ahrasis

    ahrasis Well-Known Member HowtoForge Supporter

    Just to update the status of this - the prposed code had recently been merged into ISPConfig 3.2 / master via Merge Requests #904 and now pending minor fixes approval and merger via Merge Requests #1004.

    Interested testers / users may run the code as mentioned in post #36 above to get LE SSL certs for their servers by fresh installing ISPConfig in their servers or updating their ISPConfig servers starting from the following:
    Code:
    mkdir -p ispctest
    cd ispctest
    wget -O ISPConfig-3-stable.tar.gz https://git.ispconfig.org/ahrasis/ispconfig3/-/archive/patch-3/ispconfig3-patch-3.tar.gz
    tar xfz ISPConfig-3-stable.tar.gz
    cd ispconfig*/install
    
    The above patch-3 will get the latest from the merge request for stable 3.1 via Merge Requests #911 which is similar to the fixes via Merge Requests #1004 made for Merge Requests #904.

    Please do advise me in here or in the git if there are any more feedbacks and comments relating to this code.

    A brief explanation:

    1. Should you need to use LE SSL certs that is obtained using dns validation or challenge, you need to get it for your server FQDN using your preferred LE client yourself. Using the above will detect your server already obtained FQDN LE SSL certs and simply use it and extend it to other services in that server. This is useful for those who does not wish to use port http and https in their standalone ISPConfig server.

    2. The code will have two option i.e. to obtain LE SSL certs for your server FQDN via webroot or standalone approach only. For the first, which work from any ISPConfig webserver, you must have a working accessible index.html in /var/www/html/ directory. For the later, it will open http and https while obtaining / renewing the LE SSL certs and will close them immediately after via scripts created for that purpose. This will be reflected in the LE SSL certs renewal conf file.

    3. The code will attempt to use the primary acme.sh first, then certbot, to obtain the LE SSL certs. If both not available or does not work, then it will use the previous original way i.e. create self-signed certs for your ISPConfig server.

    4. This code may not work properly for server behind NAT unless in its /etc/hosts the server public IP is specified as it is usually defined in any vps server.
     

Share This Page