Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

pyfw

Tests License Built with Claude

Eine einfache, testgetrieben entwickelte Desktop-GUI (Tkinter) zur Verwaltung von pf-Firewallregeln unter FreeBSD – im Funktionsumfang angelehnt an ufw/GUFW unter Ubuntu: Allow/Deny-Regeln nach Port, Protokoll, Richtung und optional Interface, die sich aktivieren/deaktivieren statt nur löschen lassen, konfigurierbare Standardrichtlinien (Eingehend/Ausgehend), eine kleine Liste bekannter Dienste zum schnellen Ausfüllen, optionales Logging pro Regel, sowie Backup/Rollback des gesamten Zustands.

Code, Tests, Installer und diese Dokumentation wurden testgetrieben gemeinsam mit Claude (Anthropic) entwickelt: Robert Illner (Nihjo) hat Anforderungen, Architektur und Reviews vorgegeben, Claude hat den Großteil des Codes geschrieben. Vor einem produktiven Einsatz auf einer echten FreeBSD-Firewall gilt wie bei jedem Firewall-Tool: selbst prüfen, testen, verstehen.

Architektur

src/pyfw/
  models.py           Rule- und Policy-Domänenmodell + Validierung
                       (kein pf, kein Tk)
  known_services.py    Kleine Liste bekannter Dienste (SSH, HTTP, ...) für
                       das "Bekannter Dienst"-Feld beim Regel-Hinzufügen
                       (GUFWs "Simple"-Reiter)
  config_generator.py Rule-Liste + Policy -> pf-Regeltext (mit "quick" für
                       ufw-artige Auswertungsreihenfolge: erste passende
                       Regel gewinnt; deaktivierte Regeln werden nur
                       auskommentiert, nicht geladen)
  rule_store.py        Persistiert Regeln (nummeriert wie ufw) und die
                       Standardrichtlinie als JSON; sichert vor jeder
                       Änderung automatisch den vorherigen Stand
                       (Backup/Rollback)
  pf_manager.py         Kapselt pfctl-Aufrufe hinter einer injizierbaren
                       Runner-Funktion (testbar ohne echtes pf/root)
  controller.py         Bindet Store + Generator + PfManager zusammen;
                       einzige Schicht, die die GUI aufruft
  gui.py               Tkinter-Oberfläche, ruft ausschließlich controller.py
tests/                 unittest-Tests für alle Module außer gui.py

Jede Schicht unterhalb von gui.py ist vollständig ohne Tkinter, ohne root-Rechte und ohne echtes pf testbar (Subprocess-Aufrufe werden per Dependency Injection gefaked).

Von GUFW übernommen bzw. bewusst nicht übernommen

Übernommen: Regeln aktivieren/deaktivieren ohne Löschen, konfigurierbare Standardrichtlinien für Eingehend/Ausgehend, ein Dropdown mit bekannten Diensten zum schnellen Vorausfüllen, ein Logging-Flag pro Regel.

Bewusst nicht übernommen (würde den MVP-Rahmen sprengen): ein "Routed"- Policy-Feld für NAT/Routing (pyfw macht kein NAT), ein "Report"-Reiter mit Live-Verbindungen/offenen Ports, graduelle Logging-Stufen (Off/Low/Medium/High/Full wie bei ufw/rsyslog – pf kennt nur log-an/log-aus pro Regel, keine Schweregrade), IP-/Subnetz-Einschränkung pro Regel (aktuell immer from any to any).

Wichtig: Einmalige manuelle Einrichtung auf dem FreeBSD-System

pyfw schreibt niemals in /etc/pf.conf – Regeln werden ausschließlich über einen eigenen, benannten Anchor geladen (pfctl -a pyfw -f <datei>), damit die restliche pf-Konfiguration des Systems unangetastet bleibt.

Damit die im Anchor geladenen Regeln aber überhaupt ausgewertet werden, muss der Anchor einmalig im Hauptregelwerk aufgerufen werden. Trage dazu folgende Zeile in /etc/pf.conf ein:

anchor "pyfw"

und lade das Hauptregelwerk einmal neu (service pf reload bzw. pfctl -f /etc/pf.conf). Voraussetzung ist außerdem, dass pf grundsätzlich aktiviert ist (pf_enable="YES" in /etc/rc.conf, pf-Kernelmodul geladen). pyfw verwaltet danach nur noch den Regel-Inhalt dieses einen Anchors – genau wie ufw nur seine eigenen iptables-Chains verwaltet und nicht das gesamte Regelwerk des Systems.

Die Buttons „Aktivieren“/„Deaktivieren“ in der GUI rufen pfctl -e bzw. pfctl -d auf und schalten damit pf global ein/aus (analog zu ufw enable/ufw disable). Auf einem System, das pf auch für andere Zwecke (NAT, weitere Anchors) nutzt, wirkt sich das auf die gesamte Firewall aus, nicht nur auf pyfw-Regeln.

pfctl benötigt root-Rechte – die GUI muss entsprechend gestartet werden (z. B. via doas/sudo).

Out-of-the-Box-Installation (FreeBSD)

Im entpackten Projektverzeichnis (dort, wo pyproject.toml liegt):

doas sh install.sh            # richtig installieren
doas sh install.sh --dry-run  # nur anzeigen, was passieren würde

Der Installer

  1. installiert python312, das Tkinter-Binding und pip per pkg,
  2. legt eine virtuelle Umgebung unter /usr/local/pyfw/venv an und installiert pyfw dort hinein,
  3. erzeugt den Startbefehl /usr/local/bin/pyfw-gui,
  4. setzt pf_enable="YES" in /etc/rc.conf (über sysrc, idempotent),
  5. trägt die einmalig nötige anchor "pyfw"-Zeile in /etc/pf.conf ein – mit automatischem Backup und einer Syntaxprüfung (pfctl -nf) vor jedem Neuladen; schlägt die Prüfung fehl, wird die Originaldatei automatisch wiederhergestellt und nichts angewendet.

Regeln und die daraus erzeugte pf-Regeldatei liegen bewusst im nutzereigenen ~/.config/pyfw/, nicht in einem separaten root-only- Systemverzeichnis – nur so kann PYFW_USE_DOAS=1 (siehe unten) tatsächlich ohne root-Rechte für den Tkinter-Prozess funktionieren.

Rückbau mit doas sh uninstall.sh (fragt vor dem Entfernen der Anchor-Zeile nach, Backup wird angelegt; Altlasten aus Installationen vor diesem Design ebenfalls, ~/.config/pyfw/ bleibt unangetastet).

Manuelle Installation / Tests

pkg install python312 py312-tkinter py312-pip
python3.12 -m venv .venv
. .venv/bin/activate
pip install -e .[dev]

pytest              # führt alle Tests in tests/ aus (unittest-basiert,
                     # von pytest automatisch erkannt)

GUI starten

Aktivieren/Deaktivieren-Buttons sowie der Regel-Umschalter sind farbig (grün = bewirkt eine Aktivierung, rot = bewirkt eine Deaktivierung) - über ttk.Style mit dem Theme "clam" gesetzt, damit die Farben theme-unabhängig zuverlässig angezeigt werden.

doas pyfw-gui

pyfw-gui startet auch ohne root - das Fenster öffnet sich normal, aber jeder pfctl-Aufruf (Regel anwenden, Firewall aktivieren/ deaktivieren) schlägt dann mit "Permission denied" fehl, da pfctl selbst root-Rechte braucht. pyfw-gui warnt davor direkt beim Start, falls weder als root noch mit PYFW_USE_DOAS=1 (siehe unten) gestartet wurde.

Ohne die GUI komplett als root laufen zu lassen

Trage in /usr/local/etc/doas.conf ein (Benutzername anpassen):

permit nopass DEINBENUTZER as root cmd /sbin/pfctl

und starte dann als normaler Nutzer:

PYFW_USE_DOAS=1 pyfw-gui

Nur die einzelnen pfctl-Aufrufe werden dann privilegiert, nicht der gesamte Tkinter-Prozess. Der Installer richtet diese doas-Regel bewusst nicht automatisch ein, da das Vergeben von root-Rechten für einen Nutzer eine bewusste, manuelle Entscheidung sein sollte.

Backup & Rollback von Regelliste und Standardrichtlinie

Vor jeder Änderung (Regel hinzufügen/löschen/umschalten, Standardrichtlinie ändern) sichert RuleStore automatisch den bisherigen Gesamtzustand (Standard: die letzten 20 Stände, ältere werden automatisch gelöscht). Über den Button „Letzte Sicherung wiederherstellen“ in der GUI (oder controller.rollback()) lässt sich der vorherige Stand wiederherstellen – einschließlich der angezeigten Regelliste und der Policy-Dropdowns, nicht nur der bei pf geladenen Regeldatei: alles ist dieselbe Quelle (rules.json), daher bleibt es automatisch synchron. Der wiederhergestellte Stand wird anschließend automatisch erneut bei pf angewendet.

Auch ein Rollback selbst ist nicht destruktiv: der gerade rückgängig gemachte Stand wird vor dem Zurücksetzen ebenfalls als Sicherung angelegt, sodass man bei Bedarf wieder vorwärts gehen kann (über list_backups()/restore_backup(pfad), gezielt für einen bestimmten älteren Stand statt nur den jeweils letzten).

Für Backups von /etc/pf.conf (dem Anchor-Aufrufpunkt) siehe den Abschnitt zum Installer oben – das ist unabhängig davon und wird nur von install.sh/uninstall.sh angefasst, nicht vom laufenden Programm.

Fehlerverhalten bei fehlgeschlagenem pfctl

Lehnt pfctl einen neuen Regelsatz ab (z. B. wegen eines ungültigen Interface-Namens), bleiben Regelliste, Standardrichtlinie und die auf der Platte liegende pf-Regeldatei konsistent beim zuletzt erfolgreich geladenen Stand – nichts zeigt eine Änderung als übernommen an, die bei pf tatsächlich nie aktiv wurde. Das gilt für add_rule, delete_rule, set_rule_enabled und set_policy gleichermaßen.

Datenformat (rules.json)

{
  "policy": {"incoming": "deny", "outgoing": "allow"},
  "next_number": 2,
  "rules": [
    {"number": 1, "rule": {"action": "allow", "direction": "in",
      "protocol": "tcp", "port": 22, "interface": null,
      "comment": "SSH", "enabled": true, "log": false}}
  ]
}

next_number wird persistiert, damit Regelnummern auch nach einem Neustart nicht versehentlich wiederverwendet werden, wenn die zuletzt angelegte(n) Regel(n) zwischenzeitlich gelöscht wurden. Fehlt der Schlüssel (Dateien aus einer sehr frühen pyfw-Version), wird er beim Laden aus den vorhandenen Regelnummern rekonstruiert.

Rule.from_dict() setzt enabled/log auf sinnvolle Standardwerte (true/false), falls ein Eintrag diese Schlüssel noch nicht enthält.

Nächste Schritte (nicht enthalten)

  • pf-Tables und Anchors als eigene Verwaltungseinheiten
  • NAT/Redirect-Regeln, "Routed"-Policy
  • Regeln nach IP-Adresse/Subnetz einschränken (aktuell immer from any to any)
  • IPv6
  • "Report"-Ansicht mit aktuell offenen Ports/Verbindungen (wie GUFWs Report-Reiter)

Lizenz

BSD-2-Clause, siehe LICENSE. Copyright (c) 2026 Robert Illner (Nihjo).

About

a GUFW (Ubuntu) like firewall rule manager for pf in FreeBSD

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages