parent
94aec78d4c
commit
d4255b08fa
|
|
@ -29,8 +29,10 @@ class Fail2banSettings extends Component
|
|||
#[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();
|
||||
$this->whitelist = Fail2banIpList::visibleWhitelist()->pluck('ip')->toArray();
|
||||
$this->blacklist = Fail2banIpList::visibleBlacklist()->pluck('ip')->toArray();
|
||||
// $this->whitelist = Fail2banIpList::where('type', 'whitelist')->pluck('ip')->toArray();
|
||||
// $this->blacklist = Fail2banIpList::where('type', 'blacklist')->pluck('ip')->toArray();
|
||||
}
|
||||
|
||||
public function mount(): void
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<?php
|
||||
|
||||
|
||||
namespace App\Livewire\Ui\Security\Modal;
|
||||
|
||||
use LivewireUI\Modal\ModalComponent;
|
||||
|
|
@ -20,7 +21,10 @@ class Fail2banIpModal extends ModalComponent
|
|||
/** Für "remove" vorbefüllt */
|
||||
public ?string $prefill = null;
|
||||
|
||||
public static function modalMaxWidth(): string { return 'lg'; }
|
||||
public static function modalMaxWidth(): string
|
||||
{
|
||||
return 'lg';
|
||||
}
|
||||
|
||||
public function mount(string $type = 'whitelist', string $mode = 'add', ?string $ip = null): void
|
||||
{
|
||||
|
|
@ -34,9 +38,9 @@ class Fail2banIpModal extends ModalComponent
|
|||
throw new \InvalidArgumentException('Invalid mode');
|
||||
}
|
||||
|
||||
$this->type = $type;
|
||||
$this->mode = $mode;
|
||||
$this->ip = $ip ?? '';
|
||||
$this->type = $type;
|
||||
$this->mode = $mode;
|
||||
$this->ip = $ip ?? '';
|
||||
$this->prefill = $ip;
|
||||
}
|
||||
|
||||
|
|
@ -52,46 +56,61 @@ class Fail2banIpModal extends ModalComponent
|
|||
$this->assertAddMode();
|
||||
$ip = trim($this->ip);
|
||||
|
||||
if (!$this->isValidIpOrCidr($ip)) {
|
||||
if (!Fail2banIpList::isValidIpOrCidr($ip)) {
|
||||
throw ValidationException::withMessages(['ip' => 'Ungültige IP oder CIDR.']);
|
||||
}
|
||||
|
||||
// Schutz: System-/Loopback-IPs darf der User nicht manuell pflegen
|
||||
if (Fail2banIpList::isLoopback($ip)) {
|
||||
throw ValidationException::withMessages(['ip' => 'Loopback/localhost ist bereits systemseitig erlaubt und kann nicht geändert werden.']);
|
||||
}
|
||||
|
||||
// Duplikate abfangen (es gibt einen Unique-Index ip+type; trotzdem user-freundlich)
|
||||
$exists = Fail2banIpList::where('ip', $ip)->where('type', $this->type)->exists();
|
||||
if ($exists) {
|
||||
throw ValidationException::withMessages(['ip' => ucfirst($this->type) . ' enthält diese IP bereits.']);
|
||||
}
|
||||
|
||||
// DB schreiben
|
||||
Fail2banIpList::firstOrCreate(['ip' => $ip, 'type' => $this->type]);
|
||||
Fail2banIpList::create(['ip' => $ip, 'type' => $this->type]);
|
||||
|
||||
if ($this->type === 'whitelist') {
|
||||
$this->writeWhitelistConfig();
|
||||
$this->reloadFail2ban();
|
||||
$this->writeWhitelistConfig(); // schreibt /etc/fail2ban/jail.d/mailwolt-whitelist.local
|
||||
$this->reloadFail2ban(); // f2b neu laden
|
||||
} else {
|
||||
// Blacklist = sofort bannen im dedizierten Jail
|
||||
$this->banIp($ip);
|
||||
}
|
||||
|
||||
$this->dispatch('f2b:refresh');
|
||||
$this->dispatch('notify', message: ucfirst($this->type).' aktualisiert.');
|
||||
$this->dispatch('notify', message: ucfirst($this->type) . ' aktualisiert.');
|
||||
$this->closeModal();
|
||||
$this->dispatch('f2b:refresh'); // falls du eine Liste neu laden willst
|
||||
$this->dispatch('f2b:refresh');
|
||||
}
|
||||
|
||||
public function remove(): void
|
||||
{
|
||||
$this->assertRemoveMode();
|
||||
$ip = trim($this->prefill ?? $this->ip);
|
||||
|
||||
if ($ip === '') return;
|
||||
|
||||
// System-Whitelist darf nicht entfernt werden
|
||||
$row = Fail2banIpList::where('type', $this->type)->where('ip', $ip)->first();
|
||||
if ($row && $row->is_system) {
|
||||
throw ValidationException::withMessages(['ip' => 'Systemeintrag kann nicht entfernt werden.']);
|
||||
}
|
||||
|
||||
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->dispatch('notify', message: ucfirst($this->type) . ' Eintrag entfernt.');
|
||||
$this->closeModal();
|
||||
$this->dispatch('f2b:refresh');
|
||||
}
|
||||
|
|
@ -108,53 +127,218 @@ class Fail2banIpModal extends ModalComponent
|
|||
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();
|
||||
// WICHTIG: inkl. System-IPs
|
||||
$ips = Fail2banIpList::allWhitelistForConfig();
|
||||
$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);
|
||||
// sicher in Root-Pfad schreiben (sudo tee)
|
||||
$this->writeRootFileViaTee('/etc/fail2ban/jail.d/mailwolt-whitelist.local', $content);
|
||||
}
|
||||
|
||||
private function writeRootFileViaTee(string $target, string $content): void
|
||||
{
|
||||
if (!preg_match('#^/etc/fail2ban/jail\.d/[A-Za-z0-9._-]+\.local$#', $target)) {
|
||||
throw new \RuntimeException("Illegal path: $target");
|
||||
}
|
||||
|
||||
$cmd = sprintf('sudo -n /usr/bin/tee %s >/dev/null', escapeshellarg($target));
|
||||
$desc = [
|
||||
0 => ['pipe', 'r'],
|
||||
1 => ['pipe', 'w'],
|
||||
2 => ['pipe', 'w'],
|
||||
];
|
||||
$proc = proc_open($cmd, $desc, $pipes);
|
||||
if (!is_resource($proc)) {
|
||||
throw new \RuntimeException('tee start fehlgeschlagen');
|
||||
}
|
||||
fwrite($pipes[0], $content);
|
||||
fclose($pipes[0]);
|
||||
$stdout = stream_get_contents($pipes[1]);
|
||||
fclose($pipes[1]);
|
||||
$stderr = stream_get_contents($pipes[2]);
|
||||
fclose($pipes[2]);
|
||||
$code = proc_close($proc);
|
||||
if ($code !== 0) {
|
||||
throw new \RuntimeException("tee failed (code $code): $stderr $stdout");
|
||||
}
|
||||
}
|
||||
|
||||
private function reloadFail2ban(): void
|
||||
{
|
||||
@shell_exec('sudo fail2ban-client reload 2>&1');
|
||||
@shell_exec('sudo -n /usr/bin/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)
|
||||
@shell_exec("sudo -n /usr/bin/fail2ban-client set mailwolt-blacklist banip {$ipEsc} 2>&1");
|
||||
}
|
||||
|
||||
private function unbanIp(string $ip): void
|
||||
{
|
||||
$ipEsc = escapeshellarg($ip);
|
||||
@shell_exec("sudo fail2ban-client set mailwolt-blacklist unbanip {$ipEsc} 2>&1");
|
||||
@shell_exec("sudo -n /usr/bin/fail2ban-client set mailwolt-blacklist unbanip {$ipEsc} 2>&1");
|
||||
}
|
||||
}
|
||||
//
|
||||
//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");
|
||||
// }
|
||||
//}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<?php
|
||||
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
|
@ -11,50 +12,267 @@ class Fail2banIpList extends Model
|
|||
protected $fillable = [
|
||||
'ip',
|
||||
'type',
|
||||
'is_system',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'ip' => 'string',
|
||||
'type' => 'string',
|
||||
'is_system' => 'boolean',
|
||||
];
|
||||
|
||||
const TYPE_WHITELIST = 'whitelist';
|
||||
const TYPE_BLACKLIST = 'blacklist';
|
||||
public const TYPE_WHITELIST = 'whitelist';
|
||||
public const TYPE_BLACKLIST = 'blacklist';
|
||||
|
||||
/**
|
||||
* Scopes
|
||||
*/
|
||||
public function scopeWhitelist($query)
|
||||
/* ===========================
|
||||
Boot-Hooks (Schutz & Normalisierung)
|
||||
=========================== */
|
||||
protected static function booted()
|
||||
{
|
||||
return $query->where('type', self::TYPE_WHITELIST);
|
||||
// Normalisierung & Loopback-Flag setzen
|
||||
static::saving(function (self $m) {
|
||||
$m->ip = trim($m->ip);
|
||||
|
||||
if (!self::isValidIpOrCidr($m->ip)) {
|
||||
throw new \InvalidArgumentException("Ungültige IP/CIDR: {$m->ip}");
|
||||
}
|
||||
|
||||
// Loopback immer als System markieren
|
||||
if (self::isLoopback($m->ip)) {
|
||||
$m->is_system = true;
|
||||
$m->type = self::TYPE_WHITELIST; // Loopback gehört auf die Whitelist
|
||||
}
|
||||
|
||||
// Systemeinträge dürfen nicht in die Blacklist
|
||||
if ($m->is_system && $m->type === self::TYPE_BLACKLIST) {
|
||||
throw new \InvalidArgumentException("Systemeinträge dürfen nicht auf die Blacklist.");
|
||||
}
|
||||
});
|
||||
|
||||
// Systemeinträge sind unveränderlich (bis auf interne Seeds/Maintenance – dann per DB direkt ändern)
|
||||
static::updating(function (self $m) {
|
||||
if ($m->getOriginal('is_system')) {
|
||||
// Erlaube nur no-op Updates (z. B. Timestamps), aber blocke ip/type Änderungen
|
||||
$blocked = $m->isDirty('ip') || $m->isDirty('type') || $m->isDirty('is_system');
|
||||
if ($blocked) {
|
||||
throw new \RuntimeException("Systemeinträge können nicht geändert werden.");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
static::deleting(function (self $m) {
|
||||
if ($m->is_system) {
|
||||
throw new \RuntimeException("Systemeintrag kann nicht gelöscht werden.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function scopeBlacklist($query)
|
||||
/* ===========================
|
||||
Scopes
|
||||
=========================== */
|
||||
public function scopeWhitelist($q)
|
||||
{
|
||||
return $query->where('type', self::TYPE_BLACKLIST);
|
||||
return $q->where('type', self::TYPE_WHITELIST);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validiert grob die IP.
|
||||
*/
|
||||
public function isValidIp(): bool
|
||||
public function scopeBlacklist($q)
|
||||
{
|
||||
return filter_var($this->ip, FILTER_VALIDATE_IP) !== false;
|
||||
return $q->where('type', self::TYPE_BLACKLIST);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt Liste aller Whitelist-IPs als Array zurück.
|
||||
*/
|
||||
// Für UI: blende Systemeinträge aus
|
||||
public function scopeVisible($q)
|
||||
{
|
||||
return $q->where('is_system', false);
|
||||
}
|
||||
|
||||
// Kombiniert: z. B. Fail2banIpList::visible()->whitelist()->get();
|
||||
public function scopeVisibleWhitelist($q)
|
||||
{
|
||||
return $q->visible()->whitelist();
|
||||
}
|
||||
|
||||
public function scopeVisibleBlacklist($q)
|
||||
{
|
||||
return $q->visible()->blacklist();
|
||||
}
|
||||
|
||||
/* ===========================
|
||||
Helper-Listen
|
||||
=========================== */
|
||||
// Für UI-Listen (ohne System)
|
||||
public static function whitelistArray(): array
|
||||
{
|
||||
return static::where('type', self::TYPE_WHITELIST)->pluck('ip')->all();
|
||||
return static::where('type', self::TYPE_WHITELIST)
|
||||
->where('is_system', false)
|
||||
->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();
|
||||
return static::where('type', self::TYPE_BLACKLIST)
|
||||
->where('is_system', false)
|
||||
->pluck('ip')->all();
|
||||
}
|
||||
|
||||
// Für das Schreiben der Fail2ban-Whitelist-Datei (inkl. System!)
|
||||
public static function allWhitelistForConfig(): array
|
||||
{
|
||||
return static::where('type', self::TYPE_WHITELIST)
|
||||
->pluck('ip')->all();
|
||||
}
|
||||
|
||||
/* ===========================
|
||||
Validierung
|
||||
=========================== */
|
||||
// Erlaubt IPv4/IPv6, optional mit CIDR (/0..32 bzw. /0..128)
|
||||
public static function isValidIpOrCidr(string $value): bool
|
||||
{
|
||||
$value = trim($value);
|
||||
|
||||
// IP ohne CIDR
|
||||
if (filter_var($value, FILTER_VALIDATE_IP)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// IP/CIDR
|
||||
if (strpos($value, '/') !== false) {
|
||||
[$ip, $prefix] = explode('/', $value, 2);
|
||||
if (!ctype_digit($prefix)) {
|
||||
return false;
|
||||
}
|
||||
$prefix = (int)$prefix;
|
||||
|
||||
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
|
||||
return $prefix >= 0 && $prefix <= 32;
|
||||
}
|
||||
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
|
||||
return $prefix >= 0 && $prefix <= 128;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Loopback-Erkennung (IPv4 127.0.0.0/8, IPv6 ::1/128)
|
||||
public static function isLoopback(string $value): bool
|
||||
{
|
||||
$value = trim($value);
|
||||
|
||||
// Klartext-Fälle
|
||||
if (in_array($value, ['127.0.0.1', '127.0.0.1/8', '::1', '::1/128'], true)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// IPv4 Loopback Bereich
|
||||
if (filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
|
||||
return str_starts_with($value, '127.');
|
||||
}
|
||||
|
||||
// IPv4-CIDR Loopback
|
||||
if (strpos($value, '/') !== false) {
|
||||
[$ip, $prefix] = explode('/', $value, 2);
|
||||
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) && ctype_digit($prefix)) {
|
||||
$prefix = (int)$prefix;
|
||||
// Prüfe, ob Netz 127.0.0.0/8 überlappt
|
||||
return self::cidrOverlaps($ip, $prefix, '127.0.0.0', 8);
|
||||
}
|
||||
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) && ctype_digit($prefix)) {
|
||||
$prefix = (int)$prefix;
|
||||
// Prüfe, ob ::1/128 überlappt (nur exakt ::1)
|
||||
return self::cidrOverlaps($ip, $prefix, '::1', 128);
|
||||
}
|
||||
}
|
||||
|
||||
// IPv6 single
|
||||
if (filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
|
||||
return $value === '::1';
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Simple Overlap-Check für IPv4/IPv6 Netze
|
||||
private static function cidrOverlaps(string $ip, int $prefix, string $netIp, int $netPrefix): bool
|
||||
{
|
||||
$a = inet_pton($ip);
|
||||
$b = inet_pton($netIp);
|
||||
if ($a === false || $b === false || strlen($a) !== strlen($b)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$len = strlen($a);
|
||||
$bytes = intdiv(max($prefix, $netPrefix), 8);
|
||||
$bits = max($prefix, $netPrefix) % 8;
|
||||
|
||||
// Netzmaske anwenden (auf die längere Präfixlänge)
|
||||
for ($i = 0; $i < $bytes; $i++) {
|
||||
if ($a[$i] !== $b[$i]) return false;
|
||||
}
|
||||
if ($bits > 0) {
|
||||
$mask = chr(0xFF << (8 - $bits));
|
||||
if ((ord($a[$bytes]) & ord($mask)) !== (ord($b[$bytes]) & ord($mask))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
//
|
||||
//namespace App\Models;
|
||||
//
|
||||
//use Illuminate\Database\Eloquent\Model;
|
||||
//
|
||||
//class Fail2banIpList extends Model
|
||||
//{
|
||||
// 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();
|
||||
// }
|
||||
//}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ use Illuminate\Database\Eloquent\Model;
|
|||
|
||||
class Fail2banSetting extends Model
|
||||
{
|
||||
protected $table = 'fail2ban_settings';
|
||||
|
||||
protected $fillable = [
|
||||
'bantime','max_bantime','bantime_increment','bantime_factor',
|
||||
'max_retry','findtime','cidr_v4','cidr_v6','external_mode',
|
||||
|
|
|
|||
|
|
@ -15,7 +15,10 @@ return new class extends Migration
|
|||
$table->id();
|
||||
$table->string('ip');
|
||||
$table->enum('type', ['whitelist', 'blacklist']);
|
||||
$table->boolean('is_system')->default(false)->index();
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['ip', 'type'], 'fail2ban_ip_lists_ip_type_unique');
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue