Commit Graph

7 Commits (ec1f13807eee70a4ed5ee56a4b5dd0386bb768bb)

Author SHA1 Message Date
nexxo ec1f13807e Fix-Runde 2: ein werfender Gast reisst den Lauf nicht mehr mit
guestExec() wirft bei einer echten HTTP-Antwort (->throw()), nicht nur bei
einem sauberen Fehlercode — ein abgeschalteter Gast oder ein noch nicht
gestarteter Agent liess das ungefangen durch scanInstances() nach oben
laufen und beendete handle(), bevor scanHosts() und vor allem
reapplyActiveBlocks() dran waren. Eine unbeteiligte Sperre wurde dadurch
jede Minute erneut nicht wiedereingetragen.

FailedLoginReader::fromInstance() faengt Throwable jetzt genauso wie
fromHost() es schon tat; ScanForIntrusions::scanInstances() umschliesst
zusaetzlich jede Instanz einzeln, nach dem Muster von PingHosts. Dazu ein
Testfall mit einem werfenden und einem lauten Gast nebeneinander, und
Carbon::setTestNow() jetzt in finally, damit ein abgebrochener Testfall
die Uhr nicht fuer die Folgetests eingefroren laesst.
2026-08-03 14:15:36 +02:00
nexxo 118f0203c2 Fix-Runde 1: der Zaehlstand haelt jetzt ueber Laeufe hinweg
ScanForIntrusions hielt bisher nur den Zuwachs eines einzelnen Laufs gegen
die Schwelle — bei einem Lauf pro Minute wurde aus "10 in 10 Minuten"
faktisch "10 in einer Minute", und der geduldige Angreifer mit wenigen
Versuchen je Minute lief nie darueber. Ein Zaehlstand je Subjekt und
Adresse ueber Laravels RateLimiter (cache-gestuetzt, 600s, wie
OperatorLogin es fuer Anmeldeversuche schon vormacht) addiert jeden Lauf
auf den bestehenden Stand und wird nach dem Sperren zurueckgesetzt.
2026-08-03 13:58:55 +02:00
nexxo d3407ff613 Melder: gescheiterte Anmeldungen lesen, zaehlen, sperren
FailedLoginReader liest Nextclouds Protokoll ueber den Gastagenten (mit
Byte-Versatz und Rotationserkennung) und die SSH-Anmeldungen eines Hosts
ueber journalctl. ScanForIntrusions bringt beides mit BlockAddress
zusammen, jede Minute auf der provisioning-Warteschlange, und traegt am
Ende jede noch gueltige Sperre mit ihrer RESTLAUFZEIT erneut in die
Firewall ein — der Fall, der einen Neustart des Hosts uebersteht.

NextcloudOcc bekommt einen zweiten Baustein (exec()) fuer Gastbefehle
jenseits von occ, ohne die Ein-Ort-Regel fuer "docker compose exec" zu
verletzen.
2026-08-03 13:49:34 +02:00
nexxo 09cb8aea5c Fix-Runde 1: der WG-Endpunkt-Zweig der Ausnahmeliste bekommt einen Test
Die drei hart verdrahteten Adressen waren belegt, der vierte Eintrag —
die eigene öffentliche Adresse aus CLUPILOT_WG_ENDPOINT — lief in der
Testumgebung nie durch, weil die Variable dort leer ist. Setzt die
Einstellung, prüft die Ausnahme UND die Gegenprobe am Nachbarn in
derselben Zeile, damit der Test nicht bloß beweist, dass gar nichts mehr
gesperrt wird.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 13:25:36 +02:00
nexxo fc3ff3cb28 Gesperrte Adressen: Datensatz, Verdopplung und die Liste, die nie gesperrt wird
BlockAddress trifft die Sperr-Entscheidung: Ausnahmeliste (Verwaltungsnetz,
Loopback, eigene öffentliche Adresse — hart verdrahtet), laufende Sperre nicht
doppelt, Verdopplung binnen 24h bis zur 24h-Obergrenze. Der Datensatz entsteht
unabhängig vom Rückgabewert von HostFirewall::block() — eine Sperre nur in der
Datenbank ist sichtbar und wird nachgeholt (Aufgabe 4), eine Ausnahme dort
würde den Zeitplan-Auftrag mitreißen.

Migration bringt security_blocks und im selben Zug die zwei Cursor, die
Aufgabe 4 braucht: instances.security_log_offset, hosts.security_log_seen_at.
2026-08-03 13:16:11 +02:00
nexxo 8f630c5093 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.
2026-08-03 13:03:35 +02:00
nexxo 77a4c3d990 Sperrliste in der Host-Firewall, unter der Regel fuer bestehende Verbindungen
Zwei nftables-Mengen (clupilot_blocked/clupilot_blocked6, beide mit
flags timeout) im erzeugten Regelwerk, die Drop-Regel dafuer sitzt
absichtlich unter ct state established,related accept — wer drin ist,
bleibt drin, gesperrt wird nur, was neu anklopft. HostFirewall::block()/
release() tragen eine Adresse mit Ablaufzeit ein bzw. nehmen sie heraus,
ueber die WireGuard-Adresse des Hosts, und geben false statt zu werfen,
wenn der Host nicht erreichbar ist, damit eine spaetere Wiedereintrage-
Aufgabe die Sperre einfach nochmal versuchen kann.
2026-08-03 12:55:34 +02:00