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

    I think I missed to submit the change in install.php and update.php in my PR where at least this part should be changed to support servers without ISPConfig web interface:
    Code:
    // Create SSL certs for non-webserver(s) but here we only check if ispserver.crt exists
    if(!file_exists('/usr/local/ispconfig/interface/ssl/ispserver.crt')) {
       if(strtolower($inst->simple_query('Do you want to create SSL certs for your server?', array('y', 'n'), 'y')) == 'y') {  
           $inst->make_ispconfig_ssl_cert();
       }
    }
    
    // The above is added before this code
    $inst->install_ispconfig();
    
    I believe without this, the installation process wouldn't be directed to check or create LE SSL certs or extend it to postfix and pureftpd, if no ISPConfig web interface is installed.

    I also think it could, if not would, be better that postfix self-signed certs are processed after ISPConfig since we may be able to reduce duplicity and time to install, if we can create and extend the of use LE SSL certs to it instead.

    Regarding dns challenge, I think we should rather go slow on that unti we figured out what is the best approach.
     
    Last edited: Nov 5, 2018
  2. till

    till Super Moderator Staff Member ISPConfig Developer

    My goal for ISPConfig 3.2 was to reduce the number of certs to one cert anyway (either LE or self signed), so that the ssl certs for all services configured by postfix point to the ispconfig ssl cert by symlink to reduce the need for creating multiple certs. And in case a user does not want that, he can simply replace the symlink on his server later with a dedicated cert, plus, the symlink approach should be compatible with existing setups as you use that in your ISPConfig LE shell script too.
     
    ahrasis likes this.
  3. ahrasis

    ahrasis Well-Known Member HowtoForge Supporter

    Basically, here is what I will propose after I tested it later:
    1. Disable and remove the default for creation of postfix self-signed ssl certs. We remove this part as we will make it covered in function make_ispconfig_ssl_cert() instead.
    Code:
           if(!stristr($options, 'dont-create-certs')) {
               //* Create the SSL certificate
               if(AUTOINSTALL){
                   $command = 'cd '.$config_dir.'; '
                       ."openssl req -new -subj '/C=".escapeshellcmd($autoinstall['ssl_cert_country'])."/ST=".escapeshellcmd($autoinstall['ssl_cert_state'])."/L=".escapeshellcmd($autoinstall['ssl_cert_locality'])."/O=".escapeshellcmd($autoinstall['ssl_cert_organisation'])."/OU=".escapeshellcmd($autoinstall['ssl_cert_organisation_unit'])."/CN=".escapeshellcmd($autoinstall['ssl_cert_common_name'])."' -outform PEM -out smtpd.cert -newkey rsa:4096 -nodes -keyout smtpd.key -keyform PEM -days 3650 -x509";
               } else {
                   $command = 'cd '.$config_dir.'; '
                       .'openssl req -new -outform PEM -out smtpd.cert -newkey rsa:4096 -nodes -keyout smtpd.key -keyform PEM -days 3650 -x509';
               }
               exec($command);
    
               $command = 'chmod o= '.$config_dir.'/smtpd.key';
               caselog($command.' &> /dev/null', __FILE__, __LINE__, 'EXECUTED: '.$command, 'Failed to execute the command '.$command);
           }
    
    2. Reorganize the proposed PR, so that whether LE or self-signed SSL certs are created, they will be symlinked to postfix and pure-ftpd-mysql, if both services are detected as being installed:
    Code:
           // If the LE SSL certs for this hostname exists
           if (is_dir($le_live_dir) && in_array($svr_ip, $dns_ips)) {
    
               // Backup existing ispserver ssl files
               if (file_exists($ssl_crt_file)) rename($ssl_crt_file, $ssl_crt_file . '-' .$date->format('YmdHis') . '.bak');
               if (file_exists($ssl_crt_file)) rename($ssl_key_file, $ssl_key_file . '-' .$date->format('YmdHis') . '.bak');
               if (file_exists($ssl_crt_file)) rename($ssl_pem_file, $ssl_pem_file . '-' .$date->format('YmdHis') . '.bak');
    
               // Create symlink to LE fullchain and key for ISPConfig
               symlink($le_live_dir.'/fullchain.pem', $ssl_crt_file);
               symlink($le_live_dir.'/privkey.pem', $ssl_key_file);
    
           } else {
    
               // We can still use the old self-signed method
               $ssl_pw = substr(md5(mt_rand()), 0, 6);
               exec("openssl genrsa -des3 -passout pass:$ssl_pw -out $ssl_key_file 4096");
               if(AUTOINSTALL){
                   exec("openssl req -new -passin pass:$ssl_pw -passout pass:$ssl_pw -subj '/C=".escapeshellcmd($autoinstall['ssl_cert_country'])."/ST=".escapeshellcmd($autoinstall['ssl_cert_state'])."/L=".escapeshellcmd($autoinstall['ssl_cert_locality'])."/O=".escapeshellcmd($autoinstall['ssl_cert_organisation'])."/OU=".escapeshellcmd($autoinstall['ssl_cert_organisation_unit'])."/CN=".escapeshellcmd($autoinstall['ssl_cert_common_name'])."' -key $ssl_key_file -out $ssl_csr_file");
               } else {
                   exec("openssl req -new -passin pass:$ssl_pw -passout pass:$ssl_pw -key $ssl_key_file -out $ssl_csr_file");
               }
               exec("openssl req -x509 -passin pass:$ssl_pw -passout pass:$ssl_pw -key $ssl_key_file -in $ssl_csr_file -out $ssl_crt_file -days 3650");
               exec("openssl rsa -passin pass:$ssl_pw -in $ssl_key_file -out $ssl_key_file.insecure");
               rename($ssl_key_file, $ssl_key_file.'.secure');
               rename($ssl_key_file.'.insecure', $ssl_key_file);
           }
    
           // Build ispserver.pem file and chmod it
           exec("cat $ssl_key_file $ssl_crt_file > $ssl_pem_file; chmod 600 $ssl_pem_file");
    
           // Extend LE SSL certs to postfix
           if ($conf['postfix']['installed'] == true && strtolower($this->simple_query('Symlink ISPConfig LE SSL certs to postfix?', array('y', 'n'), 'y')) == 'y') {
    
               // Define folder, file(s)
               $cf = $conf['postfix'];
               $postfix_dir = $cf['config_dir'];
               if(!is_dir($postfix_dir)) $this->error("The postfix configuration directory '$postfix_dir' does not exist.");
               $smtpd_crt = $postfix_dir.'/smtpd.cert';
               $smtpd_key = $postfix_dir.'/smtpd.key';
    
               // Backup existing postfix ssl files
               if (file_exists($smtpd_crt)) rename($smtpd_crt, $smtpd_crt . '-' .$date->format('YmdHis') . '.bak');
               if (file_exists($smtpd_key)) rename($smtpd_key, $smtpd_key . '-' .$date->format('YmdHis') . '.bak');
    
               // Create symlink to ISPConfig SSL files
               symlink($ssl_crt_file, $smtpd_crt);
               symlink($ssl_key_file, $smtpd_key);
           }
    
           // Extend LE SSL certs to pureftpd
           if ($conf['pureftpd']['installed'] == true && strtolower($this->simple_query('Symlink ISPConfig LE SSL certs to pureftpd? Creating dhparam file takes some times.', array('y', 'n'), 'y')) == 'y') {
    
               // Define folder, file(s)
               $pureftpd_dir = '/etc/ssl/private';
               if(!is_dir($pureftpd_dir)) mkdir($pureftpd_dir, 0755, true);
               $pureftpd_pem = $pureftpd_dir.'/pure-ftpd.pem';
    
               // Backup existing pureftpd ssl files
               if (file_exists($pureftpd_pem)) rename($pureftpd_pem, $pureftpd_pem . '-' .$date->format('YmdHis') . '.bak');
    
               // Create symlink to ISPConfig SSL files
               symlink($ssl_pem_file, $pureftpd_pem);
               if (!file_exists("$pureftpd_dir/pure-ftpd-dhparams.pem"))
                   exec("cd $pureftpd_dir; openssl dhparam -out dhparam4096.pem 4096; ln -sf dhparam4096.pem pure-ftpd-dhparams.pem");
           }
           
           exec("chown -R root:root $install_dir/interface/ssl");
    
     
  4. Jesse Norell

    Jesse Norell Well-Known Member Staff Member Howtoforge Staff

    It'd be good to remove comments from the file (my current one has some comments + commented hostnames I'll use in the future). Untested, but along the lines of:
    Code:
    $extra_domains = array();
    $extra_domains_raw = file($domain_file, FILE_IGNORE_NEW_LINES);
    foreach ($extra_domains_raw as $line) {
        $line = chop(strstr($line, '#', true));
        if (strlen($line) > 0) { $extra_domains[] = $line; }
    }
    $extra_domains = array_unique($extra_domains);
    
    And as @till already said, thanks for working on this!
     
    ahrasis likes this.
  5. Jesse Norell

    Jesse Norell Well-Known Member Staff Member Howtoforge Staff

    This section which handles postfix cert symlink (and likewise the pure-ftpd one) could be wrapped in is_link() checks and bypassed if the certificates are already setup as symlinks, or at lease default to 'n' for the answer to the question. That way if someone has manually changed out the postfix certificate already as @till mentioned above, they don't have to remember to hit 'n', and it would preserve their existing certificate setup by default.
     
  6. ahrasis

    ahrasis Well-Known Member HowtoForge Supporter

    @Jesse Norell, I am dropping support for multiple domains for the time being because I can't get the code to work. If you got time, you can test the code and check why it is faulty but I suspect you will need vhost files for each of them before the server ssl certs can be expanded to include them. To note, Let's Encrypt will check via the same /var/www/html since the path is set but LE server failed to connect to the second domain onwards.
     
  7. ahrasis

    ahrasis Well-Known Member HowtoForge Supporter

    Basically the code that follows your concern already do backup for the files even if it is already symlinked / created, thus, can be reverted at anytime afterwards, if enter is pressed by mistake. Anywway, changing to 'n' is also an option to consider.
     
  8. Jesse Norell

    Jesse Norell Well-Known Member Staff Member Howtoforge Staff

    note I did mean to conditionally change that, so it defaults to 'n' if the symlinks exist and stays at 'y' if they do not
     
    ahrasis likes this.
  9. ahrasis

    ahrasis Well-Known Member HowtoForge Supporter

    I revisited the code that I finalized and found out that the code I was using to check the hostname DNS A record might have some flaws:
    Code:
           // Check dns a record exist and its ip equal to server public ip
           $svr_ip = file_get_contents('http://dynamicdns.park-your-domain.com/getip');
           if (checkdnsrr(idn_to_ascii($hostname, IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46), 'A')) {
               $dnsa=dns_get_record($hostname, DNS_A);
               $dns_ips = array();
               foreach ($dnsa as $rec) {
                   $dns_ips[] = $rec['ip'];
               }
           }
    
           // Request for certs if no LE SSL folder for server fqdn exist
           $le_live_dir = '/etc/letsencrypt/live/' . $hostname;
           if (!@is_dir($le_live_dir) && in_array($svr_ip, $dns_ips))
    
    The code - "dns_get_record($hostname, DNS_A)" - depends on a file - "/etc/nsswitch.conf" - but the server ip checked may not be equivalent to the server public IP since if /etc/hosts file is read first before public DNS A record due to the default code is something like this:
    Code:
    hosts:          files mdns4_minimal [NOTFOUND=return] dns 
    It will only work in two situations, firstly, /etc/hosts file clearly specifies public ip for the hostname (this will be a little bit problematic for those who are using dynamic ip); or secondly, /etc/nsswitch.conf specifes to find public dns before /etc/hosts e.g. hosts: dns files.

    I would suggest adding some code to check and modify /etc/nsswitch.conf but I am looking for further feedback and opinion here, before I do so.
     
  10. Taleman

    Taleman Well-Known Member HowtoForge Supporter

    The IP address from /etc/hosts and from DNS may indeed be different. But what IP is needed here? The host may be multihomed so it has many IP addresses. And it may have many eth interfaces, and figuring out which to use maybe can not be automatic. Command ip a shows the addressess used on interfaces, if there is only one interface maybe there is function in PHP to show it?
    I do not want to code PHP, I got pissed when my PHP 4 programs stopped working when PHP 5 was released.
     
    ahrasis likes this.
  11. ahrasis

    ahrasis Well-Known Member HowtoForge Supporter

    I am currently not sure / forgot why I added it in the first place but may be it was added to check whether the server is publicly reachable / accessible (since LE requires it) and/or to support dns validation in the future.

    Thanks to @Jesse Norell observation and suggestion, the code was extended to check all available ips in DNS A records for the hostname against its public ip.

    If simply to check only for port 80 (and port 443) accessibility, may be the code can be rewritten.

    And @Taleman, don't worry that much about php as they are mostly the same except for the few deprecated parts and the developers will definitely double check before committing them to the git.
     
  12. Taleman

    Taleman Well-Known Member HowtoForge Supporter

    It occurred to me that if what is needed is the IP number that is visible to the Internet, then https://canihazip.com/s would show that.
     
  13. ahrasis

    ahrasis Well-Known Member HowtoForge Supporter

    Yes, that can be used to and I merely suggested to use http://dynamicdns.park-your-domain.com/getip in the code.

    The reason the ip is compared is to ensure that the ip is the same (on both the server and the domain's dns server side) before continuing.
     
  14. ahrasis

    ahrasis Well-Known Member HowtoForge Supporter

    There are some new development submitted in the git in relation to this thread other than the one that already merged which can be read: https://git.ispconfig.org/ispconfig/ispconfig3/merge_requests/904

    Basically, I rewrite the code a bit, added the fix for webroot path issues imported from the latest version 3.1.14 and I attempted to add support to acme.sh as well.

    Below is the suggested code that are not yet added to the new patch for the master branch:
    Code:
                // Support for Neilpang acme.sh
                $acme = explode("\n", shell_exec('which /usr/local/ispconfig/server/scripts/acme.sh /root/.acme.sh/acme.sh'));
                $acme_cert = "--cert-file $le_live_dir/cert.pem";
                $acme_key = "--key-file $le_live_dir/key.pem";
                $acme_chain = "--fullchain-file $le_live_dir/fullchain.pem";
                if (is_executable($acme)) {
                    // If this is a webserver, we use webroot
                    if(($conf['nginx']['installed'] || $conf['apache']['installed']) == true)
                        exec("$acme --issue -d $hostname --webroot $webroot_path");
                    // Else, it is not webserver, so we use standalone
                    else
                        exec("$acme --issue --standalone -d $hostname");
                        exec("$acme --install-cert -d $hostname $acme_cert $acme_key $acme_chain");
                }
    
    
     
  15. ahrasis

    ahrasis Well-Known Member HowtoForge Supporter

    I would like to invite any members here to help test the latest code as provided below. Simply download the latest stable ISPConfig tar.gz file then make changes to install/lib/installer_base.lib.php file at the make_ispconfig_ssl_cert() function.
    Code:
       public function make_ispconfig_ssl_cert() {
           global $conf, $autoinstall;
    
           //* Get hostname from user entry or shell command */
           if($conf['hostname'] !== ('localhost' || '')) $hostname = $conf['hostname'];
           else $hostname = exec('hostname -f');
    
           // Check dns a record exist and its ip equal to server public ip
           $svr_ip = file_get_contents('http://dynamicdns.park-your-domain.com/getip');
           if (checkdnsrr(idn_to_ascii($hostname, IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46), 'A')) {
               $dnsa=dns_get_record($hostname, DNS_A);
               $dns_ips = array();
               foreach ($dnsa as $rec) {
                   $dns_ips[] = $rec['ip'];
               }
           }
    
           // Request for certs if no LE SSL folder for server fqdn exist
           $le_live_dir = '/etc/letsencrypt/live/' . $hostname;
           if (!@is_dir($le_live_dir) && in_array($svr_ip, $dns_ips)) {
    
               // 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';
    
               // Get the default LE client name and version
               $le_client = explode("\n", shell_exec('which letsencrypt certbot /root/.local/share/letsencrypt/bin/letsencrypt /opt/eff.org/certbot/venv/bin/certbot'));
               $le_client = reset($le_client);
      
               // Check for Neilpang acme.sh as well
               $acme = explode("\n", shell_exec('which /usr/local/ispconfig/server/scripts/acme.sh /root/.acme.sh/acme.sh'));
               $acme = reset($acme);
      
               // Use LE certbot cleint if it is available
               if(is_executable($le_client)) {
          
                   $le_info = exec($le_client . ' --version  2>&1', $ret, $val);
                   if(preg_match('/^(\S+|\w+)\s+(\d+(\.\d+)+)$/', $le_info, $matches)) {
                       $le_version = $matches[2];
                   }
          
                   // Define certbot commands
                   $acme_version = '--server https://acme-v0' . (($le_version >=0.22) ? '2' : '1') . '.api.letsencrypt.org/directory';
                   $certonly = 'certonly --agree-tos --non-interactive --expand --rsa-key-size 4096';
    
                   // certbot choice of authenticator
                   $standalone_auth = '--authenticator standalone';
                   $webroot_auth = '--authenticator webroot';
    
                   // certbot webroot arguments i.e. map for >=0.30 or path for <=0.29
                   $webroot_map[$hostname] = $webroot_path;
                   if ($le_version >=0.30)
                       $webroot_args = '--webroot-map ' . escapeshellarg(str_replace(array('\r', '\n'), '', json_encode($webroot_map)));
                   else
                       $webroot_args = "-d $hostname --webroot-path $webroot_path";
    
                   // If this is a webserver, we use webroot
                   if(($conf['nginx']['installed'] || $conf['apache']['installed']) == true) {
                       exec("$le_client $certonly $acme_version $webroot_auth --email postmaster@$hostname $webroot_args");
                   }
                   // Else, it is not webserver, so we use standalone
                   else
                       exec("$le_client $certonly $acme_version $standalone_auth --email postmaster@$hostname -d $hostname");
              
               } else {
          
                   // Else try use Neilpang acme.sh, also if it is available
                   if (is_executable($acme)) {
              
                       // If this is a webserver, we use webroot
                       if(($conf['nginx']['installed'] || $conf['apache']['installed']) == true)
                           exec("$acme --issue -d $hostname --webroot $webroot_path");
                       // Else, it is not webserver, so we use standalone
                       else
                           exec("$acme --issue --standalone -d $hostname");
                  
                        // Define LE certs name and path, then install them
                        if (!@is_dir($le_live_dir)) mkdir($le_live_dir, 0755, true);
                        $acme_cert = "--cert-file $le_live_dir/cert.pem";
                        $acme_key = "--key-file $le_live_dir/privkey.pem";
                        $acme_ca = "--ca-file $le_live_dir/chain.pem";
                        $acme_chain = "--fullchain-file $le_live_dir/fullchain.pem";
                        exec("$acme --install-cert -d $hostname $acme_cert $acme_key $acme_ca $acme_chain");
    
                   }
               }
           }
    
           //* Define and check ISPConfig SSL folder */
           $ssl_dir = $conf['ispconfig_install_dir'].'/interface/ssl';
           if(!@is_dir($ssl_dir)) mkdir($ssl_dir, 0755, true);
    
           $ssl_crt_file = $ssl_dir.'/ispserver.crt';
           $ssl_csr_file = $ssl_dir.'/ispserver.csr';
           $ssl_key_file = $ssl_dir.'/ispserver.key';
           $ssl_pem_file = $ssl_dir.'/ispserver.pem';
    
           $date = new DateTime();
    
           // If the LE SSL certs for this hostname exists
           if (is_dir($le_live_dir) && in_array($svr_ip, $dns_ips)) {
    
               // Backup existing ispserver ssl files
               if (file_exists($ssl_crt_file)) rename($ssl_crt_file, $ssl_crt_file . '-' .$date->format('YmdHis') . '.bak');
               if (file_exists($ssl_crt_file)) rename($ssl_key_file, $ssl_key_file . '-' .$date->format('YmdHis') . '.bak');
               if (file_exists($ssl_crt_file)) rename($ssl_pem_file, $ssl_pem_file . '-' .$date->format('YmdHis') . '.bak');
    
               // Create symlink to LE fullchain and key for ISPConfig
               symlink($le_live_dir.'/fullchain.pem', $ssl_crt_file);
               symlink($le_live_dir.'/privkey.pem', $ssl_key_file);
    
           } else {
    
               // We can still use the old self-signed method
               $ssl_pw = substr(md5(mt_rand()), 0, 6);
               exec("openssl genrsa -des3 -passout pass:$ssl_pw -out $ssl_key_file 4096");
               if(AUTOINSTALL){
                   exec("openssl req -new -passin pass:$ssl_pw -passout pass:$ssl_pw -subj '/C=".escapeshellcmd($autoinstall['ssl_cert_country'])."/ST=".escapeshellcmd($autoinstall['ssl_cert_state'])."/L=".escapeshellcmd($autoinstall['ssl_cert_locality'])."/O=".escapeshellcmd($autoinstall['ssl_cert_organisation'])."/OU=".escapeshellcmd($autoinstall['ssl_cert_organisation_unit'])."/CN=".escapeshellcmd($autoinstall['ssl_cert_common_name'])."' -key $ssl_key_file -out $ssl_csr_file");
               } else {
                   exec("openssl req -new -passin pass:$ssl_pw -passout pass:$ssl_pw -key $ssl_key_file -out $ssl_csr_file");
               }
               exec("openssl req -x509 -passin pass:$ssl_pw -passout pass:$ssl_pw -key $ssl_key_file -in $ssl_csr_file -out $ssl_crt_file -days 3650");
               exec("openssl rsa -passin pass:$ssl_pw -in $ssl_key_file -out $ssl_key_file.insecure");
               rename($ssl_key_file, $ssl_key_file.'.secure');
               rename($ssl_key_file.'.insecure', $ssl_key_file);
           }
     
           // Build ispserver.pem file and chmod it
           exec("cat $ssl_key_file $ssl_crt_file > $ssl_pem_file; chmod 600 $ssl_pem_file");
     
           // Extend LE SSL certs to postfix
           if ($conf['postfix']['installed'] == true && strtolower($this->simple_query('Symlink ISPConfig LE SSL certs to postfix?', array('y', 'n'), 'y')) == 'y') {
      
               // Define folder, file(s)
               $cf = $conf['postfix'];
               $postfix_dir = $cf['config_dir'];
               if(!is_dir($postfix_dir)) $this->error("The postfix configuration directory '$postfix_dir' does not exist.");
               $smtpd_crt = $postfix_dir.'/smtpd.cert';
               $smtpd_key = $postfix_dir.'/smtpd.key';
      
               // Backup existing postfix ssl files
               if (file_exists($smtpd_crt)) rename($smtpd_crt, $smtpd_crt . '-' .$date->format('YmdHis') . '.bak');
               if (file_exists($smtpd_key)) rename($smtpd_key, $smtpd_key . '-' .$date->format('YmdHis') . '.bak');
      
               // Create symlink to ISPConfig SSL files
               symlink($ssl_crt_file, $smtpd_crt);
               symlink($ssl_key_file, $smtpd_key);
           }
     
           // Extend LE SSL certs to pureftpd
           if ($conf['pureftpd']['installed'] == true && strtolower($this->simple_query('Symlink ISPConfig LE SSL certs to pureftpd? Creating dhparam file takes some times.', array('y', 'n'), 'y')) == 'y') {
      
               // Define folder, file(s)
               $pureftpd_dir = '/etc/ssl/private';
               if(!is_dir($pureftpd_dir)) mkdir($pureftpd_dir, 0755, true);
               $pureftpd_pem = $pureftpd_dir.'/pure-ftpd.pem';
      
               // Backup existing pureftpd ssl files
               if (file_exists($pureftpd_pem)) rename($pureftpd_pem, $pureftpd_pem . '-' .$date->format('YmdHis') . '.bak');
      
               // Create symlink to ISPConfig SSL files
               symlink($ssl_pem_file, $pureftpd_pem);
               if (!file_exists("$pureftpd_dir/pure-ftpd-dhparams.pem"))
                   exec("cd $pureftpd_dir; openssl dhparam -out dhparam2048.pem 2048; ln -sf dhparam2048.pem pure-ftpd-dhparams.pem");
           }
     
           exec("chown -R root:root $ssl_dir");
    
       }
    
    

    Add the following code just before $inst->install_ispconfig(); in both install.php and update.php in install folder.
    Code:
    // Create SSL certs for non-webserver(s)?
    if(!file_exists('/usr/local/ispconfig/interface/ssl/ispserver.crt')) {
        if(strtolower($inst->simple_query('Do you want to create SSL certs for your server?', array('y', 'n'), 'y')) == 'y')
            $inst->make_ispconfig_ssl_cert();
    }
    

    Then use it to install your new ISPConfig test server or update existing one with ssl option.

    Note that the test is to determine whether the LE certs can be properly set upon ISPConfig installation and update, using either certbot or acme.sh, in a single or multi server setup.
     
    Last edited: Aug 17, 2019
    till likes this.
  16. ahrasis

    ahrasis Well-Known Member HowtoForge Supporter

    I try to make it easy for those who want to try but don't feel comfortable to edit manually. Here is some commands that can be cut and pasted to your terminal in your new ISPConfig test server or existing ISPConfig test server where no ssl has been installed for the server yet.
    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
    
    For new ISPConfig test server, run "php -q install.php" and choose ssl for your ISPConfig.

    For existing test server with no ssl installed, run "php -q update.php" and choose ssl for your ISPConfig.

    Do the same to test in an ISPConfig non-webserver server as well.

    Remember that your server should have the basic ISPConfig server requirements and either certbot or acme.sh installed, otherwise default method will be used to create openssl self-signed SSL certs instead.
     
    Last edited: May 31, 2020
    Croydon and till like this.
  17. ahrasis

    ahrasis Well-Known Member HowtoForge Supporter

    I noted that standalone especially for non-webserver will require port 80 (also 443) and as such added some ufw command to ensure that. I changed the above changes install/lib/installer_base.lib.php further as follows:
    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';
             
                // Set pre-hook and post hook for standalone
                $pre_hook = " --pre-hook \"ufw --force enable; ufw allow http; ufw allow https;\"";
                $post_hook = " --post-hook \"ufw --force enable; ufw deny http; ufw deny https;\"";
                $hook = $pre_hook . $post_hook;
    [...]
                    // Else, it is not webserver, so we use standalone
                    else
                        exec("$le_client $certonly $acme_version $standalone_auth --email postmaster@$hostname -d $hostname $hook");
    [...]
                        // Else, it is not webserver, so we use standalone
                        else
                            exec("$acme --issue --standalone -d $hostname $hook");
    
    The above is pending further testings and should in theory work on all perfect server tutorials that install and use ufw.

    Again, please use command in post #36 above to for easy downloading relevant modified files and testing.
     
    Last edited: Aug 18, 2019
  18. ahrasis

    ahrasis Well-Known Member HowtoForge Supporter

    I have tested the code on webserver and non-webserver, on new installation and update, and all are working good. LE SSL certs can be issued either by certbot, certbot-auto and acme.sh. Request for this code to be merged at the developers' convenient time.

    I can also PR the same code to 3.1 if the developers want to implement them sooner instead of waiting to release it in version 3.2.

    For record, renewal conf should look something like this:
    Code:
    # renew_before_expiry = 30 days
    version = 0.31.0
    archive_dir = /etc/letsencrypt/archive/server1.example.com
    cert = /etc/letsencrypt/live/server1.example.com/cert.pem
    privkey = /etc/letsencrypt/live/server1.example.com/privkey.pem
    chain = /etc/letsencrypt/live/server1.example.com/chain.pem
    fullchain = /etc/letsencrypt/live/server1.example.com/fullchain.pem
    
    # Options used in the renewal process
    [renewalparams]
    account = XXXXXXXXXXXXXXXXXXXXXXXXXXX
    rsa_key_size = 4096
    pre_hook = ufw --force enable; ufw allow http; ufw allow https;
    post_hook = ufw --force enable; ufw deny http; ufw deny https;
    server = https://acme-v02.api.letsencrypt.org/directory
    authenticator = standalone
    
    For others who tested the above code, please do report of any error or failure soonest.
     
    till likes this.
  19. till

    till Super Moderator Staff Member ISPConfig Developer

    Thank you for implementing this in ISPConfig! Please make a merge request against stable-3.1 branch. I will merge it for 3.1.16 then. We will merge the changes to master branch later, so there is no separate merge request agsinst master branch needed.

    There is just one thing that you need to add, not all servers run ufw, so you have to check if ufw is installed and many non-webservers use port 80 and 443 e.g. when they run webmail on the mail server or phpmyadmin on the database system. So closing the will cause issues, basically one would have to check if the ports are open or not. Maybe its better to start a custom shell script as pre and post hook (one script as pre hook and one as post hook and both can be located in /usr/local/ispconfig/server/scripts/ folder) and deal inside that script with detecting the current port state and the opening and closing of the ports?
     
    ahrasis likes this.
  20. ahrasis

    ahrasis Well-Known Member HowtoForge Supporter

    I think these are already considered as webservers (where nginx or apache will be checked) and thus webroot approach will work there. Non-webserver shall be the one with no nginx or apache2 enabled (no webmail or phpmyadmin exists in there too) and it will use standalone approach.

    I'll work on the script to check port 80 and/or 443 (80 will be sufficient actually) and if they are closed by default, open them for certs request / renewal and close them back thereafter. Otherwise, if they are opened by default, so we just ensure it keeps opened during certs request / renewal but will not close it thereafter.

    Edited: I am not sure what is the best approach now but will consider every possibilities and will suggest the best solutions.
     
    till likes this.

Share This Page