[Web] Sync jobs: Use STARTTLS instead of TLS; Feature: Allow to edit fail2ban-like regex filters in UI

master
andryyy 2020-08-27 20:43:33 +02:00
parent 0b0aaf0705
commit 877b9b7054
No known key found for this signature in database
GPG Key ID: 8EC34FF2794E25EF
9 changed files with 176 additions and 36 deletions

View File

@ -698,6 +698,7 @@ if (!isset($_SESSION['gal']) && $license_cache = $redis->Get('LICENSE_STATUS_CAC
<input type="number" class="form-control" name="netban_ipv6" value="<?=$f2b_data['netban_ipv6'];?>" required> <input type="number" class="form-control" name="netban_ipv6" value="<?=$f2b_data['netban_ipv6'];?>" required>
</div> </div>
</div> </div>
<hr>
<p class="help-block"><?=$lang['admin']['f2b_list_info'];?></p> <p class="help-block"><?=$lang['admin']['f2b_list_info'];?></p>
<div class="form-group"> <div class="form-group">
<label for="whitelist"><?=$lang['admin']['f2b_whitelist'];?>:</label> <label for="whitelist"><?=$lang['admin']['f2b_whitelist'];?>:</label>
@ -708,11 +709,41 @@ if (!isset($_SESSION['gal']) && $license_cache = $redis->Get('LICENSE_STATUS_CAC
<textarea class="form-control" name="blacklist" rows="5"><?=$f2b_data['blacklist'];?></textarea> <textarea class="form-control" name="blacklist" rows="5"><?=$f2b_data['blacklist'];?></textarea>
</div> </div>
<div class="btn-group"> <div class="btn-group">
<button class="btn btn-success" data-action="edit_selected" data-item="self" data-id="f2b" data-api-url='edit/fail2ban' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-check"></span> <?=$lang['admin']['save'];?></button> <button class="btn btn-sm btn-success" data-action="edit_selected" data-item="self" data-id="f2b" data-api-url='edit/fail2ban' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-check"></span> <?=$lang['admin']['save'];?></button>
<a href="#" role="button" class="btn btn-default" data-toggle="modal" data-container="netfilter-mailcow" data-target="#RestartContainer"><span class="glyphicon glyphicon-refresh"></span> <?= $lang['header']['restart_netfilter']; ?></a> <a href="#" role="button" class="btn btn-sm btn-default" data-toggle="modal" data-container="netfilter-mailcow" data-target="#RestartContainer"><span class="glyphicon glyphicon-refresh"></span> <?= $lang['header']['restart_netfilter']; ?></a>
</div> </div>
</form> </form>
<hr> <hr>
<h4><?=$lang['admin']['f2b_filter'];?></h4>
<p class="help-block"><?=$lang['admin']['f2b_regex_info'];?></p>
<form class="form-inline" data-id="f2b_regex" role="form" method="post">
<table class="table table-condensed" id="f2b_regex_table">
<tr>
<th width="50px">ID</th>
<th>RegExp</th>
<th width="100px">&nbsp;</th>
</tr>
<?php
if (!empty($f2b_data['regex'])) {
foreach ($f2b_data['regex'] as $regex_id => $regex_val) {
?>
<tr>
<td><input disabled class="input-sm form-control" style="text-align:center" data-id="f2b_regex" type="text" name="app" required value="<?=$regex_id;?>"></td>
<td><input class="input-sm form-control regex-input" data-id="f2b_regex" type="text" name="regex" required value="<?=htmlspecialchars($regex_val);?>"></td>
<td><a href="#" role="button" class="btn btn-xs btn-default" type="button"><?=$lang['admin']['remove_row'];?></a></td>
</tr>
<?php
}
}
?>
</table>
<p><div class="btn-group">
<button class="btn btn-sm btn-success" data-action="edit_selected" data-item="admin" data-id="f2b_regex" data-reload="no" data-api-url='edit/fail2ban' data-api-attr='{"action":"edit-regex"}' href="#"><span class="glyphicon glyphicon-check"></span> <?=$lang['admin']['save'];?></button>
<button class="btn btn-sm btn-default admin-ays-dialog" data-action="edit_selected" data-item="self" data-id="f2b-quick" data-api-url='edit/fail2ban' data-api-attr='{"action":"reset-regex"}' href="#"><?=$lang['admin']['reset_default'];?></button>
<button class="btn btn-sm btn-default" type="button" id="add_f2b_regex_row"><span class="glyphicon glyphicon-plus"></span> <?=$lang['admin']['add_row'];?></button>
</div></p>
</form>
<hr>
<p class="help-block"><?=$lang['admin']['ban_list_info'];?></p> <p class="help-block"><?=$lang['admin']['ban_list_info'];?></p>
<?php <?php
if (empty($f2b_data['active_bans']) && empty($f2b_data['perm_bans'])): if (empty($f2b_data['active_bans']) && empty($f2b_data['perm_bans'])):

View File

@ -73,3 +73,7 @@ table tbody tr td input[type="checkbox"] {
#quarantine_template { #quarantine_template {
margin:20px; margin:20px;
} }
.regex-input {
font-family: Consolas,monaco,monospace;
font-size: 14px;
}

View File

@ -1234,9 +1234,9 @@ if (isset($_SESSION['mailcow_cc_role'])) {
<label class="control-label col-sm-2" for="enc1"><?=$lang['edit']['encryption'];?></label> <label class="control-label col-sm-2" for="enc1"><?=$lang['edit']['encryption'];?></label>
<div class="col-sm-10"> <div class="col-sm-10">
<select id="enc1" name="enc1"> <select id="enc1" name="enc1">
<option <?=($result['enc1'] == "TLS") ? "selected" : null;?>>TLS</option> <option value="SSL" <?=($result['enc1'] == "SSL") ? "selected" : null;?>>SSL</option>
<option <?=($result['enc1'] == "SSL") ? "selected" : null;?>>SSL</option> <option value="TLS" <?=($result['enc1'] == "TLS") ? "selected" : null;?>>STARTTLS</option>
<option <?=($result['enc1'] == "PLAIN") ? "selected" : null;?>>PLAIN</option> <option value="PLAIN" <?=($result['enc1'] == "PLAIN") ? "selected" : null;?>>PLAIN</option>
</select> </select>
</div> </div>
</div> </div>

View File

@ -10,6 +10,7 @@ function fail2ban($_action, $_data = null) {
} }
try { try {
$f2b_options = json_decode($redis->Get('F2B_OPTIONS'), true); $f2b_options = json_decode($redis->Get('F2B_OPTIONS'), true);
$f2b_options['regex'] = json_decode($redis->Get('F2B_REGEX'), true);
$wl = $redis->hGetAll('F2B_WHITELIST'); $wl = $redis->hGetAll('F2B_WHITELIST');
if (is_array($wl)) { if (is_array($wl)) {
foreach ($wl as $key => $value) { foreach ($wl as $key => $value) {
@ -87,52 +88,75 @@ function fail2ban($_action, $_data = null) {
); );
return false; return false;
} }
if (isset($_data['action']) && !empty($_data['network'])) { // Start to read actions, if any
if (isset($_data['action'])) {
// Reset regex filters
if ($_data['action'] == "reset-regex") {
try {
$redis->Del('F2B_REGEX');
}
catch (RedisException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('redis_error', $e)
);
return false;
}
// Rules will also be recreated on log events, but rules may seem empty for a second in the UI
docker('post', 'netfilter-mailcow', 'restart');
$fail_count = 0;
$regex_result = json_decode($redis->Get('F2B_REGEX'), true);
while (empty($regex_result) && $fail_count < 10) {
$regex_result = json_decode($redis->Get('F2B_REGEX'), true);
$fail_count++;
sleep(1);
}
if ($fail_count >= 10) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('reset_f2b_regex')
);
return false;
}
}
elseif ($_data['action'] == "edit-regex") {
if (!empty($_data['regex'])) {
$rule_id = 1;
$regex_array = array();
foreach($_data['regex'] as $regex) {
$regex_array[$rule_id] = $regex;
$rule_id++;
}
$redis->Set('F2B_REGEX', json_encode($regex_array, JSON_UNESCAPED_SLASHES));
}
else {
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => print_r($_data, true)
);
return false;
}
$_SESSION['return'][] = array(
'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('object_modified', htmlspecialchars($network))
);
return true;
}
// Start actions in dependency of network
if (!empty($_data['network'])) {
$networks = (array)$_data['network']; $networks = (array)$_data['network'];
foreach ($networks as $network) { foreach ($networks as $network) {
try { // Unban network
if ($_data['action'] == "unban") { if ($_data['action'] == "unban") {
if (valid_network($network)) { if (valid_network($network)) {
try {
$redis->hSet('F2B_QUEUE_UNBAN', $network, 1); $redis->hSet('F2B_QUEUE_UNBAN', $network, 1);
} }
}
elseif ($_data['action'] == "whitelist") {
if (valid_network($network)) {
$redis->hSet('F2B_WHITELIST', $network, 1);
$redis->hDel('F2B_BLACKLIST', $network, 1);
$redis->hSet('F2B_QUEUE_UNBAN', $network, 1);
}
else {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('network_host_invalid', $network)
);
continue;
}
}
elseif ($_data['action'] == "blacklist") {
if (valid_network($network) && !in_array($network, array(
'0.0.0.0',
'0.0.0.0/0',
getenv('IPV4_NETWORK') . '0/24',
getenv('IPV4_NETWORK') . '0',
getenv('IPV6_NETWORK')
))) {
$redis->hSet('F2B_BLACKLIST', $network, 1);
$redis->hDel('F2B_WHITELIST', $network, 1);
//$response = docker('post', 'netfilter-mailcow', 'restart');
}
else {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('network_host_invalid', $network)
);
continue;
}
}
}
catch (RedisException $e) { catch (RedisException $e) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
@ -141,6 +165,66 @@ function fail2ban($_action, $_data = null) {
); );
continue; continue;
} }
}
}
// Whitelist network
elseif ($_data['action'] == "whitelist") {
if (valid_network($network)) {
try {
$redis->hSet('F2B_WHITELIST', $network, 1);
$redis->hDel('F2B_BLACKLIST', $network, 1);
$redis->hSet('F2B_QUEUE_UNBAN', $network, 1);
}
catch (RedisException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('redis_error', $e)
);
continue;
}
}
else {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('network_host_invalid', $network)
);
continue;
}
}
// Blacklist network
elseif ($_data['action'] == "blacklist") {
if (valid_network($network) && !in_array($network, array(
'0.0.0.0',
'0.0.0.0/0',
getenv('IPV4_NETWORK') . '0/24',
getenv('IPV4_NETWORK') . '0',
getenv('IPV6_NETWORK')
))) {
try {
$redis->hSet('F2B_BLACKLIST', $network, 1);
$redis->hDel('F2B_WHITELIST', $network, 1);
//$response = docker('post', 'netfilter-mailcow', 'restart');
}
catch (RedisException $e) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('redis_error', $e)
);
continue;
}
}
else {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data_log),
'msg' => array('network_host_invalid', $network)
);
continue;
}
}
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'success', 'type' => 'success',
'log' => array(__FUNCTION__, $_action, $_data_log), 'log' => array(__FUNCTION__, $_action, $_data_log),
@ -149,6 +233,8 @@ function fail2ban($_action, $_data = null) {
} }
return true; return true;
} }
}
// Start default edit without specific action
$is_now = fail2ban('get'); $is_now = fail2ban('get');
if (!empty($is_now)) { if (!empty($is_now)) {
$ban_time = intval((isset($_data['ban_time'])) ? $_data['ban_time'] : $is_now['ban_time']); $ban_time = intval((isset($_data['ban_time'])) ? $_data['ban_time'] : $is_now['ban_time']);

View File

@ -460,11 +460,17 @@ jQuery(function($){
} }
}) })
// App links // App links
function add_table_row(table_id) { function add_table_row(table_id, type) {
var row = $('<tr />'); var row = $('<tr />');
if (type == "app_link") {
cols = '<td><input class="input-sm form-control" data-id="app_links" type="text" name="app" required></td>'; cols = '<td><input class="input-sm form-control" data-id="app_links" type="text" name="app" required></td>';
cols += '<td><input class="input-sm form-control" data-id="app_links" type="text" name="href" required></td>'; cols += '<td><input class="input-sm form-control" data-id="app_links" type="text" name="href" required></td>';
cols += '<td><a href="#" role="button" class="btn btn-xs btn-default" type="button">Remove row</a></td>'; cols += '<td><a href="#" role="button" class="btn btn-xs btn-default" type="button">">' + lang.remove_row + '</a></td>';
} else if (type == "f2b_regex") {
cols = '<td><input style="text-align:center" class="input-sm form-control" data-id="f2b_regex" type="text" value="+" disabled></td>';
cols += '<td><input class="input-sm form-control regex-input" data-id="f2b_regex" type="text" name="regex" required></td>';
cols += '<td><a href="#" role="button" class="btn btn-xs btn-default" type="button">' + lang.remove_row + '</a></td>';
}
row.append(cols); row.append(cols);
table_id.append(row); table_id.append(row);
} }
@ -472,8 +478,15 @@ jQuery(function($){
e.preventDefault(); e.preventDefault();
$(this).parents('tr').remove(); $(this).parents('tr').remove();
}); });
$('#f2b_regex_table').on('click', 'tr a', function (e) {
e.preventDefault();
$(this).parents('tr').remove();
});
$('#add_app_link_row').click(function() { $('#add_app_link_row').click(function() {
add_table_row($('#app_link_table')); add_table_row($('#app_link_table'), "app_link");
});
$('#add_f2b_regex_row').click(function() {
add_table_row($('#f2b_regex_table'), "f2b_regex");
}); });
}); });

View File

@ -74,7 +74,7 @@
"password": "Passwort", "password": "Passwort",
"password_repeat": "Passwort wiederholen", "password_repeat": "Passwort wiederholen",
"port": "Port", "port": "Port",
"post_domain_add": "Der SOGo-Container, \"sogo-mailcow\" muss nach dem Hinzufügen einer Domain neugestartet werden!<br><br>Im Anschluss sollte die DNS-Konfiguration der Domain überprüft- und \"acme-mailcow\" gegebenenfalls neugestartet werden, um Änderungen am Zertifikat zu übernehmen (autoconfig.&lt;domain&gt;, autodiscover.&lt;domain&gt;).<br>Dieser Schritt ist optional und wird alle 24 Stunden automatisch ausgeführt.", "post_domain_add": "Der SOGo-Container - \"sogo-mailcow\" - muss nach dem Hinzufügen einer Domain neugestartet werden!<br><br>Im Anschluss sollte die DNS-Konfiguration der Domain überprüft- und \"acme-mailcow\" gegebenenfalls neugestartet werden, um Änderungen am Zertifikat zu übernehmen (autoconfig.&lt;domain&gt;, autodiscover.&lt;domain&gt;).<br>Dieser Schritt ist optional und wird alle 24 Stunden automatisch ausgeführt.",
"private_comment": "Privater Kommentar", "private_comment": "Privater Kommentar",
"public_comment": "Öffentlicher Kommentar", "public_comment": "Öffentlicher Kommentar",
"quota_mb": "Speicherplatz (MiB)", "quota_mb": "Speicherplatz (MiB)",
@ -165,11 +165,13 @@
"excludes": "Diese Empfänger ausschließen", "excludes": "Diese Empfänger ausschließen",
"f2b_ban_time": "Bannzeit in Sekunden", "f2b_ban_time": "Bannzeit in Sekunden",
"f2b_blacklist": "Blacklist für Netzwerke und Hosts", "f2b_blacklist": "Blacklist für Netzwerke und Hosts",
"f2b_filter": "Regex Filter",
"f2b_list_info": "Ein Host oder Netzwerk auf der Blacklist wird immer eine Whitelist-Einheit überwiegen. <b>Die Aktualisierung der Liste dauert einige Sekunden.</b>", "f2b_list_info": "Ein Host oder Netzwerk auf der Blacklist wird immer eine Whitelist-Einheit überwiegen. <b>Die Aktualisierung der Liste dauert einige Sekunden.</b>",
"f2b_max_attempts": "Max. Versuche", "f2b_max_attempts": "Max. Versuche",
"f2b_netban_ipv4": "Netzbereich für IPv4-Bans (8-32)", "f2b_netban_ipv4": "Netzbereich für IPv4-Bans (8-32)",
"f2b_netban_ipv6": "Netzbereich für IPv6-Bans (8-128)", "f2b_netban_ipv6": "Netzbereich für IPv6-Bans (8-128)",
"f2b_parameters": "Fail2ban-Parameter", "f2b_parameters": "Fail2ban-Parameter",
"f2b_regex_info": "Berücksichtigte Logs: SOGo, Postfix, Dovecot, PHP-FPM.",
"f2b_retry_window": "Wiederholungen im Zeitraum von (s)", "f2b_retry_window": "Wiederholungen im Zeitraum von (s)",
"f2b_whitelist": "Whitelist für Netzwerke und Hosts", "f2b_whitelist": "Whitelist für Netzwerke und Hosts",
"filter_table": "Tabelle filtern", "filter_table": "Tabelle filtern",
@ -396,6 +398,7 @@
"redis_error": "Redis Fehler: %s", "redis_error": "Redis Fehler: %s",
"relayhost_invalid": "Mapeintrag %s ist ungültig", "relayhost_invalid": "Mapeintrag %s ist ungültig",
"release_send_failed": "Die Nachricht konnte nicht versendet werden: %s", "release_send_failed": "Die Nachricht konnte nicht versendet werden: %s",
"reset_f2b_regex": "Regex Filter konnten nicht in vorgegebener Zeit zurückgesetzt werden, bitte erneut versuchen oder die Webseite neu laden.",
"resource_invalid": "Ressourcenname %s ist ungültig", "resource_invalid": "Ressourcenname %s ist ungültig",
"rl_timeframe": "Ratelimit-Zeitraum ist inkorrekt", "rl_timeframe": "Ratelimit-Zeitraum ist inkorrekt",
"rspamd_ui_pw_length": "Rspamd UI-Passwort muss mindestens 6 Zeichen lang sein", "rspamd_ui_pw_length": "Rspamd UI-Passwort muss mindestens 6 Zeichen lang sein",

View File

@ -164,11 +164,13 @@
"excludes": "Excludes these recipients", "excludes": "Excludes these recipients",
"f2b_ban_time": "Ban time (s)", "f2b_ban_time": "Ban time (s)",
"f2b_blacklist": "Blacklisted networks/hosts", "f2b_blacklist": "Blacklisted networks/hosts",
"f2b_filter": "Regex filters",
"f2b_list_info": "A blacklisted host or network will always outweigh a whitelist entity. <b>List updates will take a few seconds to be applied.</b>", "f2b_list_info": "A blacklisted host or network will always outweigh a whitelist entity. <b>List updates will take a few seconds to be applied.</b>",
"f2b_max_attempts": "Max. attempts", "f2b_max_attempts": "Max. attempts",
"f2b_netban_ipv4": "IPv4 subnet size to apply ban on (8-32)", "f2b_netban_ipv4": "IPv4 subnet size to apply ban on (8-32)",
"f2b_netban_ipv6": "IPv6 subnet size to apply ban on (8-128)", "f2b_netban_ipv6": "IPv6 subnet size to apply ban on (8-128)",
"f2b_parameters": "Fail2ban parameters", "f2b_parameters": "Fail2ban parameters",
"f2b_regex_info": "Logs taken into consideration: SOGo, Postfix, Dovecot, PHP-FPM.",
"f2b_retry_window": "Retry window (s) for max. attempts", "f2b_retry_window": "Retry window (s) for max. attempts",
"f2b_whitelist": "Whitelisted networks/hosts", "f2b_whitelist": "Whitelisted networks/hosts",
"filter_table": "Filter table", "filter_table": "Filter table",
@ -395,6 +397,7 @@
"redis_error": "Redis error: %s", "redis_error": "Redis error: %s",
"relayhost_invalid": "Map entry %s is invalid", "relayhost_invalid": "Map entry %s is invalid",
"release_send_failed": "Message could not be released: %s", "release_send_failed": "Message could not be released: %s",
"reset_f2b_regex": "Regex filter could not be reset in time, please try again or wait a few more seconds and reload the website.",
"resource_invalid": "Resource name %s is invalid", "resource_invalid": "Resource name %s is invalid",
"rl_timeframe": "Rate limit time frame is incorrect", "rl_timeframe": "Rate limit time frame is incorrect",
"rspamd_ui_pw_length": "Rspamd UI password should be at least 6 chars long", "rspamd_ui_pw_length": "Rspamd UI password should be at least 6 chars long",

View File

@ -448,9 +448,9 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
<label class="control-label col-sm-2" for="enc1"><?=$lang['add']['enc_method'];?></label> <label class="control-label col-sm-2" for="enc1"><?=$lang['add']['enc_method'];?></label>
<div class="col-sm-10"> <div class="col-sm-10">
<select name="enc1" title="<?=$lang['add']['select'];?>" required> <select name="enc1" title="<?=$lang['add']['select'];?>" required>
<option selected>TLS</option> <option value="SSL" selected>SSL</option>
<option>SSL</option> <option value="TLS">STARTTLS</option>
<option>PLAIN</option> <option value="PLAIN">PLAIN</option>
</select> </select>
</div> </div>
</div> </div>

View File

@ -44,9 +44,9 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
<label class="control-label col-sm-2" for="enc1"><?=$lang['add']['enc_method'];?></label> <label class="control-label col-sm-2" for="enc1"><?=$lang['add']['enc_method'];?></label>
<div class="col-sm-10"> <div class="col-sm-10">
<select name="enc1" title="<?=$lang['add']['select'];?>" required> <select name="enc1" title="<?=$lang['add']['select'];?>" required>
<option selected>TLS</option> <option value="SSL" selected>SSL</option>
<option>SSL</option> <option value="TLS">STARTTLS</option>
<option>PLAIN</option> <option value="PLAIN">PLAIN</option>
</select> </select>
</div> </div>
</div> </div>