From b36499bbf580448c1d311fb49e0f6962b9659491 Mon Sep 17 00:00:00 2001 From: SchiWaGoA Date: Sun, 25 Oct 2026 19:50:33 +0100 Subject: [PATCH] Docs: tested single-host install, NPM integration, installer --docker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - README: step-by-step installation of web UI and agent on one host, exactly as tested (agent after keygen, all "run" helpers before "up -d", http PUBLIC_URL and TRUSTED_PROXY_HOPS=0 without a proxy) - README: attach to an existing Nginx Proxy Manager in Docker – shared network via docker-compose.override.yaml, no published port, gw_priority so SSH to the agent keeps the source address of the own network (otherwise from= rejects it), TLS settings, test - install.sh --docker: the new manual jail bans in INPUT and DOCKER-USER - Running container: helpers via "docker compose exec … python -m webui" (run fails with "Address already in use"); troubleshooting entries Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 4 +- README.md | 268 +++++++++++++++++++++++++++++++++++++++--- agent/install.sh | 13 ++ docs/agent.md | 20 +++- docs/configuration.md | 6 +- docs/installation.md | 7 ++ docs/operations.md | 6 +- docs/reverse-proxy.md | 5 + 8 files changed, 303 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 096328e..ae2212c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,8 @@ First public version, generalised from a private single-site tool. - English and German UI with a language dropdown; new languages via `locales/xx.json` - Optional Loki history, Grafana links and GeoIP (DB-IP Lite auto-download) - Origin map: banned IPs as points on a world map (bundled, no external tiles); click filters the list -- CLI: `hash-password`, `keygen`, `keyscan`, `check` +- CLI: `hash-password`, `keygen`, `keyscan`, `check` (`docker compose exec … python -m webui …` while running) +- GeoIP download sends its own user agent (db-ip.com rejects Python's default with 403) - Hardened image: non-root, read-only root filesystem, no pip/setuptools at runtime, SSH client ignores the system ssh_config and never uses GSSAPI - Log lines (`LOGIN_FAILED user=… ip=…`) are safe for fail2ban: user-controlled values @@ -24,3 +25,4 @@ First public version, generalised from a private single-site tool. - Error codes for translated messages, `version` command, fallback for fail2ban without `--with-time` - `install.sh` / `uninstall.sh`: dedicated user with sudo rule (or root), `from=` restriction, custom authorized_keys file, optional manual jail, backups, dry run, self test +- `install.sh --docker`: manual jail bans in INPUT and DOCKER-USER (published Docker ports) diff --git a/README.md b/README.md index 3dc89eb..2e668db 100644 --- a/README.md +++ b/README.md @@ -74,40 +74,278 @@ danach den Container neu starten). ### fail2ban-Hosts (Agent) +- fail2ban ≥ 0.11 (Start-/Endzeiten der Sperren brauchen `--with-time`) +- `sudo` für den Standardbenutzer `f2bweb`; mit einem root-Schlüssel nicht nötig +- `bash`, `ssh-keygen` (Teil von OpenSSH) für den Installer + +## Installation auf einem Host + +Weboberfläche und Agent auf demselben Docker-Host – so getestet auf Ubuntu 26.04 (LXC). +Weitere fail2ban-Hosts kommen später nur mit dem Agenten dazu ([docs/agent.md](docs/agent.md)). +Im Beispiel hat der Host die LAN-Adresse `192.168.1.10` – überall durch deine ersetzen. + +### fail2ban vorbereiten + ```bash apt install -y fail2ban python3 sudo openssh-server +``` + +```bash systemctl enable --now fail2ban +``` + +```bash fail2ban-client ping # Server replied: pong ``` -- fail2ban ≥ 0.11 (Start-/Endzeiten der Sperren brauchen `--with-time`) -- `sudo` nur für den Standardbenutzer `f2bweb`; mit einem root-Schlüssel nicht nötig -- `bash`, `ssh-keygen` (Teil von OpenSSH) für den Installer +### Quellen holen -## Schnellstart +```bash +git clone https://git.retronerd.at/schiwagoa/fail2ban-webui.git /opt/fail2ban-webui-src +``` -Voraussetzungen siehe oben (Grundpakete, Docker, Agent auf den fail2ban-Hosts). +### Weboberfläche einrichten ```bash mkdir -p /opt/fail2ban-webui/{config,data} && cd /opt/fail2ban-webui -curl -fsSLo docker-compose.yaml https://git.retronerd.at/schiwagoa/fail2ban-webui/raw/branch/main/examples/docker-compose.yaml -curl -fsSLo .env https://git.retronerd.at/schiwagoa/fail2ban-webui/raw/branch/main/examples/.env.example -curl -fsSLo config/hosts.yaml https://git.retronerd.at/schiwagoa/fail2ban-webui/raw/branch/main/examples/hosts.yaml.example -chown -R 10050:10050 data ``` ```bash -docker compose run --rm -it fail2ban-webui hash-password # Zeile in die .env übernehmen -nano .env # PUBLIC_URL, Hash, DEFAULT_LANG=de … -docker compose run --rm fail2ban-webui keygen # öffentlicher Schlüssel für die Agenten -# auf jedem fail2ban-Host den Agenten installieren (docs/agent.md), danach: -nano config/hosts.yaml # Beispiel-Hosts durch deine ersetzen! +curl -fsSLo docker-compose.yaml https://git.retronerd.at/schiwagoa/fail2ban-webui/raw/branch/main/examples/docker-compose.yaml +``` + +```bash +curl -fsSLo .env https://git.retronerd.at/schiwagoa/fail2ban-webui/raw/branch/main/examples/.env.example +``` + +Der Container läuft als UID 10050 und muss in `data/` schreiben: + +```bash +chown -R 10050:10050 data && chmod 700 data +``` + +Für den Zugriff direkt im LAN (ohne Reverse Proxy) den Port an die LAN-Adresse binden – +hinter einem Nginx Proxy Manager diesen Schritt [weglassen](#hinter-einem-vorhandenen-nginx-proxy-manager-docker): + +```bash +sed -i 's/"127.0.0.1:8080:8080"/"192.168.1.10:8080:8080"/' docker-compose.yaml +``` + +Adresse, Sprache und Proxy-Einstellung setzen. Ohne TLS muss `PUBLIC_URL` mit `http://` +beginnen, sonst schickt der Browser das Sitzungs-Cookie nicht mit; ohne Proxy gehört +`TRUSTED_PROXY_HOPS` auf `0`: + +```bash +sed -i -e 's|^PUBLIC_URL=.*|PUBLIC_URL=http://192.168.1.10:8080|' -e 's|^DEFAULT_LANG=.*|DEFAULT_LANG=de|' -e 's|^TRUSTED_PROXY_HOPS=.*|TRUSTED_PROXY_HOPS=0|' .env +``` + +Passwort-Hash erzeugen (mindestens 12 Zeichen, zweimal eingeben): + +```bash +docker compose run --rm -it fail2ban-webui hash-password +``` + +Die ausgegebene Zeile `AUTH_PASSWORD_HASH='scrypt:…'` kopieren und in der `.env` die Zeile +`AUTH_PASSWORD_HASH='scrypt:...'` damit ersetzen – die einfachen Anführungszeichen bleiben: + +```bash +nano .env +``` + +SSH-Schlüssel der Weboberfläche erzeugen: + +```bash +docker compose run --rm fail2ban-webui keygen +``` + +### Agent installieren + +Probelauf – zeigt nur, was passieren würde. `172.23.51.10` ist die feste Adresse der +Weboberfläche in ihrem Docker-Netz: + +```bash +bash /opt/fail2ban-webui-src/agent/install.sh --key-file /opt/fail2ban-webui/data/ssh/id_ed25519.pub --from 172.23.51.10 --dry-run +``` + +Installieren und mit `y` bestätigen – am Ende meldet der Selbsttest +`2 jails, 0 bans: manual-block, sshd`. Laufen auf dem Host weitere Docker-Dienste mit +veröffentlichten Ports (z. B. ein Nginx Proxy Manager), zusätzlich `--docker` anhängen, +damit manuelle Sperren auch dort greifen: + +```bash +bash /opt/fail2ban-webui-src/agent/install.sh --key-file /opt/fail2ban-webui/data/ssh/id_ed25519.pub --from 172.23.51.10 +``` + +### Host eintragen und testen + +```bash +cat > config/hosts.yaml <<'EOF' +hosts: + - name: srv200 + address: host.docker.internal +EOF +``` + +Host-Schlüssel holen – die Fingerprints werden angezeigt, mit `y` bestätigen: + +```bash docker compose run --rm -it fail2ban-webui keyscan +``` + +Verbindung testen – erwartet: `OK srv200 agent 1.0.0, 2 jails, 0 bans`: + +```bash docker compose run --rm fail2ban-webui check +``` + +### Starten + +```bash docker compose up -d ``` -Die Oberfläche lauscht auf `127.0.0.1:8080` – davor gehört ein Reverse Proxy mit TLS. +```bash +docker compose ps # Up … (healthy) nach etwa 30 Sekunden +``` + +```bash +docker compose logs fail2ban-webui # hosts: srv200 – auth: local – geoip: on, danach "GeoIP database updated" +``` + +Im Browser `http://192.168.1.10:8080` öffnen und mit `admin` und dem Passwort von oben anmelden. + +> `docker compose run` funktioniert nur, solange der Dienst **nicht** läuft – die feste +> IP-Adresse wäre sonst doppelt vergeben (`Address already in use`). Im laufenden Betrieb +> die Hilfsbefehle mit `exec` aufrufen, z. B. `docker compose exec fail2ban-webui python -m webui check`. + +## Hinter einem vorhandenen Nginx Proxy Manager (Docker) + +Ausgangslage: Nginx Proxy Manager (NPM) läuft auf demselben Host als Docker-Container, +fail2ban ist auf dem Host installiert. Die Weboberfläche bekommt dann **keinen** +veröffentlichten Port, sondern hängt zusätzlich im Docker-Netz von NPM; erreichbar ist sie +nur über NPM mit TLS. + +Voraussetzung: Docker Engine ≥ 28 und Compose ≥ 2.33 (für `gw_priority`, siehe Schritt 3). + +### 1. Manuelle Sperren auch für Docker-Ports + +Verkehr zu veröffentlichten Docker-Ports – also auch zu NPM – läuft nicht über die Kette +`INPUT`, sondern über `DOCKER-USER`. Den Agenten deshalb mit `--docker` installieren: + +```bash +bash /opt/fail2ban-webui-src/agent/install.sh --key-file /opt/fail2ban-webui/data/ssh/id_ed25519.pub --from 172.23.51.10 --docker +``` + +War der Agent schon ohne `--docker` installiert, lässt der Installer das vorhandene Jail +unverändert. Dann die Datei sichern, die zwei `action`-Zeilen ergänzen und fail2ban neu laden: + +```bash +mkdir -p /backups/config_backups && cp -a /etc/fail2ban/jail.d/manual-block.local /backups/config_backups/manual-block.local_$(date +%F_%H-%M) +``` + +```bash +nano /etc/fail2ban/jail.d/manual-block.local +``` + +```ini +action = iptables-allports[name=manual-block] + iptables-allports[name=manual-block-docker, chain=DOCKER-USER] +``` + +```bash +fail2ban-client reload && fail2ban-client status manual-block +``` + +### 2. Docker-Netz von NPM ermitteln + +```bash +docker ps --format '{{.Names}} {{.Image}}' | grep -iE 'npm|proxy-manager' +``` + +```bash +docker inspect --format '{{range $n, $c := .NetworkSettings.Networks}}{{$n}}{{"\n"}}{{end}}' +``` + +### 3. Weboberfläche ins NPM-Netz hängen + +Den veröffentlichten Port entfernen – erreichbar ist die Oberfläche danach nur noch über NPM: + +```bash +cd /opt/fail2ban-webui && sed -i '/^ ports:$/,/8080:8080"$/d' docker-compose.yaml +``` + +Das NPM-Netz kommt in eine eigene Datei `docker-compose.override.yaml`, die Docker Compose +automatisch dazulädt. Den Netznamen aus Schritt 2 in `NPM_NET` setzen: + +```bash +NPM_NET=npm_default +``` + +```bash +cat > docker-compose.override.yaml </dev/null || die "--docker needs iptables (installed together with Docker)" if [ -n "$KEY_FILE" ]; then [ -r "$KEY_FILE" ] || die "cannot read $KEY_FILE" @@ -166,6 +173,7 @@ fail2ban-webui agent $AGENT_VERSION – fail2ban $F2B_VERSION – $(hostname key $KEY_FP allowed from ${FROM:-ANY ADDRESS (consider --from)} manual jail $MANUAL_JAIL $( [ $JAIL_EXISTS -eq 1 ] && echo "(exists, kept)" || { [ $INSTALL_JAIL -eq 1 ] && echo "(will be created)" || echo "(MISSING – bans will fail)"; } ) + ban chains $( [ $DOCKER_CHAIN -eq 1 ] && echo "INPUT + DOCKER-USER (--docker)" || echo "from the banaction in jail.local" ) backups $BACKUP_DIR EOF echo @@ -282,6 +290,11 @@ EOF echo "maxretry = 1" echo "bantime = -1" [ -z "$BANACTION" ] || echo "banaction = $BANACTION" + if [ $DOCKER_CHAIN -eq 1 ]; then + # published Docker ports bypass INPUT and pass DOCKER-USER instead + echo "action = iptables-allports[name=$MANUAL_JAIL]" + echo " iptables-allports[name=$MANUAL_JAIL-docker, chain=DOCKER-USER]" + fi } > "/etc/fail2ban/jail.d/$MANUAL_JAIL.local" fail2ban-client reload >/dev/null || die "fail2ban reload failed – check: fail2ban-client -t" fail2ban-client status "$MANUAL_JAIL" >/dev/null 2>&1 || die "jail $MANUAL_JAIL did not start – check the fail2ban log" diff --git a/docs/agent.md b/docs/agent.md index d53f39e..ed890aa 100644 --- a/docs/agent.md +++ b/docs/agent.md @@ -101,6 +101,7 @@ Schlüsseleintrag und lässt deine Konfiguration unverändert. | `--authorized-keys-file PFAD` | `~USER/.ssh/authorized_keys` | wohin der Schlüsseleintrag kommt – siehe [verwaltete Schlüssel](#hosts-mit-verwalteten-authorized_keys) | | `--manual-jail NAME` | `manual-block` | Jail für manuelle Sperren | | `--banaction AKTION` | aus `jail.local` | Banaction für ein neu angelegtes manuelles Jail | +| `--docker` | – | Docker-Host: das neue manuelle Jail sperrt in `INPUT` **und** `DOCKER-USER` (iptables) – nicht mit `--banaction` kombinierbar | | `--protect NETZE` | – | zusätzliche geschützte Adressen (nur wenn die Konfigurationsdatei geschrieben wird) | | `--no-jail` | – | kein manuelles Jail anlegen | | `--force-config` | – | vorhandene `/etc/f2b-webctl.conf` überschreiben | @@ -150,18 +151,27 @@ manuelle Sperren, dessen Namen mit `--manual-jail` übergeben. Die Standardaktionen von fail2ban sperren in der Kette `INPUT`. Verkehr zu **veröffentlichten Docker-Ports** läuft aber nicht über `INPUT`, sondern über `FORWARD` → `DOCKER-USER`; solche -Sperren würden deine Container also nicht schützen. Auf einem Docker-Host das manuelle Jail -deshalb mit einer Aktion anlegen, die die Kette `DOCKER-USER` nutzt, z. B. mit iptables: +Sperren würden deine Container also nicht schützen. Auf einem Docker-Host den Agenten deshalb +mit `--docker` installieren – das manuelle Jail sperrt dann in beiden Ketten: ```bash -bash install.sh --key-file /tmp/webui.pub --from 198.51.100.10 \ - --banaction "iptables-allports[chain=DOCKER-USER]" +bash install.sh --key-file /tmp/webui.pub --from 198.51.100.10 --docker ``` +Das erzeugt im Jail: + +```ini +action = iptables-allports[name=manual-block] + iptables-allports[name=manual-block-docker, chain=DOCKER-USER] +``` + +Ein schon vorhandenes Jail ändert der Installer nicht; die zwei Zeilen dann von Hand in +`/etc/fail2ban/jail.d/manual-block.local` eintragen und `fail2ban-client reload` ausführen. + Nach der ersten Sperre prüfen: ```bash -iptables -S DOCKER-USER | head +iptables -S DOCKER-USER | grep f2b ``` Läuft die Weboberfläche auf demselben Docker-Host, in `hosts.yaml` `address: host.docker.internal` diff --git a/docs/configuration.md b/docs/configuration.md index 9544cd6..21ffe60 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -21,9 +21,9 @@ hosts: Nach dem Hinzufügen eines Hosts `keyscan` erneut ausführen, dann neu starten: ```bash -docker compose run --rm -it fail2ban-webui keyscan -docker compose run --rm fail2ban-webui check -docker compose up -d --force-recreate +docker compose exec -it fail2ban-webui python -m webui keyscan +docker compose exec fail2ban-webui python -m webui check +docker compose restart ``` `F2B_HOSTS=name=adresse,name2=adresse2` (Port 22, Benutzer `SSH_USER`) wird aus diff --git a/docs/installation.md b/docs/installation.md index 72bfe64..00bcc7c 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -229,6 +229,13 @@ docker compose logs -f fail2ban-webui # mit Strg+C verlassen Das Log zeigt eine Zusammenfassung wie `fail2ban-webui 1.0.0 – hosts: web01, lxc-proxy, dockerhost – auth: local – loki: off – geoip: on`. +Ab jetzt läuft der Dienst. `docker compose run …` würde wegen der festen IP-Adresse mit +`Address already in use` scheitern – Hilfsbefehle deshalb im laufenden Container aufrufen: + +```bash +docker compose exec fail2ban-webui python -m webui check +``` + Lokaler Test ohne Proxy: ```bash diff --git a/docs/operations.md b/docs/operations.md index 8726f9b..8bbb45a 100644 --- a/docs/operations.md +++ b/docs/operations.md @@ -8,7 +8,7 @@ Alle Befehle im Verzeichnis mit der `docker-compose.yaml` ausführen (z. B. `/op docker compose ps # Up … (healthy) docker compose logs --tail 100 fail2ban-webui docker compose logs -f fail2ban-webui | grep -E 'AUDIT|LOGIN|ERROR|WARNING' -docker compose run --rm fail2ban-webui check # jeder Agent erreichbar? +docker compose exec fail2ban-webui python -m webui check # jeder Agent erreichbar? ``` Wichtige Logzeilen: @@ -39,7 +39,7 @@ cp -a .env docker-compose.yaml config /backups/fail2ban-webui_$(date +%F)/ # o docker compose pull docker compose up -d docker compose ps -docker compose run --rm fail2ban-webui check +docker compose exec fail2ban-webui python -m webui check ``` Vorher das [Changelog](../CHANGELOG.md) lesen – nennt es eine neue Agent-Version, auch die @@ -134,6 +134,8 @@ ein präparierter Benutzername kann also keine zweite Logzeile einschleusen. | Symptom | Ursache / Lösung | |---|---| | `curl: command not found` / `git: command not found` | frisches Minimal-System (z. B. LXC-Template) → `apt install -y curl git` | +| `failed to set up container networking: Address already in use` | `docker compose run …` bei laufendem Dienst – die feste IP ist belegt → `docker compose exec fail2ban-webui python -m webui ` | +| `Permission denied (publickey)` nur hinter NPM | Container hängt in zwei Netzen und verbindet mit der Adresse aus dem NPM-Netz → `gw_priority: 1` am eigenen Netz, siehe [README](../README.md#3-weboberfläche-ins-npm-netz-hängen) | | `docker: 'compose' is not a docker command` | nur das eigenständige `docker-compose` vorhanden → `apt install -y docker-compose-plugin` | | Container beendet sich: `… is not writable for UID 10050` | `chown -R 10050:10050 data` | | Container beendet sich: `AUTH_MODE=local needs AUTH_PASSWORD_HASH` | `hash-password` ausführen, Zeile mit einfachen Anführungszeichen einfügen | diff --git a/docs/reverse-proxy.md b/docs/reverse-proxy.md index 3ae6135..667cbce 100644 --- a/docs/reverse-proxy.md +++ b/docs/reverse-proxy.md @@ -14,6 +14,11 @@ IP-Adressen zu beschränken kostet wenig und ist dringend empfohlen. ## Nginx Proxy Manager +Läuft NPM als Docker-Container auf demselben Host: die getestete Schritt-für-Schritt-Anleitung +steht im [README](../README.md#hinter-einem-vorhandenen-nginx-proxy-manager-docker) (gemeinsames +Docker-Netz, kein veröffentlichter Port, `gw_priority`, Sperren in `DOCKER-USER`). +Für NPM auf einem anderen Host: + **Hosts → Proxy Hosts → Add Proxy Host** | Feld | Wert |