Files
infrastructure/docs/GNAI-005-SEC_Reverse-Proxy.md
T

10 KiB
Raw Blame History

Technical Architecture Specification

Dokumenten-ID: GNAI-006-SEC
Titel: Homelab / Infrastructure – Reverse Proxy & Containerized IPS Specification
Version: 1.0
Status: Aktiv
Vorgänger / Referenzen: GNAI-003-INFRA, GNAI-004-SEC


1. Zweck & Geltungsbereich

Dieses Dokument definiert die verbindliche System- und Sicherheitsarchitektur für den zentralen Reverse Proxy und das Intrusion Prevention System (IPS) der Ghostnet-Infrastruktur auf Proxmox LXC 103 (reverse-proxy).

Umfang dieses Dokuments:

  • Betrieb von Nginx Proxy Manager (NPM) als zentraler Ingress-Controller
  • Containerisiertes Fail2Ban als IPS mit nftables-Integration auf dem LXC
  • Log-Parsing-Regeln und Filterdefinitionen für das NPM-spezifische Access-Log-Format
  • Gehärtete Firewall-Baseline und Zugriffssteuerung

2. System- Baseline (LXC 103)

Parameter Wert
Container-ID CT 103
Hostname reverse-proxy
Betriebssystem Debian GNU/Linux 13 (Trixie)
Unprivileged Ja
Cores / RAM / Swap 2 Cores / 1024 MB RAM / 512 MB Swap
RootFS 8 GB
Netzwerkkonfiguration IP: 10.0.0.10/24
LXC Features nesting=1, keyctl=1
Proxmox Host Proxmox VE 8.4.21 (Kernel 6.8.12-23-pve)

Hinweis zur Proxmox-Firewall: Proxmox Cluster- und Node-Firewalls sind deaktiviert (enable 0). Die Paketfilterung erfolgt exklusiv lokal über nftables innerhalb des LXC.


3. Netzwerk- & Firewall-Architektur (nftables)

Die lokale Paketfilterung basiert auf nftables mit einer strikten INPUT policy drop-Doktrin.

table inet filter {
        chain input {
                type filter hook input priority filter; policy drop;
                iifname "lo" accept
                ct state invalid drop
                ct state established,related accept
                ip protocol icmp accept
                ip saddr 10.0.0.0/24 tcp dport { 22, 81 } accept
                tcp dport { 80, 443 } accept
        }

        chain forward {
                type filter hook forward priority filter; policy accept;
        }

        chain output {
                type filter hook output priority filter; policy accept;
        }
}

Port-Matrix

Port Protokoll Zweck Erreichbarkeit
80 TCP HTTP Ingress (NPM) Öffentlich
443 TCP HTTPS Ingress (NPM) Öffentlich
81 TCP NPM Web Admin Interface Nur LAN (10.0.0.0/24)
22 TCP OpenSSH Management Nur LAN (10.0.0.0/24)
3000 TCP NPM Node.js Backend Intern / Nicht exponiert

4. Container-Infrastruktur (Podman Quadlets)

Nginx Proxy Manager und Fail2Ban werden als rootful Podman-Container via systemd Quadlets mit Network=host betrieben.

4.1 Nginx Proxy Manager Quadlet

  • Datei: /etc/containers/systemd/nginx-proxy-manager.container
  • Image: docker.io/jc21/nginx-proxy-manager:2.11.1 (ausdrücklich gepinnt)
[Unit]
Description=Nginx Proxy Manager
Wants=network-online.target
After=network-online.target

[Container]
Image=docker.io/jc21/nginx-proxy-manager:2.11.1
ContainerName=nginx-proxy-manager
Network=host
Volume=/opt/nginx-proxy-manager/data:/data:rw
Volume=/opt/nginx-proxy-manager/letsencrypt:/etc/letsencrypt:rw
Environment=DISABLE_IPV6=true
Environment=TZ=Europe/Berlin

[Service]
Restart=always
TimeoutStartSec=300

[Install]
WantedBy=multi-user.target

4.2 Fail2Ban IPS Quadlet

  • Datei: /etc/containers/systemd/fail2ban.container
  • Image: lscr.io/linuxserver/fail2ban:latest
  • Capabilities: NET_ADMIN, NET_RAW (kein --privileged)
[Unit]
Description=Fail2Ban
Wants=network-online.target
After=network-online.target

[Container]
Image=lscr.io/linuxserver/fail2ban:latest
ContainerName=fail2ban
Network=host
AddCapability=NET_ADMIN
AddCapability=NET_RAW
Volume=/opt/fail2ban/config:/config:rw
Volume=/opt/nginx-proxy-manager/data/logs:/remotelogs/npm:ro
Environment=TZ=Europe/Berlin

[Service]
Restart=always
TimeoutStartSec=300

[Install]
WantedBy=multi-user.target

5. NPM Logformat & Parsing-Regeln

NPM nutzt ein eigenes Access-Log-Format, bei dem die eigentliche Client-IP nicht am Zeilenanfang steht.

Beispielzeile:

[03/Sep/2026:18:35:02 +0200] - 204 204 - POST https jellyfin.ghostnet.selfhost.eu "/Sessions/Playing/Progress" [Client 93.228.100.74] [Length 0] [Gzip -] [Sent-to 10.0.0.11] "JellyfinDesktop/2.0.0" "-"

Verbindliche Parser-Regeln:

  1. Die Client-IP muss zwingend aus dem Feld [Client <HOST>] extrahiert werden.
  2. URL-Pfade oder Host-Header dürfen nicht fälschlicherweise als IP-Quelle interpretiert werden.
  3. Statuscodes oder Response-Zeiten sind für automatisches Banning ungeeignet, da legitim nicht erreichbare Backends (502) False Positives auslösen würden.

6. Fail2Ban-Konfiguration & Jails

LinuxServer.io nutzt /config/fail2ban/ als interne Konfigurationsstruktur. Sämtliche Konfigurationen liegen persistent auf dem Host unter /opt/fail2ban/config/fail2ban/.

6.1 Wichtige Erkenntnis zur Action-Syntax

Standardmäßig nutzt die Action nftables-multiport bei fehlenden Parametern Port 22 (SSH). Sperrports müssen zwingend direkt in der action-Zeile des Jails in eckigen Klammern übergeben werden, um Anfragen auf HTTP/HTTPS durchzusetzen:

action = nftables-multiport[port="80,443", protocol=tcp]

6.2 Jail 1: Exploit & Scanner Protection (npm-scanner)

Gezielte Blockade von Pfad-Traversal-Versuchen, Secret-Abfragen und automatisierten Framework-Scans.

  • Filter: /opt/fail2ban/config/fail2ban/filter.d/npm-scanner.conf

    [Definition]
    failregex = ^\[.*\] - [0-9]{3} [0-9]{3} - (GET|POST|HEAD) [^ ]+ [^ ]+ "/(?:[^"]*/)?(?:\.env[^"/]*|\.git/[^" ]*|\.aws/[^" ]*|wp-config\.php[^" ]*|wp-login\.php(?:\?[^" ]*)?|wp-admin(?:/[^" ]*)?|actuator(?:/[^" ]*)?|_ignition(?:/[^" ]*)?|storage/logs(?:/[^" ]*))" \[Client <HOST>\]
    ignoreregex =
    
  • Jail: /opt/fail2ban/config/fail2ban/jail.d/npm-scanner.local

    [npm-scanner]
    enabled  = true
    filter   = npm-scanner
    backend  = auto
    logpath  = /remotelogs/npm/proxy-host-*_access.log
    findtime = 10m
    maxretry = 3
    bantime  = 1h
    ignoreip = 127.0.0.1/8 10.0.0.0/24
    action   = nftables-multiport[port="80,443", protocol=tcp]
    

6.3 Jail 2: Bad User-Agents (npm-badbots)

Blockade bekannter Angriffs-Scanner und Reconnaissance-Tools.

  • Filter: /opt/fail2ban/config/fail2ban/filter.d/npm-badbots.conf
    (Wichtig: Nutzt lokale Case-Insensitivity (?i:...), um Python-RegEx-Kompilierungsfehler zu vermeiden)

    [Definition]
    failregex = ^\[.*\] - [0-9]{3} [0-9]{3} - (?:GET|POST|HEAD|OPTIONS) [^ ]+ [^ ]+ "[^"]*" \[Client <HOST>\] .* "[^"]*(?i:sqlmap|nikto|nmap|masscan|zgrab|nessus|openvas|dirbuster|gobuster|wpscan|python-requests|go-http-client|censys|whatweb|acunetix)[^"]*"
    ignoreregex =
    
  • Jail: /opt/fail2ban/config/fail2ban/jail.d/npm-badbots.local

    [npm-badbots]
    enabled  = true
    filter   = npm-badbots
    backend  = auto
    logpath  = /remotelogs/npm/proxy-host-*_access.log
    findtime = 10m
    maxretry = 2
    bantime  = 24h
    ignoreip = 127.0.0.1/8 10.0.0.0/24
    action   = nftables-multiport[port="80,443", protocol=tcp]
    

7. Aktive Proxy Hosts

Aktuell im Nginx Proxy Manager konfigurierte und aktive Routen:

ID Hostname Backend-Ziel Dienst
1 jellyfin.ghostnet.selfhost.eu 10.0.0.11:8096 Jellyfin Media Server
3 nextcloud.ghostnet.selfhost.eu 10.0.0.12:8080 Nextcloud Cloud Storage
4 git.ghostnet.selfhost.eu 10.0.0.17:3000 Gitea Code Hosting
7 auth.ghostnet.selfhost.eu 10.0.0.4:8080 Keycloak Identity Provider
10 japan.ghostnet.selfhost.eu 10.0.0.23:80 Japanese Web App
12 vault.ghostnet.selfhost.eu 10.0.0.47:8080 Vaultwarden Password Manager

8. Betriebs- & Diagnosekommandos

Fail2Ban-Status

# Gesamtstatus der aktiven Jails
podman exec fail2ban fail2ban-client status

# Spezifischen Jail prüfen
podman exec fail2ban fail2ban-client status npm-scanner
podman exec fail2ban fail2ban-client status npm-badbots

Filter mit echten Logs testen

podman exec fail2ban fail2ban-regex \
    /remotelogs/npm/proxy-host-10_access.log \
    /config/fail2ban/filter.d/npm-scanner.conf

nftables-Regeln & aktive Bans auf dem Host verifizieren

nft list ruleset | grep -A 10 "table inet f2b-table"

Manuelle Sperre / Freigabe

# IP manuell sperren
podman exec fail2ban fail2ban-client set npm-scanner banip <IP>

# IP manuell freigeben
podman exec fail2ban fail2ban-client set npm-scanner unbanip <IP>

9. Sicherheits- & Betriebsregeln für Administrierende

  1. Kein generisches Statuscode-Banning: Generische HTTP 4xx- oder 5xx-Fehler dürfen nicht als pauschales Ban-Kriterium verwendet werden.
  2. Keine verfrühten Upgrades: Die NPM-Version ist auf 2.11.1 festgeschrieben. Upgrades müssen separat in einer Testumgebung evaluiert werden.
  3. Privilegien-Minimierung: Der Fail2Ban-Container wird strikt ohne --privileged betrieben (NET_ADMIN und NET_RAW reichen aus).
  4. Whitelist-Pflicht: Das Subnetz 10.0.0.0/24 sowie 127.0.0.1/8 müssen in allen Jails in ignoreip hinterlegt bleiben, um Aussperrungen zu verhindern.
  5. Syntax-Check vor Reload: Jede Änderung an Fail2Ban-Konfigurationen ist vorab mit podman exec fail2ban fail2ban-client -t auf Gültigkeit zu prüfen.

10. Backup & Disaster Recovery

Es existiert ein vollständiges Proxmox LXC Snapshot-Backup von CT 103:

  • Backup-Datei: /var/lib/vz/dump/vzdump-lxc-103-2026_08_31-19_11_10.tar.zst

Vor strukturellen Änderungen an nftables oder den Podman Quadlets ist stets ein neuer Proxmox LXC-Snapshot zu erstellen.


11. Änderungsverlauf

Datum Version Geändert von Beschreibung
2026-08-28 0.1 spektr Erstellung der ersten Baseline-Drafts.
2026-09-05 0.9 spektr Implementierung Fail2Ban Container & Test-Setup (F2B-01).
2026-09-07 1.0 spektr Korrektur Action-Syntax (port="80,443"), Behebung RegEx-Fehler (npm-badbots), E2E-Validierung in nftables abgeschlossen (F2B-02). Finales SOP erstellt.