Added manual block section and BanIP method to for all connectors, like the UnbanIP functionallity

This commit is contained in:
2026-01-07 16:14:48 +01:00
parent ad6f86146d
commit b5e8324762
13 changed files with 265 additions and 0 deletions

View File

@@ -101,6 +101,11 @@ func (ac *AgentConnector) UnbanIP(ctx context.Context, jail, ip string) error {
return ac.post(ctx, fmt.Sprintf("/v1/jails/%s/unban", url.PathEscape(jail)), payload, nil) return ac.post(ctx, fmt.Sprintf("/v1/jails/%s/unban", url.PathEscape(jail)), payload, nil)
} }
func (ac *AgentConnector) BanIP(ctx context.Context, jail, ip string) error {
payload := map[string]string{"ip": ip}
return ac.post(ctx, fmt.Sprintf("/v1/jails/%s/ban", url.PathEscape(jail)), payload, nil)
}
func (ac *AgentConnector) Reload(ctx context.Context) error { func (ac *AgentConnector) Reload(ctx context.Context) error {
return ac.post(ctx, "/v1/actions/reload", nil, nil) return ac.post(ctx, "/v1/actions/reload", nil, nil)
} }

View File

@@ -140,6 +140,15 @@ func (lc *LocalConnector) UnbanIP(ctx context.Context, jail, ip string) error {
return nil return nil
} }
// BanIP implements Connector.
func (lc *LocalConnector) BanIP(ctx context.Context, jail, ip string) error {
args := []string{"set", jail, "banip", ip}
if _, err := lc.runFail2banClient(ctx, args...); err != nil {
return fmt.Errorf("error banning IP %s in jail %s: %w", ip, jail, err)
}
return nil
}
// Reload implements Connector. // Reload implements Connector.
func (lc *LocalConnector) Reload(ctx context.Context) error { func (lc *LocalConnector) Reload(ctx context.Context) error {
out, err := lc.runFail2banClient(ctx, "reload") out, err := lc.runFail2banClient(ctx, "reload")

View File

@@ -187,6 +187,11 @@ func (sc *SSHConnector) UnbanIP(ctx context.Context, jail, ip string) error {
return err return err
} }
func (sc *SSHConnector) BanIP(ctx context.Context, jail, ip string) error {
_, err := sc.runFail2banCommand(ctx, "set", jail, "banip", ip)
return err
}
func (sc *SSHConnector) Reload(ctx context.Context) error { func (sc *SSHConnector) Reload(ctx context.Context) error {
_, err := sc.runFail2banCommand(ctx, "reload") _, err := sc.runFail2banCommand(ctx, "reload")
return err return err

View File

@@ -16,6 +16,7 @@ type Connector interface {
GetJailInfos(ctx context.Context) ([]JailInfo, error) GetJailInfos(ctx context.Context) ([]JailInfo, error)
GetBannedIPs(ctx context.Context, jail string) ([]string, error) GetBannedIPs(ctx context.Context, jail string) ([]string, error)
UnbanIP(ctx context.Context, jail, ip string) error UnbanIP(ctx context.Context, jail, ip string) error
BanIP(ctx context.Context, jail, ip string) error
Reload(ctx context.Context) error Reload(ctx context.Context) error
Restart(ctx context.Context) error Restart(ctx context.Context) error
GetFilterConfig(ctx context.Context, jail string) (string, string, error) // Returns (config, filePath, error) GetFilterConfig(ctx context.Context, jail string) (string, string, error) // Returns (config, filePath, error)

View File

@@ -38,6 +38,20 @@
"dashboard.table.log_line": "Logzeile", "dashboard.table.log_line": "Logzeile",
"dashboard.no_banned_ips": "Keine gesperrten IPs", "dashboard.no_banned_ips": "Keine gesperrten IPs",
"dashboard.unban": "Entsperren", "dashboard.unban": "Entsperren",
"dashboard.manual_block.title": "Manuelle IP-Sperre",
"dashboard.manual_block.subtitle": "Manuell eine IP-Adresse in einem bestimmten Jail sperren.",
"dashboard.manual_block.expand_hint": "Klicken Sie, um zu erweitern und eine IP-Adresse zu sperren",
"dashboard.manual_block.jail_label": "Jail auswählen",
"dashboard.manual_block.jail_placeholder": "Jail auswählen...",
"dashboard.manual_block.ip_label": "IP-Adresse",
"dashboard.manual_block.ip_placeholder": "z.B. 88.76.21.123",
"dashboard.manual_block.button": "IP sperren",
"dashboard.manual_block.confirm": "IP {ip} im Jail {jail} sperren?",
"dashboard.manual_block.success": "IP erfolgreich gesperrt",
"dashboard.manual_block.error": "Fehler beim Sperren der IP",
"dashboard.manual_block.jail_required": "Bitte wählen Sie ein Jail aus",
"dashboard.manual_block.ip_required": "Bitte geben Sie eine IP-Adresse ein",
"dashboard.manual_block.invalid_ip": "Bitte geben Sie eine gültige IP-Adresse ein",
"dashboard.banned.show_more": "Mehr anzeigen", "dashboard.banned.show_more": "Mehr anzeigen",
"dashboard.banned.show_less": "Weniger anzeigen", "dashboard.banned.show_less": "Weniger anzeigen",
"logs.overview.title": "Interne Log-Übersicht", "logs.overview.title": "Interne Log-Übersicht",

View File

@@ -38,6 +38,20 @@
"dashboard.table.log_line": "Log-Zile", "dashboard.table.log_line": "Log-Zile",
"dashboard.no_banned_ips": "Ke g'sperrti IPs", "dashboard.no_banned_ips": "Ke g'sperrti IPs",
"dashboard.unban": "Entsperre", "dashboard.unban": "Entsperre",
"dashboard.manual_block.title": "Manuelli IP-Sperri",
"dashboard.manual_block.subtitle": "Manuell e IP-Adrässe inme bestimmte Jail sperre.",
"dashboard.manual_block.expand_hint": "Klick zum Ufklappe und e IP-Adrässe z sperre",
"dashboard.manual_block.jail_label": "Jail uswähle",
"dashboard.manual_block.jail_placeholder": "Jail uswähle...",
"dashboard.manual_block.ip_label": "IP-Adrässe",
"dashboard.manual_block.ip_placeholder": "z.B. 88.76.21.123",
"dashboard.manual_block.button": "IP sperre",
"dashboard.manual_block.confirm": "IP {ip} im Jail {jail} sperre?",
"dashboard.manual_block.success": "IP erfolgriich g'sperrt",
"dashboard.manual_block.error": "Fehler bim Sperre vo dr IP",
"dashboard.manual_block.jail_required": "Bitte wähl es Jail us",
"dashboard.manual_block.ip_required": "Bitte gib e IP-Adrässe ii",
"dashboard.manual_block.invalid_ip": "Bitte gib e gültigi IP-Adrässe ii",
"dashboard.banned.show_more": "Meh azeige", "dashboard.banned.show_more": "Meh azeige",
"dashboard.banned.show_less": "Weniger azeige", "dashboard.banned.show_less": "Weniger azeige",
"logs.overview.title": "Generelli Log-Übersicht", "logs.overview.title": "Generelli Log-Übersicht",

View File

@@ -38,6 +38,20 @@
"dashboard.table.log_line": "Log Line", "dashboard.table.log_line": "Log Line",
"dashboard.no_banned_ips": "No banned IPs", "dashboard.no_banned_ips": "No banned IPs",
"dashboard.unban": "Unban", "dashboard.unban": "Unban",
"dashboard.manual_block.title": "Manual Block IP",
"dashboard.manual_block.subtitle": "Manually block an IP address in a specific jail.",
"dashboard.manual_block.expand_hint": "Click to expand and block an IP address",
"dashboard.manual_block.jail_label": "Select Jail",
"dashboard.manual_block.jail_placeholder": "Choose a jail...",
"dashboard.manual_block.ip_label": "IP Address",
"dashboard.manual_block.ip_placeholder": "e.g., 88.76.21.123",
"dashboard.manual_block.button": "Block IP",
"dashboard.manual_block.confirm": "Block IP {ip} in jail {jail}?",
"dashboard.manual_block.success": "IP blocked successfully",
"dashboard.manual_block.error": "Error blocking IP",
"dashboard.manual_block.jail_required": "Please select a jail",
"dashboard.manual_block.ip_required": "Please enter an IP address",
"dashboard.manual_block.invalid_ip": "Please enter a valid IP address",
"dashboard.banned.show_more": "Show more", "dashboard.banned.show_more": "Show more",
"dashboard.banned.show_less": "Hide extra", "dashboard.banned.show_less": "Hide extra",
"logs.overview.title": "Internal Log Overview", "logs.overview.title": "Internal Log Overview",

View File

@@ -38,6 +38,20 @@
"dashboard.table.log_line": "Línea de log", "dashboard.table.log_line": "Línea de log",
"dashboard.no_banned_ips": "No hay IP bloqueadas", "dashboard.no_banned_ips": "No hay IP bloqueadas",
"dashboard.unban": "Desbloquear", "dashboard.unban": "Desbloquear",
"dashboard.manual_block.title": "Bloqueo manual de IP",
"dashboard.manual_block.subtitle": "Bloquear manualmente una dirección IP en una cárcel específica.",
"dashboard.manual_block.expand_hint": "Haga clic para expandir y bloquear una dirección IP",
"dashboard.manual_block.jail_label": "Seleccionar cárcel",
"dashboard.manual_block.jail_placeholder": "Elegir una cárcel...",
"dashboard.manual_block.ip_label": "Dirección IP",
"dashboard.manual_block.ip_placeholder": "ej. 88.76.21.123",
"dashboard.manual_block.button": "Bloquear IP",
"dashboard.manual_block.confirm": "¿Bloquear IP {ip} en la cárcel {jail}?",
"dashboard.manual_block.success": "IP bloqueada exitosamente",
"dashboard.manual_block.error": "Error al bloquear la IP",
"dashboard.manual_block.jail_required": "Por favor seleccione una cárcel",
"dashboard.manual_block.ip_required": "Por favor ingrese una dirección IP",
"dashboard.manual_block.invalid_ip": "Por favor ingrese una dirección IP válida",
"dashboard.banned.show_more": "Mostrar más", "dashboard.banned.show_more": "Mostrar más",
"dashboard.banned.show_less": "Mostrar menos", "dashboard.banned.show_less": "Mostrar menos",
"logs.overview.title": "Resumen interno de registros", "logs.overview.title": "Resumen interno de registros",

View File

@@ -38,6 +38,20 @@
"dashboard.table.log_line": "Ligne de log", "dashboard.table.log_line": "Ligne de log",
"dashboard.no_banned_ips": "Aucune IP bloquée", "dashboard.no_banned_ips": "Aucune IP bloquée",
"dashboard.unban": "Débloquer", "dashboard.unban": "Débloquer",
"dashboard.manual_block.title": "Blocage manuel d'IP",
"dashboard.manual_block.subtitle": "Bloquer manuellement une adresse IP dans une prison spécifique.",
"dashboard.manual_block.expand_hint": "Cliquez pour développer et bloquer une adresse IP",
"dashboard.manual_block.jail_label": "Sélectionner une prison",
"dashboard.manual_block.jail_placeholder": "Choisir une prison...",
"dashboard.manual_block.ip_label": "Adresse IP",
"dashboard.manual_block.ip_placeholder": "ex. 88.76.21.123",
"dashboard.manual_block.button": "Bloquer l'IP",
"dashboard.manual_block.confirm": "Bloquer l'IP {ip} dans la prison {jail}?",
"dashboard.manual_block.success": "IP bloquée avec succès",
"dashboard.manual_block.error": "Erreur lors du blocage de l'IP",
"dashboard.manual_block.jail_required": "Veuillez sélectionner une prison",
"dashboard.manual_block.ip_required": "Veuillez entrer une adresse IP",
"dashboard.manual_block.invalid_ip": "Veuillez entrer une adresse IP valide",
"dashboard.banned.show_more": "Afficher plus", "dashboard.banned.show_more": "Afficher plus",
"dashboard.banned.show_less": "Afficher moins", "dashboard.banned.show_less": "Afficher moins",
"logs.overview.title": "Vue d'ensemble interne des journaux", "logs.overview.title": "Vue d'ensemble interne des journaux",

View File

@@ -38,6 +38,20 @@
"dashboard.table.log_line": "Riga di log", "dashboard.table.log_line": "Riga di log",
"dashboard.no_banned_ips": "Nessuna IP bloccata", "dashboard.no_banned_ips": "Nessuna IP bloccata",
"dashboard.unban": "Sblocca", "dashboard.unban": "Sblocca",
"dashboard.manual_block.title": "Blocco manuale IP",
"dashboard.manual_block.subtitle": "Bloccare manualmente un indirizzo IP in una prigione specifica.",
"dashboard.manual_block.expand_hint": "Fare clic per espandere e bloccare un indirizzo IP",
"dashboard.manual_block.jail_label": "Seleziona prigione",
"dashboard.manual_block.jail_placeholder": "Scegli una prigione...",
"dashboard.manual_block.ip_label": "Indirizzo IP",
"dashboard.manual_block.ip_placeholder": "es. 88.76.21.123",
"dashboard.manual_block.button": "Blocca IP",
"dashboard.manual_block.confirm": "Bloccare IP {ip} nella prigione {jail}?",
"dashboard.manual_block.success": "IP bloccato con successo",
"dashboard.manual_block.error": "Errore nel bloccare l'IP",
"dashboard.manual_block.jail_required": "Si prega di selezionare una prigione",
"dashboard.manual_block.ip_required": "Si prega di inserire un indirizzo IP",
"dashboard.manual_block.invalid_ip": "Si prega di inserire un indirizzo IP valido",
"dashboard.banned.show_more": "Mostra di più", "dashboard.banned.show_more": "Mostra di più",
"dashboard.banned.show_less": "Mostra meno", "dashboard.banned.show_less": "Mostra meno",
"logs.overview.title": "Panoramica interna dei log", "logs.overview.title": "Panoramica interna dei log",

View File

@@ -170,6 +170,29 @@ func UnbanIPHandler(c *gin.Context) {
}) })
} }
// BanIPHandler bans a given IP in a specific jail.
func BanIPHandler(c *gin.Context) {
config.DebugLog("----------------------------")
config.DebugLog("BanIPHandler called (handlers.go)") // entry point
jail := c.Param("jail")
ip := c.Param("ip")
conn, err := resolveConnector(c)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := conn.BanIP(c.Request.Context(), jail, ip); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
fmt.Println(ip + " in jail " + jail + " banned successfully.")
c.JSON(http.StatusOK, gin.H{
"message": "IP banned successfully",
})
}
// BanNotificationHandler processes incoming ban notifications from Fail2Ban. // BanNotificationHandler processes incoming ban notifications from Fail2Ban.
func BanNotificationHandler(c *gin.Context) { func BanNotificationHandler(c *gin.Context) {
// Validate callback secret // Validate callback secret

View File

@@ -32,6 +32,7 @@ func RegisterRoutes(r *gin.Engine, hub *Hub) {
{ {
api.GET("/summary", SummaryHandler) api.GET("/summary", SummaryHandler)
api.POST("/jails/:jail/unban/:ip", UnbanIPHandler) api.POST("/jails/:jail/unban/:ip", UnbanIPHandler)
api.POST("/jails/:jail/ban/:ip", BanIPHandler)
// Routes for jail-filter management (TODO: rename API-call) // Routes for jail-filter management (TODO: rename API-call)
api.GET("/jails/:jail/config", GetJailFilterConfigHandler) api.GET("/jails/:jail/config", GetJailFilterConfigHandler)

View File

@@ -441,6 +441,24 @@ function toggleBannedList(hiddenId, buttonId) {
} }
} }
function toggleManualBlockSection() {
var container = document.getElementById('manualBlockFormContainer');
var icon = document.getElementById('manualBlockToggleIcon');
if (!container || !icon) {
return;
}
var isHidden = container.classList.contains("hidden");
if (isHidden) {
container.classList.remove("hidden");
icon.classList.remove("fa-chevron-down");
icon.classList.add("fa-chevron-up");
} else {
container.classList.add("hidden");
icon.classList.remove("fa-chevron-up");
icon.classList.add("fa-chevron-down");
}
}
function unbanIP(jail, ip) { function unbanIP(jail, ip) {
const confirmMsg = isLOTRModeActive const confirmMsg = isLOTRModeActive
? 'Restore ' + ip + ' to the realm from ' + jail + '?' ? 'Restore ' + ip + ' to the realm from ' + jail + '?'
@@ -470,6 +488,75 @@ function unbanIP(jail, ip) {
}); });
} }
function banIP(jail, ip) {
const confirmMsg = isLOTRModeActive
? 'Banish ' + ip + ' from the realm in ' + jail + '?'
: 'Block IP ' + ip + ' in jail ' + jail + '?';
if (!confirm(confirmMsg)) {
return;
}
showLoading(true);
var url = '/api/jails/' + encodeURIComponent(jail) + '/ban/' + encodeURIComponent(ip);
fetch(withServerParam(url), {
method: 'POST',
headers: serverHeaders()
})
.then(function(res) { return res.json(); })
.then(function(data) {
if (data.error) {
showToast("Error blocking IP: " + data.error, 'error');
} else {
showToast(t('dashboard.manual_block.success', 'IP blocked successfully'), 'success');
return refreshData({ silent: true });
}
})
.catch(function(err) {
showToast("Error: " + err, 'error');
})
.finally(function() {
showLoading(false);
});
}
function handleManualBlock() {
var jailSelect = document.getElementById('blockJailSelect');
var ipInput = document.getElementById('blockIPInput');
if (!jailSelect || !ipInput) {
return;
}
var jail = jailSelect.value;
var ip = ipInput.value.trim();
if (!jail) {
showToast(t('dashboard.manual_block.jail_required', 'Please select a jail'), 'error');
jailSelect.focus();
return;
}
if (!ip) {
showToast(t('dashboard.manual_block.ip_required', 'Please enter an IP address'), 'error');
ipInput.focus();
return;
}
// Basic IP validation
var ipv4Pattern = /^([0-9]{1,3}\.){3}[0-9]{1,3}$/;
var ipv6Pattern = /^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}$/;
if (!ipv4Pattern.test(ip) && !ipv6Pattern.test(ip)) {
showToast(t('dashboard.manual_block.invalid_ip', 'Please enter a valid IP address'), 'error');
ipInput.focus();
return;
}
banIP(jail, ip);
// Clear form after submission
ipInput.value = '';
jailSelect.value = '';
}
function renderDashboard() { function renderDashboard() {
var container = document.getElementById('dashboard'); var container = document.getElementById('dashboard');
if (!container) return; if (!container) return;
@@ -590,6 +677,56 @@ function renderDashboard() {
html += '</div>'; // close overview card html += '</div>'; // close overview card
} }
// Manual Block IP Section
if (summary && summary.jails && summary.jails.length > 0) {
var enabledJails = summary.jails.filter(function(j) { return j.enabled !== false; });
if (enabledJails.length > 0) {
html += ''
+ '<div class="bg-white rounded-lg shadow p-6 mb-6">'
+ ' <div class="cursor-pointer hover:bg-gray-50 -m-6 p-6 rounded-lg transition-colors" onclick="toggleManualBlockSection()">'
+ ' <div class="flex items-center justify-between">'
+ ' <div class="flex-1">'
+ ' <h3 class="text-lg font-medium text-gray-900 mb-2" data-i18n="dashboard.manual_block.title">Manual Block IP</h3>'
+ ' <p class="text-sm text-gray-500" data-i18n="dashboard.manual_block.subtitle">Manually block an IP address in a specific jail.</p>'
+ ' <p class="text-xs text-gray-400 mt-1" data-i18n="dashboard.manual_block.expand_hint">Click to expand and block an IP address</p>'
+ ' </div>'
+ ' <div class="ml-4">'
+ ' <i id="manualBlockToggleIcon" class="fas fa-chevron-down text-gray-400 transition-transform"></i>'
+ ' </div>'
+ ' </div>'
+ ' </div>'
+ ' <div id="manualBlockFormContainer" class="hidden mt-4">'
+ ' <form id="manualBlockForm" onsubmit="return false;">'
+ ' <div class="grid grid-cols-1 md:grid-cols-3 gap-4">'
+ ' <div>'
+ ' <label for="blockJailSelect" class="block text-sm font-medium text-gray-700 mb-2" data-i18n="dashboard.manual_block.jail_label">Select Jail</label>'
+ ' <select id="blockJailSelect" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" required>'
+ ' <option value="" data-i18n="dashboard.manual_block.jail_placeholder">Choose a jail...</option>';
enabledJails.forEach(function(jail) {
html += ' <option value="' + escapeHtml(jail.jailName) + '">' + escapeHtml(jail.jailName) + '</option>';
});
html += ''
+ ' </select>'
+ ' </div>'
+ ' <div>'
+ ' <label for="blockIPInput" class="block text-sm font-medium text-gray-700 mb-2" data-i18n="dashboard.manual_block.ip_label">IP Address</label>'
+ ' <input type="text" id="blockIPInput" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" data-i18n-placeholder="dashboard.manual_block.ip_placeholder" placeholder="e.g., 88.76.21.123" pattern="^([0-9]{1,3}\\.){3}[0-9]{1,3}$|^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}$" required>'
+ ' </div>'
+ ' <div class="flex items-end">'
+ ' <button type="button" onclick="handleManualBlock()" class="w-full bg-red-600 text-white px-4 py-2 rounded hover:bg-red-700 transition-colors flex items-center justify-center gap-2">'
+ ' <i class="fas fa-ban"></i>'
+ ' <span data-i18n="dashboard.manual_block.button">Block IP</span>'
+ ' </button>'
+ ' </div>'
+ ' </div>'
+ ' </form>'
+ ' </div>'
+ '</div>';
}
}
html += '<div id="logOverview">' + renderLogOverviewContent() + '</div>'; html += '<div id="logOverview">' + renderLogOverviewContent() + '</div>';
container.innerHTML = html; container.innerHTML = html;