From 019c74a418827e2580321634f612c43a010abd6c Mon Sep 17 00:00:00 2001 From: SchiWaGoA Date: Sun, 25 Oct 2026 15:41:59 +0100 Subject: [PATCH] Docs: translate README and guides to German README and all guides under docs/ are now in German, with German anchors for the cross references. Code and configuration blocks are unchanged. Co-Authored-By: Claude Opus 5.5 --- README.md | 80 ++++++++-------- docs/agent.md | 207 +++++++++++++++++++++-------------------- docs/authentication.md | 114 +++++++++++------------ docs/configuration.md | 154 +++++++++++++++--------------- docs/development.md | 91 +++++++++--------- docs/installation.md | 111 +++++++++++----------- docs/operations.md | 123 ++++++++++++------------ docs/reverse-proxy.md | 52 +++++------ 8 files changed, 469 insertions(+), 463 deletions(-) diff --git a/README.md b/README.md index a3fc532..238a029 100644 --- a/README.md +++ b/README.md @@ -1,34 +1,34 @@ # fail2ban-webui -A small, self-hosted web interface to **view, ban and unban fail2ban IPs on several hosts** – -bare metal, VMs, LXC containers or Docker hosts. +Eine schlanke, selbst gehostete Weboberfläche, um **fail2ban-Sperren auf mehreren Hosts +anzuzeigen, zu setzen und aufzuheben** – Bare Metal, VMs, LXC-Container oder Docker-Hosts. -- One page for all hosts: current bans per jail, origin (GeoIP) with a world map, search and filters -- Ban an IP permanently on any selection of hosts, unban per jail or everywhere -- Audit log of every action (who, when, from where) -- Optional: log history per IP from **Loki**, links into **Grafana** -- Sign-in with a local user, **OpenID Connect** (e.g. Authentik) or your reverse proxy -- English and German UI, more languages via one JSON file -- Runs as a hardened container (read-only, no capabilities, non-root), `amd64` and `arm64` +- Eine Seite für alle Hosts: aktuelle Sperren pro Jail, Herkunft (GeoIP) mit Weltkarte, Suche und Filter +- IP dauerhaft auf beliebigen Hosts sperren, pro Jail oder überall entsperren +- Protokoll jeder Aktion (wer, wann, von wo) +- Optional: Log-Verlauf pro IP aus **Loki**, Links nach **Grafana** +- Anmeldung mit lokalem Benutzer, **OpenID Connect** (z. B. Authentik) oder über den Reverse Proxy +- Oberfläche auf Englisch und Deutsch, weitere Sprachen über eine JSON-Datei +- Läuft als gehärteter Container (schreibgeschützt, ohne Capabilities, nicht als root), `amd64` und `arm64` ``` - Browser ──HTTPS──▶ reverse proxy ──▶ fail2ban-webui (Docker) - │ SSH, one restricted key + Browser ──HTTPS──▶ Reverse Proxy ──▶ fail2ban-webui (Docker) + │ SSH, ein eingeschränkter Schlüssel ┌─────────────────────┼─────────────────────┐ ▼ ▼ ▼ - host A (bare) LXC container Docker host + Host A (Bare Metal) LXC-Container Docker-Host f2b-webctl f2b-webctl f2b-webctl fail2ban fail2ban fail2ban ``` -The web UI never gets a shell on your hosts. Every host runs a tiny agent, -[`f2b-webctl`](agent/f2b-webctl), as SSH *forced command*. It only knows -`list`, `ban `, `unban `, `unban-all ` and `version`, refuses to ban -private networks, the host's own addresses or anything on its protected list, and logs -every action to syslog. Access to the fail2ban socket would be equivalent to root – -the agent is the security boundary, not the web UI. +Die Weboberfläche bekommt nie eine Shell auf deinen Hosts. Auf jedem Host läuft ein kleiner +Agent, [`f2b-webctl`](agent/f2b-webctl), als SSH-*Forced-Command*. Er kennt nur +`list`, `ban `, `unban `, `unban-all ` und `version`, sperrt niemals +private Netze, die eigenen Adressen des Hosts oder Einträge seiner Schutzliste und +protokolliert jede Aktion im Syslog. Zugriff auf den fail2ban-Socket wäre gleichbedeutend +mit root – die Sicherheitsgrenze ist der Agent, nicht die Weboberfläche. -## Quick start +## Schnellstart ```bash mkdir -p /opt/fail2ban-webui/{config,data} && cd /opt/fail2ban-webui @@ -37,40 +37,40 @@ curl -fsSLo .env https://git.retronerd.at/schiwagoa/fail2ban-webui curl -fsSLo config/hosts.yaml https://git.retronerd.at/schiwagoa/fail2ban-webui/raw/branch/main/examples/hosts.yaml.example chown -R 10050:10050 data -docker compose run --rm -it fail2ban-webui hash-password # paste the line into .env -docker compose run --rm fail2ban-webui keygen # public key for the agents -# on every fail2ban host: install the agent (docs/agent.md), then: +docker compose run --rm -it fail2ban-webui hash-password # Zeile in die .env übernehmen +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: docker compose run --rm -it fail2ban-webui keyscan docker compose run --rm fail2ban-webui check docker compose up -d ``` -The UI listens on `127.0.0.1:8080` – put a reverse proxy with TLS in front of it. +Die Oberfläche lauscht auf `127.0.0.1:8080` – davor gehört ein Reverse Proxy mit TLS. -## Documentation +## Dokumentation | | | |---|---| -| [Installation](docs/installation.md) | web UI step by step, from `mkdir` to the first login | -| [Agent](docs/agent.md) | installing `f2b-webctl` on bare metal, LXC and Docker hosts | -| [Configuration](docs/configuration.md) | every setting in `.env` and `hosts.yaml` | -| [Authentication](docs/authentication.md) | local login, OpenID Connect (Authentik), reverse-proxy auth | -| [Reverse proxy](docs/reverse-proxy.md) | Nginx Proxy Manager, Caddy, Traefik, nginx | -| [Operations](docs/operations.md) | logs, updates, backups, troubleshooting | -| [Development](docs/development.md) | local testbed, tests, adding a language | -| [Security](SECURITY.md) | security model and reporting vulnerabilities | +| [Installation](docs/installation.md) | Weboberfläche Schritt für Schritt, von `mkdir` bis zur ersten Anmeldung | +| [Agent](docs/agent.md) | `f2b-webctl` auf Bare Metal, LXC und Docker-Hosts installieren | +| [Konfiguration](docs/configuration.md) | alle Einstellungen in `.env` und `hosts.yaml` | +| [Anmeldung](docs/authentication.md) | lokaler Login, OpenID Connect (Authentik), Anmeldung über den Reverse Proxy | +| [Reverse Proxy](docs/reverse-proxy.md) | Nginx Proxy Manager, Caddy, Traefik, nginx | +| [Betrieb](docs/operations.md) | Logs, Updates, Backups, Fehlersuche | +| [Entwicklung](docs/development.md) | lokales Testbed, Tests, neue Sprache hinzufügen | +| [Sicherheit](SECURITY.md) | Sicherheitsmodell und Melden von Schwachstellen | -## Requirements +## Voraussetzungen -- Web UI: Docker with Compose v2, `amd64` or `arm64` -- Every fail2ban host: fail2ban ≥ 0.11 (start/end times need `--with-time`), `python3`, - `sshd`, `sudo` (or a root key), `bash` +- Weboberfläche: Docker mit Compose v2, `amd64` oder `arm64` +- Jeder fail2ban-Host: fail2ban ≥ 0.11 (Start-/Endzeiten brauchen `--with-time`), `python3`, + `sshd`, `sudo` (oder ein root-Schlüssel), `bash` -## License +## Lizenz -[MIT](LICENSE). IP geolocation by [DB-IP Lite](https://db-ip.com) (CC BY 4.0), downloaded -only when `GEOIP_AUTO_DOWNLOAD=1`. World map from [Natural Earth](https://www.naturalearthdata.com) -(public domain), bundled with the app. +[MIT](LICENSE). IP-Geolokalisierung durch [DB-IP Lite](https://db-ip.com) (CC BY 4.0), wird +nur bei `GEOIP_AUTO_DOWNLOAD=1` heruntergeladen. Weltkarte von [Natural Earth](https://www.naturalearthdata.com) +(Public Domain), in der App enthalten. --- diff --git a/docs/agent.md b/docs/agent.md index 802839f..d53f39e 100644 --- a/docs/agent.md +++ b/docs/agent.md @@ -1,61 +1,61 @@ # Agent – f2b-webctl -Every fail2ban host runs the agent [`f2b-webctl`](../agent/f2b-webctl): a small Python -script (standard library only) started as SSH **forced command** of the web UI's key. -The web UI cannot run anything else on the host. +Auf jedem fail2ban-Host läuft der Agent [`f2b-webctl`](../agent/f2b-webctl): ein kleines +Python-Skript (nur Standardbibliothek), das als SSH-**Forced-Command** für den Schlüssel der +Weboberfläche gestartet wird. Etwas anderes kann die Weboberfläche auf dem Host nicht ausführen. -| Command (sent by the web UI) | Effect | +| Befehl (von der Weboberfläche) | Wirkung | |---|---| -| `list` | all jails with their banned IPs (JSON) | -| `ban ` | ban permanently in the manual jail (`manual-block`) | -| `unban ` | unban in one jail | -| `unban-all ` | unban in all jails | -| `version` | agent version | +| `list` | alle Jails mit ihren gesperrten IPs (JSON) | +| `ban ` | dauerhaft im manuellen Jail (`manual-block`) sperren | +| `unban ` | in einem Jail entsperren | +| `unban-all ` | in allen Jails entsperren | +| `version` | Agent-Version | -Never banned, whatever the web UI sends: +Wird nie gesperrt, egal was die Weboberfläche schickt: -- private, loopback, link-local, CGNAT, multicast and reserved ranges +- private, Loopback-, Link-Local-, CGNAT-, Multicast- und reservierte Bereiche (`10/8`, `172.16/12`, `192.168/16`, `100.64/10`, `127/8`, `169.254/16`, `fc00::/7`, `fe80::/10`, …) -- all addresses of the host itself and the address the web UI connects from -- everything listed in `PROTECTED` in `/etc/f2b-webctl.conf` (e.g. your home IP) +- alle Adressen des Hosts selbst und die Adresse, von der die Weboberfläche zugreift +- alles, was unter `PROTECTED` in `/etc/f2b-webctl.conf` steht (z. B. deine Heim-IP) -Every action is logged to syslog (facility `auth`, tag `f2b-webctl`), including rejected ones. +Jede Aktion landet im Syslog (Facility `auth`, Tag `f2b-webctl`), auch abgelehnte. -## Requirements +## Voraussetzungen | | | |---|---| -| fail2ban | ≥ 0.11, running (`fail2ban-client ping`) – older versions without `--with-time` work, but without start/end times | +| fail2ban | ≥ 0.11, laufend (`fail2ban-client ping`) – ältere Versionen ohne `--with-time` funktionieren, aber ohne Start-/Endzeiten | | python3 | ≥ 3.8 | -| sshd | reachable from the web UI host | -| sudo | for the default user `f2bweb` (`apt install sudo`); not needed with `--user root` | -| bash, ssh-keygen | for the installer | +| sshd | vom Webui-Host erreichbar | +| sudo | für den Standardbenutzer `f2bweb` (`apt install sudo`); mit `--user root` nicht nötig | +| bash, ssh-keygen | für den Installer | -Works on Debian, Ubuntu, Proxmox VE and other systemd or non-systemd Linux systems – -bare metal, VMs, LXC containers (privileged or unprivileged) and Docker hosts. +Läuft auf Debian, Ubuntu, Proxmox VE und anderen Linux-Systemen mit oder ohne systemd – +Bare Metal, VMs, LXC-Container (privilegiert oder unprivilegiert) und Docker-Hosts. ## Installation -You need the public key printed by `fail2ban-webui keygen` and the address the web UI -connects from (the public IP of the Docker host, or its LAN address). +Gebraucht werden der öffentliche Schlüssel aus `fail2ban-webui keygen` und die Adresse, von +der die Weboberfläche zugreift (öffentliche IP des Docker-Hosts oder seine LAN-Adresse). ```bash -# as root on the fail2ban host +# als root auf dem fail2ban-Host git clone https://git.retronerd.at/schiwagoa/fail2ban-webui.git /tmp/fail2ban-webui cd /tmp/fail2ban-webui/agent echo 'ssh-ed25519 AAAA… fail2ban-webui' > /tmp/webui.pub -# see what would happen +# anzeigen, was passieren würde bash install.sh --key-file /tmp/webui.pub --from 198.51.100.10 --dry-run -# install +# installieren bash install.sh --key-file /tmp/webui.pub --from 198.51.100.10 ``` -Without `git`: download the three files `f2b-webctl`, `install.sh` and `uninstall.sh` from -the `agent/` directory into one directory and run `install.sh` from there. +Ohne `git`: die drei Dateien `f2b-webctl`, `install.sh` und `uninstall.sh` aus dem Ordner +`agent/` in ein Verzeichnis laden und `install.sh` von dort starten. -The installer shows a summary and asks before it changes anything: +Der Installer zeigt eine Zusammenfassung und fragt, bevor er etwas ändert: ``` fail2ban-webui agent 1.0.0 – fail2ban 1.1.0 – web01 @@ -71,50 +71,51 @@ fail2ban-webui agent 1.0.0 – fail2ban 1.1.0 – web01 backups /var/backups/fail2ban-webui-agent ``` -What it does: +Was er tut: -1. backs up every file it is going to change to `--backup-dir` -2. installs `/usr/local/sbin/f2b-webctl` (`root:root 0750`) -3. writes `/etc/f2b-webctl.conf` (kept if it already exists) -4. creates the system user `f2bweb` (home `/var/lib/f2bweb`, no password) and the sudo rule +1. sichert jede Datei, die er ändert, nach `--backup-dir` +2. installiert `/usr/local/sbin/f2b-webctl` (`root:root 0750`) +3. schreibt `/etc/f2b-webctl.conf` (bleibt erhalten, wenn sie schon existiert) +4. legt den Systembenutzer `f2bweb` an (Home `/var/lib/f2bweb`, ohne Passwort) und die sudo-Regel ``` Defaults!/usr/local/sbin/f2b-webctl env_keep += "SSH_ORIGINAL_COMMAND SSH_CLIENT" f2bweb ALL=(root) NOPASSWD: /usr/local/sbin/f2b-webctl ``` -5. adds the key with all restrictions: +5. trägt den Schlüssel mit allen Einschränkungen ein: ``` restrict,from="198.51.100.10",command="sudo -n /usr/local/sbin/f2b-webctl" ssh-ed25519 AAAA… fail2ban-webui ``` -6. creates the jail `manual-block` if it does not exist (see below) -7. runs a self test through the same sudo path the web UI uses +6. legt das Jail `manual-block` an, falls es noch nicht existiert (siehe unten) +7. führt einen Selbsttest über denselben sudo-Weg aus, den die Weboberfläche nutzt -Running the installer again is safe: it updates the script, replaces the key entry and -keeps your configuration. +Den Installer erneut auszuführen ist unbedenklich: Er aktualisiert das Skript, ersetzt den +Schlüsseleintrag und lässt deine Konfiguration unverändert. -### Options +### Optionen -| Option | Default | Meaning | +| Option | Standard | Bedeutung | |---|---|---| -| `--key-file FILE` / `--key "KEY"` | – | public key of the web UI (required) | -| `--from ADDRESSES` | any | only accept the key from these IPs/networks (comma separated). **Strongly recommended.** | -| `--user NAME` | `f2bweb` | SSH user; `root` works without sudo | -| `--authorized-keys-file PATH` | `~USER/.ssh/authorized_keys` | where the key entry goes – see [managed keys](#hosts-with-managed-authorized_keys) | -| `--manual-jail NAME` | `manual-block` | jail for manual bans | -| `--banaction ACTION` | from `jail.local` | banaction of a newly created manual jail | -| `--protect NETWORKS` | – | additional protected addresses (only when the config file is written) | -| `--no-jail` | – | do not create the manual jail | -| `--force-config` | – | overwrite an existing `/etc/f2b-webctl.conf` | -| `--backup-dir DIR` | `/var/backups/fail2ban-webui-agent` | backups of changed files | -| `--dry-run` | – | only show what would be done | -| `-y`, `--yes` | – | do not ask | +| `--key-file DATEI` / `--key "SCHLÜSSEL"` | – | öffentlicher Schlüssel der Weboberfläche (Pflicht) | +| `--from ADRESSEN` | jede | Schlüssel nur von diesen IPs/Netzen akzeptieren (durch Komma getrennt). **Dringend empfohlen.** | +| `--user NAME` | `f2bweb` | SSH-Benutzer; `root` funktioniert ohne sudo | +| `--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 | +| `--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 | +| `--backup-dir VERZEICHNIS` | `/var/backups/fail2ban-webui-agent` | Sicherungen geänderter Dateien | +| `--dry-run` | – | nur anzeigen, was passieren würde | +| `-y`, `--yes` | – | nicht nachfragen | -The installer warns when sshd would not read the chosen key file (`AuthorizedKeysFile`), -when `AllowUsers`/`AllowGroups` are set, or when `--user root` meets `PermitRootLogin no`. +Der Installer warnt, wenn sshd die gewählte Schlüsseldatei gar nicht liest +(`AuthorizedKeysFile`), wenn `AllowUsers`/`AllowGroups` gesetzt sind oder wenn `--user root` +auf `PermitRootLogin no` trifft. -## The manual jail +## Das manuelle Jail -Bans from the web UI go into one jail, `manual-block` by default, with `bantime = -1` -(permanent). If the jail does not exist, the installer creates: +Sperren aus der Weboberfläche landen in einem Jail, standardmäßig `manual-block`, mit +`bantime = -1` (dauerhaft). Gibt es das Jail noch nicht, legt der Installer an: `/etc/fail2ban/filter.d/manual-block.conf` @@ -139,64 +140,64 @@ maxretry = 1 bantime = -1 ``` -The jail uses your default `banaction` from `jail.local`. Bans survive restarts through -the fail2ban database. +Das Jail nutzt deine Standard-`banaction` aus `jail.local`. Sperren überstehen Neustarts +über die fail2ban-Datenbank. -An existing jail with that name is never touched – if you already have a jail for manual -bans, pass its name with `--manual-jail`. +Ein vorhandenes Jail mit diesem Namen wird nie angefasst – gibt es schon ein Jail für +manuelle Sperren, dessen Namen mit `--manual-jail` übergeben. -### Docker hosts +### Docker-Hosts -fail2ban's default actions block in the `INPUT` chain. Traffic to **published Docker -ports** does not pass `INPUT` but `FORWARD` → `DOCKER-USER`, so those bans would not -affect your containers. For a Docker host, create the manual jail with an action that -uses the `DOCKER-USER` chain, e.g. with iptables: +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: ```bash bash install.sh --key-file /tmp/webui.pub --from 198.51.100.10 \ --banaction "iptables-allports[chain=DOCKER-USER]" ``` -Check the result after the first ban: +Nach der ersten Sperre prüfen: ```bash iptables -S DOCKER-USER | head ``` -If the web UI runs on the same Docker host, use `address: host.docker.internal` in -`hosts.yaml` and allow SSH from the Docker network if a firewall (e.g. UFW) blocks it: +Läuft die Weboberfläche auf demselben Docker-Host, in `hosts.yaml` `address: host.docker.internal` +verwenden und SSH aus dem Docker-Netz erlauben, falls eine Firewall (z. B. UFW) es blockiert: ```bash ufw allow from 172.23.51.0/24 to any port 22 proto tcp comment 'fail2ban-webui' ``` -and install the agent with `--from 172.23.51.10`. +und den Agenten mit `--from 172.23.51.10` installieren. -### LXC containers +### LXC-Container -The agent runs inside the container where fail2ban runs – nothing special is needed. -In **unprivileged** containers fail2ban itself must be able to change the container's -firewall (nftables/iptables inside the CT); test with a manual ban: +Der Agent läuft im Container, in dem fail2ban läuft – sonst ist nichts Besonderes nötig. +In **unprivilegierten** Containern muss fail2ban selbst die Firewall des Containers ändern +können (nftables/iptables im CT); mit einer manuellen Sperre testen: ```bash fail2ban-client set manual-block banip 203.0.113.1 && nft list ruleset | grep 203.0.113.1 fail2ban-client set manual-block unbanip 203.0.113.1 ``` -### Hosts with managed authorized_keys +### Hosts mit verwalteten authorized_keys -Some tools (Ansible, Keywarden, cloud-init, …) overwrite `~/.ssh/authorized_keys`. -Put the key into a file they do not manage and make sure sshd reads it: +Manche Werkzeuge (Ansible, Keywarden, cloud-init, …) überschreiben `~/.ssh/authorized_keys`. +Den Schlüssel dann in eine Datei legen, die sie nicht verwalten, und sicherstellen, dass sshd sie liest: ```bash grep -i '^AuthorizedKeysFile' /etc/ssh/sshd_config /etc/ssh/sshd_config.d/*.conf -# e.g. AuthorizedKeysFile .ssh/authorized_keys .ssh/authorized_keys2 +# z. B. AuthorizedKeysFile .ssh/authorized_keys .ssh/authorized_keys2 bash install.sh --key-file /tmp/webui.pub --from 198.51.100.10 \ --user root --authorized-keys-file /root/.ssh/authorized_keys2 ``` -## Configuration – /etc/f2b-webctl.conf +## Konfiguration – /etc/f2b-webctl.conf ```ini # fail2ban-webui agent (f2b-webctl) – settings @@ -210,60 +211,60 @@ PROTECT_OWN_ADDRESSES=yes PROTECT_CLIENT=yes ``` -Changes take effect immediately – the agent reads the file on every call. +Änderungen wirken sofort – der Agent liest die Datei bei jedem Aufruf. -## Testing on the host +## Test auf dem Host ```bash -# as root, directly +# direkt als root f2b-webctl list | python3 -m json.tool | head -30 -f2b-webctl ban 10.0.0.1 # → rejected, protected +f2b-webctl ban 10.0.0.1 # → abgelehnt, geschützt -# the same path as the web UI (user f2bweb through sudo) +# derselbe Weg wie die Weboberfläche (Benutzer f2bweb über sudo) runuser -u f2bweb -- env SSH_ORIGINAL_COMMAND=version sudo -n /usr/local/sbin/f2b-webctl -# what the agent did -journalctl -t f2b-webctl --since today # or: grep f2b-webctl /var/log/auth.log +# was der Agent getan hat +journalctl -t f2b-webctl --since today # oder: grep f2b-webctl /var/log/auth.log ``` -## Update +## Aktualisieren -Run the installer of the new version again with the same options. Your configuration -and the jail are kept; the script and the key entry are replaced. +Den Installer der neuen Version mit denselben Optionen erneut ausführen. Konfiguration und +Jail bleiben erhalten; Skript und Schlüsseleintrag werden ersetzt. -## Uninstall +## Deinstallieren ```bash bash uninstall.sh --dry-run bash uninstall.sh ``` -| Option | Meaning | +| Option | Bedeutung | |---|---| -| `--user NAME` | agent user (default `f2bweb`) | -| `--authorized-keys-file PATH` | file with the key entry | -| `--remove-user` | also delete the user and its home | -| `--remove-jail` | also delete the manual jail – **lifts all manual bans** (they are listed first) | -| `--backup-dir DIR`, `--dry-run`, `--yes` | as for the installer | +| `--user NAME` | Agent-Benutzer (Standard `f2bweb`) | +| `--authorized-keys-file PFAD` | Datei mit dem Schlüsseleintrag | +| `--remove-user` | auch den Benutzer samt Home löschen | +| `--remove-jail` | auch das manuelle Jail löschen – **hebt alle manuellen Sperren auf** (sie werden vorher aufgelistet) | +| `--backup-dir VERZEICHNIS`, `--dry-run`, `--yes` | wie beim Installer | -## Manual installation +## Manuelle Installation -If you prefer to do it by hand, these are all the steps the installer performs: +Wer es lieber von Hand macht – das sind alle Schritte des Installers: ```bash install -o root -g root -m 0750 f2b-webctl /usr/local/sbin/f2b-webctl -nano /etc/f2b-webctl.conf # content see above +nano /etc/f2b-webctl.conf # Inhalt siehe oben useradd --system --create-home --home-dir /var/lib/f2bweb --shell /bin/sh f2bweb usermod -p '*' f2bweb -visudo -f /etc/sudoers.d/f2b-webctl # content see "What it does", step 4 +visudo -f /etc/sudoers.d/f2b-webctl # Inhalt siehe "Was er tut", Schritt 4 install -d -o f2bweb -g f2bweb -m 700 /var/lib/f2bweb/.ssh -nano /var/lib/f2bweb/.ssh/authorized_keys # line see step 5 +nano /var/lib/f2bweb/.ssh/authorized_keys # Zeile siehe Schritt 5 chown f2bweb: /var/lib/f2bweb/.ssh/authorized_keys chmod 600 /var/lib/f2bweb/.ssh/authorized_keys -# manual jail: the two files from "The manual jail", then +# manuelles Jail: die zwei Dateien aus "Das manuelle Jail", dann touch /var/log/fail2ban-manual-block.log fail2ban-client reload ``` diff --git a/docs/authentication.md b/docs/authentication.md index 77fdc1d..7239daf 100644 --- a/docs/authentication.md +++ b/docs/authentication.md @@ -1,19 +1,19 @@ -# Authentication +# Anmeldung -`AUTH_MODE` selects how users sign in. The container refuses to start without a working -configuration – there is no unauthenticated mode. +`AUTH_MODE` legt fest, wie sich Benutzer anmelden. Ohne funktionierende Konfiguration startet +der Container nicht – einen Modus ohne Anmeldung gibt es nicht. -| Mode | Use when | +| Modus | Einsatz | |---|---| -| `local` | one shared admin account is enough | -| `oidc` | you have an identity provider (Authentik, Keycloak, Authelia, Zitadel, …) | -| `local,oidc` | SSO with a local fallback account | -| `proxy` | your reverse proxy already authenticates (forward auth, basic auth, IP allow list) | +| `local` | ein gemeinsames Admin-Konto reicht | +| `oidc` | ein Identity Provider ist vorhanden (Authentik, Keycloak, Authelia, Zitadel, …) | +| `local,oidc` | Single Sign-on mit lokalem Notfallkonto | +| `proxy` | der Reverse Proxy meldet bereits an (Forward Auth, Basic Auth, IP-Liste) | -Every change (ban/unban) additionally needs the per-session CSRF token and a matching -`Origin` header, and every action is written to the audit log with the user name. +Jede Änderung (Sperren/Entsperren) braucht zusätzlich das CSRF-Token der Sitzung und einen +passenden `Origin`-Header; jede Aktion wird mit Benutzernamen protokolliert. -## Local user +## Lokaler Benutzer ```bash docker compose run --rm -it fail2ban-webui hash-password @@ -25,42 +25,43 @@ AUTH_USER=admin AUTH_PASSWORD_HASH='scrypt:32768:8:1$…' ``` -- Passwords need at least 12 characters; the hash is scrypt (Werkzeug). -- After `LOGIN_MAX_ATTEMPTS` (5) failures a client IP is locked for `LOGIN_LOCK_MINUTES` (15). -- Failed logins are logged as `LOGIN_FAILED user=… ip=…` – see - [operations.md](operations.md#protecting-the-login-with-fail2ban) to let fail2ban ban - them at the proxy. -- Set `TRUSTED_PROXY_HOPS=1` behind a reverse proxy, otherwise all clients appear with the - proxy's address and share one lockout counter. +- Passwörter brauchen mindestens 12 Zeichen; der Hash ist scrypt (Werkzeug). +- Nach `LOGIN_MAX_ATTEMPTS` (5) Fehlversuchen wird eine Client-IP für `LOGIN_LOCK_MINUTES` (15) gesperrt. +- Fehlgeschlagene Anmeldungen werden als `LOGIN_FAILED user=… ip=…` protokolliert – siehe + [operations.md](operations.md#login-mit-fail2ban-absichern), um sie von fail2ban am Proxy + sperren zu lassen. +- Hinter einem Reverse Proxy `TRUSTED_PROXY_HOPS=1` setzen, sonst erscheinen alle Clients + mit der Adresse des Proxys und teilen sich einen Sperrzähler. ## OpenID Connect -Requirements: `PUBLIC_URL` is set to the external `https://` URL, and the provider allows -the redirect URI **`/auth/callback`**. +Voraussetzungen: `PUBLIC_URL` ist auf die externe `https://`-URL gesetzt, und der Provider +erlaubt die Redirect-URI **`/auth/callback`**. -The UI uses the authorization code flow with PKCE (`S256`) and a confidential client. -User name: `OIDC_USERNAME_CLAIM` (default `preferred_username`); groups: -`OIDC_GROUPS_CLAIM` (default `groups`). +Die Oberfläche nutzt den Authorization Code Flow mit PKCE (`S256`) und einen vertraulichen +Client. Benutzername: `OIDC_USERNAME_CLAIM` (Standard `preferred_username`); Gruppen: +`OIDC_GROUPS_CLAIM` (Standard `groups`). -**Always restrict who may sign in** with `OIDC_ALLOWED_GROUPS` and/or -`OIDC_ALLOWED_USERS` – or with an access policy in the provider. Without either, every -account that the provider lets use this client can ban IPs (a warning is logged at start). +**Immer einschränken, wer sich anmelden darf** – mit `OIDC_ALLOWED_GROUPS` und/oder +`OIDC_ALLOWED_USERS` oder mit einer Zugriffsrichtlinie im Provider. Ohne beides kann jedes +Konto, das der Provider für diesen Client zulässt, IPs sperren (beim Start wird eine Warnung +protokolliert). ### Authentik 1. **Applications → Providers → Create → OAuth2/OpenID Provider** - Name: `fail2ban-webui` - - Authorization flow: `default-provider-authorization-explicit-consent` (or implicit) - - Client type: **Confidential** – note *Client ID* and *Client Secret* + - Authorization flow: `default-provider-authorization-explicit-consent` (oder implicit) + - Client type: **Confidential** – *Client ID* und *Client Secret* notieren - Redirect URIs: `strict` · `https://fail2ban.example.org/auth/callback` - - Signing key: any certificate (e.g. `authentik Self-signed Certificate`) + - Signing key: ein beliebiges Zertifikat (z. B. `authentik Self-signed Certificate`) - Advanced protocol settings → Scopes: `openid`, `email`, `profile` - (the default *profile* mapping contains the `groups` claim) + (das Standard-Mapping *profile* enthält den Claim `groups`) 2. **Applications → Applications → Create** - - Name: `fail2ban-webui`, Slug: `fail2ban-webui`, Provider: the one from step 1 -3. **Directory → Groups → Create** `fail2ban-admins` and add the users. -4. Optional, recommended: in the application under **Policy / Group / User Bindings** - bind the group `fail2ban-admins`, so nobody else even reaches the consent screen. + - Name: `fail2ban-webui`, Slug: `fail2ban-webui`, Provider: der aus Schritt 1 +3. **Directory → Groups → Create** `fail2ban-admins` anlegen und die Benutzer hinzufügen. +4. Optional, empfohlen: in der Application unter **Policy / Group / User Bindings** die + Gruppe `fail2ban-admins` binden – dann kommt sonst niemand bis zur Zustimmungsseite. 5. `.env`: ```ini @@ -73,27 +74,27 @@ account that the provider lets use this client can ban IPs (a warning is logged OIDC_BUTTON_LABEL=Authentik ``` -6. `docker compose up -d`, open `PUBLIC_URL`, click **Sign in with Authentik**. +6. `docker compose up -d`, `PUBLIC_URL` öffnen, auf **Anmelden mit Authentik** klicken. -The issuer URL is shown in Authentik on the provider page (*OpenID Configuration Issuer*). +Die Issuer-URL zeigt Authentik auf der Provider-Seite (*OpenID Configuration Issuer*). -### Other providers +### Andere Provider -| Provider | `OIDC_ISSUER` | Groups | +| Provider | `OIDC_ISSUER` | Gruppen | |---|---|---| -| Keycloak | `https://kc.example.org/realms/` | add a *Group Membership* mapper with claim name `groups`, "Full group path" off | -| Authelia | `https://auth.example.org` | `groups` scope; set `OIDC_SCOPES=openid email profile groups` | -| Zitadel | `https://.zitadel.cloud` | roles instead of groups – use `OIDC_ALLOWED_USERS` or a project role check in Zitadel | +| Keycloak | `https://kc.example.org/realms/` | einen *Group Membership*-Mapper mit Claim-Name `groups` anlegen, „Full group path" aus | +| Authelia | `https://auth.example.org` | Scope `groups`; `OIDC_SCOPES=openid email profile groups` setzen | +| Zitadel | `https://.zitadel.cloud` | Rollen statt Gruppen – `OIDC_ALLOWED_USERS` oder eine Projektrollen-Prüfung in Zitadel verwenden | -### Sign out +### Abmelden -"Sign out" ends the session in the web UI. The session at the provider stays active, so -the next "Sign in with …" usually logs in again without a password. +„Abmelden" beendet die Sitzung in der Weboberfläche. Die Sitzung beim Provider bleibt aktiv; +der nächste Klick auf „Anmelden mit …" meldet daher meist ohne Passwort wieder an. -## Reverse-proxy mode +## Proxy-Modus -The proxy does the authentication; the UI only checks where requests come from and, -optionally, which user the proxy reports. +Der Proxy übernimmt die Anmeldung; die Oberfläche prüft nur, woher Anfragen kommen, und +optional, welchen Benutzer der Proxy meldet. ```ini AUTH_MODE=proxy @@ -106,14 +107,13 @@ AUTH_PROXY_HEADER=X-authentik-username AUTH_PROXY_LOGOUT_URL=https://fail2ban.example.org/outpost.goauthentik.io/sign_out ``` -- `AUTH_PROXY_ALLOWED_IPS` is checked against the **direct** peer of the container, not - against `X-Forwarded-For`. If the proxy runs on the same Docker host and reaches the - container via the published port, that is the network gateway (`172.23.51.1` in the - example compose file); check with `docker compose logs` after a request. -- With `AUTH_PROXY_HEADER` set, requests without the header are rejected (401). Make sure - the proxy **removes** that header from client requests – forward-auth integrations - (Authentik, Authelia) do that. -- Without `AUTH_PROXY_HEADER` the audit log shows the user `proxy`. +- `AUTH_PROXY_ALLOWED_IPS` wird gegen den **direkten** Gegenüber des Containers geprüft, + nicht gegen `X-Forwarded-For`. Läuft der Proxy auf demselben Docker-Host und erreicht den + Container über den veröffentlichten Port, ist das das Netzwerk-Gateway (`172.23.51.1` in + der Beispiel-Compose); nach einer Anfrage mit `docker compose logs` prüfen. +- Ist `AUTH_PROXY_HEADER` gesetzt, werden Anfragen ohne diesen Header abgelehnt (401). Der + Proxy muss diesen Header aus Client-Anfragen **entfernen** – Forward-Auth-Integrationen + (Authentik, Authelia) tun das. +- Ohne `AUTH_PROXY_HEADER` steht im Protokoll der Benutzer `proxy`. -Examples for Nginx Proxy Manager, Traefik and Caddy are in -[reverse-proxy.md](reverse-proxy.md). +Beispiele für Nginx Proxy Manager, Traefik und Caddy stehen in [reverse-proxy.md](reverse-proxy.md). diff --git a/docs/configuration.md b/docs/configuration.md index 47f64fe..9544cd6 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1,12 +1,12 @@ -# Configuration +# Konfiguration -The web UI is configured with environment variables (`.env`, loaded via `env_file:`) and -the host list `config/hosts.yaml`. A complete, commented template is -[`examples/.env.example`](../examples/.env.example). Changes need -`docker compose up -d` (which recreates the container). +Die Weboberfläche wird über Umgebungsvariablen (`.env`, geladen per `env_file:`) und die +Hostliste `config/hosts.yaml` konfiguriert. Eine vollständige, kommentierte Vorlage ist +[`examples/.env.example`](../examples/.env.example). Änderungen brauchen +`docker compose up -d` (das legt den Container neu an). -Values containing `$` (password hashes, some secrets) must be wrapped in **single quotes** -in `.env`, otherwise Docker Compose expands them. +Werte mit `$` (Passwort-Hashes, manche Secrets) müssen in der `.env` in **einfachen +Anführungszeichen** stehen, sonst wertet Docker Compose sie als Variablen aus. ## Hosts – config/hosts.yaml @@ -18,7 +18,7 @@ hosts: user: f2bweb # optional, default SSH_USER ``` -After adding a host run `keyscan` again, then restart: +Nach dem Hinzufügen eines Hosts `keyscan` erneut ausführen, dann neu starten: ```bash docker compose run --rm -it fail2ban-webui keyscan @@ -26,99 +26,99 @@ docker compose run --rm fail2ban-webui check docker compose up -d --force-recreate ``` -`F2B_HOSTS=name=address,name2=address2` (port 22, user `SSH_USER`) is still accepted for -compatibility; entries from both are combined. +`F2B_HOSTS=name=adresse,name2=adresse2` (Port 22, Benutzer `SSH_USER`) wird aus +Kompatibilitätsgründen weiter akzeptiert; Einträge aus beiden Quellen werden zusammengeführt. -## General +## Allgemein -| Variable | Default | Meaning | +| Variable | Standard | Bedeutung | |---|---|---| -| `PUBLIC_URL` | – | external URL, e.g. `https://fail2ban.example.org`. Required for OIDC; with `https://` session cookies get the `Secure` flag | -| `DEFAULT_LANG` | `en` | language until a user picks one in the dropdown (`en`, `de`, …) | -| `TRUSTED_PROXY_HOPS` | `0` | number of reverse proxies whose `X-Forwarded-For`/`-Proto` headers are trusted. Behind one proxy: `1`. Wrong values make the audit log and the login lockout use the wrong client IP | -| `SESSION_HOURS` | `12` | session lifetime | -| `SESSION_COOKIE_SECURE` | from `PUBLIC_URL` | force the `Secure` cookie flag on/off | -| `SECRET_KEY` | generated | key that signs session cookies; by default created once in `data/secret_key` | -| `TZ` | `Europe/Vienna` (compose) | time zone of the log and audit timestamps | -| `CONFIG_FILE` | `/config/hosts.yaml` | path of the host list inside the container | -| `DATA_DIR` | `/data` | data directory inside the container | +| `PUBLIC_URL` | – | externe URL, z. B. `https://fail2ban.example.org`. Pflicht für OIDC; mit `https://` bekommen Sitzungs-Cookies das `Secure`-Flag | +| `DEFAULT_LANG` | `en` | Sprache, solange ein Benutzer im Dropdown keine gewählt hat (`en`, `de`, …) | +| `TRUSTED_PROXY_HOPS` | `0` | Anzahl der Reverse Proxys, deren `X-Forwarded-For`/`-Proto`-Header vertraut wird. Hinter einem Proxy: `1`. Falsche Werte führen dazu, dass Protokoll und Login-Sperre die falsche Client-IP verwenden | +| `SESSION_HOURS` | `12` | Gültigkeit einer Sitzung | +| `SESSION_COOKIE_SECURE` | aus `PUBLIC_URL` | `Secure`-Flag des Cookies erzwingen oder abschalten | +| `SECRET_KEY` | wird erzeugt | Schlüssel zum Signieren der Sitzungs-Cookies; standardmäßig einmalig in `data/secret_key` angelegt | +| `TZ` | `Europe/Vienna` (Compose) | Zeitzone für Log- und Protokoll-Zeitstempel | +| `CONFIG_FILE` | `/config/hosts.yaml` | Pfad der Hostliste im Container | +| `DATA_DIR` | `/data` | Datenverzeichnis im Container | -## Authentication +## Anmeldung -See [authentication.md](authentication.md) for the complete setup. +Die vollständige Einrichtung steht in [authentication.md](authentication.md). -| Variable | Default | Meaning | +| Variable | Standard | Bedeutung | |---|---|---| -| `AUTH_MODE` | `local` | `local`, `oidc`, `local,oidc` or `proxy` | -| `AUTH_USER` | `admin` | local user name | -| `AUTH_PASSWORD_HASH` | – | scrypt hash from `hash-password` (single quotes!) | -| `AUTH_PASSWORD_HASH_FILE` | – | alternative: file containing the hash (e.g. a Docker secret) | -| `LOGIN_MAX_ATTEMPTS` | `5` | failed logins per client IP before the lockout | -| `LOGIN_LOCK_MINUTES` | `15` | duration of the lockout window | -| `OIDC_ISSUER` | – | issuer URL; the discovery document is read from `/.well-known/openid-configuration` | -| `OIDC_DISCOVERY_URL` | – | full discovery URL instead of `OIDC_ISSUER` | -| `OIDC_CLIENT_ID` / `OIDC_CLIENT_SECRET` | – | client credentials | -| `OIDC_SCOPES` | `openid email profile` | requested scopes | -| `OIDC_USERNAME_CLAIM` | `preferred_username` | claim shown as user name (falls back to `email`, then `sub`) | -| `OIDC_GROUPS_CLAIM` | `groups` | claim with the group list | -| `OIDC_ALLOWED_GROUPS` | – | only members of these groups may sign in (comma separated) | -| `OIDC_ALLOWED_USERS` | – | only these user names may sign in | -| `OIDC_BUTTON_LABEL` | `SSO` | text on the login button ("Sign in with …") | -| `AUTH_PROXY_ALLOWED_IPS` | – | proxy mode: only accept requests from these addresses/networks | -| `AUTH_PROXY_HEADER` | – | proxy mode: header with the user name, e.g. `X-authentik-username` | -| `AUTH_PROXY_LOGOUT_URL` | – | proxy mode: target of the "Sign out" button | +| `AUTH_MODE` | `local` | `local`, `oidc`, `local,oidc` oder `proxy` | +| `AUTH_USER` | `admin` | lokaler Benutzername | +| `AUTH_PASSWORD_HASH` | – | scrypt-Hash aus `hash-password` (einfache Anführungszeichen!) | +| `AUTH_PASSWORD_HASH_FILE` | – | Alternative: Datei mit dem Hash (z. B. ein Docker-Secret) | +| `LOGIN_MAX_ATTEMPTS` | `5` | Fehlversuche pro Client-IP bis zur Sperre | +| `LOGIN_LOCK_MINUTES` | `15` | Dauer des Sperrfensters | +| `OIDC_ISSUER` | – | Issuer-URL; das Discovery-Dokument wird von `/.well-known/openid-configuration` gelesen | +| `OIDC_DISCOVERY_URL` | – | vollständige Discovery-URL statt `OIDC_ISSUER` | +| `OIDC_CLIENT_ID` / `OIDC_CLIENT_SECRET` | – | Client-Zugangsdaten | +| `OIDC_SCOPES` | `openid email profile` | angeforderte Scopes | +| `OIDC_USERNAME_CLAIM` | `preferred_username` | Claim, der als Benutzername angezeigt wird (Rückfall auf `email`, dann `sub`) | +| `OIDC_GROUPS_CLAIM` | `groups` | Claim mit der Gruppenliste | +| `OIDC_ALLOWED_GROUPS` | – | nur Mitglieder dieser Gruppen dürfen sich anmelden (durch Komma getrennt) | +| `OIDC_ALLOWED_USERS` | – | nur diese Benutzernamen dürfen sich anmelden | +| `OIDC_BUTTON_LABEL` | `SSO` | Text auf dem Anmeldeknopf („Anmelden mit …") | +| `AUTH_PROXY_ALLOWED_IPS` | – | Proxy-Modus: nur Anfragen von diesen Adressen/Netzen annehmen | +| `AUTH_PROXY_HEADER` | – | Proxy-Modus: Header mit dem Benutzernamen, z. B. `X-authentik-username` | +| `AUTH_PROXY_LOGOUT_URL` | – | Proxy-Modus: Ziel des „Abmelden"-Knopfs | -## Agents (SSH) +## Agenten (SSH) -| Variable | Default | Meaning | +| Variable | Standard | Bedeutung | |---|---|---| -| `SSH_USER` | `f2bweb` | default user for hosts without `user:` | -| `SSH_KEY` | `/data/ssh/id_ed25519` | private key | -| `KNOWN_HOSTS` | `/data/ssh/known_hosts` | pinned host keys (written by `keyscan`) | -| `SSH_TIMEOUT` | `40` | seconds per agent call | -| `CACHE_SECONDS` | `15` | how long the ban list is cached between page refreshes | -| `MANUAL_JAIL` | `manual-block` | jail name shown in the UI hint – the agent decides the real jail (`/etc/f2b-webctl.conf`) | -| `AUDIT_LOG` | `/data/audit.log` | audit log (JSON lines) | +| `SSH_USER` | `f2bweb` | Standardbenutzer für Hosts ohne `user:` | +| `SSH_KEY` | `/data/ssh/id_ed25519` | privater Schlüssel | +| `KNOWN_HOSTS` | `/data/ssh/known_hosts` | festgelegte Host-Schlüssel (geschrieben von `keyscan`) | +| `SSH_TIMEOUT` | `40` | Sekunden pro Agent-Aufruf | +| `CACHE_SECONDS` | `15` | wie lange die Sperrliste zwischen zwei Seitenaufrufen zwischengespeichert wird | +| `MANUAL_JAIL` | `manual-block` | Jail-Name im Hinweis der Oberfläche – das tatsächliche Jail bestimmt der Agent (`/etc/f2b-webctl.conf`) | +| `AUDIT_LOG` | `/data/audit.log` | Protokoll (JSON-Zeilen) | -The SSH client ignores the system `ssh_config`, uses only the configured key, never -asks questions and refuses unknown host keys. +Der SSH-Client ignoriert die System-`ssh_config`, verwendet nur den konfigurierten +Schlüssel, stellt nie Rückfragen und lehnt unbekannte Host-Schlüssel ab. ## GeoIP -| Variable | Default | Meaning | +| Variable | Standard | Bedeutung | |---|---|---| -| `GEOIP_AUTO_DOWNLOAD` | `0` | `1`: download the free [DB-IP Lite](https://db-ip.com/db/lite.php) city database (CC BY 4.0) and refresh it monthly | -| `GEOIP_DB` | `/data/geoip/dbip-city-lite.mmdb` | path of the database – any MaxMind-format city database works (e.g. GeoLite2 City) | +| `GEOIP_AUTO_DOWNLOAD` | `0` | `1`: die freie Städte-Datenbank [DB-IP Lite](https://db-ip.com/db/lite.php) (CC BY 4.0) herunterladen und monatlich erneuern | +| `GEOIP_DB` | `/data/geoip/dbip-city-lite.mmdb` | Pfad der Datenbank – jede Städte-Datenbank im MaxMind-Format funktioniert (z. B. GeoLite2 City) | -With a city database the UI also shows the **origin map** (country-only databases have no -coordinates). The map is bundled with the app (Natural Earth, public domain) – no external -tile server is contacted. Without a database the "Origin" column stays empty and the map -is hidden. +Mit einer Städte-Datenbank zeigt die Oberfläche auch die **Herkunftskarte** (Datenbanken +nur mit Ländern haben keine Koordinaten). Die Karte ist Teil der App (Natural Earth, Public +Domain) – es wird kein externer Kartendienst abgefragt. Ohne Datenbank bleibt die Spalte +„Herkunft" leer und die Karte ausgeblendet. -## Loki and Grafana +## Loki und Grafana -With Loki the UI gets a **History** button per IP showing the matching log lines of the -last days; with Grafana every ban gets a link to Explore. +Mit Loki bekommt die Oberfläche pro IP einen **Verlauf**-Knopf mit den passenden Logzeilen +der letzten Tage; mit Grafana bekommt jede Sperre einen Link nach Explore. -| Variable | Default | Meaning | +| Variable | Standard | Bedeutung | |---|---|---| -| `LOKI_URL` | – | e.g. `http://loki:3100`; empty = history disabled | -| `LOKI_SELECTOR` | `{job=~".+"}` | LogQL stream selector for the search, e.g. `{job=~"fail2ban\|sshd\|nginx"}` | -| `LOKI_DAYS` | `7` | how far back to search | -| `LOKI_LIMIT` | `50` | maximum lines | -| `LOKI_USER` / `LOKI_PASSWORD` | – | basic auth | -| `LOKI_TENANT` | – | `X-Scope-OrgID` for multi-tenant Loki | -| `GRAFANA_URL` | – | e.g. `https://grafana.example.org` | -| `GRAFANA_DASHBOARD` | – | path of a dashboard for the header link, e.g. `/d/abc123/security` | -| `GRAFANA_LOKI_UID` | `loki` | UID of the Loki data source in Grafana (for Explore links) | +| `LOKI_URL` | – | z. B. `http://loki:3100`; leer = Verlauf ausgeschaltet | +| `LOKI_SELECTOR` | `{job=~".+"}` | LogQL-Stream-Selektor für die Suche, z. B. `{job=~"fail2ban\|sshd\|nginx"}` | +| `LOKI_DAYS` | `7` | wie weit zurück gesucht wird | +| `LOKI_LIMIT` | `50` | maximale Anzahl Zeilen | +| `LOKI_USER` / `LOKI_PASSWORD` | – | Basic Auth | +| `LOKI_TENANT` | – | `X-Scope-OrgID` für mandantenfähiges Loki | +| `GRAFANA_URL` | – | z. B. `https://grafana.example.org` | +| `GRAFANA_DASHBOARD` | – | Pfad eines Dashboards für den Link in der Kopfzeile, z. B. `/d/abc123/security` | +| `GRAFANA_LOKI_UID` | `loki` | UID der Loki-Datenquelle in Grafana (für Explore-Links) | -Example – exclude the UI's own access log from the search: +Beispiel – das eigene Zugriffslog der Oberfläche von der Suche ausnehmen: ```ini LOKI_SELECTOR={job=~"fail2ban|ufw|nginx|sshd", domain!="fail2ban.example.org"} ``` -## Agent settings +## Agent-Einstellungen -Protected addresses and the manual jail are configured **on each host** in -`/etc/f2b-webctl.conf` – see [agent.md](agent.md#configuration--etcf2b-webctlconf). +Geschützte Adressen und das manuelle Jail werden **auf jedem Host** in +`/etc/f2b-webctl.conf` eingestellt – siehe [agent.md](agent.md#konfiguration--etcf2b-webctlconf). diff --git a/docs/development.md b/docs/development.md index f4fc63f..031239d 100644 --- a/docs/development.md +++ b/docs/development.md @@ -1,24 +1,24 @@ -# Development +# Entwicklung -## Layout +## Aufbau ``` -app/webui/ Flask application (package "webui") - app.py routes, security headers, audit log - auth.py local / OIDC / proxy authentication, login lockout - backend.py SSH to the agents, GeoIP, Loki +app/webui/ Flask-Anwendung (Paket "webui") + app.py Routen, Sicherheits-Header, Protokoll + auth.py Anmeldung lokal / OIDC / Proxy, Login-Sperre + backend.py SSH zu den Agenten, GeoIP, Loki cli.py serve, hash-password, keygen, keyscan, check - config.py settings from the environment, hosts.yaml - i18n.py, locales/ translations (one JSON file per language) - templates/, static/ + config.py Einstellungen aus der Umgebung, hosts.yaml + i18n.py, locales/ Übersetzungen (eine JSON-Datei pro Sprache) + templates/, static/ (static/world.svg: Weltkarte, Natural Earth) agent/ f2b-webctl, install.sh, uninstall.sh -docker/Dockerfile production image +docker/Dockerfile Produktions-Image examples/ docker-compose.yaml, .env.example, hosts.yaml.example -dev/testbed/ web UI + two fail2ban hosts in Docker -tests/smoke_test.py web UI tests without Docker/SSH +dev/testbed/ Weboberfläche + zwei fail2ban-Hosts in Docker +tests/smoke_test.py Tests der Weboberfläche ohne Docker/SSH ``` -## Smoke test (no Docker needed) +## Smoke-Test (ohne Docker) ```bash python3 -m venv .venv @@ -26,20 +26,20 @@ python3 -m venv .venv .venv/bin/python tests/smoke_test.py ``` -The agents are simulated. Covered: login, lockout, CSRF/Origin checks, translations, -ban/unban validation, audit log, proxy mode, config validation, SSH command line, log -injection. +Die Agenten werden simuliert. Abgedeckt: Anmeldung, Login-Sperre, CSRF-/Origin-Prüfung, +Übersetzungen, Prüfung beim Sperren/Entsperren, Protokoll, Proxy-Modus, Prüfung der +Konfiguration, SSH-Befehlszeile, Log-Injection. ## Testbed (Docker) -`dev/testbed` starts the web UI and two Debian hosts with sshd and fail2ban (dummy ban -actions – nothing is blocked). The agents are installed with `agent/install.sh` at -container start: +`dev/testbed` startet die Weboberfläche und zwei Debian-Hosts mit sshd und fail2ban +(Dummy-Banactions – es wird nichts gesperrt). Die Agenten werden beim Containerstart mit +`agent/install.sh` installiert: -| Host | Agent setup | +| Host | Agent-Einrichtung | |---|---| -| `agent1` | user `f2bweb` + sudo, `--from` the testbed network, extra protected IP | -| `agent2` | user `root`, key in `authorized_keys2`, manual jail named `blocklist` | +| `agent1` | Benutzer `f2bweb` + sudo, `--from` auf das Testbed-Netz, zusätzliche geschützte IP | +| `agent2` | Benutzer `root`, Schlüssel in `authorized_keys2`, manuelles Jail namens `blocklist` | ```bash cd dev/testbed @@ -52,42 +52,43 @@ docker compose up -d agent1 agent2 docker compose run --rm webui keyscan --yes docker compose run --rm webui check docker compose up -d webui # http://127.0.0.1:18080 -docker compose down -v # remove everything +docker compose down -v # alles entfernen ``` -`hosts.yaml` is written into the data volume because Docker Desktop cannot bind-mount -network drives. After recreating an agent its host key changes – run `keyscan` again. +`hosts.yaml` wird ins Daten-Volume geschrieben, weil Docker Desktop Netzlaufwerke nicht +einbinden kann. Nach dem Neuanlegen eines Agenten ändert sich sein Host-Schlüssel – +`keyscan` erneut ausführen. -On Windows with Git Bash, prefix `docker compose exec … /path` with `MSYS_NO_PATHCONV=1`, -otherwise the path is rewritten to a Windows path. +Unter Windows mit Git Bash `docker compose exec … /pfad` mit `MSYS_NO_PATHCONV=1` +voranstellen, sonst wird der Pfad in einen Windows-Pfad umgeschrieben. -## Adding a language +## Neue Sprache hinzufügen -1. Copy `app/webui/locales/en.json` to `app/webui/locales/.json` (e.g. `fr.json`). -2. Set `"_name"` to the language's own name (`"Français"`) – it appears in the dropdown. -3. Translate the values; keep `{placeholders}` unchanged. Missing keys fall back to English. -4. Run the smoke test and check the UI. +1. `app/webui/locales/en.json` nach `app/webui/locales/.json` kopieren (z. B. `fr.json`). +2. `"_name"` auf den Eigennamen der Sprache setzen (`"Français"`) – so erscheint sie im Dropdown. +3. Die Werte übersetzen; `{Platzhalter}` unverändert lassen. Fehlende Schlüssel fallen auf Englisch zurück. +4. Smoke-Test ausführen und die Oberfläche prüfen. -## Building the image +## Image bauen ```bash docker build -f docker/Dockerfile --build-arg VERSION=1.0.0 -t fail2ban-webui:1.0.0 . -# multi-arch, as in CI +# Multi-Arch, wie in der CI docker buildx build -f docker/Dockerfile --platform linux/amd64,linux/arm64 \ --build-arg VERSION=1.0.0 -t git.retronerd.at/schiwagoa/fail2ban-webui:1.0.0 --push . ``` -Scan before publishing: +Vor dem Veröffentlichen scannen: ```bash docker run --rm -v /var/run/docker.sock:/var/run/docker.sock aquasec/trivy:0.58.1 \ image --scanners vuln --ignore-unfixed --severity HIGH,CRITICAL fail2ban-webui:1.0.0 ``` -## Agent protocol +## Agent-Protokoll -The web UI runs `ssh … @ ""`; the agent reads the command from -`SSH_ORIGINAL_COMMAND` and prints exactly one JSON line: +Die Weboberfläche führt `ssh … @ ""` aus; der Agent liest den Befehl +aus `SSH_ORIGINAL_COMMAND` und gibt genau eine JSON-Zeile aus: ```json {"ok": true, "host": "web01", "agent_version": "1.0.0", "manual_jail": "manual-block", @@ -95,12 +96,12 @@ The web UI runs `ssh … @ ""`; the agent reads the command "bantime": 600, "end": "2026-10-25 10:10:00"}], "total": 42}}} ``` -Errors: `{"ok": false, "error_key": "agent_protected", "error_args": {"ip": "…"}, "error": "English text"}`. -The web UI translates `error_key` with `locales/*.json` (keys starting with `agent_`); -unknown keys show the English `error`. +Fehler: `{"ok": false, "error_key": "agent_protected", "error_args": {"ip": "…"}, "error": "English text"}`. +Die Weboberfläche übersetzt `error_key` mit `locales/*.json` (Schlüssel mit Präfix `agent_`); +unbekannte Schlüssel zeigen den englischen `error`-Text. -## Versioning +## Versionierung -- Version numbers follow SemVer and are set at build time (`VERSION` build arg → - `APP_VERSION`); the agent has its own `VERSION` in `agent/f2b-webctl`. -- Every change goes into `CHANGELOG.md` under *Unreleased*. +- Versionsnummern folgen SemVer und werden beim Bauen gesetzt (Build-Argument `VERSION` → + `APP_VERSION`); der Agent hat eine eigene `VERSION` in `agent/f2b-webctl`. +- Jede Änderung kommt in `CHANGELOG.md` unter *Unreleased*. diff --git a/docs/installation.md b/docs/installation.md index c99fc39..3dc2efd 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -1,43 +1,43 @@ -# Installation – web UI +# Installation – Weboberfläche -This guide sets up the web UI on a Docker host, step by step. The agents on your -fail2ban hosts are covered in [agent.md](agent.md); you need both. +Diese Anleitung richtet die Weboberfläche Schritt für Schritt auf einem Docker-Host ein. +Die Agenten auf den fail2ban-Hosts sind in [agent.md](agent.md) beschrieben; du brauchst beides. -- Source: +- Quelle: - Image: `git.retronerd.at/schiwagoa/fail2ban-webui` (`amd64`, `arm64`) -- Tags: `1.0.0` (exact), `1` (latest 1.x), `latest` +- Tags: `1.0.0` (genau diese Version), `1` (neueste 1.x), `latest` -## 1. Prerequisites +## 1. Voraussetzungen -- Docker Engine with Compose v2 (`docker compose version`) -- Outgoing SSH (TCP 22 or your port) from the Docker host to every fail2ban host -- A DNS name and a reverse proxy with TLS for the UI (see [reverse-proxy.md](reverse-proxy.md)) +- Docker Engine mit Compose v2 (`docker compose version`) +- Ausgehendes SSH (TCP 22 oder dein Port) vom Docker-Host zu jedem fail2ban-Host +- Ein DNS-Name und ein Reverse Proxy mit TLS für die Oberfläche (siehe [reverse-proxy.md](reverse-proxy.md)) -Check that the subnet used in the example (`172.23.51.0/24`) is free: +Prüfen, ob das Subnetz aus dem Beispiel (`172.23.51.0/24`) frei ist: ```bash docker network inspect $(docker network ls -q) --format '{{.Name}} {{range .IPAM.Config}}{{.Subnet}}{{end}}' | grep 172.23.51 \ || echo "172.23.51.0/24 is free" ``` -If it is taken, pick another `/24` and change it in step 3. +Ist es belegt, ein anderes `/24` wählen und in Schritt 3 anpassen. -## 2. Directories +## 2. Verzeichnisse ```bash mkdir -p /opt/fail2ban-webui/config /opt/fail2ban-webui/data cd /opt/fail2ban-webui -# the container runs as UID 10050 and needs to write to data/ +# der Container läuft als UID 10050 und muss in data/ schreiben chown -R 10050:10050 data chmod 700 data ``` -| Path | Content | +| Pfad | Inhalt | |---|---| -| `docker-compose.yaml` | the service | -| `.env` | settings and secrets (password hash, OIDC secret) | -| `config/hosts.yaml` | the fail2ban hosts (mounted read-only) | -| `data/` | SSH key, `known_hosts`, session secret, audit log, GeoIP database | +| `docker-compose.yaml` | der Dienst | +| `.env` | Einstellungen und Geheimnisse (Passwort-Hash, OIDC-Secret) | +| `config/hosts.yaml` | die fail2ban-Hosts (schreibgeschützt eingebunden) | +| `data/` | SSH-Schlüssel, `known_hosts`, Sitzungsschlüssel, Protokoll, GeoIP-Datenbank | ## 3. docker-compose.yaml @@ -102,13 +102,13 @@ networks: ip_range: 172.23.51.128/25 ``` -If the reverse proxy runs on another machine, bind the port to the LAN address instead, -e.g. `"192.168.1.10:8080:8080"` – never to `0.0.0.0` without a firewall in front. +Läuft der Reverse Proxy auf einer anderen Maschine, den Port stattdessen an die LAN-Adresse +binden, z. B. `"192.168.1.10:8080:8080"` – niemals ohne Firewall davor an `0.0.0.0`. ## 4. .env -Start with this minimal file; every option is described in -[configuration.md](configuration.md) and in [`examples/.env.example`](../examples/.env.example). +Mit dieser minimalen Datei beginnen; alle Optionen stehen in +[configuration.md](configuration.md) und in [`examples/.env.example`](../examples/.env.example). ```bash nano .env @@ -129,35 +129,38 @@ MANUAL_JAIL=manual-block GEOIP_AUTO_DOWNLOAD=1 ``` -### Password hash +`DEFAULT_LANG=de` stellt die Oberfläche standardmäßig auf Deutsch; jeder Benutzer kann die +Sprache zusätzlich im Dropdown wählen. + +### Passwort-Hash ```bash docker compose run --rm -it fail2ban-webui hash-password ``` -Copy the printed line (`AUTH_PASSWORD_HASH='scrypt:…'`) into `.env` and replace the -placeholder. **Keep the single quotes** – the hash contains `$` signs that Docker Compose -would otherwise try to expand. For single sign-on instead see -[authentication.md](authentication.md). +Die ausgegebene Zeile (`AUTH_PASSWORD_HASH='scrypt:…'`) in die `.env` kopieren und den +Platzhalter ersetzen. **Die einfachen Anführungszeichen beibehalten** – der Hash enthält +`$`-Zeichen, die Docker Compose sonst als Variablen auswerten würde. Für Single Sign-on +stattdessen siehe [authentication.md](authentication.md). -## 5. SSH key +## 5. SSH-Schlüssel ```bash docker compose run --rm fail2ban-webui keygen ``` -The command creates `data/ssh/id_ed25519` and prints the public key -(`ssh-ed25519 AAAA… fail2ban-webui`). Copy it – you need it for the agent installer. +Der Befehl erzeugt `data/ssh/id_ed25519` und gibt den öffentlichen Schlüssel aus +(`ssh-ed25519 AAAA… fail2ban-webui`). Kopieren – er wird für den Agent-Installer gebraucht. -## 6. Agents +## 6. Agenten -Install the agent on every fail2ban host as described in [agent.md](agent.md). In short: +Den Agenten auf jedem fail2ban-Host installieren, wie in [agent.md](agent.md) beschrieben. Kurzfassung: ```bash -# on each fail2ban host, as root +# auf jedem fail2ban-Host, als root git clone https://git.retronerd.at/schiwagoa/fail2ban-webui.git /tmp/fail2ban-webui echo 'ssh-ed25519 AAAA… fail2ban-webui' > /tmp/webui.pub -bash /tmp/fail2ban-webui/agent/install.sh --key-file /tmp/webui.pub --from +bash /tmp/fail2ban-webui/agent/install.sh --key-file /tmp/webui.pub --from <öffentliche IP des Webui-Hosts> ``` ## 7. config/hosts.yaml @@ -180,24 +183,24 @@ hosts: address: host.docker.internal ``` -`user` defaults to `SSH_USER` (`f2bweb`). Set `user: root` for hosts where the agent was -installed with `--user root`. +`user` ist standardmäßig `SSH_USER` (`f2bweb`). Für Hosts, auf denen der Agent mit +`--user root` installiert wurde, `user: root` setzen. -## 8. Host keys and connection test +## 8. Host-Schlüssel und Verbindungstest ```bash docker compose run --rm -it fail2ban-webui keyscan ``` -`keyscan` shows the fingerprint of every host and asks before writing -`data/ssh/known_hosts`. Compare the fingerprints with the hosts: +`keyscan` zeigt den Fingerprint jedes Hosts und fragt vor dem Schreiben von +`data/ssh/known_hosts` nach. Die Fingerprints mit den Hosts vergleichen: ```bash -# on the fail2ban host +# auf dem fail2ban-Host ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub ``` -Then test every agent: +Danach jeden Agenten testen: ```bash docker compose run --rm fail2ban-webui check @@ -209,33 +212,33 @@ OK lxc-proxy agent 1.0.0, 2 jails, 0 bans OK dockerhost agent 1.0.0, 4 jails, 7 bans ``` -Every host must show `OK` – see [operations.md](operations.md#troubleshooting) otherwise. +Jeder Host muss `OK` melden – sonst siehe [operations.md](operations.md#fehlersuche). -## 9. Start +## 9. Starten ```bash docker compose up -d docker compose ps # STATUS: Up … (healthy) -docker compose logs -f fail2ban-webui # Ctrl+C to leave +docker compose logs -f fail2ban-webui # mit Strg+C verlassen ``` -The log shows a summary like +Das Log zeigt eine Zusammenfassung wie `fail2ban-webui 1.0.0 – hosts: web01, lxc-proxy, dockerhost – auth: local – loki: off – geoip: on`. -Local test without the proxy: +Lokaler Test ohne Proxy: ```bash curl -s http://127.0.0.1:8080/healthz # ok ``` -## 10. Reverse proxy +## 10. Reverse Proxy -Publish `http://127.0.0.1:8080` (or the LAN address from step 3) under your DNS name with -TLS – examples for Nginx Proxy Manager, Caddy, Traefik and nginx are in -[reverse-proxy.md](reverse-proxy.md). Then open `PUBLIC_URL` and sign in. +`http://127.0.0.1:8080` (oder die LAN-Adresse aus Schritt 3) unter deinem DNS-Namen mit +TLS veröffentlichen – Beispiele für Nginx Proxy Manager, Caddy, Traefik und nginx stehen in +[reverse-proxy.md](reverse-proxy.md). Dann `PUBLIC_URL` öffnen und anmelden. -## Next steps +## Nächste Schritte -- [Protect the login with fail2ban](operations.md#protecting-the-login-with-fail2ban) -- [Updates and backups](operations.md) -- Optional: [Loki history and Grafana links](configuration.md#loki-and-grafana) +- [Login mit fail2ban absichern](operations.md#login-mit-fail2ban-absichern) +- [Updates und Backups](operations.md) +- Optional: [Loki-Verlauf und Grafana-Links](configuration.md#loki-und-grafana) diff --git a/docs/operations.md b/docs/operations.md index 14a0391..739d563 100644 --- a/docs/operations.md +++ b/docs/operations.md @@ -1,32 +1,32 @@ -# Operations +# Betrieb -All commands are run in the directory with `docker-compose.yaml` (e.g. `/opt/fail2ban-webui`). +Alle Befehle im Verzeichnis mit der `docker-compose.yaml` ausführen (z. B. `/opt/fail2ban-webui`). -## Status and logs +## Status und Logs ```bash 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 # every agent reachable? +docker compose run --rm fail2ban-webui check # jeder Agent erreichbar? ``` -Log lines of interest: +Wichtige Logzeilen: -| Line | Meaning | +| Zeile | Bedeutung | |---|---| -| `fail2ban-webui 1.0.0 – hosts: … – auth: … – loki: … – geoip: …` | start summary | -| `AUDIT {…}` | ban/unban with user, client IP, hosts and result | -| `LOGIN_OK` / `LOGIN_FAILED` / `LOGIN_LOCKED` / `LOGOUT` | sign-in events | -| `OIDC_FAILED` / `OIDC_DENIED` | SSO errors / account not allowed | +| `fail2ban-webui 1.0.0 – hosts: … – auth: … – loki: … – geoip: …` | Zusammenfassung beim Start | +| `AUDIT {…}` | Sperren/Entsperren mit Benutzer, Client-IP, Hosts und Ergebnis | +| `LOGIN_OK` / `LOGIN_FAILED` / `LOGIN_LOCKED` / `LOGOUT` | Anmeldeereignisse | +| `OIDC_FAILED` / `OIDC_DENIED` | SSO-Fehler / Konto nicht zugelassen | -The audit log is also kept in `data/audit.log` (one JSON object per line): +Das Protokoll liegt zusätzlich in `data/audit.log` (ein JSON-Objekt pro Zeile): ```bash tail -n 20 data/audit.log | python3 -m json.tool --json-lines ``` -On the hosts every agent action is in syslog: +Auf den Hosts steht jede Agent-Aktion im Syslog: ```bash journalctl -t f2b-webctl --since today @@ -35,63 +35,64 @@ journalctl -t f2b-webctl --since today ## Update ```bash -cp -a .env docker-compose.yaml config /backups/fail2ban-webui_$(date +%F)/ # or your backup location +cp -a .env docker-compose.yaml config /backups/fail2ban-webui_$(date +%F)/ # oder dein Backup-Ort docker compose pull docker compose up -d docker compose ps docker compose run --rm fail2ban-webui check ``` -Read the [changelog](../CHANGELOG.md) first – if it mentions a new agent version, update -the agents as well (re-run `install.sh` with the same options, see [agent.md](agent.md#update)). -The `1` tag only moves within the 1.x series; breaking changes get a new major version. +Vorher das [Changelog](../CHANGELOG.md) lesen – nennt es eine neue Agent-Version, auch die +Agenten aktualisieren (`install.sh` mit denselben Optionen erneut ausführen, siehe +[agent.md](agent.md#aktualisieren)). Der Tag `1` bewegt sich nur innerhalb von 1.x; +inkompatible Änderungen bekommen eine neue Hauptversion. -Rebuild/pull regularly even without new features: the image is rebuilt with current -Debian security updates. +Auch ohne neue Funktionen regelmäßig neu ziehen bzw. bauen: Das Image wird mit aktuellen +Debian-Sicherheitsupdates neu gebaut. ## Backup -| What | Why | +| Was | Warum | |---|---| -| `.env` | settings, password hash, OIDC secret | -| `config/hosts.yaml` | host list | -| `data/ssh/` | private key and pinned host keys – without them every agent needs the new key | -| `data/audit.log` | audit trail | -| `data/secret_key` | only keeps sessions valid; losing it signs everybody out | +| `.env` | Einstellungen, Passwort-Hash, OIDC-Secret | +| `config/hosts.yaml` | Hostliste | +| `data/ssh/` | privater Schlüssel und festgelegte Host-Schlüssel – ohne sie braucht jeder Agent den neuen Schlüssel | +| `data/audit.log` | Protokoll | +| `data/secret_key` | hält nur Sitzungen gültig; geht er verloren, werden alle abgemeldet | -`data/geoip/` is downloaded again automatically. +`data/geoip/` wird automatisch neu heruntergeladen. ```bash tar -cpzf /backups/fail2ban-webui_$(date +%F_%H-%M).tar.gz .env docker-compose.yaml config data sha256sum /backups/fail2ban-webui_*.tar.gz | tail -n1 ``` -The archive contains the private SSH key – store it accordingly. +Das Archiv enthält den privaten SSH-Schlüssel – entsprechend sicher aufbewahren. -## Moving to another host +## Umzug auf einen anderen Host -1. `docker compose stop` on the old host, create the archive as above. -2. Copy it, unpack with `tar -xpzf` (keeps owner 10050 and permissions). -3. On **every agent** add the new address to `from=` – easiest by re-running - `install.sh … --from `. -4. `docker compose up -d`, then `check`. +1. Auf dem alten Host `docker compose stop`, Archiv wie oben erstellen. +2. Kopieren, mit `tar -xpzf` entpacken (behält Besitzer 10050 und Rechte). +3. Auf **jedem Agenten** die neue Adresse in `from=` aufnehmen – am einfachsten + `install.sh … --from ` erneut ausführen. +4. `docker compose up -d`, danach `check`. -## Rotating the SSH key +## SSH-Schlüssel tauschen ```bash docker compose run --rm fail2ban-webui keygen --force -# on every host: -bash install.sh --key-file new.pub --from … (same options as before) +# auf jedem Host: +bash install.sh --key-file new.pub --from … (dieselben Optionen wie zuvor) ``` -The installer replaces only entries with the same key; remove the old one with -`uninstall.sh` first or delete its line from `authorized_keys` (it contains +Der Installer ersetzt nur Einträge mit demselben Schlüssel; den alten vorher mit +`uninstall.sh` entfernen oder seine Zeile aus `authorized_keys` löschen (sie enthält `command="…f2b-webctl"`). -## Protecting the login with fail2ban +## Login mit fail2ban absichern -The UI locks a client after 5 failed logins itself. To ban persistent attackers on the -firewall, let fail2ban on the **proxy host** read the container log. +Die Oberfläche sperrt einen Client nach 5 Fehlversuchen selbst. Um hartnäckige Angreifer an +der Firewall zu sperren, liest fail2ban auf dem **Proxy-Host** das Container-Log. `/etc/fail2ban/filter.d/fail2ban-webui.conf` @@ -124,27 +125,27 @@ fail2ban-client reload fail2ban-regex /var/lib/docker/containers/*/*-json.log /etc/fail2ban/filter.d/fail2ban-webui.conf ``` -This only works when the container sees the real client address (`TRUSTED_PROXY_HOPS` -set correctly). User names are logged with spaces and control characters replaced, so a -crafted user name cannot inject a second log line. +Das funktioniert nur, wenn der Container die echte Client-Adresse sieht (`TRUSTED_PROXY_HOPS` +richtig gesetzt). Benutzernamen werden mit ersetzten Leer- und Steuerzeichen protokolliert; +ein präparierter Benutzername kann also keine zweite Logzeile einschleusen. -## Troubleshooting +## Fehlersuche -| Symptom | Cause / fix | +| Symptom | Ursache / Lösung | |---|---| -| Container exits: `… is not writable for UID 10050` | `chown -R 10050:10050 data` | -| Container exits: `AUTH_MODE=local needs AUTH_PASSWORD_HASH` | run `hash-password`, paste the line with single quotes | -| Container exits: `No hosts configured` | `config/hosts.yaml` missing or empty; on Windows/Docker Desktop: the drive cannot be bind-mounted | -| Login always fails after pasting the hash | single quotes missing in `.env` – Compose ate the `$` parts | -| Everybody locked out together / audit shows the proxy IP | `TRUSTED_PROXY_HOPS` not set | -| `Host key verification failed` | host not in `known_hosts` or its key changed → `keyscan`, compare fingerprints | -| `Permission denied (publickey)` | key not installed for that user, wrong `user:` in `hosts.yaml`, wrong `from=` (the address the host sees!), or sshd does not read that `authorized_keys` file | -| `sudo: a password is required` | sudo rule missing → re-run `install.sh` | -| `The agent does not run as root` | forced command without `sudo -n` for a non-root user → re-run `install.sh` | -| `Timeout talking to …` | firewall/port, or the host is slow – raise `SSH_TIMEOUT` | -| `The jail manual-block does not exist on this host` | installed with `--no-jail` or with another `--manual-jail`; check `MANUAL_JAIL` in `/etc/f2b-webctl.conf` | -| Bans have no start/end time | fail2ban older than 0.11 (no `--with-time`) | -| Banned IP still reaches a Docker service | banaction blocks only `INPUT` – see [Docker hosts](agent.md#docker-hosts) | -| OIDC: `redirect_uri` mismatch | provider must allow exactly `/auth/callback` | -| OIDC: `mismatching_state` | session cookie lost between redirect and callback: `PUBLIC_URL` host differs from the one in the browser, or `https` without `Secure`/proxy headers | -| OIDC: "account … is not allowed" | user not in `OIDC_ALLOWED_GROUPS`; check that the provider sends the `groups` claim | +| 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 | +| Container beendet sich: `No hosts configured` | `config/hosts.yaml` fehlt oder ist leer; unter Windows/Docker Desktop: das Laufwerk lässt sich nicht einbinden | +| Anmeldung scheitert immer, nachdem der Hash eingefügt wurde | einfache Anführungszeichen in der `.env` fehlen – Compose hat die `$`-Teile verschluckt | +| Alle werden gemeinsam gesperrt / Protokoll zeigt die Proxy-IP | `TRUSTED_PROXY_HOPS` nicht gesetzt | +| `Host key verification failed` | Host nicht in `known_hosts` oder sein Schlüssel hat sich geändert → `keyscan`, Fingerprints vergleichen | +| `Permission denied (publickey)` | Schlüssel für diesen Benutzer nicht installiert, falsches `user:` in `hosts.yaml`, falsches `from=` (die Adresse, die der Host sieht!) oder sshd liest diese `authorized_keys`-Datei nicht | +| `sudo: a password is required` | sudo-Regel fehlt → `install.sh` erneut ausführen | +| `The agent does not run as root` | Forced-Command ohne `sudo -n` für einen Nicht-root-Benutzer → `install.sh` erneut ausführen | +| `Timeout talking to …` | Firewall/Port, oder der Host ist langsam – `SSH_TIMEOUT` erhöhen | +| `The jail manual-block does not exist on this host` | mit `--no-jail` oder einem anderen `--manual-jail` installiert; `MANUAL_JAIL` in `/etc/f2b-webctl.conf` prüfen | +| Sperren haben keine Start-/Endzeit | fail2ban älter als 0.11 (kein `--with-time`) | +| Gesperrte IP erreicht trotzdem einen Docker-Dienst | Banaction sperrt nur `INPUT` – siehe [Docker-Hosts](agent.md#docker-hosts) | +| OIDC: `redirect_uri` passt nicht | Provider muss genau `/auth/callback` erlauben | +| OIDC: `mismatching_state` | Sitzungs-Cookie zwischen Weiterleitung und Callback verloren: Host in `PUBLIC_URL` weicht vom Browser ab, oder `https` ohne `Secure`/Proxy-Header | +| OIDC: „Konto … darf diese Anwendung nicht verwenden" | Benutzer nicht in `OIDC_ALLOWED_GROUPS`; prüfen, ob der Provider den Claim `groups` mitschickt | diff --git a/docs/reverse-proxy.md b/docs/reverse-proxy.md index 92374c1..3ae6135 100644 --- a/docs/reverse-proxy.md +++ b/docs/reverse-proxy.md @@ -1,42 +1,42 @@ -# Reverse proxy +# Reverse Proxy -The container serves plain HTTP on port 8080 (published as `127.0.0.1:8080` in the -example). Put a reverse proxy with TLS in front of it and set in `.env`: +Der Container liefert reines HTTP auf Port 8080 (im Beispiel veröffentlicht als +`127.0.0.1:8080`). Davor gehört ein Reverse Proxy mit TLS; in der `.env` dazu setzen: ```ini PUBLIC_URL=https://fail2ban.example.org TRUSTED_PROXY_HOPS=1 ``` -The proxy must pass `Host` unchanged and set `X-Forwarded-For` and `X-Forwarded-Proto` – -all examples below do. Restricting access to known IP addresses at the proxy is a cheap -extra layer and strongly recommended. +Der Proxy muss `Host` unverändert weitergeben und `X-Forwarded-For` sowie `X-Forwarded-Proto` +setzen – alle Beispiele unten tun das. Den Zugriff am Proxy zusätzlich auf bekannte +IP-Adressen zu beschränken kostet wenig und ist dringend empfohlen. ## Nginx Proxy Manager **Hosts → Proxy Hosts → Add Proxy Host** -| Field | Value | +| Feld | Wert | |---|---| | Domain Names | `fail2ban.example.org` | -| Scheme / Forward Hostname / Port | `http` · IP of the Docker host (or `127.0.0.1` if NPM runs with host networking) · `8080` | -| Block Common Exploits | on | -| SSL | request a certificate, *Force SSL*, *HTTP/2*, *HSTS* | -| Access List | optional: an access list with your IP addresses (*Satisfy any* off) | +| Scheme / Forward Hostname / Port | `http` · IP des Docker-Hosts (oder `127.0.0.1`, wenn NPM im Host-Netzwerk läuft) · `8080` | +| Block Common Exploits | an | +| SSL | Zertifikat anfordern, *Force SSL*, *HTTP/2*, *HSTS* | +| Access List | optional: eine Access List mit deinen IP-Adressen (*Satisfy any* aus) | -If NPM runs in another Docker network on the same host, bind the UI port to the host's -LAN address (e.g. `"192.168.1.10:8080:8080"`) or attach the UI to NPM's network and use -`fail2ban-webui:8080` as target. +Läuft NPM auf demselben Host in einem anderen Docker-Netz, den Port der Oberfläche an die +LAN-Adresse des Hosts binden (z. B. `"192.168.1.10:8080:8080"`) oder die Oberfläche an das +Netz von NPM hängen und `fail2ban-webui:8080` als Ziel verwenden. -Response headers belong into **Advanced → Custom Nginx Configuration**; NPM's image -includes the *headers-more* module, which also applies them to proxied responses: +Antwort-Header gehören nach **Advanced → Custom Nginx Configuration**; das NPM-Image enthält +das Modul *headers-more*, das sie auch auf weitergeleitete Antworten anwendet: ```nginx more_set_headers "Strict-Transport-Security: max-age=31536000; includeSubDomains"; ``` -(The app sets CSP, `X-Frame-Options`, `X-Content-Type-Options`, `Referrer-Policy` and -`Cache-Control` itself.) +(CSP, `X-Frame-Options`, `X-Content-Type-Options`, `Referrer-Policy` und `Cache-Control` +setzt die App selbst.) ## Caddy @@ -50,11 +50,11 @@ fail2ban.example.org { } ``` -Caddy sets `X-Forwarded-For`/`-Proto` automatically. +Caddy setzt `X-Forwarded-For`/`-Proto` automatisch. -## Traefik (Docker labels) +## Traefik (Docker-Labels) -Attach the container to Traefik's network and add labels instead of publishing the port: +Den Container an das Netz von Traefik hängen und Labels setzen, statt den Port zu veröffentlichen: ```yaml services: @@ -73,7 +73,7 @@ services: - traefik ``` -### Traefik with Authentik forward auth (proxy mode) +### Traefik mit Authentik Forward Auth (Proxy-Modus) ```yaml - traefik.http.routers.f2bwebui.middlewares=authentik@docker @@ -110,12 +110,12 @@ server { } ``` -## Check +## Prüfen ```bash curl -sI https://fail2ban.example.org/login | grep -iE '^(HTTP|content-security|set-cookie)' ``` -Expect `HTTP/2 200`, the `Content-Security-Policy` header and a session cookie with -`Secure; HttpOnly; SameSite=Lax`. A cookie without `Secure` means `PUBLIC_URL` does not -start with `https://`. +Erwartet werden `HTTP/2 200`, der Header `Content-Security-Policy` und ein Sitzungs-Cookie +mit `Secure; HttpOnly; SameSite=Lax`. Fehlt `Secure` am Cookie, beginnt `PUBLIC_URL` nicht +mit `https://`.