add new task to (re)configure mail/ftp services with let's encrypt; refs #1297
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
This commit is contained in:
@@ -268,7 +268,7 @@ return [
|
|||||||
'dovecot' => 'dovecot (imap/pop3)',
|
'dovecot' => 'dovecot (imap/pop3)',
|
||||||
'proftpd' => 'proftpd (ftp)',
|
'proftpd' => 'proftpd (ftp)',
|
||||||
],
|
],
|
||||||
'save_method' => 'storeSettingField',
|
'save_method' => 'storeSettingFieldInsertUpdateServicesTask',
|
||||||
'advanced_mode' => true
|
'advanced_mode' => true
|
||||||
],
|
],
|
||||||
'system_le_renew_hook' => [
|
'system_le_renew_hook' => [
|
||||||
@@ -278,7 +278,7 @@ return [
|
|||||||
'type' => 'text',
|
'type' => 'text',
|
||||||
'string_regexp' => '/^[a-z0-9\/\._\- ]+$/i',
|
'string_regexp' => '/^[a-z0-9\/\._\- ]+$/i',
|
||||||
'default' => 'systemctl restart postfix dovecot proftpd',
|
'default' => 'systemctl restart postfix dovecot proftpd',
|
||||||
'save_method' => 'storeSettingField',
|
'save_method' => 'storeSettingFieldInsertUpdateServicesTask',
|
||||||
'advanced_mode' => true,
|
'advanced_mode' => true,
|
||||||
'required_otp' => true
|
'required_otp' => true
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ final class MasterCron extends CliCommand
|
|||||||
Cronjob::inserttask(TaskId::REBUILD_RSPAMD);
|
Cronjob::inserttask(TaskId::REBUILD_RSPAMD);
|
||||||
Cronjob::inserttask(TaskId::CREATE_QUOTA);
|
Cronjob::inserttask(TaskId::CREATE_QUOTA);
|
||||||
Cronjob::inserttask(TaskId::REBUILD_CRON);
|
Cronjob::inserttask(TaskId::REBUILD_CRON);
|
||||||
|
Cronjob::inserttask(TaskId::UPDATE_LE_SERVICES);
|
||||||
$jobs[] = 'tasks';
|
$jobs[] = 'tasks';
|
||||||
}
|
}
|
||||||
define('CRON_IS_FORCED', 1);
|
define('CRON_IS_FORCED', 1);
|
||||||
|
|||||||
@@ -69,7 +69,8 @@ class AcmeSh extends FroxlorCron
|
|||||||
* run the task
|
* run the task
|
||||||
*
|
*
|
||||||
* @param bool $internal
|
* @param bool $internal
|
||||||
* @return number
|
* @return int
|
||||||
|
* @throws \Exception
|
||||||
*/
|
*/
|
||||||
public static function run(bool $internal = false)
|
public static function run(bool $internal = false)
|
||||||
{
|
{
|
||||||
@@ -85,6 +86,9 @@ class AcmeSh extends FroxlorCron
|
|||||||
if ($issue_froxlor || !empty($issue_domains) || !empty($renew_froxlor) || $renew_domains) {
|
if ($issue_froxlor || !empty($issue_domains) || !empty($renew_froxlor) || $renew_domains) {
|
||||||
// insert task to generate certificates and vhost-configs
|
// insert task to generate certificates and vhost-configs
|
||||||
Cronjob::inserttask(TaskId::REBUILD_VHOST);
|
Cronjob::inserttask(TaskId::REBUILD_VHOST);
|
||||||
|
if ($renew_froxlor) {
|
||||||
|
Cronjob::inserttask(TaskId::UPDATE_LE_SERVICES);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -217,6 +221,7 @@ class AcmeSh extends FroxlorCron
|
|||||||
* check whether we need to issue a new certificate for froxlor itself
|
* check whether we need to issue a new certificate for froxlor itself
|
||||||
*
|
*
|
||||||
* @return boolean
|
* @return boolean
|
||||||
|
* @throws \Exception
|
||||||
*/
|
*/
|
||||||
private static function issueFroxlorVhost()
|
private static function issueFroxlorVhost()
|
||||||
{
|
{
|
||||||
@@ -340,6 +345,7 @@ EOC;
|
|||||||
* check whether we need to renew-check the certificate for froxlor itself
|
* check whether we need to renew-check the certificate for froxlor itself
|
||||||
*
|
*
|
||||||
* @return boolean
|
* @return boolean
|
||||||
|
* @throws \Exception
|
||||||
*/
|
*/
|
||||||
private static function renewFroxlorVhost()
|
private static function renewFroxlorVhost()
|
||||||
{
|
{
|
||||||
@@ -539,6 +545,7 @@ EOC;
|
|||||||
* @param array $domains
|
* @param array $domains
|
||||||
* @param int $domain_id
|
* @param int $domain_id
|
||||||
* @param FroxlorLogger $cronlog
|
* @param FroxlorLogger $cronlog
|
||||||
|
* @throws \Exception
|
||||||
*/
|
*/
|
||||||
private static function validateDns(array &$domains, $domain_id, &$cronlog)
|
private static function validateDns(array &$domains, $domain_id, &$cronlog)
|
||||||
{
|
{
|
||||||
@@ -619,18 +626,39 @@ EOC;
|
|||||||
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, "Successful exit-code returned - storing certificate");
|
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, "Successful exit-code returned - storing certificate");
|
||||||
$cert_stored = self::certToDb($certrow, $cronlog, $acme_result);
|
$cert_stored = self::certToDb($certrow, $cronlog, $acme_result);
|
||||||
|
|
||||||
if ($cert_stored
|
if ($cert_stored && $renew_hook) {
|
||||||
&& $renew_hook
|
self::renewHookConfigs($cronlog);
|
||||||
&& !empty(trim(Settings::Get('system.le_renew_services') ?? ""))
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function renewHookConfigs($cronlog)
|
||||||
|
{
|
||||||
|
if (!empty(trim(Settings::Get('system.le_renew_services') ?? ""))
|
||||||
&& !empty(trim(Settings::Get('system.le_renew_hook') ?? ""))
|
&& !empty(trim(Settings::Get('system.le_renew_hook') ?? ""))
|
||||||
) {
|
) {
|
||||||
|
|
||||||
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, "Renew-hook is enabled - adjusting configurations");
|
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, "Renew-hook is enabled - adjusting configurations");
|
||||||
|
|
||||||
$certificate_folder = self::getCertificateFolder(strtolower(Settings::Get('system.hostname')));
|
$certificate_folder = self::getCertificateFolder(strtolower(Settings::Get('system.hostname')));
|
||||||
|
|
||||||
|
if (empty($certificate_folder)) {
|
||||||
|
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "No certificate folder for '" . Settings::Get('system.hostname') . "' found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$fullchain = FileDir::makeCorrectFile($certificate_folder . '/fullchain.cer');
|
$fullchain = FileDir::makeCorrectFile($certificate_folder . '/fullchain.cer');
|
||||||
$keyfile = FileDir::makeCorrectFile($certificate_folder . '/' . strtolower(Settings::Get('system.hostname')) . '.key');
|
$keyfile = FileDir::makeCorrectFile($certificate_folder . '/' . strtolower(Settings::Get('system.hostname')) . '.key');
|
||||||
$ca_file = FileDir::makeCorrectFile($certificate_folder . '/ca.cer');
|
$ca_file = FileDir::makeCorrectFile($certificate_folder . '/ca.cer');
|
||||||
|
|
||||||
|
if (!file_exists($fullchain) || !file_exists($keyfile) || !file_exists($ca_file)) {
|
||||||
|
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "At least one of the required certificate files for '" . Settings::Get('system.hostname') . "' could not be found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$dovecot_conf = '/etc/dovecot/conf.d/99-froxlor.ssl.conf'; // @fixme setting?
|
||||||
|
|
||||||
if (Settings::IsInList('system.le_renew_services', 'postfix')) {
|
if (Settings::IsInList('system.le_renew_services', 'postfix')) {
|
||||||
// "postconf -e" for postfix
|
// "postconf -e" for postfix
|
||||||
FileDir::safe_exec('postconf -e smtpd_tls_cert_file=' . escapeshellarg($fullchain));
|
FileDir::safe_exec('postconf -e smtpd_tls_cert_file=' . escapeshellarg($fullchain));
|
||||||
@@ -638,7 +666,6 @@ EOC;
|
|||||||
}
|
}
|
||||||
if (Settings::IsInList('system.le_renew_services', 'dovecot')) {
|
if (Settings::IsInList('system.le_renew_services', 'dovecot')) {
|
||||||
// custom config for dovecot
|
// custom config for dovecot
|
||||||
$dovecot_conf = '/etc/dovecot/conf.d/99-froxlor.ssl.conf'; // @fixme setting?
|
|
||||||
$ssl_content = <<<EOSSL
|
$ssl_content = <<<EOSSL
|
||||||
# Autogenerated configuration by froxlor.
|
# Autogenerated configuration by froxlor.
|
||||||
# Do not manually edit this file as it will be overwritten.
|
# Do not manually edit this file as it will be overwritten.
|
||||||
@@ -648,6 +675,9 @@ ssl_cert = <{$fullchain}
|
|||||||
ssl_key = <{$keyfile}
|
ssl_key = <{$keyfile}
|
||||||
EOSSL;
|
EOSSL;
|
||||||
file_put_contents($dovecot_conf, $ssl_content);
|
file_put_contents($dovecot_conf, $ssl_content);
|
||||||
|
} elseif (file_exists($dovecot_conf)) {
|
||||||
|
// safely remove the autogenerated config file
|
||||||
|
unlink($dovecot_conf);
|
||||||
}
|
}
|
||||||
if (Settings::IsInList('system.le_renew_services', 'proftpd')) {
|
if (Settings::IsInList('system.le_renew_services', 'proftpd')) {
|
||||||
$proftpd_conf = '/etc/proftpd/tls.conf'; // @fixme setting?
|
$proftpd_conf = '/etc/proftpd/tls.conf'; // @fixme setting?
|
||||||
@@ -670,12 +700,11 @@ EOSSL;
|
|||||||
}
|
}
|
||||||
FileDir::safe_exec("sed -i.bak 's|^#\?\s\?TLSCACertificateFile.*|TLSCACertificateFile " . $ca_file . "|' " . escapeshellarg($proftpd_conf), $rval, ['|', '?']);
|
FileDir::safe_exec("sed -i.bak 's|^#\?\s\?TLSCACertificateFile.*|TLSCACertificateFile " . $ca_file . "|' " . escapeshellarg($proftpd_conf), $rval, ['|', '?']);
|
||||||
}
|
}
|
||||||
|
|
||||||
// reload the services
|
// reload the services
|
||||||
FileDir::safe_exec(Settings::Get('system.le_renew_hook'));
|
FileDir::safe_exec(Settings::Get('system.le_renew_hook'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function certToDb($certrow, &$cronlog, $acme_result): bool
|
private static function certToDb($certrow, &$cronlog, $acme_result): bool
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ use Exception;
|
|||||||
use Froxlor\Cron\FroxlorCron;
|
use Froxlor\Cron\FroxlorCron;
|
||||||
use Froxlor\Cron\Http\ConfigIO;
|
use Froxlor\Cron\Http\ConfigIO;
|
||||||
use Froxlor\Cron\Http\HttpConfigBase;
|
use Froxlor\Cron\Http\HttpConfigBase;
|
||||||
|
use Froxlor\Cron\Http\LetsEncrypt\AcmeSh;
|
||||||
use Froxlor\Cron\Mail\Rspamd;
|
use Froxlor\Cron\Mail\Rspamd;
|
||||||
use Froxlor\Cron\TaskId;
|
use Froxlor\Cron\TaskId;
|
||||||
use Froxlor\Database\Database;
|
use Froxlor\Database\Database;
|
||||||
@@ -125,6 +126,12 @@ class TasksCron extends FroxlorCron
|
|||||||
*/
|
*/
|
||||||
FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_NOTICE, "Removing Let's Encrypt entries for domain " . $row['data']['domain']);
|
FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_NOTICE, "Removing Let's Encrypt entries for domain " . $row['data']['domain']);
|
||||||
Domain::doLetsEncryptCleanUp($row['data']['domain']);
|
Domain::doLetsEncryptCleanUp($row['data']['domain']);
|
||||||
|
} elseif ($row['type'] == TaskId::UPDATE_LE_SERVICES) {
|
||||||
|
/**
|
||||||
|
* TYPE=13 set configuration for selected services regarding the use of Let's Encrypt certificate
|
||||||
|
*/
|
||||||
|
FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_NOTICE, "Updating Let's Encrypt configuration for selected services");
|
||||||
|
AcmeSh::renewHookConfigs(FroxlorLogger::getInstanceOf());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -87,6 +87,11 @@ final class TaskId
|
|||||||
*/
|
*/
|
||||||
const DELETE_DOMAIN_SSL = 12;
|
const DELETE_DOMAIN_SSL = 12;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TYPE=13 set configuration for selected services regarding the use of Let's Encrypt certificate
|
||||||
|
*/
|
||||||
|
const UPDATE_LE_SERVICES = 13;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TYPE=20 CUSTUMER DATA DUMP
|
* TYPE=20 CUSTUMER DATA DUMP
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -237,6 +237,17 @@ class Store
|
|||||||
return $returnvalue;
|
return $returnvalue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function storeSettingFieldInsertUpdateServicesTask($fieldname, $fielddata, $newfieldvalue)
|
||||||
|
{
|
||||||
|
// first save the setting itself
|
||||||
|
$returnvalue = self::storeSettingField($fieldname, $fielddata, $newfieldvalue);
|
||||||
|
|
||||||
|
if ($returnvalue !== false) {
|
||||||
|
Cronjob::inserttask(TaskId::UPDATE_LE_SERVICES);
|
||||||
|
}
|
||||||
|
return $returnvalue;
|
||||||
|
}
|
||||||
|
|
||||||
public static function storeSettingHostname($fieldname, $fielddata, $newfieldvalue)
|
public static function storeSettingHostname($fieldname, $fielddata, $newfieldvalue)
|
||||||
{
|
{
|
||||||
$returnvalue = self::storeSettingField($fieldname, $fielddata, $newfieldvalue);
|
$returnvalue = self::storeSettingField($fieldname, $fielddata, $newfieldvalue);
|
||||||
|
|||||||
@@ -2192,6 +2192,7 @@ Vielen Dank, Ihr Administrator',
|
|||||||
'CREATE_CUSTOMER_DATADUMP' => 'Daten-Export für Kunde %s',
|
'CREATE_CUSTOMER_DATADUMP' => 'Daten-Export für Kunde %s',
|
||||||
'DELETE_DOMAIN_PDNS' => 'Lösche Domain %s von PowerDNS Datenbank',
|
'DELETE_DOMAIN_PDNS' => 'Lösche Domain %s von PowerDNS Datenbank',
|
||||||
'DELETE_DOMAIN_SSL' => 'Lösche SSL Dateien von Domain %s',
|
'DELETE_DOMAIN_SSL' => 'Lösche SSL Dateien von Domain %s',
|
||||||
|
'UPDATE_LE_SERVICES' => 'Aktualisiere Systemdienste für Let\'s Encrypt',
|
||||||
],
|
],
|
||||||
'terms' => 'AGB',
|
'terms' => 'AGB',
|
||||||
'traffic' => [
|
'traffic' => [
|
||||||
|
|||||||
@@ -2326,6 +2326,7 @@ Yours sincerely, your administrator',
|
|||||||
'CREATE_CUSTOMER_DATADUMP' => 'Data export job for customer %s',
|
'CREATE_CUSTOMER_DATADUMP' => 'Data export job for customer %s',
|
||||||
'DELETE_DOMAIN_PDNS' => 'Delete domain %s from PowerDNS database',
|
'DELETE_DOMAIN_PDNS' => 'Delete domain %s from PowerDNS database',
|
||||||
'DELETE_DOMAIN_SSL' => 'Delete ssl files of domain %s',
|
'DELETE_DOMAIN_SSL' => 'Delete ssl files of domain %s',
|
||||||
|
'UPDATE_LE_SERVICES' => 'Updating system services for Let\'s Encrypt',
|
||||||
],
|
],
|
||||||
'terms' => 'Terms of use',
|
'terms' => 'Terms of use',
|
||||||
'traffic' => [
|
'traffic' => [
|
||||||
|
|||||||
Reference in New Issue
Block a user