feat(auth): clusev:unban CLI + host wrapper escape hatch

feat/v1-foundation
boban 2026-06-20 18:07:43 +02:00
parent 01867dba09
commit 96f86e2761
3 changed files with 75 additions and 0 deletions

View File

@ -0,0 +1,41 @@
<?php
namespace App\Console\Commands;
use App\Models\BannedIp;
use App\Services\BruteforceGuard;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Cache;
class UnbanIp extends Command
{
protected $signature = 'clusev:unban {ip? : Zu entsperrende IP-Adresse} {--all : Alle aktiven Bänne entfernen}';
protected $description = 'Entfernt App-Level-IP-Bänne des Anmeldeschutzes per Shell (Aussperr-Rettung)';
public function handle(BruteforceGuard $guard): int
{
if ($this->option('all')) {
$count = BannedIp::query()->count();
foreach (BannedIp::query()->pluck('ip') as $ip) {
Cache::forget('bruteforce:banned:'.$ip);
}
BannedIp::query()->delete();
$this->info("{$count} Bann/Bänne entfernt.");
return self::SUCCESS;
}
$ip = (string) $this->argument('ip');
if ($ip === '') {
$this->error('Bitte eine IP angeben oder --all verwenden.');
return self::FAILURE;
}
$guard->unban($ip);
$this->info("Bann für {$ip} entfernt (falls vorhanden).");
return self::SUCCESS;
}
}

View File

@ -19,6 +19,7 @@ clusev — Fleet-Control Verwaltung (Host)
sudo clusev update Update holen, Image neu bauen, Migrationen anwenden (root)
clusev reset-admin Admin-Zugang zuruecksetzen (2FA entfernen, Passwort neu setzen)
clusev unban <ip> Gebannte IP entsperren (--all fuer alle)
clusev restart Stack neu starten
clusev logs [dienst] Logs folgen (Strg-C beendet)
clusev ps Dienst-Status (Alias: status)
@ -38,6 +39,7 @@ cmd="${1:-help}"; shift 2>/dev/null || true
case "$cmd" in
update) exec "${CLUSEV_DIR}/update.sh" "$@" ;;
reset-admin) compose exec app php artisan clusev:reset-admin "$@" ;;
unban) compose exec app php artisan clusev:unban "$@" ;;
restart) compose up -d "$@" ;;
logs) compose logs -f "$@" ;;
ps|status) compose ps "$@" ;;

View File

@ -0,0 +1,32 @@
<?php
namespace Tests\Feature;
use App\Models\BannedIp;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class UnbanCommandTest extends TestCase
{
use RefreshDatabase;
public function test_unban_single_ip(): void
{
BannedIp::create(['ip' => '203.0.113.5', 'banned_until' => now()->addHour(), 'attempts' => 10]);
$this->artisan('clusev:unban', ['ip' => '203.0.113.5'])->assertSuccessful();
$this->assertDatabaseCount('banned_ips', 0);
}
public function test_unban_all(): void
{
BannedIp::create(['ip' => '203.0.113.5', 'banned_until' => now()->addHour(), 'attempts' => 10]);
BannedIp::create(['ip' => '203.0.113.6', 'banned_until' => now()->addHour(), 'attempts' => 10]);
$this->artisan('clusev:unban', ['--all' => true])->assertSuccessful();
$this->assertDatabaseCount('banned_ips', 0);
}
public function test_unban_without_args_fails(): void
{
$this->artisan('clusev:unban')->assertFailed();
}
}