Een Deadman-schakelaar voor Firewall-wijzigingen

De enge regel in homelab-automatisering is de regel die een firewallregel aanpast op de router waarmee je via SSH verbonden bent.

Zo bewerken Claude Code en ik ze toch.


De Angst

Ik moest een UDM Pro-regel genaamd “Block inter-VLAN traffic” van accept naar drop omzetten. De regel was al eeuwen verkeerd ingesteld — op accept gezet, waardoor elke per-flow toestemming daarboven werd kortgesloten — en dit weer sluiten was het hele doel. De orchestrator die de wijziging uitvoerde was een NixOS VM op mijn thuisnetwerk. Als de wijziging het pad verstoorde dat die VM (of mijn eigen SSH) gebruikte, zou ik buitengesloten zijn van mijn eigen router zonder consolefallback.

De oplossing is niet “voorzichtig zijn.” De oplossing is ervoor zorgen dat de gevaarlijke bewerking automatisch terugdraait tenzij ik bevestig dat het in orde is.

Je hebt dit patroon eerder gezien. Verander de resolutie van je monitor op macOS of Windows en het systeem past die vijftien seconden toe met een aftellend dialoogvenster “Deze weergave-instellingen behouden?”. Als je scherm zwart is gegaan, kun je nergens op klikken — en dat is het punt. Het standaardresultaat is terugdraaien. Bevestiging is opt-in.

Dat is precies wat we willen voor een firewallwijziging op een router waarvan we de regels gaan aanpassen.


De Truc

udm_set_firewall_rules.py --flip-with-revert 300 --apply

Lees: “verander de regel, maar draai automatisch terug in 300 seconden tenzij ik je zeg dat niet te doen.”

Wat het doet, op volgorde:

  1. Snapshot. GETs de huidige regel en bewaart de volledige JSON-payload op de UDM op /root/.udm-flip-revert/payload-<id>.json.
  2. Terugdraaiing klaarzetten. Schrijft een klein shellscript naast de payload dat de snapshot terug naar de UDM API PUT.
  3. Terugdraaiing inplannen. systemd-run --on-active=300s --unit=udm-flip-revert-<id> zet het script in de wachtrij als een tijdelijke timer.
  4. Omzetten. PUT de nieuwe regel (action: drop).

Nu heb je een venster van 5 minuten om te verifiëren:

✓ flipped 'Block inter-VLAN traffic' to action='drop'
⏱  auto-revert scheduled (~300s)

TO KEEP THE FLIP:    ssh udm 'systemctl stop udm-flip-revert-66c1d…timer'
TO ROLL BACK NOW:    ssh udm 'systemctl stop …timer; bash /root/.udm-flip-revert/revert-….sh'
WAIT IT OUT:         do nothing — timer will revert in ~300s

Drie uitkomsten:

Het derde geval is de reden waarom dit bestaat.


Waarom systemd-run en niet at(1)

UDM Pro (Debian 11) wordt niet geleverd met atd. Het heeft wel systemd. systemd-run --on-active=Ns maakt een tijdelijke timer aan die eenmalig afgaat en zichzelf opruimt, wat precies de vorm is van “doe dit ding over N seconden en ga dan weg.”

ssh_udm(
    f"systemd-run --on-active={seconds} --unit={unit} --collect "
    f"--quiet /bin/bash {script_file}"
)

--collect is de sleutelvlag — zonder deze blijft de unit in failed/inactive staat hangen en maakt systemctl rommeliger.


Het Voorbehoud

Tijdelijke systemd-units leven in /run, wat tmpfs is. Als de UDM opnieuw opstart binnen het terugdraaivenster, is de timer verdwenen — en de wijziging blijft toegepast zonder een deadman die erop toekijkt.

Contra-intuïtief maar reëel: houd het venster kort. Een venster van 5 minuten met een klein herstarrisico is veiliger dan een venster van 1 uur waarbij een UPS-storing op minuut 12 je achterlaat met een permanente buitensluiting. Lang genoeg om te verifiëren, kort genoeg zodat “ben ik een terugdraaiing vergeten?” nooit een vraag is.


Conclusie

Het deadman-patroon generaliseert voorbij UDM-firewallregels. Overal waar je een wijziging gaat toepassen die het pad zou kunnen verbreken waardoor je het toepaste — routeringstabellen, sshd_config, nftables, BGP — werkt dezelfde vorm:

  1. Huidige staat vastleggen
  2. Een script klaarzetten dat die herstelt
  3. Het script inplannen om over N seconden te draaien
  4. De wijziging toepassen

Als je er nog bent over N+ε seconden en alles er goed uitziet, annuleer dan de timer. Zo niet, dan annuleert de timer jou.

Ik slaap beter met dit in de gereedschapskist.