parent
dcf9a8d3e9
commit
d3783e1717
|
|
@ -1,9 +0,0 @@
|
|||
[90m= [39m[34;4mApp\Models\Setting[39;24m {#6409
|
||||
[34mid[39m: [35m13[39m,
|
||||
[34mgroup[39m: "[32mwoltguard[39m",
|
||||
[34mkey[39m: "[32mservices[39m",
|
||||
[34mvalue[39m: "[32m{"ts":1761504019,"rows":[{"name":"postfix","ok":true},{"name":"dovecot","ok":true},{"name":"rspamd","ok":true},{"name":"clamav","ok":true},{"name":"db","ok":true},{"name":"redis","ok":true},{"name":"php-fpm","ok":true},{"name":"nginx","ok":true},{"name":"mw-queue","ok":true},{"name":"mw-schedule","ok":true},{"name":"mw-ws","ok":true},{"name":"fail2ban","ok":true},{"name":"journal","ok":true}]}[39m",
|
||||
[34mcreated_at[39m: "[32m2025-10-26 19:40:19[39m",
|
||||
[34mupdated_at[39m: "[32m2025-10-26 19:40:19[39m",
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
<?php
|
||||
|
||||
namespace App\Livewire\Ui\Security;
|
||||
|
||||
use Livewire\Attributes\On;
|
||||
use Livewire\Component;
|
||||
use App\Models\Fail2banSetting;
|
||||
use App\Models\Fail2banIpList;
|
||||
|
||||
class Fail2banSettings extends Component
|
||||
{
|
||||
// Formfelder
|
||||
public int $bantime;
|
||||
public int $max_bantime;
|
||||
public bool $bantime_increment;
|
||||
public float $bantime_factor;
|
||||
public int $max_retry;
|
||||
public int $findtime;
|
||||
public int $cidr_v4;
|
||||
public int $cidr_v6;
|
||||
public bool $external_mode;
|
||||
|
||||
public array $whitelist = [];
|
||||
public array $blacklist = [];
|
||||
|
||||
public Fail2banSetting $settings;
|
||||
|
||||
#[On('f2b:refresh')]
|
||||
public function refreshLists(): void
|
||||
{
|
||||
$this->whitelist = Fail2banIpList::where('type', 'whitelist')->pluck('ip')->toArray();
|
||||
$this->blacklist = Fail2banIpList::where('type', 'blacklist')->pluck('ip')->toArray();
|
||||
}
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
// Setting holen oder mit Defaults anlegen
|
||||
$this->settings = Fail2banSetting::first() ?? Fail2banSetting::create([
|
||||
'bantime' => 3600, 'max_bantime' => 43200, 'bantime_increment' => true,
|
||||
'bantime_factor' => 1.5, 'max_retry' => 3, 'findtime' => 600,
|
||||
'cidr_v4' => 32, 'cidr_v6' => 128, 'external_mode' => false,
|
||||
]);
|
||||
|
||||
// Properties füllen (KEINE Mixed-Objekte in Inputs binden)
|
||||
$this->fill([
|
||||
'bantime' => (int)$this->settings->bantime,
|
||||
'max_bantime' => (int)$this->settings->max_bantime,
|
||||
'bantime_increment' => (bool)$this->settings->bantime_increment,
|
||||
'bantime_factor' => (float)$this->settings->bantime_factor,
|
||||
'max_retry' => (int)$this->settings->max_retry,
|
||||
'findtime' => (int)$this->settings->findtime,
|
||||
'cidr_v4' => (int)$this->settings->cidr_v4,
|
||||
'cidr_v6' => (int)$this->settings->cidr_v6,
|
||||
'external_mode' => (bool)$this->settings->external_mode,
|
||||
]);
|
||||
|
||||
$this->whitelist = Fail2banIpList::where('type','whitelist')->pluck('ip')->toArray();
|
||||
$this->blacklist = Fail2banIpList::where('type','blacklist')->pluck('ip')->toArray();
|
||||
}
|
||||
|
||||
public function save(): void
|
||||
{
|
||||
$this->validate([
|
||||
'bantime' => 'required|integer|min:60',
|
||||
'max_bantime' => 'required|integer|min:60',
|
||||
'bantime_factor' => 'required|numeric|min:1',
|
||||
'max_retry' => 'required|integer|min:1',
|
||||
'findtime' => 'required|integer|min:60',
|
||||
'cidr_v4' => 'required|integer|min:8|max:32',
|
||||
'cidr_v6' => 'required|integer|min:8|max:128',
|
||||
]);
|
||||
|
||||
$this->settings->update([
|
||||
'bantime' => $this->bantime,
|
||||
'max_bantime' => $this->max_bantime,
|
||||
'bantime_increment' => $this->bantime_increment,
|
||||
'bantime_factor' => $this->bantime_factor,
|
||||
'max_retry' => $this->max_retry,
|
||||
'findtime' => $this->findtime,
|
||||
'cidr_v4' => $this->cidr_v4,
|
||||
'cidr_v6' => $this->cidr_v6,
|
||||
'external_mode' => $this->external_mode,
|
||||
]);
|
||||
|
||||
$this->writeDefaultsConfig();
|
||||
$this->writeWhitelistConfig();
|
||||
|
||||
@shell_exec('sudo fail2ban-client reload');
|
||||
$this->dispatch('notify', message: 'Gespeichert & Fail2Ban neu geladen.');
|
||||
}
|
||||
|
||||
protected function writeDefaultsConfig(): void
|
||||
{
|
||||
$s = $this->settings;
|
||||
$content = <<<CONF
|
||||
[DEFAULT]
|
||||
bantime = {$s->bantime}
|
||||
findtime = {$s->findtime}
|
||||
maxretry = {$s->max_retry}
|
||||
bantime.increment = {$this->boolToStr($s->bantime_increment)}
|
||||
bantime.factor = {$s->bantime_factor}
|
||||
bantime.maxtime = {$s->max_bantime}
|
||||
CONF;
|
||||
file_put_contents('/etc/fail2ban/jail.d/00-mailwolt-defaults.local', $content);
|
||||
}
|
||||
|
||||
protected function writeWhitelistConfig(): void
|
||||
{
|
||||
$ips = Fail2banIpList::where('type','whitelist')->pluck('ip')->toArray();
|
||||
$ignore = implode(' ', array_unique(array_filter($ips)));
|
||||
$content = "[DEFAULT]\nignoreip = {$ignore}\n";
|
||||
file_put_contents('/etc/fail2ban/jail.d/mailwolt-whitelist.local', $content);
|
||||
}
|
||||
|
||||
private function boolToStr(bool $v): string
|
||||
{
|
||||
return $v ? 'true' : 'false';
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.ui.security.fail2ban-settings');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,160 @@
|
|||
<?php
|
||||
|
||||
namespace App\Livewire\Ui\Security\Modal;
|
||||
|
||||
use LivewireUI\Modal\ModalComponent;
|
||||
use App\Models\Fail2banIpList;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class Fail2banIpModal extends ModalComponent
|
||||
{
|
||||
/** 'whitelist' | 'blacklist' */
|
||||
public string $type = 'whitelist';
|
||||
|
||||
/** 'add' | 'remove' */
|
||||
public string $mode = 'add';
|
||||
|
||||
/** IP/CIDR im Formular */
|
||||
public string $ip = '';
|
||||
|
||||
/** Für "remove" vorbefüllt */
|
||||
public ?string $prefill = null;
|
||||
|
||||
public static function modalMaxWidth(): string { return 'lg'; }
|
||||
|
||||
public function mount(string $type = 'whitelist', string $mode = 'add', ?string $ip = null): void
|
||||
{
|
||||
$type = strtolower($type);
|
||||
$mode = strtolower($mode);
|
||||
|
||||
if (!in_array($type, ['whitelist', 'blacklist'], true)) {
|
||||
throw new \InvalidArgumentException('Invalid type');
|
||||
}
|
||||
if (!in_array($mode, ['add', 'remove'], true)) {
|
||||
throw new \InvalidArgumentException('Invalid mode');
|
||||
}
|
||||
|
||||
$this->type = $type;
|
||||
$this->mode = $mode;
|
||||
$this->ip = $ip ?? '';
|
||||
$this->prefill = $ip;
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.ui.security.modal.fail2ban-ip-modal');
|
||||
}
|
||||
|
||||
/* ---------------- actions ---------------- */
|
||||
|
||||
public function save(): void
|
||||
{
|
||||
$this->assertAddMode();
|
||||
$ip = trim($this->ip);
|
||||
|
||||
if (!$this->isValidIpOrCidr($ip)) {
|
||||
throw ValidationException::withMessages(['ip' => 'Ungültige IP oder CIDR.']);
|
||||
}
|
||||
|
||||
// DB schreiben
|
||||
Fail2banIpList::firstOrCreate(['ip' => $ip, 'type' => $this->type]);
|
||||
|
||||
if ($this->type === 'whitelist') {
|
||||
$this->writeWhitelistConfig();
|
||||
$this->reloadFail2ban();
|
||||
} else {
|
||||
// Blacklist = sofort bannen im dedizierten Jail
|
||||
$this->banIp($ip);
|
||||
}
|
||||
|
||||
$this->dispatch('f2b:refresh');
|
||||
$this->dispatch('notify', message: ucfirst($this->type).' aktualisiert.');
|
||||
$this->closeModal();
|
||||
$this->dispatch('f2b:refresh'); // falls du eine Liste neu laden willst
|
||||
}
|
||||
|
||||
public function remove(): void
|
||||
{
|
||||
$this->assertRemoveMode();
|
||||
$ip = trim($this->prefill ?? $this->ip);
|
||||
|
||||
if ($ip === '') return;
|
||||
|
||||
Fail2banIpList::where('type', $this->type)->where('ip', $ip)->delete();
|
||||
|
||||
if ($this->type === 'whitelist') {
|
||||
$this->writeWhitelistConfig();
|
||||
$this->reloadFail2ban();
|
||||
} else {
|
||||
// aus Blacklist-Jail entbannen, falls noch aktiv
|
||||
$this->unbanIp($ip);
|
||||
}
|
||||
|
||||
$this->dispatch('f2b:refresh');
|
||||
$this->dispatch('notify', message: ucfirst($this->type).' Eintrag entfernt.');
|
||||
$this->closeModal();
|
||||
$this->dispatch('f2b:refresh');
|
||||
}
|
||||
|
||||
/* ---------------- helper ---------------- */
|
||||
|
||||
private function assertAddMode(): void
|
||||
{
|
||||
if ($this->mode !== 'add') throw new \LogicException('Wrong mode');
|
||||
}
|
||||
|
||||
private function assertRemoveMode(): void
|
||||
{
|
||||
if ($this->mode !== 'remove') throw new \LogicException('Wrong mode');
|
||||
}
|
||||
|
||||
private function isValidIpOrCidr(string $s): bool
|
||||
{
|
||||
// IP
|
||||
if (filter_var($s, FILTER_VALIDATE_IP)) return true;
|
||||
|
||||
// CIDR
|
||||
if (strpos($s, '/') !== false) {
|
||||
[$ip, $mask] = explode('/', $s, 2);
|
||||
if (!filter_var($ip, FILTER_VALIDATE_IP)) return false;
|
||||
if (strpos($ip, ':') !== false) {
|
||||
// IPv6
|
||||
return ctype_digit($mask) && (int)$mask >= 8 && (int)$mask <= 128;
|
||||
}
|
||||
// IPv4
|
||||
return ctype_digit($mask) && (int)$mask >= 8 && (int)$mask <= 32;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private function writeWhitelistConfig(): void
|
||||
{
|
||||
$ips = Fail2banIpList::where('type', 'whitelist')->pluck('ip')->toArray();
|
||||
$ignore = implode(' ', array_unique(array_filter($ips)));
|
||||
$content = "[DEFAULT]\nignoreip = {$ignore}\n";
|
||||
|
||||
$file = '/etc/fail2ban/jail.d/mailwolt-whitelist.local';
|
||||
$tmp = $file.'.tmp';
|
||||
@file_put_contents($tmp, $content, LOCK_EX);
|
||||
@chmod($tmp, 0644);
|
||||
@rename($tmp, $file);
|
||||
}
|
||||
|
||||
private function reloadFail2ban(): void
|
||||
{
|
||||
@shell_exec('sudo fail2ban-client reload 2>&1');
|
||||
}
|
||||
|
||||
private function banIp(string $ip): void
|
||||
{
|
||||
$ipEsc = escapeshellarg($ip);
|
||||
@shell_exec("sudo fail2ban-client set mailwolt-blacklist banip {$ipEsc} 2>&1");
|
||||
// optional: in DB zusätzlich behalten, damit UI konsistent ist (bereits oben getan)
|
||||
}
|
||||
|
||||
private function unbanIp(string $ip): void
|
||||
{
|
||||
$ipEsc = escapeshellarg($ip);
|
||||
@shell_exec("sudo fail2ban-client set mailwolt-blacklist unbanip {$ipEsc} 2>&1");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Fail2banIpList extends Model
|
||||
{
|
||||
protected $table = 'fail2ban_ip_lists';
|
||||
|
||||
protected $fillable = [
|
||||
'ip',
|
||||
'type',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'ip' => 'string',
|
||||
'type' => 'string',
|
||||
];
|
||||
|
||||
const TYPE_WHITELIST = 'whitelist';
|
||||
const TYPE_BLACKLIST = 'blacklist';
|
||||
|
||||
/**
|
||||
* Scopes
|
||||
*/
|
||||
public function scopeWhitelist($query)
|
||||
{
|
||||
return $query->where('type', self::TYPE_WHITELIST);
|
||||
}
|
||||
|
||||
public function scopeBlacklist($query)
|
||||
{
|
||||
return $query->where('type', self::TYPE_BLACKLIST);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validiert grob die IP.
|
||||
*/
|
||||
public function isValidIp(): bool
|
||||
{
|
||||
return filter_var($this->ip, FILTER_VALIDATE_IP) !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt Liste aller Whitelist-IPs als Array zurück.
|
||||
*/
|
||||
public static function whitelistArray(): array
|
||||
{
|
||||
return static::where('type', self::TYPE_WHITELIST)->pluck('ip')->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt Liste aller Blacklist-IPs als Array zurück.
|
||||
*/
|
||||
public static function blacklistArray(): array
|
||||
{
|
||||
return static::where('type', self::TYPE_BLACKLIST)->pluck('ip')->all();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Fail2banSetting extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'bantime','max_bantime','bantime_increment','bantime_factor',
|
||||
'max_retry','findtime','cidr_v4','cidr_v6','external_mode',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'bantime' => 'integer',
|
||||
'max_bantime' => 'integer',
|
||||
'bantime_increment' => 'boolean',
|
||||
'bantime_factor' => 'float',
|
||||
'max_retry' => 'integer',
|
||||
'findtime' => 'integer',
|
||||
'cidr_v4' => 'integer',
|
||||
'cidr_v6' => 'integer',
|
||||
'external_mode' => 'boolean',
|
||||
];
|
||||
|
||||
// /**
|
||||
// * Gibt die erste Konfiguration oder Default-Werte zurück.
|
||||
// */
|
||||
// public static function current(): self
|
||||
// {
|
||||
// return static::first() ?? new static([
|
||||
// 'bantime' => 3600,
|
||||
// 'max_bantime' => 43200,
|
||||
// 'bantime_increment' => true,
|
||||
// 'bantime_factor' => 1.5,
|
||||
// 'max_retry' => 5,
|
||||
// 'findtime' => 600,
|
||||
// 'cidr_v4' => 32,
|
||||
// 'cidr_v6' => 128,
|
||||
// 'external_mode' => false,
|
||||
// ]);
|
||||
// }
|
||||
|
||||
/**
|
||||
* Konvertiert bool zu "true"/"false" (für Config-Dateien).
|
||||
*/
|
||||
public function boolToString(bool $val): string
|
||||
{
|
||||
return $val ? 'true' : 'false';
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('fail2ban_settings', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->integer('bantime')->default(3600);
|
||||
$table->integer('max_bantime')->default(43200);
|
||||
$table->boolean('bantime_increment')->default(true);
|
||||
$table->float('bantime_factor')->default(1.5);
|
||||
$table->integer('max_retry')->default(3);
|
||||
$table->integer('findtime')->default(600);
|
||||
$table->integer('cidr_v4')->default(32);
|
||||
$table->integer('cidr_v6')->default(128);
|
||||
$table->boolean('external_mode')->default(false);
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('fail2ban_settings');
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('fail2ban_ip_lists', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('ip');
|
||||
$table->enum('type', ['whitelist', 'blacklist']);
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('fail2ban_ip_lists');
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
use App\Models\Fail2banSetting;
|
||||
use App\Models\Fail2banIpList;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class Fail2banSeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
$this->command->info('⚙️ Fail2ban Defaults werden initialisiert …');
|
||||
|
||||
// -----------------------------------------------------------
|
||||
// 1) Standardwerte für Fail2ban Settings
|
||||
// -----------------------------------------------------------
|
||||
$settings = Fail2banSetting::firstOrCreate([], [
|
||||
'bantime' => 3600, // 1h
|
||||
'max_bantime' => 43200, // 12h
|
||||
'bantime_increment' => true,
|
||||
'bantime_factor' => 1.5,
|
||||
'max_retry' => 5,
|
||||
'findtime' => 600, // 10m
|
||||
'cidr_v4' => 32,
|
||||
'cidr_v6' => 128,
|
||||
'external_mode' => false,
|
||||
]);
|
||||
|
||||
// -----------------------------------------------------------
|
||||
// 2) Standard-IPs für Whitelist
|
||||
// -----------------------------------------------------------
|
||||
$defaultWhitelist = [
|
||||
'127.0.0.1/8',
|
||||
'::1',
|
||||
];
|
||||
|
||||
foreach ($defaultWhitelist as $ip) {
|
||||
Fail2banIpList::firstOrCreate([
|
||||
'ip' => $ip,
|
||||
'type' => Fail2banIpList::TYPE_WHITELIST,
|
||||
]);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------
|
||||
// 3) Fail2ban Config-Dateien erzeugen
|
||||
// -----------------------------------------------------------
|
||||
$this->writeDefaultsConfig($settings);
|
||||
$this->writeWhitelistConfig();
|
||||
|
||||
// -----------------------------------------------------------
|
||||
// 4) Fail2ban reload (optional, falls Dienst läuft)
|
||||
// -----------------------------------------------------------
|
||||
$out = shell_exec('sudo -n fail2ban-client reload 2>&1') ?? '';
|
||||
if (stripos($out, 'OK') === false && stripos($out, 'Reloaded') === false) {
|
||||
Log::warning('Fail2ban reload output', ['out' => $out]);
|
||||
$this->command->warn('⚠️ Fail2ban reload möglicherweise nicht erfolgreich.');
|
||||
} else {
|
||||
$this->command->info('✅ Fail2ban reload erfolgreich.');
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------
|
||||
// interne Hilfsfunktionen
|
||||
// -----------------------------------------------------------
|
||||
|
||||
private function writeDefaultsConfig(Fail2banSetting $s): void
|
||||
{
|
||||
$content = <<<CONF
|
||||
[DEFAULT]
|
||||
bantime = {$s->bantime}
|
||||
findtime = {$s->findtime}
|
||||
maxretry = {$s->max_retry}
|
||||
bantime.increment = {$this->boolToString($s->bantime_increment)}
|
||||
bantime.factor = {$s->bantime_factor}
|
||||
bantime.maxtime = {$s->max_bantime}
|
||||
CONF;
|
||||
|
||||
$this->atomicWrite('/etc/fail2ban/jail.d/00-mailwolt-defaults.local', $content);
|
||||
}
|
||||
|
||||
private function writeWhitelistConfig(): void
|
||||
{
|
||||
$ips = Fail2banIpList::where('type', Fail2banIpList::TYPE_WHITELIST)
|
||||
->pluck('ip')
|
||||
->toArray();
|
||||
|
||||
$ignore = implode(' ', array_unique(array_filter($ips)));
|
||||
$content = "[DEFAULT]\nignoreip = {$ignore}\n";
|
||||
|
||||
$this->atomicWrite('/etc/fail2ban/jail.d/mailwolt-whitelist.local', $content);
|
||||
}
|
||||
|
||||
private function atomicWrite(string $path, string $content): void
|
||||
{
|
||||
$tmp = $path . '.tmp';
|
||||
file_put_contents($tmp, $content);
|
||||
rename($tmp, $path);
|
||||
@chown($path, 'root');
|
||||
@chgrp($path, 'root');
|
||||
@chmod($path, 0644);
|
||||
}
|
||||
|
||||
private function boolToString(bool $v): string
|
||||
{
|
||||
return $v ? 'true' : 'false';
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
<div class="grid grid-cols-1 xl:grid-cols-3 gap-5">
|
||||
{{-- LEFT 2/3 --}}
|
||||
<div class="xl:col-span-2 space-y-5">
|
||||
<div class="glass-card p-5">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="inline-flex items-center gap-2 rounded-full bg-white/5 border border-white/10 px-2.5 py-1">
|
||||
<i class="ph ph-shield text-white/70 text-[13px]"></i>
|
||||
<span class="text-[11px] uppercase tracking-wide text-white/70">Fail2Ban Konfiguration</span>
|
||||
</div>
|
||||
<button wire:click="save"
|
||||
class="inline-flex items-center gap-2 rounded-xl border border-white/10 bg-white/5 px-3 py-1.5 text-white/80 hover:text-white hover:border-white/20">
|
||||
<i class="ph ph-floppy-disk text-[14px]"></i> Speichern & Reload
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-white/60 text-sm mb-1">Bantime (Sekunden)</label>
|
||||
<input type="number" wire:model.defer="bantime"
|
||||
class="w-full h-11 rounded-xl border border-white/10 bg-white/[0.04] px-3 text-white/90">
|
||||
<p class="mt-1 text-xs text-white/45">Standard-Sperrzeit.</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-white/60 text-sm mb-1">Max. Bantime (Sekunden)</label>
|
||||
<input type="number" wire:model.defer="max_bantime"
|
||||
class="w-full h-11 rounded-xl border border-white/10 bg-white/[0.04] px-3 text-white/90">
|
||||
<p class="mt-1 text-xs text-white/45">Obergrenze bei dynamischer Erhöhung.</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-white/60 text-sm mb-1">Findtime (Sekunden)</label>
|
||||
<input type="number" wire:model.defer="findtime"
|
||||
class="w-full h-11 rounded-xl border border-white/10 bg-white/[0.04] px-3 text-white/90">
|
||||
<p class="mt-1 text-xs text-white/45">Zeitraum für Wiederholungen.</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-white/60 text-sm mb-1">Max. Retry</label>
|
||||
<input type="number" wire:model.defer="max_retry"
|
||||
class="w-full h-11 rounded-xl border border-white/10 bg-white/[0.04] px-3 text-white/90">
|
||||
<p class="mt-1 text-xs text-white/45">Fehlversuche bis Bann.</p>
|
||||
</div>
|
||||
|
||||
<div class="md:col-span-2">
|
||||
<label class="inline-flex items-center gap-2 cursor-pointer select-none group">
|
||||
<input type="checkbox" wire:model.defer="bantime_increment" class="peer sr-only">
|
||||
<span class="w-5 h-5 flex items-center justify-center rounded-md border border-white/15 bg-white/5 peer-checked:bg-emerald-500/20 peer-checked:border-emerald-400/40">
|
||||
<i class="ph ph-check text-[12px] text-emerald-300 opacity-0 peer-checked:opacity-100"></i>
|
||||
</span>
|
||||
<span class="text-white/80 text-sm">Bantime dynamisch erhöhen (increment)</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="md:col-span-2">
|
||||
<label class="block text-white/60 text-sm mb-1">Erhöhungs-Faktor</label>
|
||||
<input type="number" step="0.1" wire:model.defer="bantime_factor"
|
||||
class="w-full h-11 rounded-xl border border-white/10 bg-white/[0.04] px-3 text-white/90">
|
||||
<p class="mt-1 text-xs text-white/45">Multiplikator (z. B. 1.5).</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="glass-card p-5">
|
||||
<div class="inline-flex items-center gap-2 rounded-full bg-white/5 border border-white/10 px-2.5 py-1 mb-4">
|
||||
<i class="ph ph-info text-white/70 text-[13px]"></i>
|
||||
<span class="text-[11px] uppercase tracking-wide text-white/70">Hinweise</span>
|
||||
</div>
|
||||
|
||||
<ul class="list-disc list-inside text-sm text-white/60 space-y-1">
|
||||
<li><strong>bantime.increment</strong> = true bedeutet, dass sich die Sperrzeit bei wiederholten
|
||||
Angriffen erhöht (z. B. 1 h → 1.5 h → 2.25 h …).
|
||||
</li>
|
||||
<li>Die SQLite-Datenbank befindet sich unter <code>/var/lib/fail2ban/fail2ban.sqlite3</code>.</li>
|
||||
<li>Alle Änderungen hier werden nach Klick auf <em>„Speichern & Reload“</em> sofort aktiv.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- RIGHT 1/3 --}}
|
||||
<div class="space-y-5">
|
||||
<div class="glass-card p-5">
|
||||
<div class="inline-flex items-center gap-2 rounded-full bg-white/5 border border-white/10 px-2.5 py-1 mb-3">
|
||||
<i class="ph ph-list text-white/70 text-[13px]"></i>
|
||||
<span class="text-[11px] uppercase tracking-wide text-white/70">Whitelist</span>
|
||||
</div>
|
||||
|
||||
@forelse($whitelist as $ip)
|
||||
<div class="flex items-center justify-between rounded-lg border border-white/10 bg-white/[0.03] px-3 py-2 mb-2">
|
||||
<span class="text-white/80 text-sm">{{ $ip }}</span>
|
||||
<button class="text-[12px] px-2 py-0.5 rounded border border-white/10 hover:border-white/20"
|
||||
wire:click="$dispatch('openModal',{component:'ui.security.modal.fail2ban-ip-modal',arguments:{mode:'remove',type:'whitelist',ip:'{{ $ip }}'}})">
|
||||
Entfernen
|
||||
</button>
|
||||
</div>
|
||||
@empty
|
||||
<div class="text-sm text-white/50">Keine Einträge.</div>
|
||||
@endforelse
|
||||
|
||||
<button class="primary-btn w-full justify-center mt-2"
|
||||
wire:click="$dispatch('openModal',{component:'ui.security.modal.fail2ban-ip-modal',arguments:{type:'whitelist'}})">
|
||||
IP hinzufügen
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="glass-card p-5">
|
||||
<div class="inline-flex items-center gap-2 rounded-full bg-rose-500/10 border border-rose-400/30 px-2.5 py-1 mb-3">
|
||||
<i class="ph ph-hand text-rose-300 text-[13px]"></i>
|
||||
<span class="text-[11px] uppercase tracking-wide text-rose-300">Blacklist</span>
|
||||
</div>
|
||||
|
||||
@forelse($blacklist as $ip)
|
||||
<div class="flex items-center justify-between rounded-lg border border-white/10 bg-white/[0.03] px-3 py-2 mb-2">
|
||||
<span class="text-white/80 text-sm">{{ $ip }}</span>
|
||||
<button class="text-[12px] px-2 py-0.5 rounded border border-white/10 hover:border-white/20"
|
||||
wire:click="$dispatch('openModal',{component:'ui.security.modal.fail2ban-ip-modal',arguments:{mode:'remove',type:'blacklist',ip:'{{ $ip }}'}})">
|
||||
Entfernen
|
||||
</button>
|
||||
</div>
|
||||
@empty
|
||||
<div class="text-sm text-white/50">Keine Einträge.</div>
|
||||
@endforelse
|
||||
|
||||
<button class="text-[13px] w-full px-3 py-2 rounded-xl border border-rose-400/30 bg-rose-500/10 text-rose-200 hover:border-rose-400/50"
|
||||
wire:click="$dispatch('openModal',{component:'ui.security.modal.fail2ban-ip-modal',arguments:{type:'blacklist'}})">
|
||||
IP hinzufügen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
<div class="p-5">
|
||||
{{-- Header --}}
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="inline-flex items-center gap-2 rounded-full
|
||||
{{ $type === 'blacklist' ? 'bg-rose-500/10 border border-rose-400/30' : 'bg-white/5 border border-white/10' }}
|
||||
px-2.5 py-1">
|
||||
<i class="ph {{ $type === 'blacklist' ? 'ph-hand text-rose-300' : 'ph-list text-white/70' }} text-[13px]"></i>
|
||||
<span class="text-[11px] uppercase tracking-wide
|
||||
{{ $type === 'blacklist' ? 'text-rose-300' : 'text-white/70' }}">
|
||||
{{ strtoupper($type) }} – {{ $mode === 'add' ? 'hinzufügen' : 'entfernen' }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<button type="button" wire:click="$dispatch('closeModal')"
|
||||
class="rounded-lg border border-white/10 bg-white/5 px-2.5 py-1 text-white/70 hover:text-white">
|
||||
Schließen
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{{-- Body --}}
|
||||
<div class="space-y-3">
|
||||
@if($mode === 'add')
|
||||
<div>
|
||||
<label class="block text-white/60 text-sm mb-1">IP oder CIDR</label>
|
||||
<input type="text" wire:model.defer="ip" placeholder="z. B. 203.0.113.4 oder 203.0.113.0/24 oder 2001:db8::/32"
|
||||
class="w-full h-11 rounded-xl border border-white/10 bg-white/[0.04] px-3 text-white/90">
|
||||
@error('ip') <p class="text-sm text-rose-400 mt-1">{{ $message }}</p> @enderror
|
||||
</div>
|
||||
<button wire:click="save"
|
||||
class="primary-btn w-full justify-center">
|
||||
{{ $type === 'blacklist' ? 'Zur Blacklist hinzufügen & bannen' : 'Zur Whitelist hinzufügen' }}
|
||||
</button>
|
||||
|
||||
@if($type === 'blacklist')
|
||||
<p class="text-xs text-white/50 mt-2">
|
||||
Wird sofort im Jail <code>mailwolt-blacklist</code> gebannt (bantime = permanent).
|
||||
</p>
|
||||
@endif
|
||||
@else
|
||||
<div class="rounded-xl border border-white/10 bg-white/[0.04] px-3 py-2">
|
||||
<div class="text-white/80 text-sm">IP: {{ $prefill ?? $ip }}</div>
|
||||
<div class="text-white/50 text-xs">Wird aus der {{ $type }} entfernt
|
||||
@if($type === 'blacklist') und im Blacklist-Jail entbannt @endif.
|
||||
</div>
|
||||
</div>
|
||||
<button wire:click="remove"
|
||||
class="text-[13px] w-full px-3 py-2 rounded-xl border
|
||||
{{ $type === 'blacklist'
|
||||
? 'border-rose-400/40 bg-rose-500/10 text-rose-200 hover:border-rose-400/70'
|
||||
: 'border-white/20 bg-white/5 text-white/80 hover:border-white/40' }}">
|
||||
Entfernen
|
||||
</button>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1 +1,10 @@
|
|||
<?php
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', 'Fail2Ban')
|
||||
@section('header_title', 'Fail2Ban')
|
||||
|
||||
@section('content')
|
||||
|
||||
<livewire:ui.security.fail2ban-settings />
|
||||
|
||||
@endsection
|
||||
|
|
|
|||
Loading…
Reference in New Issue