145 lines
6.0 KiB
PHP
145 lines
6.0 KiB
PHP
<?php // tests/Feature/Security/BlockAddressTest.php
|
|
|
|
use App\Models\Host;
|
|
use App\Models\Instance;
|
|
use App\Models\SecurityBlock;
|
|
use App\Services\Security\BlockAddress;
|
|
use App\Support\DnsLookup;
|
|
use Illuminate\Support\Carbon;
|
|
|
|
it('sperrt beim ersten Mal fuer eine Stunde', function () {
|
|
$instance = Instance::factory()->create();
|
|
|
|
$block = app(BlockAddress::class)->forInstance($instance, '203.0.113.7', 12);
|
|
|
|
expect($block)->not->toBeNull()
|
|
// diffInMinutes ist SIGNED (Carbon 3) — wie App\Models\Incident und
|
|
// Host::healthState() schon halten es hier: früher.diffInX(später)
|
|
// für ein positives Ergebnis, nicht wortwörtlich wie im Auftragszettel.
|
|
->and(now()->diffInMinutes($block->expires_at))->toBeGreaterThan(55)
|
|
->and(now()->diffInMinutes($block->expires_at))->toBeLessThan(65)
|
|
->and($block->strikes)->toBe(1)
|
|
->and($block->attempts)->toBe(12);
|
|
});
|
|
|
|
it('verdoppelt bei Wiederholung und haelt bei 24 Stunden an', function () {
|
|
$instance = Instance::factory()->create();
|
|
$dienst = app(BlockAddress::class);
|
|
|
|
$dauern = [];
|
|
for ($i = 0; $i < 7; $i++) {
|
|
$block = $dienst->forInstance($instance, '203.0.113.7', 10);
|
|
$dauern[] = (int) round($block->blocked_at->diffInHours($block->expires_at));
|
|
$block->release(null); // freigegeben, aber der Zähler bleibt
|
|
}
|
|
|
|
expect($dauern)->toBe([1, 2, 4, 8, 16, 24, 24]);
|
|
});
|
|
|
|
it('faengt nach 24 Stunden ohne Vorfall wieder bei einer Stunde an', function () {
|
|
$instance = Instance::factory()->create();
|
|
$dienst = app(BlockAddress::class);
|
|
|
|
$dienst->forInstance($instance, '203.0.113.7', 10)->release(null);
|
|
|
|
Carbon::setTestNow(now()->addHours(25));
|
|
$zweiter = $dienst->forInstance($instance, '203.0.113.7', 10);
|
|
|
|
expect($zweiter->strikes)->toBe(1);
|
|
});
|
|
|
|
it('sperrt NIEMALS eine Adresse aus dem Verwaltungsnetz', function () {
|
|
// Eine Sperrliste, die sich selbst aussperren kann, ist eine Falle: über
|
|
// genau dieses Netz erreicht CluPilot den Host.
|
|
$host = Host::factory()->create();
|
|
|
|
expect(app(BlockAddress::class)->forHost($host, '10.66.0.1', 999))->toBeNull()
|
|
->and(app(BlockAddress::class)->forHost($host, '127.0.0.1', 999))->toBeNull()
|
|
->and(app(BlockAddress::class)->forHost($host, '::1', 999))->toBeNull()
|
|
->and(SecurityBlock::count())->toBe(0);
|
|
});
|
|
|
|
it('haelt eine laufende Sperre nicht zweimal', function () {
|
|
$instance = Instance::factory()->create();
|
|
$dienst = app(BlockAddress::class);
|
|
|
|
$dienst->forInstance($instance, '203.0.113.7', 10);
|
|
|
|
expect($dienst->forInstance($instance, '203.0.113.7', 10))->toBeNull()
|
|
->and(SecurityBlock::count())->toBe(1);
|
|
});
|
|
|
|
it('sperrt niemals die eigene oeffentliche Adresse des CluPilot-Servers', function () {
|
|
// Der vierte Eintrag der Ausnahmeliste, und der einzige, der nicht hart
|
|
// verdrahtet ist. Ohne diesen Test bliebe er unbelegt — ausgerechnet der,
|
|
// der verhindert, dass sich der Server selbst aussperrt.
|
|
config()->set('provisioning.wireguard.endpoint', '203.0.113.10:51820');
|
|
|
|
$host = Host::factory()->create();
|
|
|
|
expect(app(BlockAddress::class)->forHost($host, '203.0.113.10', 999))->toBeNull()
|
|
->and(SecurityBlock::count())->toBe(0);
|
|
|
|
// Und die Gegenprobe, damit der Test nicht bloss beweist, dass gar nichts
|
|
// gesperrt wird: der Nachbar in derselben Zeile ist NICHT ausgenommen.
|
|
expect(app(BlockAddress::class)->forHost($host, '203.0.113.11', 999))->not->toBeNull();
|
|
});
|
|
|
|
it('nimmt auch einen NAMEN als Endpunkt aus, mit allen seinen Adressen', function () {
|
|
// `.env.example` dokumentiert genau diese Form (vpn.clupilot.com:51820).
|
|
// `Str::beforeLast(':')` lieferte damit einen Hostnamen, und
|
|
// `IpUtils::checkIp()` sagt zu einem Hostnamen immer false — die eigene
|
|
// Adresse war in dokumentierter Konfiguration still NICHT ausgenommen.
|
|
config()->set('provisioning.wireguard.endpoint', 'vpn.example.com:51820');
|
|
|
|
// Nicht das echte DNS: ein Test, der dafuer aufloesen muesste, prueft das
|
|
// Netz des Rechners, auf dem er gerade laeuft, nicht die Regel.
|
|
app()->instance(DnsLookup::class, new class extends DnsLookup
|
|
{
|
|
public function addressesFor(string $name): array
|
|
{
|
|
return $name === 'vpn.example.com' ? ['203.0.113.10', '2001:db8:ff::5'] : [];
|
|
}
|
|
});
|
|
|
|
$host = Host::factory()->create();
|
|
|
|
// ALLE Ergebnisse, nicht nur das erste: sonst sperrte sich der Server bei
|
|
// einem Wechsel im Round-Robin doch wieder selbst aus.
|
|
expect(app(BlockAddress::class)->forHost($host, '203.0.113.10', 999))->toBeNull()
|
|
->and(app(BlockAddress::class)->forHost($host, '2001:db8:ff::5', 999))->toBeNull()
|
|
->and(SecurityBlock::count())->toBe(0)
|
|
// Gegenprobe: der Nachbar ist es nicht.
|
|
->and(app(BlockAddress::class)->forHost($host, '203.0.113.11', 999))->not->toBeNull();
|
|
});
|
|
|
|
it('streift bei einem IPv6-Endpunkt die Klammern ab', function () {
|
|
// `[2001:db8::1]:51820` — beforeLast(':') liess die Klammern stehen, und
|
|
// `[2001:db8::1]` ist fuer IpUtils keine Adresse.
|
|
config()->set('provisioning.wireguard.endpoint', '[2001:db8::1]:51820');
|
|
|
|
$host = Host::factory()->create();
|
|
|
|
expect(app(BlockAddress::class)->forHost($host, '2001:db8::1', 999))->toBeNull()
|
|
->and(app(BlockAddress::class)->forHost($host, '2001:db8::2', 999))->not->toBeNull();
|
|
});
|
|
|
|
it('sperrt weiter, wenn sich der Endpunkt-Name gerade nicht aufloesen laesst', function () {
|
|
// Schlaegt die Aufloesung fehl, faellt der Eintrag weg wie bei leerem
|
|
// Endpunkt. Ein Name, der gerade nicht aufloest, darf nicht dazu fuehren,
|
|
// dass gar nichts mehr gesperrt wird.
|
|
config()->set('provisioning.wireguard.endpoint', 'vpn.example.invalid:51820');
|
|
|
|
app()->instance(DnsLookup::class, new class extends DnsLookup
|
|
{
|
|
public function addressesFor(string $name): array
|
|
{
|
|
return [];
|
|
}
|
|
});
|
|
|
|
$host = Host::factory()->create();
|
|
|
|
expect(app(BlockAddress::class)->forHost($host, '203.0.113.7', 999))->not->toBeNull();
|
|
});
|