No description
  • Go 67.7%
  • TypeScript 23.8%
  • HTML 3.7%
  • JavaScript 2.4%
  • PowerShell 0.9%
  • Other 1.4%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Manuel Gläser a9919a6480
All checks were successful
Image bauen / image (push) Successful in 3s
Regel-Audit: verwaiste und doppelte Policies finden und aufräumen
Das Audit sah bisher nur die Pro-Host-Analyse (redundant/überdeckt). Zwei
flottenweite Aufräum-Funde kommen dazu:

- verwaist: die Gegenseite der Regel löst zu keiner Adresse auf — exakt die
  Regeln, die der Rollout mit "Policy übersprungen" verwirft (etwa ein Objekt,
  das seinen einzigen Host durch Löschen/Neu-Enrollment verloren hat). Gleiche
  Richtungswahl wie im Rollout (Quelle bei in, Ziel bei out).
- duplikat: deckungsgleiche Dubletten — gleiche Richtung, gleiche Quell-/Ziel-/
  Dienst-Objekte, gleiche Aktion. Verglichen über die OBJEKT-IDs, denn genau so
  entstehen sie beim Neu-Enrollment/Import. Die erste je Gruppe (kleinste
  Position) bleibt, die weiteren werden gemeldet.

Jede Regel wird nur einmal gemeldet (Dedup über die Regel-ID). Beide sind
löschbar — einzeln oder gebündelt über "N löschen" (regelSammelaktion). Nur
"greift nie, weil überdeckt" bleibt bewusst ohne Löschknopf: erst verstehen,
warum, dann handeln.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-05 07:18:29 +00:00
.forgejo/workflows nfthub heisst jetzt fwhub — und zwei Fehler im Windows-Uninstall 2026-08-06 19:28:35 +00:00
agent Fremdfw: fremde nft-Tables übernehmen (sichern, löschen, zurücknehmen) 2026-08-30 13:47:33 +00:00
deploy install.sh: Selbstupdate auf dem Server-Host automatisch einrichten 2026-08-30 14:06:38 +00:00
design nfthub heisst jetzt fwhub — und zwei Fehler im Windows-Uninstall 2026-08-06 19:28:35 +00:00
docs Den Katalog vom Management-Zugang abgrenzen 2026-08-06 21:47:43 +00:00
server Regel-Audit: verwaiste und doppelte Policies finden und aufräumen 2026-09-05 07:18:29 +00:00
shared Conntrack: Live-Verbindungstabelle je Host (Linux) 2026-08-29 09:05:15 +00:00
web Regel-Audit: verwaiste und doppelte Policies finden und aufräumen 2026-09-05 07:18:29 +00:00
.dockerignore nfthub heisst jetzt fwhub — und zwei Fehler im Windows-Uninstall 2026-08-06 19:28:35 +00:00
.gitignore Zertifikat und SPKI-Pin wieder im Startkopf, auch bei bootendem IdP 2026-08-09 12:00:12 +00:00
CHANGELOG.md Dienste-Liste: durchsuchbar und Einträge als Chips 2026-08-16 20:00:29 +00:00
DECISIONS.md nfthub heisst jetzt fwhub — und zwei Fehler im Windows-Uninstall 2026-08-06 19:28:35 +00:00
go.mod nfthub heisst jetzt fwhub — und zwei Fehler im Windows-Uninstall 2026-08-06 19:28:35 +00:00
go.sum nfthub 0.0.1 2026-08-01 07:15:24 +00:00
Makefile nfthub heisst jetzt fwhub — und zwei Fehler im Windows-Uninstall 2026-08-06 19:28:35 +00:00
README.md nfthub heisst jetzt fwhub — und zwei Fehler im Windows-Uninstall 2026-08-06 19:28:35 +00:00

fwhub

Zentrale Verwaltung der nftables-Firewalls mehrerer Linux-Hosts. Mehrere Besitzer („Owner") verwalten jeweils ausschließlich ihre eigenen Hosts: Firewall-Ereignisse einsehen und geblockten Verkehr per Quick-Action freischalten. Administratoren sehen und verwalten alles.

Kein SSH vom Server zu den Hosts, keine zentral hinterlegten Host-Zugangsdaten. Stattdessen verbindet sich ein Agent von jedem Host aus nach außen zum Server.

Frühe Alpha. Im eigenen Netz im Einsatz, aber weder im Funktionsumfang noch in den Schnittstellen festgelegt. Solange die Hauptversion 0 ist, kann sich in jeder Nebenversion alles ändern — auch das Datenbankschema und das Agent-Protokoll. Siehe CHANGELOG.md.


Architektur

flowchart TB
    browser["Browser<br/>OIDC-Sitzung"]
    server["fwhub-server<br/>REST-API + WebSocket<br/>Regelrenderer<br/>Weboberfläche (eingebettet)"]

    agent1["fwhub-agent<br/>pve01"]
    agent2["fwhub-agent<br/>docker01"]
    agent3["fwhub-agent<br/>mail01"]

    tab1["table inet fwhub<br/>NFLOG → Ereignisse"]
    tab2["table inet fwhub<br/>NFLOG → Ereignisse"]
    tab3["table inet fwhub<br/>NFLOG → Ereignisse"]

    browser -- HTTPS --> server
    agent1 -- "TLS, vom Host aufgebaut" --> server
    agent2 --> server
    agent3 --> server

    agent1 --- tab1
    agent2 --- tab2
    agent3 --- tab3

Die Pfeile zeigen die Richtung des Verbindungsaufbaus, und sie ist bewusst so gewählt: Der Agent verbindet sich nach außen zum Server. Kein Host braucht einen eingehenden Port für die Verwaltung, und der Server hält keine Zugangsdaten zu den Hosts vor. Über dieselbe Verbindung fließen Ereignisse nach oben und Regeländerungen nach unten.

Drei Bestandteile in einem Repository:

  • server/ — Go, ein einzelnes Binary. REST-API, OIDC-Anmeldung, Agent-Anbindung samt Freigabe, Regelrenderer. Die Weboberfläche und die Agent-Binaries sind per go:embed eingebettet; es gibt nichts getrennt auszuliefern.
  • agent/ — Go, statisch gelinkt, ohne cgo. Läuft als Dienst auf jedem Host, liest Firewall-Ereignisse über NFLOG und wendet Regeln an.
  • web/ — React 19 mit TypeScript, Vite, TailwindCSS v4.

Wie ein Host dazukommt

Hosts werden nicht vorab angelegt. Sie entstehen dadurch, dass sich ein Agent meldet:

  1. In der Oberfläche unter Hosts → Host hinzufügen stehen das Instanz-Token und — bei eigenem TLS — der Agent-TLS-Pin. Das Instanz-Token ist dauerhaft und mehrfach nutzbar; es meldet beliebig viele Hosts an.

  2. Auf dem Host läuft das Einrichtungsskript:

    curl -fsSL https://fwhub.example.org/download/install.sh \
      | sh -s -- --server wss://fwhub.example.org:8443/agent/connect --token TOKEN
    

    Es erkennt Debian/Ubuntu, die RHEL-Familie und Alpine, dazu systemd und OpenRC, wählt das passende Binary, prüft die Prüfsumme, legt Dienst und Konfiguration an und startet ihn. Wer die Schritte einzeln gehen will, findet sie im selben Dialog.

  3. Der Agent tauscht das Enrollment-Token gegen ein dauerhaftes, host-gebundenes Token, das serverseitig nur als Hash liegt.

  4. Der neue Host erscheint als ausstehend und bekommt erst nach der Freigabe durch einen Administrator Regeln. Bis dahin liefert er nichts und empfängt nichts.

Danach hält der Agent eine ausgehende TLS-Verbindung offen, sendet alle 30 Sekunden einen Heartbeat und empfängt darüber Regeländerungen. Fällt die Verbindung aus, wendet er beim nächsten Start den zuletzt bestätigten Regelstand aus seinem Zustand an — der Host steht also nie ungefiltert da, nur weil der Server gerade nicht erreichbar ist.

Entfernen geht denselben Weg zurück: install.sh --uninstall hält den Dienst an, löscht die eigene nft-Table und räumt Binary, Konfiguration und Zustand ab.


Regeln

Regeln entstehen in der Oberfläche, nicht in einer Datei. Sie tragen einen Geltungsbereich — Instanz, Owner oder einzelner Host — und werden in dieser Reihenfolge zusammengeführt; das Speziellere gewinnt. Aus dem Ergebnis rendert der Server eine vollständige table inet fwhub und schickt sie an den Agent, der sie mit nft -f atomar anwendet und bei einem Fehler zurückrollt.

Zwei Betriebsarten je Host:

  • Monitor — es wird nichts verworfen, nur protokolliert. Was eine Regel blockieren würde, erscheint als Ereignis. Der Weg, ein Regelwerk gefahrlos einzufahren.
  • Enforce — die Regeln greifen.

Unantastbar sind zwei Dinge, die keine Regel überschreiben kann: die Management-Ports (Vorgabe 22) und der Weg des Agents zum Server. Steht die Grundregel auf „ausgehend alles verwerfen", setzt der Renderer die nötigen Ausnahmen selbst an den Anfang der Kette — inklusive Namensauflösung, ohne die ein Agent seinen Server nach einem Neustart nicht wiederfände. Ist der Weg zum Server nicht bekannt, verweigert er die Konfiguration lieber, als den Host abzuschneiden.

Geblockter Verkehr lässt sich direkt an der Ereigniszeile freischalten (Quick-Action): Umfang, Dauer und Geltungsbereich wählen, fertig. Die Rückmeldung nennt den tatsächlichen Rollout-Ausgang und bietet „Rückgängig" an.


Voraussetzungen

  • Go ≥ 1.25 und Node ≥ 24 zum Bauen
  • Ein OIDC-Provider (entwickelt und getestet gegen Authentik)
  • Optional PostgreSQL ≥ 18; ohne Angabe wird SQLite verwendet
  • Auf den Hosts: nft und conntrack — das Einrichtungsskript installiert beides

Schnellstart

make build          # Frontend bauen, einbetten, Binaries erzeugen
./dist/fwhub-server

Beim ersten Start läuft der Server im Einrichtungsmodus und gibt ein Setup-Token aus:

────────────────────────────────────────────────────────────────────────
  fwhub läuft im Einrichtungsmodus.

  Oberfläche:   http://localhost:8080/setup
  Setup-Token:  BXGoqfyFD6Wj0B7v4o4nXDWZxryNNYwT
────────────────────────────────────────────────────────────────────────

Das Token gilt nur für diesen Prozess. Der Wizard führt durch fünf Schritte:

  1. Datenbank — SQLite (eine Datei) oder PostgreSQL (DSN), auf Wunsch mit mTLS. Die Migrationen laufen automatisch; ein Wechsel ist ohne Neustart möglich.
  2. Anmeldung — externe Basis-URL, Issuer, Client-ID und -Secret, Claims und Bootstrap-Admin-Gruppe. Die einzutragende Redirect-URI steht zum Kopieren bereit.
  3. Basis-Policy — Management-Ports und die Vorgabe für ausgehenden Verkehr.
  4. Benachrichtigung — SMTP für die Meldung, dass ein Host auf Freigabe wartet. Überspringbar.
  5. Abschluss — Konfiguration festschreiben, Einrichtungsmodus sperren.

Für die vollständige Inbetriebnahme — Server mit Docker oder systemd, eigenes TLS, Reverse Proxy, Agent-Verteilung per Ansible — siehe docs/deployment.md. Ergänzend: Authentik einrichten, Windows-Hosts verwalten.

Entwicklung

make dev            # Server auf :8080 plus Vite auf :5173 mit Hot Reload
make dev-seed       # dasselbe mit erzeugten Fake-Ereignissen, ohne echte Hosts
make test           # Go- und Frontend-Tests
make check          # Formatierung, go vet, Typecheck, Tests
make test-postgres  # dieselben Tests gegen eine echte PostgreSQL-Instanz

make test-postgres erwartet eine erreichbare Datenbank (PG_DSN=postgres://… überschreibt das Ziel) und legt für jeden Test ein eigenes Schema an, das danach wieder verschwindet. Ohne FWHUB_TEST_PG_DSN laufen alle Tests gegen SQLite. Beides zu fahren lohnt sich: Der PostgreSQL-Lauf hat mehrfach Fehler aufgedeckt, die unter SQLite nicht auftreten können — zuletzt eine Ganzzahl in einer BOOLEAN-Spalte, die dort jede Host-Anmeldung scheitern ließ.


Einrichtung ohne Wizard

Für Infrastructure-as-Code lässt sich alles per Umgebung vorgeben. Ist die Konfiguration vollständig, wird der Wizard übersprungen. Halb ausgefüllt startet der Server bewusst nicht — lieber ein klarer Abbruch als eine Instanz, die teilweise konfiguriert ins Netz geht.

FWHUB_BASE_URL=https://fwhub.example.org
FWHUB_LISTEN=:8080
FWHUB_DATA_DIR=/var/lib/fwhub
FWHUB_MASTER_KEY_FILE=/run/secrets/fwhub-master-key
FWHUB_TRUSTED_PROXIES=10.0.0.0/8

FWHUB_DB_DRIVER=postgres
FWHUB_DB_DSN=postgres://fwhub@db.example.org:5432/fwhub

FWHUB_OIDC_ISSUER=https://auth.example.org/application/o/fwhub/
FWHUB_OIDC_CLIENT_ID=FWHUB_OIDC_CLIENT_SECRET=FWHUB_OIDC_ADMIN_GROUPS=fwhub-admins
FWHUB_OIDC_USERNAME_CLAIM=preferred_username
FWHUB_OIDC_GROUPS_CLAIM=groups

FWHUB_MGMT_TCP_PORTS=22

Alternativ nimmt FWHUB_CONFIG=/etc/fwhub/fwhub.env dieselben Schlüssel aus einer Datei entgegen; echte Umgebungsvariablen haben Vorrang.

Einen Master-Key erzeugen:

./dist/fwhub-server -master-key

Wird keiner angegeben, legt der Server beim ersten Start selbst einen unter $FWHUB_DATA_DIR/master.key an und weist deutlich darauf hin. Geht diese Datei verloren, sind alle verschlüsselt gespeicherten Secrets unlesbar.


Sicherheitsmodell

Owner-Trennung wird serverseitig erzwungen, nicht im Frontend. Jede Abfrage, die Hosts, Ereignisse, Regeln oder Audit-Einträge liest oder verändert, nimmt einen Zugriffsbereich entgegen und baut die Einschränkung selbst in das SQL ein. Es gibt keine Variante ohne Zugriffsbereich, die man versehentlich verwenden könnte. Zugriffe auf fremde Objekte werden mit „nicht gefunden" beantwortet — mit derselben Antwort wie für nicht existierende Objekte, damit deren Existenz nicht durchsickert. Tests prüfen genau diese Grenze.

Anmeldung ausschließlich über OIDC, mit PKCE (S256), Nonce-Prüfung und einem state, der genau einmal eingelöst werden kann. Die Rolle wird bei jeder Anmeldung aus dem Gruppen-Claim neu bestimmt.

Sitzungen liegen serverseitig und sind damit widerrufbar. In der Datenbank steht nur der SHA-256 des Cookie-Werts — ein Datenbankleck liefert keine verwendbaren Sitzungen. Das Cookie ist HttpOnly und SameSite=Lax; das Secure-Flag richtet sich nach dem Schema der konfigurierten Basis-URL. Verändernde Anfragen brauchen zusätzlich das CSRF-Token der Sitzung.

Agent-Anmeldung: Ein neuer Host bleibt „ausstehend", bis ein Administrator ihn freigibt — der Besitz des Instanz-Tokens allein reicht nicht, um verwaltet zu werden. Das dauerhafte Agent-Token liegt serverseitig nur als Hash.

IP-Zugriffsbeschränkung wird bei jeder Anfrage geprüft, nicht nur bei der Anmeldung — eine bestehende Sitzung soll einen Standortwechsel nicht überdauern. X-Forwarded-For wird nur ausgewertet, wenn die direkte Gegenstelle in der Liste vertrauenswürdiger Proxys steht; sonst zählt allein die Verbindungsadresse.

Aussperrschutz auf zwei Ebenen: Der letzte aktive Administrator lässt sich weder herabstufen noch sperren. Und der Regelrenderer verweigert Konfigurationen, die dem Agent den Weg zum Server abschneiden würden.

Secrets at rest: OIDC-Client-Secret und SMTP-Passwort werden mit AES-256-GCM verschlüsselt gespeichert. Der Master-Key kommt aus der Umgebung, aus einer Datei oder wird beim ersten Start erzeugt.

Content-Security-Policy ohne externe Quellen. Schriften sind lokal gebündelt, es gibt keine CDN-Abhängigkeit. script-src kommt ohne unsafe-inline aus: Der Server berechnet beim Start den SHA-256 des einen Inline-Skripts aus der eingebetteten index.html und nimmt genau diesen Hash auf.

Der Server läuft unprivilegiert. Nur der Agent braucht Rechte auf dem Host, und er führt niemals Nutzereingaben als Shell-Befehl aus.


Konfigurationsschlüssel

Variable Vorgabe Bedeutung
FWHUB_LISTEN :8080 Adresse des HTTP-Servers
FWHUB_DATA_DIR ./data SQLite-Datei, Master-Key, Zustand
FWHUB_BASE_URL Externe Adresse; Grundlage für Redirect-URI und Cookies
FWHUB_TRUSTED_PROXIES leer CIDRs, deren X-Forwarded-For ausgewertet wird
FWHUB_DB_DRIVER sqlite sqlite oder postgres
FWHUB_DB_DSN Datei im Datenverzeichnis Verbindungszeichenfolge
FWHUB_DB_SSL* sslmode und Zertifikate für PostgreSQL-mTLS
FWHUB_DB_TLS_DIR /etc/fwhub/db-tls Wird nach vorbereiteten DB-Zertifikaten abgesucht
FWHUB_MASTER_KEY / _FILE wird erzeugt Schlüssel für Secrets at rest
FWHUB_OIDC_* Issuer, Client, Claims, Admin-Gruppen
FWHUB_TLS_MODE leer file, selfsigned oder acme — eigenes HTTPS ohne Proxy
FWHUB_TLS_LISTEN Adresse des HTTPS-Servers, etwa :8443
FWHUB_MGMT_TCP_PORTS 22 Management-Ports der Basis-Policy
FWHUB_SETUP_TOKEN_DISABLED false Setup-Token abschalten
FWHUB_LOG_LEVEL info debug, info, warn, error
FWHUB_DEV false Frontend vom Vite-Server beziehen

Die vollständige Liste mit allen TLS- und SMTP-Schlüsseln steht kommentiert in deploy/docker/.env.example.


Betrieb hinter einem Reverse-Proxy

fwhub kann TLS selbst terminieren (FWHUB_TLS_MODE) oder es einem Proxy überlassen. Bei einem Proxy sind zwei Punkte wichtig:

  1. FWHUB_BASE_URL muss die öffentliche Adresse sein. Sie bestimmt die Redirect-URI und entscheidet, ob Sitzungscookies das Secure-Flag tragen.
  2. FWHUB_TRUSTED_PROXIES muss die Adresse des Proxys enthalten. Ohne diesen Eintrag sieht fwhub bei jedem Aufruf die Proxy-Adresse als Client-IP — die IP-Beschränkung liefe ins Leere, und das Audit-Log wäre wertlos.

Die Agents gehen üblicherweise am Proxy vorbei direkt auf den TLS-Port, damit zwischen Host und Server nichts vermittelt. Bei selbstsigniertem Zertifikat sichert der Agent-TLS-Pin diese Verbindung.


Projektstruktur

server/
  cmd/fwhub-server/     Einstiegspunkt
  internal/
    agenthub/            Agent-Verbindungen, Handshake, Heartbeat
    api/                 Router, Handler, Auslieferung von Frontend und Agent
    app/                 Laufzeitverdrahtung, austauschbare Datenbank
    auth/                OIDC, Sitzungen, CSRF, IP-Beschränkung
    config/              Konfiguration aus Umgebung und Datei
    db/                  SQLite/PostgreSQL, Migrationen je Dialekt
    dbtest/              Testdatenbanken für beide Dialekte
    demo/                erzeugte Ereignisse für die Oberflächenentwicklung
    live/                Verteilung der Ereignisse an die Browser
    notify/              SMTP-Benachrichtigung
    rollout/             Ausrollen der gerenderten Regeln
    rules/               Regelmodell, Merge-Reihenfolge, nft-Renderer
    secret/              Verschlüsselung der Secrets at rest
    setup/               First-Run-Wizard
    store/               Datenmodelle, Abfragen mit Owner-Scoping
    tlsserve/            eigenes HTTPS: file, selfsigned, ACME
agent/
  cmd/fwhub-agent/      Einstiegspunkt, -check, -version, -uninstall
  internal/
    agent/               Verdrahtung, Zustand über Neustarts hinweg
    detect/              Firewall-Stack und Fähigkeiten des Hosts
    events/              NFLOG über Netlink, /dev/kmsg als Rückfall
    nft/                 Anwenden mit Bestätigung und Rücknahme
    selfupdate/          Selbstaktualisierung mit Rückfall
    transport/           ausgehende TLS-Verbindung zum Server
    uninstall/           restloses Entfernen vom Host
web/                     React-Oberfläche
design/                  Design-Referenz und destillierte Design-Tokens
deploy/
  install.sh             Einrichtungsskript für Debian, RHEL und Alpine
  systemd/               Units für Server und Agent
  openrc/                Init-Skript und conf.d für Alpine
  docker/                Dockerfile, compose (SQLite und PostgreSQL), .env
  ansible/               Rolle und Beispiel-Playbook für das Agent-Rollout
docs/                    Inbetriebnahme, Authentik, Windows

Getroffene Annahmen und ihre Begründung stehen in DECISIONS.md.


Lizenz

Noch nicht festgelegt.