initial integration of let's encrypt renew-hook for froxlor-vhost; refs #1186
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
This commit is contained in:
@@ -251,8 +251,37 @@ return [
|
|||||||
'string_type' => 'validate_ip',
|
'string_type' => 'validate_ip',
|
||||||
'string_emptyallowed' => true,
|
'string_emptyallowed' => true,
|
||||||
'default' => '',
|
'default' => '',
|
||||||
'save_method' => 'storeSettingField'
|
'save_method' => 'storeSettingField',
|
||||||
]
|
'advanced_mode' => true
|
||||||
|
],
|
||||||
|
'system_le_renew_services' => [
|
||||||
|
'label' => lng('serversettings.le_renew_services'),
|
||||||
|
'settinggroup' => 'system',
|
||||||
|
'varname' => 'le_renew_services',
|
||||||
|
'type' => 'select',
|
||||||
|
'default' => '',
|
||||||
|
'select_mode' => 'multiple',
|
||||||
|
'option_emptyallowed' => true,
|
||||||
|
'select_var' => [
|
||||||
|
'' => lng('panel.none_value'),
|
||||||
|
'postfix' => 'postfix (smtp)',
|
||||||
|
'dovecot' => 'dovecot (imap/pop3)',
|
||||||
|
'proftpd' => 'proftpd (ftp)',
|
||||||
|
],
|
||||||
|
'save_method' => 'storeSettingField',
|
||||||
|
'advanced_mode' => true
|
||||||
|
],
|
||||||
|
'system_le_renew_hook' => [
|
||||||
|
'label' => lng('serversettings.le_renew_hook'),
|
||||||
|
'settinggroup' => 'system',
|
||||||
|
'varname' => 'le_renew_hook',
|
||||||
|
'type' => 'text',
|
||||||
|
'string_regexp' => '/^[a-z0-9\/\._\- ]+$/i',
|
||||||
|
'default' => 'systemctl restart postfix dovecot proftpd',
|
||||||
|
'save_method' => 'storeSettingField',
|
||||||
|
'advanced_mode' => true,
|
||||||
|
'required_otp' => true
|
||||||
|
],
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -638,6 +638,8 @@ opcache.validate_timestamps'),
|
|||||||
('system', 'available_shells', ''),
|
('system', 'available_shells', ''),
|
||||||
('system', 'le_froxlor_enabled', '0'),
|
('system', 'le_froxlor_enabled', '0'),
|
||||||
('system', 'le_froxlor_redirect', '0'),
|
('system', 'le_froxlor_redirect', '0'),
|
||||||
|
('system', 'le_renew_hook', 'systemctl restart postfix dovecot proftpd'),
|
||||||
|
('system', 'le_renew_services', ''),
|
||||||
('system', 'letsencryptacmeconf', '/etc/apache2/conf-enabled/acme.conf'),
|
('system', 'letsencryptacmeconf', '/etc/apache2/conf-enabled/acme.conf'),
|
||||||
('system', 'mail_use_smtp', '0'),
|
('system', 'mail_use_smtp', '0'),
|
||||||
('system', 'mail_smtp_host', 'localhost'),
|
('system', 'mail_smtp_host', 'localhost'),
|
||||||
@@ -729,7 +731,7 @@ opcache.validate_timestamps'),
|
|||||||
('panel', 'settings_mode', '0'),
|
('panel', 'settings_mode', '0'),
|
||||||
('panel', 'menu_collapsed', '1'),
|
('panel', 'menu_collapsed', '1'),
|
||||||
('panel', 'version', '2.2.0-dev1'),
|
('panel', 'version', '2.2.0-dev1'),
|
||||||
('panel', 'db_version', '202312230');
|
('panel', 'db_version', '202401090');
|
||||||
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `panel_tasks`;
|
DROP TABLE IF EXISTS `panel_tasks`;
|
||||||
|
|||||||
@@ -91,3 +91,13 @@ if (Froxlor::isFroxlorVersion('2.1.4')) {
|
|||||||
Froxlor::updateToDbVersion('202312230');
|
Froxlor::updateToDbVersion('202312230');
|
||||||
Froxlor::updateToVersion('2.2.0-dev1');
|
Froxlor::updateToVersion('2.2.0-dev1');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Froxlor::isDatabaseVersion('202312230')) {
|
||||||
|
|
||||||
|
Update::showUpdateStep("Adding new settings");
|
||||||
|
Settings::AddNew("system.le_renew_services", "");
|
||||||
|
Settings::AddNew("system.le_renew_hook", "systemctl restart postfix dovecot proftpd");
|
||||||
|
Update::lastStepStatus(0);
|
||||||
|
|
||||||
|
Froxlor::updateToDbVersion('202401090');
|
||||||
|
}
|
||||||
|
|||||||
@@ -518,7 +518,7 @@ EOC;
|
|||||||
|
|
||||||
self::validateDns($domains, $certrow['domainid'], $cronlog);
|
self::validateDns($domains, $certrow['domainid'], $cronlog);
|
||||||
|
|
||||||
self::runAcmeSh($certrow, $domains, $cronlog, $do_force);
|
self::runAcmeSh($certrow, $domains, $cronlog, $do_force, $certrow['domainid'] == 0);
|
||||||
} else {
|
} else {
|
||||||
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_WARNING, "Skipping Let's Encrypt generation for " . $certrow['domain'] . " due to an enabled ssl_redirect");
|
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_WARNING, "Skipping Let's Encrypt generation for " . $certrow['domain'] . " due to an enabled ssl_redirect");
|
||||||
}
|
}
|
||||||
@@ -566,7 +566,7 @@ EOC;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function runAcmeSh(array $certrow, array $domains, &$cronlog = null, $force = false)
|
private static function runAcmeSh(array $certrow, array $domains, &$cronlog = null, bool $force = false, bool $renew_hook = false)
|
||||||
{
|
{
|
||||||
if (!empty($domains)) {
|
if (!empty($domains)) {
|
||||||
$acmesh_cmd = self::getAcmeSh() . " --server " . self::$apiserver . " --issue -d " . implode(" -d ", $domains);
|
$acmesh_cmd = self::getAcmeSh() . " --server " . self::$apiserver . " --issue -d " . implode(" -d ", $domains);
|
||||||
@@ -587,6 +587,12 @@ EOC;
|
|||||||
if ($force) {
|
if ($force) {
|
||||||
$acmesh_cmd .= " --force";
|
$acmesh_cmd .= " --force";
|
||||||
}
|
}
|
||||||
|
if ($renew_hook
|
||||||
|
&& !empty(trim(Settings::Get('system.le_renew_services') ?? ""))
|
||||||
|
&& !empty(trim(Settings::Get('system.le_renew_hook') ?? ""))
|
||||||
|
) {
|
||||||
|
$acmesh_cmd .= " --renew-hook '" . Settings::Get('system.le_renew_hook') . "'";
|
||||||
|
}
|
||||||
if (defined('CRON_DEBUG_FLAG')) {
|
if (defined('CRON_DEBUG_FLAG')) {
|
||||||
$acmesh_cmd .= " --debug";
|
$acmesh_cmd .= " --debug";
|
||||||
}
|
}
|
||||||
@@ -603,12 +609,48 @@ EOC;
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$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");
|
||||||
self::certToDb($certrow, $cronlog, $acme_result);
|
$cert_stored = self::certToDb($certrow, $cronlog, $acme_result);
|
||||||
|
|
||||||
|
if ($cert_stored
|
||||||
|
&& $renew_hook
|
||||||
|
&& !empty(trim(Settings::Get('system.le_renew_services') ?? ""))
|
||||||
|
&& !empty(trim(Settings::Get('system.le_renew_hook') ?? ""))
|
||||||
|
) {
|
||||||
|
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, "Renew-hook is enabled - adjusting configurations");
|
||||||
|
|
||||||
|
$certificate_folder = self::getCertificateFolder(strtolower(Settings::Get('system.hostname')));
|
||||||
|
$fullchain = FileDir::makeCorrectFile($certificate_folder . '/fullchain.cer');
|
||||||
|
$keyfile = FileDir::makeCorrectFile($certificate_folder . '/' . strtolower(Settings::Get('system.hostname')) . '.key');
|
||||||
|
|
||||||
|
if (Settings::IsInList('system.le_renew_services', 'postfix')) {
|
||||||
|
// "postconf -e" for postfix
|
||||||
|
FileDir::safe_exec('postconf -e smtpd_tls_cert_file = ' . escapeshellarg($fullchain));
|
||||||
|
FileDir::safe_exec('postconf -e smtpd_tls_key_file = ' . escapeshellarg($keyfile));
|
||||||
|
}
|
||||||
|
if (Settings::IsInList('system.le_renew_services', 'dovecot')) {
|
||||||
|
// custom config for dovecot
|
||||||
|
$dovecot_conf = '/etc/dovecot/conf.d/99-froxlor-ssl.conf'; // @fixme setting?
|
||||||
|
$ssl_content = <<<EOSSL
|
||||||
|
# Autogenerated configuration by froxlor.
|
||||||
|
# Do not manually edit this file as it will be overwritten.
|
||||||
|
|
||||||
|
ssl = yes
|
||||||
|
ssl_cert = <{$fullchain}
|
||||||
|
ssl_key = <{$keyfile}
|
||||||
|
EOSSL;
|
||||||
|
file_put_contents($dovecot_conf, $ssl_content);
|
||||||
|
}
|
||||||
|
if (Settings::IsInList('system.le_renew_services', 'proftpd')) {
|
||||||
|
// @todo
|
||||||
|
}
|
||||||
|
// reload the services
|
||||||
|
FileDir::safe_exec(Settings::Get('system.le_renew_hook'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function certToDb($certrow, &$cronlog, $acme_result)
|
private static function certToDb($certrow, &$cronlog, $acme_result): bool
|
||||||
{
|
{
|
||||||
$return = [];
|
$return = [];
|
||||||
self::readCertificateToVar(strtolower($certrow['domain']), $return, $cronlog);
|
self::readCertificateToVar(strtolower($certrow['domain']), $return, $cronlog);
|
||||||
@@ -639,12 +681,14 @@ EOC;
|
|||||||
}
|
}
|
||||||
|
|
||||||
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Updated Let's Encrypt certificate for " . $certrow['domain']);
|
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Updated Let's Encrypt certificate for " . $certrow['domain']);
|
||||||
|
return true;
|
||||||
} else {
|
} else {
|
||||||
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_ERR, "Got non-successful Let's Encrypt response for " . $certrow['domain'] . ":\n" . implode("\n", $acme_result));
|
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_ERR, "Got non-successful Let's Encrypt response for " . $certrow['domain'] . ":\n" . implode("\n", $acme_result));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_ERR, "Could not get Let's Encrypt certificate for " . $certrow['domain'] . ":\n" . implode("\n", $acme_result));
|
$cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_ERR, "Could not get Let's Encrypt certificate for " . $certrow['domain'] . ":\n" . implode("\n", $acme_result));
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ final class Froxlor
|
|||||||
const VERSION = '2.2.0-dev1';
|
const VERSION = '2.2.0-dev1';
|
||||||
|
|
||||||
// Database version (YYYYMMDDC where C is a daily counter)
|
// Database version (YYYYMMDDC where C is a daily counter)
|
||||||
const DBVERSION = '202312230';
|
const DBVERSION = '202401090';
|
||||||
|
|
||||||
// Distribution branding-tag (used for Debian etc.)
|
// Distribution branding-tag (used for Debian etc.)
|
||||||
const BRANDING = '';
|
const BRANDING = '';
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ class Core
|
|||||||
// create entries
|
// create entries
|
||||||
$this->doDataEntries($pdo);
|
$this->doDataEntries($pdo);
|
||||||
// create JSON array for config-services
|
// create JSON array for config-services
|
||||||
$this->createJsonArray();
|
$this->createJsonArray($pdo);
|
||||||
if ($create_ud_str) {
|
if ($create_ud_str) {
|
||||||
$this->createUserdataParamStr();
|
$this->createUserdataParamStr();
|
||||||
}
|
}
|
||||||
@@ -660,9 +660,16 @@ class Core
|
|||||||
@umask($umask);
|
@umask($umask);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function createJsonArray()
|
private function createJsonArray(&$db_user)
|
||||||
{
|
{
|
||||||
$system_params = ["cron", "libnssextrausers", "logrotate", "goaccess"];
|
// use traffic analyzer from settings as we could define defaults in the lib/configfiles/*.xml templates
|
||||||
|
// which can be useful for third-party package-maintainer (e.g. other distros) to have more control
|
||||||
|
// over the installation defaults (less hardcoded values)
|
||||||
|
$traffic_analyzer = $db_user->query("
|
||||||
|
SELECT `value` FROM `" . TABLE_PANEL_SETTINGS . "` WHERE `settinggroup` = 'system' AND `varname` = 'traffictool'
|
||||||
|
");
|
||||||
|
$ta_result = $traffic_analyzer->fetch(\PDO::FETCH_ASSOC);
|
||||||
|
$system_params = ["cron", "libnssextrausers", "logrotate", $ta_result['value']];
|
||||||
if ($this->validatedData['webserver_backend'] == 'php-fpm') {
|
if ($this->validatedData['webserver_backend'] == 'php-fpm') {
|
||||||
$system_params[] = 'php-fpm';
|
$system_params[] = 'php-fpm';
|
||||||
} elseif ($this->validatedData['webserver_backend'] == 'fcgid') {
|
} elseif ($this->validatedData['webserver_backend'] == 'fcgid') {
|
||||||
|
|||||||
@@ -273,6 +273,10 @@ class Form
|
|||||||
if (\Froxlor\Validate\Form::validateFieldDefinition($groupdetails)) {
|
if (\Froxlor\Validate\Form::validateFieldDefinition($groupdetails)) {
|
||||||
// Check fields for plausibility
|
// Check fields for plausibility
|
||||||
foreach ($groupdetails['fields'] as $fieldname => $fielddetails) {
|
foreach ($groupdetails['fields'] as $fieldname => $fielddetails) {
|
||||||
|
if (!isset($submitted_fields[$fieldname])) {
|
||||||
|
// skip unset fields due to unavailability for this system/settings-set
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (!$only_enabledisable || ($only_enabledisable && isset($fielddetails['overview_option']))) {
|
if (!$only_enabledisable || ($only_enabledisable && isset($fielddetails['overview_option']))) {
|
||||||
if (($plausibility_check = self::checkPlausibilityFormField($fieldname, $fielddetails, $submitted_fields[$fieldname], $submitted_fields)) !== false) {
|
if (($plausibility_check = self::checkPlausibilityFormField($fieldname, $fielddetails, $submitted_fields[$fieldname], $submitted_fields)) !== false) {
|
||||||
if (is_array($plausibility_check) && isset($plausibility_check[0])) {
|
if (is_array($plausibility_check) && isset($plausibility_check[0])) {
|
||||||
|
|||||||
Reference in New Issue
Block a user