diff --git a/app/Livewire/Ui/Security/Fail2BanCard.php b/app/Livewire/Ui/Security/Fail2BanCard.php index 3cb1180..5251949 100644 --- a/app/Livewire/Ui/Security/Fail2BanCard.php +++ b/app/Livewire/Ui/Security/Fail2BanCard.php @@ -1,5 +1,6 @@ [['ip','remaining','until'],...]],...] - public array $topIps = []; // [['ip'=>'x.x.x.x','count'=>N], ...] + public int $activeBans = 0; // Summe gebannter IPs + /** @var array */ + public array $jails = []; public function mount(): void { @@ -22,105 +23,71 @@ class Fail2BanCard extends Component return view('livewire.ui.security.fail2-ban-card'); } - /** Button „Neu prüfen“ */ public function refresh(): void { $this->load(true); } - /* --------------------- intern --------------------- */ + // Optional: öffnet später dein Detail-Modal/Tab + public function openDetails(string $jail): void + { + $this->dispatch('security:open-f2b', jail: $jail); + } + + /* ---------------- intern ---------------- */ protected function load(bool $force = false): void { - // existiert fail2ban-client? $bin = trim((string)@shell_exec('command -v fail2ban-client 2>/dev/null')) ?: ''; if ($bin === '') { $this->available = false; $this->permDenied = false; $this->activeBans = 0; $this->jails = []; - $this->topIps = []; return; } - // ping → prüft zugleich Rechte (bei Permission-Fehler kommt Klartext) - [$ok, $raw] = $this->f2b('ping'); // ok == "pong" erkannt + // Rechtecheck + [$ok, $raw] = $this->f2b('ping'); if (!$ok && stripos($raw, 'permission denied') !== false) { $this->available = true; $this->permDenied = true; $this->activeBans = 0; $this->jails = []; - $this->topIps = $this->collectTopIps(); return; } - // Jails auflisten + // Jails laden [, $status] = $this->f2b('status'); $jailsLn = $this->firstMatch('/Jail list:\s*(.+)$/mi', $status); $jails = $jailsLn ? array_filter(array_map('trim', preg_split('/\s*,\s*/', $jailsLn))) : []; - $total = 0; $rows = []; + $sum = 0; foreach ($jails as $j) { - $bantimeSecs = $this->getBantime($j); // Sek., -1 = permanent - [, $s] = $this->f2b('status ' . escapeshellarg($j)); $banned = (int)($this->firstMatch('/Currently banned:\s+(\d+)/i', $s) ?: 0); - $ipList = $this->firstMatch('/Banned IP list:\s*(.+)$/mi', $s) ?: ''; - $ips = $ipList !== '' ? array_values(array_filter(array_map('trim', preg_split('/\s+/', $ipList)))) : []; - - // Restzeiten je IP bestimmen (aus /var/log/fail2ban.log) - $ipDetails = []; - foreach (array_slice($ips, 0, 50) as $ip) { - $banAt = $this->lastBanTimestamp($j, $ip); // Unix-Timestamp oder null - $remaining = null; - $until = null; - - if ($banAt !== null) { - if ((int)$bantimeSecs === -1) { - $remaining = -1; // permanent - } else { - $remaining = max(0, $bantimeSecs - (time() - $banAt)); - $until = $remaining > 0 ? ($banAt + $bantimeSecs) : null; - } - } - - $ipDetails[] = [ - 'ip' => $ip, - 'remaining' => $remaining, // -1 = permanent, 0 = abgelaufen, >0 Sek. - 'until' => $until, // Unix-Timestamp oder null - ]; - } - - $rows[] = [ - 'name' => $j, - 'banned' => $banned, - 'ips' => $ipDetails, - 'bantime' => (int)$bantimeSecs, - ]; - $total += $banned; + $bantime = $this->getBantime($j); // Sek.; -1 = permanent + $rows[] = ['name' => $j, 'banned' => $banned, 'bantime' => $bantime]; + $sum += $banned; } $this->available = true; $this->permDenied = false; - $this->activeBans = $total; + $this->activeBans = $sum; $this->jails = $rows; - $this->topIps = $this->collectTopIps(); } - /** führt fail2ban-client via sudo aus; gibt [ok, output] zurück */ + /** sudo + fail2ban-client ausführen; [ok, output] */ private function f2b(string $args): array { $sudo = '/usr/bin/sudo'; $f2b = '/usr/bin/fail2ban-client'; - $cmd = "timeout 2 $sudo -n $f2b $args 2>&1"; - $out = (string)@shell_exec($cmd); - + $out = (string)@shell_exec("timeout 2 $sudo -n $f2b $args 2>&1"); $ok = stripos($out, 'Status') !== false || stripos($out, 'Jail list') !== false || stripos($out, 'pong') !== false; - return [$ok, $out]; } @@ -128,69 +95,208 @@ class Fail2BanCard extends Component { [, $out] = $this->f2b('get ' . escapeshellarg($jail) . ' bantime'); $val = trim($out); - if (preg_match('/-?\d+/', $val, $m)) { - return (int)$m[0]; - } + if (preg_match('/-?\d+/', $val, $m)) return (int)$m[0]; return 600; // defensiver Default } - /** letzte Ban-Zeile aus /var/log/fail2ban.log → Unix-Timestamp */ - private function lastBanTimestamp(string $jail, string $ip): ?int - { - $cmd = "grep -F \"[{$jail}] Ban {$ip}\" /var/log/fail2ban.log 2>/dev/null | tail -n 1"; - $line = trim((string)@shell_exec($cmd)); - if ($line === '') return null; - - // "YYYY-MM-DD HH:MM:SS,mmm ..." - if (preg_match('/^(\d{4}-\d{2}-\d{2})\s+(\d{2}:\d{2}:\d{2})/', $line, $m)) { - $ts = strtotime($m[1] . ' ' . $m[2]); - return $ts ?: null; - } - return null; - } - private function firstMatch(string $pattern, string $haystack): ?string { return preg_match($pattern, $haystack, $m) ? trim($m[1]) : null; } - - /** Top-IPs grob zählen (aus der aktuellen Jail-Liste; Fallback: Log) */ - private function collectTopIps(): array - { - $map = []; - foreach ($this->jails as $jail) { - foreach ($jail['ips'] as $row) { - $ip = $row['ip'] ?? null; - if (!$ip) continue; - $map[$ip] = ($map[$ip] ?? 0) + 1; - } - } - - if (!empty($map)) { - arsort($map); - $out = []; - foreach (array_slice($map, 0, 5, true) as $ip => $count) { - $out[] = ['ip' => $ip, 'count' => $count]; - } - return $out; - } - - // Fallback: aus fail2ban.log - $cmd = 'grep -Eo "([0-9]{1,3}\.){3}[0-9]{1,3}" /var/log/fail2ban.log 2>/dev/null' - . ' | sort | uniq -c | sort -nr | head -5'; - $log = (string)@shell_exec($cmd); - $rows = []; - if ($log !== '') { - foreach (preg_split('/\R+/', trim($log)) as $l) { - if (preg_match('/^\s*(\d+)\s+(\d+\.\d+\.\d+\.\d+)/', $l, $m)) { - $rows[] = ['ip' => $m[2], 'count' => (int)$m[1]]; - } - } - } - return $rows; - } } + +//namespace App\Livewire\Ui\Security; +// +//use Livewire\Component; +// +//class Fail2BanCard extends Component +//{ +// public bool $available = true; // fail2ban-client vorhanden? +// public bool $permDenied = false; // Socket/Root-Rechte fehlen? +// public int $activeBans = 0; // Summe gebannter IPs über alle Jails +// public array $jails = []; // [['name','banned','bantime','ips'=>[['ip','remaining','until'],...]],...] +// public array $topIps = []; // [['ip'=>'x.x.x.x','count'=>N], ...] +// +// public function mount(): void +// { +// $this->load(); +// } +// +// public function render() +// { +// return view('livewire.ui.security.fail2-ban-card'); +// } +// +// /** Button „Neu prüfen“ */ +// public function refresh(): void +// { +// $this->load(true); +// } +// +// /* --------------------- intern --------------------- */ +// +// protected function load(bool $force = false): void +// { +// // existiert fail2ban-client? +// $bin = trim((string)@shell_exec('command -v fail2ban-client 2>/dev/null')) ?: ''; +// if ($bin === '') { +// $this->available = false; +// $this->permDenied = false; +// $this->activeBans = 0; +// $this->jails = []; +// $this->topIps = []; +// return; +// } +// +// // ping → prüft zugleich Rechte (bei Permission-Fehler kommt Klartext) +// [$ok, $raw] = $this->f2b('ping'); // ok == "pong" erkannt +// if (!$ok && stripos($raw, 'permission denied') !== false) { +// $this->available = true; +// $this->permDenied = true; +// $this->activeBans = 0; +// $this->jails = []; +// $this->topIps = $this->collectTopIps(); +// return; +// } +// +// // Jails auflisten +// [, $status] = $this->f2b('status'); +// $jailsLn = $this->firstMatch('/Jail list:\s*(.+)$/mi', $status); +// $jails = $jailsLn ? array_filter(array_map('trim', preg_split('/\s*,\s*/', $jailsLn))) : []; +// +// $total = 0; +// $rows = []; +// +// foreach ($jails as $j) { +// $bantimeSecs = $this->getBantime($j); // Sek., -1 = permanent +// +// [, $s] = $this->f2b('status ' . escapeshellarg($j)); +// $banned = (int)($this->firstMatch('/Currently banned:\s+(\d+)/i', $s) ?: 0); +// $ipList = $this->firstMatch('/Banned IP list:\s*(.+)$/mi', $s) ?: ''; +// $ips = $ipList !== '' ? array_values(array_filter(array_map('trim', preg_split('/\s+/', $ipList)))) : []; +// +// // Restzeiten je IP bestimmen (aus /var/log/fail2ban.log) +// $ipDetails = []; +// foreach (array_slice($ips, 0, 50) as $ip) { +// $banAt = $this->lastBanTimestamp($j, $ip); // Unix-Timestamp oder null +// $remaining = null; +// $until = null; +// +// if ($banAt !== null) { +// if ((int)$bantimeSecs === -1) { +// $remaining = -1; // permanent +// } else { +// $remaining = max(0, $bantimeSecs - (time() - $banAt)); +// $until = $remaining > 0 ? ($banAt + $bantimeSecs) : null; +// } +// } +// +// $ipDetails[] = [ +// 'ip' => $ip, +// 'remaining' => $remaining, // -1 = permanent, 0 = abgelaufen, >0 Sek. +// 'until' => $until, // Unix-Timestamp oder null +// ]; +// } +// +// $rows[] = [ +// 'name' => $j, +// 'banned' => $banned, +// 'ips' => $ipDetails, +// 'bantime' => (int)$bantimeSecs, +// ]; +// $total += $banned; +// } +// +// $this->available = true; +// $this->permDenied = false; +// $this->activeBans = $total; +// $this->jails = $rows; +// $this->topIps = $this->collectTopIps(); +// } +// +// /** führt fail2ban-client via sudo aus; gibt [ok, output] zurück */ +// private function f2b(string $args): array +// { +// $sudo = '/usr/bin/sudo'; +// $f2b = '/usr/bin/fail2ban-client'; +// $cmd = "timeout 2 $sudo -n $f2b $args 2>&1"; +// $out = (string)@shell_exec($cmd); +// +// $ok = stripos($out, 'Status') !== false +// || stripos($out, 'Jail list') !== false +// || stripos($out, 'pong') !== false; +// +// return [$ok, $out]; +// } +// +// private function getBantime(string $jail): int +// { +// [, $out] = $this->f2b('get ' . escapeshellarg($jail) . ' bantime'); +// $val = trim($out); +// if (preg_match('/-?\d+/', $val, $m)) { +// return (int)$m[0]; +// } +// return 600; // defensiver Default +// } +// +// /** letzte Ban-Zeile aus /var/log/fail2ban.log → Unix-Timestamp */ +// private function lastBanTimestamp(string $jail, string $ip): ?int +// { +// $cmd = "grep -F \"[{$jail}] Ban {$ip}\" /var/log/fail2ban.log 2>/dev/null | tail -n 1"; +// $line = trim((string)@shell_exec($cmd)); +// if ($line === '') return null; +// +// // "YYYY-MM-DD HH:MM:SS,mmm ..." +// if (preg_match('/^(\d{4}-\d{2}-\d{2})\s+(\d{2}:\d{2}:\d{2})/', $line, $m)) { +// $ts = strtotime($m[1] . ' ' . $m[2]); +// return $ts ?: null; +// } +// return null; +// } +// +// private function firstMatch(string $pattern, string $haystack): ?string +// { +// return preg_match($pattern, $haystack, $m) ? trim($m[1]) : null; +// } +// +// /** Top-IPs grob zählen (aus der aktuellen Jail-Liste; Fallback: Log) */ +// private function collectTopIps(): array +// { +// $map = []; +// foreach ($this->jails as $jail) { +// foreach ($jail['ips'] as $row) { +// $ip = $row['ip'] ?? null; +// if (!$ip) continue; +// $map[$ip] = ($map[$ip] ?? 0) + 1; +// } +// } +// +// if (!empty($map)) { +// arsort($map); +// $out = []; +// foreach (array_slice($map, 0, 5, true) as $ip => $count) { +// $out[] = ['ip' => $ip, 'count' => $count]; +// } +// return $out; +// } +// +// // Fallback: aus fail2ban.log +// $cmd = 'grep -Eo "([0-9]{1,3}\.){3}[0-9]{1,3}" /var/log/fail2ban.log 2>/dev/null' +// . ' | sort | uniq -c | sort -nr | head -5'; +// $log = (string)@shell_exec($cmd); +// $rows = []; +// if ($log !== '') { +// foreach (preg_split('/\R+/', trim($log)) as $l) { +// if (preg_match('/^\s*(\d+)\s+(\d+\.\d+\.\d+\.\d+)/', $l, $m)) { +// $rows[] = ['ip' => $m[2], 'count' => (int)$m[1]]; +// } +// } +// } +// return $rows; +// } +//} + // //namespace App\Livewire\Ui\Security; // diff --git a/resources/views/livewire/ui/security/fail2-ban-card.blade.php b/resources/views/livewire/ui/security/fail2-ban-card.blade.php index 87b9e2f..4f07e8e 100644 --- a/resources/views/livewire/ui/security/fail2-ban-card.blade.php +++ b/resources/views/livewire/ui/security/fail2-ban-card.blade.php @@ -19,66 +19,44 @@ @if(!$available)
fail2ban-client wurde nicht gefunden.
+ @elseif($permDenied) +
+ Keine Berechtigung auf /var/run/fail2ban/fail2ban.sock. + Sudo-Regel prüfen. +
@else - {{-- Jails --}}
@forelse($jails as $j)
{{ $j['name'] }}
+
- + Bannzeit: - @if($j['bantime'] === -1) permanent - @else {{ $j['bantime'] }}s + @if($j['bantime'] === -1) + permanent + @else + {{ $j['bantime'] }}s @endif {{ $j['banned'] }} gebannt + {{-- Optional: Details öffnen (Tab/Modal) --}} +
- - @if(!empty($j['ips'])) -
- @foreach($j['ips'] as $ip) -
- {{ $ip['ip'] }} - @if($ip['remaining'] === -1) - - permanent - - @elseif(is_int($ip['remaining']) && $ip['remaining'] > 0) - - {{ gmdate('H\h i\m s\s', $ip['remaining']) }} - - @endif -
- @endforeach -
- @endif
@empty
Keine Jails gefunden.
@endforelse
- {{-- Top IPs aus Ban-Events --}} -
-
Top IPs (letzte Fail2Ban-Logs):
- -
-
--}} +{{-- @if(!$available)--}} +{{--
fail2ban-client wurde nicht gefunden.
--}} +{{-- @else--}} +{{-- --}}{{-- Jails --}} +{{--
--}} +{{-- @forelse($jails as $j)--}} +{{--
--}} +{{--
--}} +{{--
{{ $j['name'] }}
--}} +{{--
--}} +{{-- --}} +{{-- Bannzeit:--}} +{{-- @if($j['bantime'] === -1) permanent--}} +{{-- @else {{ $j['bantime'] }}s--}} +{{-- @endif--}} +{{-- --}} +{{-- --}} +{{-- {{ $j['banned'] }} gebannt--}} +{{-- --}} +{{--
--}} +{{--
--}} + +{{-- @if(!empty($j['ips']))--}} +{{--
--}} +{{-- @foreach($j['ips'] as $ip)--}} +{{--
--}} +{{-- {{ $ip['ip'] }}--}} +{{-- @if($ip['remaining'] === -1)--}} +{{-- --}} +{{-- permanent--}} +{{-- --}} +{{-- @elseif(is_int($ip['remaining']) && $ip['remaining'] > 0)--}} +{{-- --}} +{{-- {{ gmdate('H\h i\m s\s', $ip['remaining']) }}--}} +{{-- --}} +{{-- @endif--}} +{{--
--}} +{{-- @endforeach--}} +{{--
--}} +{{-- @endif--}} +{{--
--}} +{{-- @empty--}} +{{--
Keine Jails gefunden.
--}} +{{-- @endforelse--}} +{{--
--}} + +{{-- --}}{{-- Top IPs aus Ban-Events --}} +{{--
--}} +{{--
Top IPs (letzte Fail2Ban-Logs):
--}} +{{-- --}} +{{--
--}} + +{{--
--}} +{{-- --}} +{{--
--}} +{{-- @endif--}} +{{----}} + +{{--
--}} +{{--
--}} +{{--
--}} +{{-- --}} +{{-- Fail2Ban--}} +{{--
--}} + +{{-- @if($available)--}} +{{-- --}} +{{-- {{ $activeBans }} aktuell--}} +{{-- --}} +{{-- @else--}} +{{-- --}} +{{-- nicht installiert--}} +{{-- --}} +{{-- @endif--}} +{{--
--}} + {{-- @if(!$available)--}} {{--
fail2ban-client wurde nicht gefunden.
--}} {{-- @else--}}