Fix-Runde 1: strukturelle Pruefung statt Zaehler, und der nicht erreichbare Host bekommt einen Test

substr_count('flags timeout') lief ueber den ganzen Text inklusive
Kommentare und belegte nur "die Phrase kommt zweimal vor", nicht "beide
set-Bloecke tragen die Ablaufzeit". Ersetzt durch je einen strukturellen
Ausdruck pro Menge; der Originalkommentar aus dem Auftragszettel kann
damit wieder wortgenau stehen. Dazu zwei neue Tests mit failConnect, die
belegen, dass block()/release() bei einem nicht erreichbaren Host false
liefern statt zu werfen - der Pfad, auf dem das Wiedereintragen in
Aufgabe 4 aufbaut.
feat/versandtakt
nexxo 2026-08-03 13:03:35 +02:00
parent 77a4c3d990
commit 8f630c5093
2 changed files with 36 additions and 8 deletions

View File

@ -123,10 +123,9 @@ class SecureHostFirewall extends HostStep
flush ruleset
table inet clupilot_filter {
# Adressen, die gerade gesperrt sind. Die Option `timeout` in den `flags`
# ist nicht schmückend: ohne sie nimmt nftables beim Eintragen gar keine
# Zeitangabe an — und die Frist liefe dann nur in unserer Datenbank ab,
# nicht im Kernel.
# Adressen, die gerade gesperrt sind. `flags timeout` ist nicht schmückend:
# ohne das nimmt nftables beim Eintragen gar keine Zeitangabe an — und die
# Frist liefe dann nur in unserer Datenbank ab, nicht im Kernel.
set clupilot_blocked {
type ipv4_addr
flags timeout

View File

@ -27,10 +27,14 @@ it('stellt die Sperrregel UNTER die Regel fuer bestehende Verbindungen', functio
->and($established)->toBeLessThan($sperre);
// Beide Mengen, und beide mit Ablaufzeit — ohne `flags timeout` nimmt
// nftables die Zeitangabe beim Eintragen gar nicht an.
expect($regelwerk)->toContain('set clupilot_blocked')
->and($regelwerk)->toContain('set clupilot_blocked6')
->and(substr_count($regelwerk, 'flags timeout'))->toBe(2);
// nftables die Zeitangabe beim Eintragen gar nicht an. Strukturell geprüft,
// je Menge einzeln: ein bloßer substr_count('flags timeout') über den
// GANZEN Text (Kommentare eingeschlossen) belegt nur „die Phrase kommt
// zweimal vor", nicht „beide set-Blöcke tragen die Ablaufzeit" — er bliebe
// grün, wenn clupilot_blocked6 die Zeitoption verlöre, während dieselbe
// Phrase zufällig zweimal in Prosa stünde.
expect($regelwerk)->toMatch('/set clupilot_blocked \{\s*type ipv4_addr\s*flags timeout\s*\}/')
->and($regelwerk)->toMatch('/set clupilot_blocked6 \{\s*type ipv6_addr\s*flags timeout\s*\}/');
});
it('traegt eine Adresse mit Ablaufzeit ein und nimmt sie wieder heraus', function () {
@ -54,3 +58,28 @@ it('waehlt fuer eine IPv6-Adresse die zweite Menge', function () {
expect($shell->ran('clupilot_blocked6 { 2001:db8::1 timeout 3600s }'))->toBeTrue();
});
it('gibt false zurueck statt zu werfen, wenn der Host beim Sperren nicht erreichbar ist', function () {
// Trägt Aufgabe 4: der Sperrdatensatz entsteht auch dann in der Datenbank
// und wird beim nächsten Lauf erneut eingetragen. Eine Ausnahme hier würde
// stattdessen den ganzen Zeitplan-Auftrag mitreißen, der block() aufruft.
$shell = new FakeRemoteShell;
$shell->failConnect = true;
app()->instance(\App\Services\Ssh\RemoteShell::class, $shell);
$host = Host::factory()->active()->create(['ssh_host_key' => 'SHA256:abc']);
$result = app(HostFirewall::class)->block($host, '203.0.113.7', 3600);
expect($result)->toBeFalse();
});
it('gibt false zurueck statt zu werfen, wenn der Host beim Entsperren nicht erreichbar ist', function () {
$shell = new FakeRemoteShell;
$shell->failConnect = true;
app()->instance(\App\Services\Ssh\RemoteShell::class, $shell);
$host = Host::factory()->active()->create(['ssh_host_key' => 'SHA256:abc']);
$result = app(HostFirewall::class)->release($host, '203.0.113.7');
expect($result)->toBeFalse();
});