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.
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.
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.
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.