Wissen, was Ihre Nginx-Konfiguration tatsächlich zulässt – ein Auditor, der effektiven Zugriff berechnet
Eine nginx-Konfiguration sagt selten auf einen Blick, wer tatsächlich worauf zugreifen kann.
allow/deny-Direktiven vererben sich nach unten, location-Blöcke überschreiben sich gegenseitig,
ein auth_basic zwei Ebenen höher kann gelten oder nicht, und ein einzeln fehlendes
default deny lässt leise eine Tür offen. nginx-acl-guard beantwortet eine
Frage deterministisch: Für jedes server/location, was ist die effektive
Zugriffsbeschränkung – und hat die Änderung, die ich vornehme, diese gerade erweitert?
Die Richtlinie berechnen, sie nicht lesen
Der Audit greift nie in der Konfiguration. Er parst mit crossplane in den echten
Direktivenbaum, löst die Vererbung so auf, wie es nginx tatsächlich tut, und emittiert
die effektive Allow/Deny für jeden Endpunkt. Darüber hinaus gibt es benannte Prüfungen –
zufällige Offenlegungen (A1..A7), TLS-Schwächen (T1..T6), riskante Upstream-Muster
(U1..U4) –, sodass „dieses location ist vom öffentlichen Internet erreichbar“ als
Feststellung und nicht als etwas, das man während eines Vorfalls entdeckt, erscheint.
Eine der Grundregeln des Projekts ist bei der Funktionsweise sehr direkt:
Deterministisches Parsen nur —
crossplane, keine Regex, und kein LLM, das Sicherheitsentscheidungen trifft.
Ein Regex, der zu 99 % richtig über eine Zugriffsbeschränkung ist, ist eine Schwachstelle. Die Sicherheitslage muss berechnet werden können, reproduzierbar, sonst kann man ihr nicht vertrauen.
Die Änderung schützen, nicht nur den Audit
Das Auditing ist die einfache Hälfte. Die interessante Hälfte ist es, jemandem zu erlauben, den Zugriff sicher zu bearbeiten. ACL-Profile können aus bestehenden Snippets importiert oder von Grund auf neu generiert werden, für Sites zugewiesen und als union-allow + default-deny kombiniert werden. Aber die Live-Konfiguration auf der Festplatte ist heilig – nichts schreibt direkt darauf. Jeder Apply durchläuft eine Pipeline:
edit / assign an ACL profile
│
├─ git backup of the current config
├─ render into a staging tree
├─ nginx -t on staging → ist es überhaupt gültig?
├─ diff the EFFECTIVE policy → was hat sich tatsächlich geändert?
├─ access expanded? ───────────→ erfordert ein explizites Bestätigungstoken
├─ atomic swap (symlink flip)
└─ rollback on any error
Der Diff ist der ganze Punkt. Es ist kein Text-Diff der Konfiguration – es ist ein
Diff der berechneten effektiven Richtlinie. Das Verengen des Zugriffs wird leise angewendet; die Erweiterung – ein neues allow, ein entferntes deny, eine Route, die öffentlich wird – verweigert das Vorgehen ohne ein explizites Bestätigungstoken. Man kann den Zugriff nicht versehentlich erweitern, weil die Erweiterung durch Konstruktion und nicht durch Überprüfung erkannt wird.
Least privilege, bis ganz nach unten
Der Dienst läuft als unprivilegierter nginxguard-Benutzer, standardmäßig schreibgeschützt, schreibt nur in sein eigenes Profilverzeichnis. Die wenigen privilegierten Schritte – das Testen des Staging-Baums, der atomare Swap – laufen über ein winziges Hilfsprogramm mit einem engen Vertrag, niemals über den Webprozess selbst. Bei jedem Fehler ist die Regel ein Hard Stop: kein automatischer Workaround, kein halb angewendeter „Best Effort“-Zustand zum späteren Entwirren. Und das Generieren von server-Blöcken von Grund auf ist absichtlich außer Reichweite – das Tool schützt den Zugriff auf Sites, es erfindet sie nicht.
Der Stack ist bewusst klein und langweilig: Python, FastAPI, Jinja2 + HTMX, rohes SQLite mit versionierten Migrationen, crossplane für das Parsen. Der Wert lag nie im Framework. Er liegt darin, „was diese Konfiguration erlaubt“ als etwas zu behandeln, das man berechnet und diffet – so wie man eine Zahl behandelt –, anstatt es sich anzusehen und zu hoffen, dass man es richtig gelesen hat.
Transparenz: Die meisten Beiträge werden mit KI-Unterstützung verfasst, und die nicht-englischen Versionen werden von einem lokalen LLM maschinell übersetzt und anschließend geprüft. Fehler entdeckt? Bitte gib mir Bescheid.