diff --git a/install/install.php b/install/install.php index 496ab3fdcef6b7de009f8bc6b2d523c3424ca885..294298653a6849370c7cdb6375f9e8953188d5ba 100644 --- a/install/install.php +++ b/install/install.php @@ -536,12 +536,6 @@ if($force) { $inst->configure_fail2ban(); } -if($conf['services']['web'] == true) { - //** Configure apps vhost - swriteln('Configuring Apps vhost'); - $inst->configure_apps_vhost(); -} - //** Configure ISPConfig :-) $install_ispconfig_interface_default = ($conf['mysql']['master_slave_setup'] == 'y')?'n':'y'; if($install_mode == 'standard' || strtolower($inst->simple_query('Install ISPConfig Web Interface', array('y', 'n'), $install_ispconfig_interface_default,'install_ispconfig_web_interface')) == 'y') { @@ -582,6 +576,12 @@ if(!file_exists('/usr/local/ispconfig/interface/ssl/ispserver.crt')) { $inst->make_ispconfig_ssl_cert(); } +if($conf['services']['web'] == true) { + //** Configure apps vhost + swriteln('Configuring Apps vhost'); + $inst->configure_apps_vhost(); +} + $inst->install_ispconfig(); //* Configure DBServer diff --git a/install/lib/installer_base.lib.php b/install/lib/installer_base.lib.php index e262f31fa52f54ecb89f00695b447d2677728bef..d9eb7aec02a8df604b03bb7800d0f5e5ad2fc33a 100644 --- a/install/lib/installer_base.lib.php +++ b/install/lib/installer_base.lib.php @@ -2720,6 +2720,54 @@ class installer_base { return $response; } + private function make_acme_vhost($server_name, $server = 'apache') { + global $conf; + + $use_template = 'apache_acme.conf.master'; + $use_symlink = '999-acme.conf'; + $use_name = 'acme.conf'; + if($server === 'nginx') { + $use_template = 'nginx_acme.vhost.master'; + $use_symlink = '999-acme.vhost'; + $use_name = 'acme.vhost'; + } + + $vhost_conf_dir = $conf[$server]['vhost_conf_dir']; + $vhost_conf_enabled_dir = $conf[$server]['vhost_conf_enabled_dir']; + + $tpl = new tpl($use_template); + $tpl->setVar('domain', $server_name); + + if($server !== 'nginx') { + $tpl->setVar('apache_version',getapacheversion()); + } + + $acme_dir = $conf['ispconfig_install_dir'] . '/interface/acme'; + + //* Create the ISPConfig installation directory + if(!@is_dir($acme_dir)) { + $command = "mkdir -p $acme_dir"; + caselog($command.' &> /dev/null', __FILE__, __LINE__, "EXECUTED: $command", "Failed to execute the command $command"); + } + + wf($vhost_conf_dir.'/' . $use_name, $tpl->grab()); + + if(@is_link($vhost_conf_enabled_dir.'/' . $use_symlink)) { + unlink($vhost_conf_enabled_dir.'/' . $use_symlink); + } + if(!@is_link($vhost_conf_enabled_dir.'' . $use_symlink)) { + symlink($vhost_conf_dir.'/' . $use_name, $vhost_conf_enabled_dir.'/' . $use_symlink); + } + + if($conf[$server]['installed'] == true && $conf[$server]['init_script'] != '') { + if($this->is_update) { + system($this->getinitcommand($conf[$server]['init_script'], 'force-reload').' &> /dev/null || ' . $this->getinitcommand($conf[$server]['init_script'], 'restart').' &> /dev/null'); + } else { + system($this->getinitcommand($conf[$server]['init_script'], 'restart').' &> /dev/null'); + } + } + } + public function make_ispconfig_ssl_cert() { global $conf, $autoinstall; @@ -2759,19 +2807,42 @@ class installer_base { } } + //* 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(); + // 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) && (($svr_ip4 && in_array($svr_ip4, $dns_ips)) || ($svr_ip6 && in_array($svr_ip6, $dns_ips)))) { + + $acme_cert_dir = '/usr/local/ispconfig/server/scripts/' . $hostname; + $check_acme_file = $acme_cert_dir . '/' . $hostname . '.cer'; + if(!@is_dir($acme_cert_dir)) { + $acme_cert_dir = '/root/.acme.sh/' . $hostname; + $check_acme_file = $acme_cert_dir . '/' . $hostname . '.cer'; + if(!@is_dir($acme_cert_dir)) { + $acme_cert_dir = '/etc/letsencrypt/live/' . $hostname; + $check_acme_file = $acme_cert_dir . '/cert.pem'; + } + } + if ((!@is_dir($acme_cert_dir) || !@file_exists($check_acme_file) || !@file_exists($ssl_crt_file)) && (($svr_ip4 && in_array($svr_ip4, $dns_ips)) || ($svr_ip6 && in_array($svr_ip6, $dns_ips)))) { // This script is needed earlier to check and open http port 80 or standalone might fail // Make executable and temporary symlink latest letsencrypt pre, post and renew hook script before install - if(file_exists(dirname(getcwd()) . '/server/scripts/letsencrypt_pre_hook.sh')) { + if(file_exists(dirname(getcwd()) . '/server/scripts/letsencrypt_pre_hook.sh') && !file_exists('/usr/local/bin/letsencrypt_pre_hook.sh')) { symlink(dirname(getcwd()) . '/server/scripts/letsencrypt_pre_hook.sh', '/usr/local/bin/letsencrypt_pre_hook.sh'); } - if(file_exists(dirname(getcwd()) . '/server/scripts/letsencrypt_post_hook.sh')) { + if(file_exists(dirname(getcwd()) . '/server/scripts/letsencrypt_post_hook.sh') && !file_exists('/usr/local/bin/letsencrypt_post_hook.sh')) { symlink(dirname(getcwd()) . '/server/scripts/letsencrypt_post_hook.sh', '/usr/local/bin/letsencrypt_post_hook.sh'); } - if(file_exists(dirname(getcwd()) . '/server/scripts/letsencrypt_renew_hook.sh')) { + if(file_exists(dirname(getcwd()) . '/server/scripts/letsencrypt_renew_hook.sh') && !file_exists('/usr/local/bin/letsencrypt_renew_hook.sh')) { symlink(dirname(getcwd()) . '/server/scripts/letsencrypt_renew_hook.sh', '/usr/local/bin/letsencrypt_renew_hook.sh'); } chown('/usr/local/bin/letsencrypt_pre_hook.sh', 'root'); @@ -2802,32 +2873,67 @@ class installer_base { $acme = explode("\n", shell_exec('which /usr/local/ispconfig/server/scripts/acme.sh /root/.acme.sh/acme.sh')); $acme = reset($acme); + $restore_conf_symlink = false; + + // we only need this for apache, so use fixed conf index + $vhost_conf_dir = $conf['apache']['vhost_conf_dir']; + $vhost_conf_enabled_dir = $conf['apache']['vhost_conf_enabled_dir']; + + // first of all create the acme vhosts if not existing + if($conf['nginx']['installed'] == true) { + $this->make_acme_vhost($hostname, 'nginx'); + } elseif($conf['apache']['installed'] == true) { + if($this->is_update == false && @is_link($vhost_conf_enabled_dir.'/000-ispconfig.conf')) { + $restore_conf_symlink = true; + unlink($vhost_conf_enabled_dir.'/000-ispconfig.conf'); + } + + $this->make_acme_vhost($hostname, 'apache'); + } + + $issued_successfully = false; + // Attempt to use Neilpang acme.sh first, as it is now the preferred LE client if (is_executable($acme)) { - if($conf['nginx']['installed'] == true) { - exec("$acme --issue --nginx -d $hostname $renew_hook"); - } elseif($conf['apache']['installed'] == true) { - exec("$acme --issue --apache -d $hostname $renew_hook"); + $out = null; + $ret = null; + if($conf['nginx']['installed'] == true || $conf['apache']['installed'] == true) { + exec("$acme --issue -w /usr/local/ispconfig/interface/acme -d $hostname $renew_hook", $out, $ret); } // Else, it is not webserver, so we use standalone else { - exec("$acme --issue --standalone -d $hostname $hook"); + exec("$acme --issue --standalone -d " . escapeshellarg($hostname) . " $hook", $out, $ret); } - // 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"); + if($ret == 0 || ($ret == 2 && file_exists($check_acme_file))) { + // acme.sh returns with 2 on issue for already existing certificate + + // Backup existing ispserver ssl files + if(file_exists($ssl_crt_file) || is_link($ssl_crt_file)) { + rename($ssl_crt_file, $ssl_crt_file . '-' . $date->format('YmdHis') . '.bak'); + } + if(file_exists($ssl_key_file) || is_link($ssl_key_file)) { + rename($ssl_key_file, $ssl_key_file . '-' . $date->format('YmdHis') . '.bak'); + } + if(file_exists($ssl_pem_file) || is_link($ssl_pem_file)) { + rename($ssl_pem_file, $ssl_pem_file . '-' . $date->format('YmdHis') . '.bak'); + } + // Define LE certs name and path, then install them + //$acme_cert = "--cert-file $acme_cert_dir/cert.pem"; + $acme_key = "--key-file " . escapeshellarg($ssl_key_file); + $acme_chain = "--fullchain-file " . escapeshellarg($ssl_crt_file); + exec("$acme --install-cert -d $hostname $acme_key $acme_chain"); + $issued_successfully = true; + } // Else, we attempt to use the official LE certbot client certbot } else { // But only if it is otherwise available if(is_executable($le_client)) { + $out = null; + $ret = null; // Get its version info due to be used for webroot arguement issues $le_info = exec($le_client . ' --version 2>&1', $ret, $val); @@ -2840,42 +2946,45 @@ class installer_base { $certonly = 'certonly --agree-tos --non-interactive --expand --rsa-key-size 4096'; // If this is a webserver - if($conf['nginx']['installed'] == true) - exec("$le_client $certonly $acme_version --nginx --email postmaster@$hostname -d $hostname $renew_hook"); - elseif($conf['apache']['installed'] == true) - exec("$le_client $certonly $acme_version --apache --email postmaster@$hostname -d $hostname $renew_hook"); + if($conf['nginx']['installed'] == true || $conf['apache']['installed'] == true) { + exec("$le_client $certonly $acme_version --authenticator webroot --webroot-path /usr/local/ispconfig/interface/acme --email " . escapeshellarg('postmaster@$hostname') . " -d " . escapeshellarg($hostname) . " $renew_hook", $out, $ret); + } // Else, it is not webserver, so we use standalone - else - exec("$le_client $certonly $acme_version --standalone --email postmaster@$hostname -d $hostname $hook"); - } - } - } - - //* 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(); + else { + exec("$le_client $certonly $acme_version --standalone --email " . escapeshellarg('postmaster@$hostname') . " -d " . escapeshellarg($hostname) . " $hook", $out, $ret); + } - // If the LE SSL certs for this hostname exists - if (is_dir($le_live_dir) && (($svr_ip4 && in_array($svr_ip4, $dns_ips)) || ($svr_ip6 && in_array($svr_ip6, $dns_ips)))) { + if($ret == 0) { + // certbot returns with 0 on issue for already existing certificate - // 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_key_file)) rename($ssl_key_file, $ssl_key_file . '-' .$date->format('YmdHis') . '.bak'); - if (file_exists($ssl_pem_file)) rename($ssl_pem_file, $ssl_pem_file . '-' .$date->format('YmdHis') . '.bak'); + // Backup existing ispserver ssl files + if(file_exists($ssl_crt_file) || is_link($ssl_crt_file)) { + rename($ssl_crt_file, $ssl_crt_file . '-' . $date->format('YmdHis') . '.bak'); + } + if(file_exists($ssl_key_file) || is_link($ssl_key_file)) { + rename($ssl_key_file, $ssl_key_file . '-' . $date->format('YmdHis') . '.bak'); + } + if(file_exists($ssl_pem_file) || is_link($ssl_pem_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); + $issued_successfully = true; + } + } + } - } else { + if($restore_conf_symlink) { + if(!@is_link($vhost_conf_enabled_dir.'/000-ispconfig.conf')) { + symlink($vhost_conf_dir.'/ispconfig.conf', $vhost_conf_enabled_dir.'/000-ispconfig.conf'); + } + } + } elseif(($svr_ip4 && in_array($svr_ip4, $dns_ips)) || ($svr_ip6 && in_array($svr_ip6, $dns_ips))) { + // the directory already exists so we have to assume that it was created previously + $issued_successfully = true; + } + // If the LE SSL certs for this hostname exists + if(!is_dir($acme_cert_dir) || !file_exists($check_acme_file) || !$issued_successfully) { // 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"); diff --git a/install/tpl/apache_acme.conf.master b/install/tpl/apache_acme.conf.master new file mode 100644 index 0000000000000000000000000000000000000000..4a1629433525ca40208b9e9e4db66f8f4d5730d2 --- /dev/null +++ b/install/tpl/apache_acme.conf.master @@ -0,0 +1,11 @@ + Alias /.well-known/acme-challenge /usr/local/ispconfig/interface/acme/.well-known/acme-challenge + + + AllowOverride None + + Require all granted + + Order allow,deny + Allow from all + + diff --git a/install/tpl/nginx_acme.vhost.master b/install/tpl/nginx_acme.vhost.master new file mode 100644 index 0000000000000000000000000000000000000000..d7c576b04d35e06180cea48db6ff933914ec917c --- /dev/null +++ b/install/tpl/nginx_acme.vhost.master @@ -0,0 +1,25 @@ +server { + listen 80; + listen [::]:80; + + server_name ; + + root /usr/local/ispconfig/interface/acme; + + autoindex off; + index index.html; + + ## Disable .htaccess and other hidden files + location ~ / { + deny all; + } + + ## Allow access for .well-known/acme-challenge + location ^~ /.well-known/acme-challenge/ { + access_log off; + log_not_found off; + auth_basic off; + root /usr/local/ispconfig/interface/acme/; + try_files $uri $uri/ =404; + } +} \ No newline at end of file diff --git a/server/scripts/letsencrypt_post_hook.sh b/server/scripts/letsencrypt_post_hook.sh index 02653f79a1112d94087c9441326b2714d115f600..27d593196c8fb3b926fba072cd6f6797efa74dbd 100644 --- a/server/scripts/letsencrypt_post_hook.sh +++ b/server/scripts/letsencrypt_post_hook.sh @@ -12,7 +12,7 @@ ## If you need a custom hook file, create a file with the same name in ## /usr/local/ispconfig/server/conf-custom/scripts/ -if [[ -e "/usr/local/ispconfig/server/conf-custom/scripts/letsencrypt_post_hook.sh" ]] ; then +if [ -e "/usr/local/ispconfig/server/conf-custom/scripts/letsencrypt_post_hook.sh" ] ; then . /usr/local/ispconfig/server/conf-custom/scripts/letsencrypt_post_hook.sh && exit 0 || exit 1; fi diff --git a/server/scripts/letsencrypt_pre_hook.sh b/server/scripts/letsencrypt_pre_hook.sh index 56f246e8037d4f456b691d87a378be4dd22ebaac..60964a86d45b8a0476f30c28673538a9cc3e18ed 100644 --- a/server/scripts/letsencrypt_pre_hook.sh +++ b/server/scripts/letsencrypt_pre_hook.sh @@ -12,7 +12,7 @@ ## If you need a custom hook file, create a file with the same name in ## /usr/local/ispconfig/server/conf-custom/scripts/ -if [[ -e "/usr/local/ispconfig/server/conf-custom/scripts/letsencrypt_pre_hook.sh" ]] ; then +if [ -e "/usr/local/ispconfig/server/conf-custom/scripts/letsencrypt_pre_hook.sh" ] ; then . /usr/local/ispconfig/server/conf-custom/scripts/letsencrypt_pre_hook.sh && exit 0 || exit 1 ; fi diff --git a/server/scripts/letsencrypt_renew_hook.sh b/server/scripts/letsencrypt_renew_hook.sh index 0a71f30d01d0e14ea7f8e065bbcc11c1608d576b..53fc31befd65af69b245347358de7f13c3938f6b 100644 --- a/server/scripts/letsencrypt_renew_hook.sh +++ b/server/scripts/letsencrypt_renew_hook.sh @@ -12,11 +12,20 @@ ## If you need a custom hook file, create a file with the same name in ## /usr/local/ispconfig/server/conf-custom/scripts/ -if [[ -e "/usr/local/ispconfig/server/conf-custom/scripts/letsencrypt_renew_hook.sh" ]] ; then +if [ -e "/usr/local/ispconfig/server/conf-custom/scripts/letsencrypt_renew_hook.sh" ] ; then . /usr/local/ispconfig/server/conf-custom/scripts/letsencrypt_renew_hook.sh && exit 0 || exit 1; fi -lelive=/etc/letsencrypt/live/$(hostname -f); if [ -d "$lelive" ]; then +hostname=$(hostname -f) +if [ -d "/usr/local/ispconfig/server/scripts/${hostname}" ] ; then + lelive="/usr/local/ispconfig/server/scripts/${hostname}" ; +elif [ -d "/root/.acme.sh/${hostname}" ] ; then + lelive="/root/.acme.sh/${hostname}" ; +else + lelive="/etc/letsencrypt/live/${hostname}" ; +fi + +if [ -d "$lelive" ]; then cd /usr/local/ispconfig/interface/ssl; ibak=ispserver.*.bak; ipem=ispserver.pem; icrt=ispserver.crt; ikey=ispserver.key if ls $ibak 1> /dev/null 2>&1; then rm $ibak; fi if [ -e "$ipem" ]; then mv $ipem $ipem-$(date +"%y%m%d%H%M%S").bak; cat $ikey $icrt > $ipem; chmod 600 $ipem; fi @@ -42,6 +51,5 @@ lelive=/etc/letsencrypt/live/$(hostname -f); if [ -d "$lelive" ]; then if [ $(dpkg-query -W -f='${Status}' mariadb 2>/dev/null | grep -c "ok installed") -eq 1 ]; then service mysql restart; fi if [ $(dpkg-query -W -f='${Status}' nginx 2>/dev/null | grep -c "ok installed") -eq 1 ]; then service nginx restart; fi if [ $(dpkg-query -W -f='${Status}' apache2 2>/dev/null | grep -c "ok installed") -eq 1 ]; then service apache2 restart; fi - else fi else echo `/bin/date` "Your Lets Encrypt SSL certs path for your ISPConfig server FQDN is missing.$line" >> /var/log/ispconfig/ispconfig.log; fi \ No newline at end of file