From fc0ede4840d8406bc8ef77c5189f05acf467fafd Mon Sep 17 00:00:00 2001 From: boban Date: Mon, 27 Apr 2026 04:03:01 +0200 Subject: [PATCH] Feat: Fail2ban-Banlist zeigt Restlaufzeit und lesbaren Dienst-Namen --- app/Livewire/Ui/Security/Fail2banBanlist.php | 100 +++++++++++------- .../ui/security/fail2ban-banlist.blade.php | 7 +- 2 files changed, 67 insertions(+), 40 deletions(-) diff --git a/app/Livewire/Ui/Security/Fail2banBanlist.php b/app/Livewire/Ui/Security/Fail2banBanlist.php index c219c7e..1754661 100644 --- a/app/Livewire/Ui/Security/Fail2banBanlist.php +++ b/app/Livewire/Ui/Security/Fail2banBanlist.php @@ -4,6 +4,7 @@ namespace App\Livewire\Ui\Security; use Livewire\Attributes\On; use Livewire\Component; +use SQLite3; class Fail2banBanlist extends Component { @@ -15,22 +16,9 @@ class Fail2banBanlist extends Component public ?string $jail = null; /** - * Struktur für Blade (reine Ausgabe, keine Logik im Blade): - * [ - * [ - * 'ip' => '1.2.3.4', - * 'jail' => 'recidive', - * 'permanent' => false, - * 'label' => 'Temporär', // oder 'Permanent' - * 'box' => 'border-amber-400/20 bg-white/3', // Kartenstil - * 'badge' => 'border-amber-400/30 bg-amber-500/10 text-amber-200', - * 'btn' => 'border-rose-400/30 bg-rose-500/10 text-rose-200 hover:border-rose-400/50', - * ], - * ... - * ] - * * @var array */ public array $rows = []; @@ -75,7 +63,9 @@ class Fail2banBanlist extends Component continue; } - $permanent = $this->isPermanent($j, $ip); + $banInfo = $this->getBanInfo($j, $ip); + $permanent = $banInfo['permanent']; + $remaining = $banInfo['remaining']; if ($permanent) { $box = 'border-rose-400/30 bg-rose-500/5'; @@ -94,9 +84,11 @@ class Fail2banBanlist extends Component $rows[] = [ 'ip' => $ip, 'jail' => $j, + 'service' => $this->serviceLabel($j), 'permanent' => $permanent, 'style' => $style, 'label' => $label, + 'remaining' => $remaining, 'box' => $box, 'badge' => $badge, 'dot' => $dot, @@ -140,35 +132,67 @@ class Fail2banBanlist extends Component /* ================= helpers ================= */ - /** Prüft via SQLite, ob der **letzte** Ban für (jail, ip) permanent ist (bantime < 0). */ - private function isPermanent(string $jail, string $ip): bool + /** Gibt permanent-Flag und verbleibende Zeit für (jail, ip) zurück. */ + private function getBanInfo(string $jail, string $ip): array { $db = $this->getDbFile(); + $fallbackPermanent = ($jail === 'mailwolt-blacklist'); + if ($db === '' || !is_readable($db)) { - // Fallback: Blacklist-Jail ist per Design permanent - return $jail === 'mailwolt-blacklist'; + return ['permanent' => $fallbackPermanent, 'remaining' => '']; } - $q = <<&1', - escapeshellarg($db), - escapeshellarg($q) + $q = sprintf( + "WITH last AS (SELECT MAX(timeofban) AS t FROM bans WHERE jail='%s' AND ip='%s') ". + "SELECT timeofban, bantime FROM bans, last ". + "WHERE jail='%s' AND ip='%s' AND timeofban=last.t LIMIT 1;", + SQLite3::escapeString($jail), SQLite3::escapeString($ip), + SQLite3::escapeString($jail), SQLite3::escapeString($ip) ); + + $cmd = sprintf('sudo -n /usr/bin/sqlite3 -readonly %s %s 2>&1', + escapeshellarg($db), escapeshellarg($q)); $out = trim((string)@shell_exec($cmd)); - if ($out === '') return ($jail === 'mailwolt-blacklist'); // Fallback - return ((int)$out) < 0; + + if ($out === '') { + return ['permanent' => $fallbackPermanent, 'remaining' => '']; + } + + [$timeofban, $bantime] = array_pad(explode('|', $out), 2, '0'); + $timeofban = (int)$timeofban; + $bantime = (int)$bantime; + + if ($bantime < 0) { + return ['permanent' => true, 'remaining' => '']; + } + + $remaining = max(0, ($timeofban + $bantime) - time()); + return [ + 'permanent' => false, + 'remaining' => $this->formatRemaining($remaining), + ]; + } + + private function formatRemaining(int $seconds): string + { + if ($seconds <= 0) return 'läuft ab'; + if ($seconds < 60) return "noch {$seconds} Sek."; + if ($seconds < 3600) return 'noch ' . (int)ceil($seconds / 60) . ' Min.'; + if ($seconds < 86400) return 'noch ' . round($seconds / 3600, 1) . ' Std.'; + return 'noch ' . (int)ceil($seconds / 86400) . ' Tage'; + } + + private function serviceLabel(string $jail): string + { + return match(true) { + $jail === 'sshd' => 'SSH', + $jail === 'dovecot' => 'IMAP/POP3', + str_starts_with($jail, 'postfix') => 'SMTP', + str_starts_with($jail, 'nginx') => 'Web', + $jail === 'recidive' => 'Recidive', + str_contains($jail, 'blacklist') => 'Blacklist', + default => $jail, + }; } /** Liste aller Jails */ diff --git a/resources/views/livewire/ui/security/fail2ban-banlist.blade.php b/resources/views/livewire/ui/security/fail2ban-banlist.blade.php index 39470b9..afa6433 100644 --- a/resources/views/livewire/ui/security/fail2ban-banlist.blade.php +++ b/resources/views/livewire/ui/security/fail2ban-banlist.blade.php @@ -21,11 +21,14 @@
@foreach($rows as $r)
-
+
{{ $r['ip'] }} - {{ $r['jail'] }} + {{ $r['service'] }} {{ $r['label'] }} + @if($r['remaining'] !== '') + · {{ $r['remaining'] }} + @endif