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 <noreply@anthropic.com>
This commit is contained in:
schiwagoaandClaude Opus 5.5 committed 2026-10-25 15:41:59 +01:00
1 parent 2e3f79236c
commit 019c74a418
8 files changed
+469 -463

No files matched your search

+40 -40
View File
@@ -1,34 +1,34 @@
# fail2ban-webui # fail2ban-webui
A small, self-hosted web interface to **view, ban and unban fail2ban IPs on several hosts** – Eine schlanke, selbst gehostete Weboberfläche, um **fail2ban-Sperren auf mehreren Hosts
bare metal, VMs, LXC containers or Docker 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 - Eine Seite für alle Hosts: aktuelle Sperren pro Jail, Herkunft (GeoIP) mit Weltkarte, Suche und Filter
- Ban an IP permanently on any selection of hosts, unban per jail or everywhere - IP dauerhaft auf beliebigen Hosts sperren, pro Jail oder überall entsperren
- Audit log of every action (who, when, from where) - Protokoll jeder Aktion (wer, wann, von wo)
- Optional: log history per IP from **Loki**, links into **Grafana** - Optional: Log-Verlauf pro IP aus **Loki**, Links nach **Grafana**
- Sign-in with a local user, **OpenID Connect** (e.g. Authentik) or your reverse proxy - Anmeldung mit lokalem Benutzer, **OpenID Connect** (z. B. Authentik) oder über den Reverse Proxy
- English and German UI, more languages via one JSON file - Oberfläche auf Englisch und Deutsch, weitere Sprachen über eine JSON-Datei
- Runs as a hardened container (read-only, no capabilities, non-root), `amd64` and `arm64` - Läuft als gehärteter Container (schreibgeschützt, ohne Capabilities, nicht als root), `amd64` und `arm64`
``` ```
Browser ──HTTPS──▶ reverse proxy ──▶ fail2ban-webui (Docker) Browser ──HTTPS──▶ Reverse Proxy ──▶ fail2ban-webui (Docker)
│ SSH, one restricted key │ 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 f2b-webctl f2b-webctl f2b-webctl
fail2ban fail2ban fail2ban fail2ban fail2ban fail2ban
``` ```
The web UI never gets a shell on your hosts. Every host runs a tiny agent, Die Weboberfläche bekommt nie eine Shell auf deinen Hosts. Auf jedem Host läuft ein kleiner
[`f2b-webctl`](agent/f2b-webctl), as SSH *forced command*. It only knows Agent, [`f2b-webctl`](agent/f2b-webctl), als SSH-*Forced-Command*. Er kennt nur
`list`, `ban <ip>`, `unban <jail> <ip>`, `unban-all <ip>` and `version`, refuses to ban `list`, `ban <ip>`, `unban <jail> <ip>`, `unban-all <ip>` und `version`, sperrt niemals
private networks, the host's own addresses or anything on its protected list, and logs private Netze, die eigenen Adressen des Hosts oder Einträge seiner Schutzliste und
every action to syslog. Access to the fail2ban socket would be equivalent to root – protokolliert jede Aktion im Syslog. Zugriff auf den fail2ban-Socket wäre gleichbedeutend
the agent is the security boundary, not the web UI. mit root – die Sicherheitsgrenze ist der Agent, nicht die Weboberfläche.
## Quick start ## Schnellstart
```bash ```bash
mkdir -p /opt/fail2ban-webui/{config,data} && cd /opt/fail2ban-webui 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 curl -fsSLo config/hosts.yaml https://git.retronerd.at/schiwagoa/fail2ban-webui/raw/branch/main/examples/hosts.yaml.example
chown -R 10050:10050 data chown -R 10050:10050 data
docker compose run --rm -it fail2ban-webui hash-password # paste the line into .env docker compose run --rm -it fail2ban-webui hash-password # Zeile in die .env übernehmen
docker compose run --rm fail2ban-webui keygen # public key for the agents docker compose run --rm fail2ban-webui keygen # öffentlicher Schlüssel für die Agenten
# on every fail2ban host: install the agent (docs/agent.md), then: # auf jedem fail2ban-Host den Agenten installieren (docs/agent.md), danach:
docker compose run --rm -it fail2ban-webui keyscan docker compose run --rm -it fail2ban-webui keyscan
docker compose run --rm fail2ban-webui check docker compose run --rm fail2ban-webui check
docker compose up -d 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 | | [Installation](docs/installation.md) | Weboberfläche Schritt für Schritt, von `mkdir` bis zur ersten Anmeldung |
| [Agent](docs/agent.md) | installing `f2b-webctl` on bare metal, LXC and Docker hosts | | [Agent](docs/agent.md) | `f2b-webctl` auf Bare Metal, LXC und Docker-Hosts installieren |
| [Configuration](docs/configuration.md) | every setting in `.env` and `hosts.yaml` | | [Konfiguration](docs/configuration.md) | alle Einstellungen in `.env` und `hosts.yaml` |
| [Authentication](docs/authentication.md) | local login, OpenID Connect (Authentik), reverse-proxy auth | | [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 | | [Reverse Proxy](docs/reverse-proxy.md) | Nginx Proxy Manager, Caddy, Traefik, nginx |
| [Operations](docs/operations.md) | logs, updates, backups, troubleshooting | | [Betrieb](docs/operations.md) | Logs, Updates, Backups, Fehlersuche |
| [Development](docs/development.md) | local testbed, tests, adding a language | | [Entwicklung](docs/development.md) | lokales Testbed, Tests, neue Sprache hinzufügen |
| [Security](SECURITY.md) | security model and reporting vulnerabilities | | [Sicherheit](SECURITY.md) | Sicherheitsmodell und Melden von Schwachstellen |
## Requirements ## Voraussetzungen
- Web UI: Docker with Compose v2, `amd64` or `arm64` - Weboberfläche: Docker mit Compose v2, `amd64` oder `arm64`
- Every fail2ban host: fail2ban ≥ 0.11 (start/end times need `--with-time`), `python3`, - Jeder fail2ban-Host: fail2ban ≥ 0.11 (Start-/Endzeiten brauchen `--with-time`), `python3`,
`sshd`, `sudo` (or a root key), `bash` `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 [MIT](LICENSE). IP-Geolokalisierung durch [DB-IP Lite](https://db-ip.com) (CC BY 4.0), wird
only when `GEOIP_AUTO_DOWNLOAD=1`. World map from [Natural Earth](https://www.naturalearthdata.com) nur bei `GEOIP_AUTO_DOWNLOAD=1` heruntergeladen. Weltkarte von [Natural Earth](https://www.naturalearthdata.com)
(public domain), bundled with the app. (Public Domain), in der App enthalten.
--- ---
+104 -103
View File
@@ -1,61 +1,61 @@
# Agent – f2b-webctl # Agent – f2b-webctl
Every fail2ban host runs the agent [`f2b-webctl`](../agent/f2b-webctl): a small Python Auf jedem fail2ban-Host läuft der Agent [`f2b-webctl`](../agent/f2b-webctl): ein kleines
script (standard library only) started as SSH **forced command** of the web UI's key. Python-Skript (nur Standardbibliothek), das als SSH-**Forced-Command** für den Schlüssel der
The web UI cannot run anything else on the host. 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) | | `list` | alle Jails mit ihren gesperrten IPs (JSON) |
| `ban <ip>` | ban permanently in the manual jail (`manual-block`) | | `ban <ip>` | dauerhaft im manuellen Jail (`manual-block`) sperren |
| `unban <jail> <ip>` | unban in one jail | | `unban <jail> <ip>` | in einem Jail entsperren |
| `unban-all <ip>` | unban in all jails | | `unban-all <ip>` | in allen Jails entsperren |
| `version` | agent version | | `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`, …) (`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 - alle Adressen des Hosts selbst und die Adresse, von der die Weboberfläche zugreift
- everything listed in `PROTECTED` in `/etc/f2b-webctl.conf` (e.g. your home IP) - 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 | | python3 | ≥ 3.8 |
| sshd | reachable from the web UI host | | sshd | vom Webui-Host erreichbar |
| sudo | for the default user `f2bweb` (`apt install sudo`); not needed with `--user root` | | sudo | für den Standardbenutzer `f2bweb` (`apt install sudo`); mit `--user root` nicht nötig |
| bash, ssh-keygen | for the installer | | bash, ssh-keygen | für den Installer |
Works on Debian, Ubuntu, Proxmox VE and other systemd or non-systemd Linux systems – Läuft auf Debian, Ubuntu, Proxmox VE und anderen Linux-Systemen mit oder ohne systemd –
bare metal, VMs, LXC containers (privileged or unprivileged) and Docker hosts. Bare Metal, VMs, LXC-Container (privilegiert oder unprivilegiert) und Docker-Hosts.
## Installation ## Installation
You need the public key printed by `fail2ban-webui keygen` and the address the web UI Gebraucht werden der öffentliche Schlüssel aus `fail2ban-webui keygen` und die Adresse, von
connects from (the public IP of the Docker host, or its LAN address). der die Weboberfläche zugreift (öffentliche IP des Docker-Hosts oder seine LAN-Adresse).
```bash ```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 git clone https://git.retronerd.at/schiwagoa/fail2ban-webui.git /tmp/fail2ban-webui
cd /tmp/fail2ban-webui/agent cd /tmp/fail2ban-webui/agent
echo 'ssh-ed25519 AAAA… fail2ban-webui' > /tmp/webui.pub 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 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 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 Ohne `git`: die drei Dateien `f2b-webctl`, `install.sh` und `uninstall.sh` aus dem Ordner
the `agent/` directory into one directory and run `install.sh` from there. `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 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 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` 1. sichert jede Datei, die er ändert, nach `--backup-dir`
2. installs `/usr/local/sbin/f2b-webctl` (`root:root 0750`) 2. installiert `/usr/local/sbin/f2b-webctl` (`root:root 0750`)
3. writes `/etc/f2b-webctl.conf` (kept if it already exists) 3. schreibt `/etc/f2b-webctl.conf` (bleibt erhalten, wenn sie schon existiert)
4. creates the system user `f2bweb` (home `/var/lib/f2bweb`, no password) and the sudo rule 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" Defaults!/usr/local/sbin/f2b-webctl env_keep += "SSH_ORIGINAL_COMMAND SSH_CLIENT"
f2bweb ALL=(root) NOPASSWD: /usr/local/sbin/f2b-webctl 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 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) 6. legt das Jail `manual-block` an, falls es noch nicht existiert (siehe unten)
7. runs a self test through the same sudo path the web UI uses 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 Den Installer erneut auszuführen ist unbedenklich: Er aktualisiert das Skript, ersetzt den
keeps your configuration. 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) | | `--key-file DATEI` / `--key "SCHLÜSSEL"` | – | öffentlicher Schlüssel der Weboberfläche (Pflicht) |
| `--from ADDRESSES` | any | only accept the key from these IPs/networks (comma separated). **Strongly recommended.** | | `--from ADRESSEN` | jede | Schlüssel nur von diesen IPs/Netzen akzeptieren (durch Komma getrennt). **Dringend empfohlen.** |
| `--user NAME` | `f2bweb` | SSH user; `root` works without sudo | | `--user NAME` | `f2bweb` | SSH-Benutzer; `root` funktioniert ohne sudo |
| `--authorized-keys-file PATH` | `~USER/.ssh/authorized_keys` | where the key entry goes – see [managed keys](#hosts-with-managed-authorized_keys) | | `--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 for manual bans | | `--manual-jail NAME` | `manual-block` | Jail für manuelle Sperren |
| `--banaction ACTION` | from `jail.local` | banaction of a newly created manual jail | | `--banaction AKTION` | aus `jail.local` | Banaction für ein neu angelegtes manuelles Jail |
| `--protect NETWORKS` | – | additional protected addresses (only when the config file is written) | | `--protect NETZE` | – | zusätzliche geschützte Adressen (nur wenn die Konfigurationsdatei geschrieben wird) |
| `--no-jail` | – | do not create the manual jail | | `--no-jail` | – | kein manuelles Jail anlegen |
| `--force-config` | – | overwrite an existing `/etc/f2b-webctl.conf` | | `--force-config` | – | vorhandene `/etc/f2b-webctl.conf` überschreiben |
| `--backup-dir DIR` | `/var/backups/fail2ban-webui-agent` | backups of changed files | | `--backup-dir VERZEICHNIS` | `/var/backups/fail2ban-webui-agent` | Sicherungen geänderter Dateien |
| `--dry-run` | – | only show what would be done | | `--dry-run` | – | nur anzeigen, was passieren würde |
| `-y`, `--yes` | – | do not ask | | `-y`, `--yes` | – | nicht nachfragen |
The installer warns when sshd would not read the chosen key file (`AuthorizedKeysFile`), Der Installer warnt, wenn sshd die gewählte Schlüsseldatei gar nicht liest
when `AllowUsers`/`AllowGroups` are set, or when `--user root` meets `PermitRootLogin no`. (`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` Sperren aus der Weboberfläche landen in einem Jail, standardmäßig `manual-block`, mit
(permanent). If the jail does not exist, the installer creates: `bantime = -1` (dauerhaft). Gibt es das Jail noch nicht, legt der Installer an:
`/etc/fail2ban/filter.d/manual-block.conf` `/etc/fail2ban/filter.d/manual-block.conf`
@@ -139,64 +140,64 @@ maxretry = 1
bantime = -1 bantime = -1
``` ```
The jail uses your default `banaction` from `jail.local`. Bans survive restarts through Das Jail nutzt deine Standard-`banaction` aus `jail.local`. Sperren überstehen Neustarts
the fail2ban database. über die fail2ban-Datenbank.
An existing jail with that name is never touched – if you already have a jail for manual Ein vorhandenes Jail mit diesem Namen wird nie angefasst – gibt es schon ein Jail für
bans, pass its name with `--manual-jail`. 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 Die Standardaktionen von fail2ban sperren in der Kette `INPUT`. Verkehr zu **veröffentlichten
ports** does not pass `INPUT` but `FORWARD` → `DOCKER-USER`, so those bans would not Docker-Ports** läuft aber nicht über `INPUT`, sondern über `FORWARD` → `DOCKER-USER`; solche
affect your containers. For a Docker host, create the manual jail with an action that Sperren würden deine Container also nicht schützen. Auf einem Docker-Host das manuelle Jail
uses the `DOCKER-USER` chain, e.g. with iptables: deshalb mit einer Aktion anlegen, die die Kette `DOCKER-USER` nutzt, z. B. mit iptables:
```bash ```bash
bash install.sh --key-file /tmp/webui.pub --from 198.51.100.10 \ bash install.sh --key-file /tmp/webui.pub --from 198.51.100.10 \
--banaction "iptables-allports[chain=DOCKER-USER]" --banaction "iptables-allports[chain=DOCKER-USER]"
``` ```
Check the result after the first ban: Nach der ersten Sperre prüfen:
```bash ```bash
iptables -S DOCKER-USER | head iptables -S DOCKER-USER | head
``` ```
If the web UI runs on the same Docker host, use `address: host.docker.internal` in Läuft die Weboberfläche auf demselben Docker-Host, in `hosts.yaml` `address: host.docker.internal`
`hosts.yaml` and allow SSH from the Docker network if a firewall (e.g. UFW) blocks it: verwenden und SSH aus dem Docker-Netz erlauben, falls eine Firewall (z. B. UFW) es blockiert:
```bash ```bash
ufw allow from 172.23.51.0/24 to any port 22 proto tcp comment 'fail2ban-webui' 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. Der Agent läuft im Container, in dem fail2ban läuft – sonst ist nichts Besonderes nötig.
In **unprivileged** containers fail2ban itself must be able to change the container's In **unprivilegierten** Containern muss fail2ban selbst die Firewall des Containers ändern
firewall (nftables/iptables inside the CT); test with a manual ban: können (nftables/iptables im CT); mit einer manuellen Sperre testen:
```bash ```bash
fail2ban-client set manual-block banip 203.0.113.1 && nft list ruleset | grep 203.0.113.1 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 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`. Manche Werkzeuge (Ansible, Keywarden, cloud-init, …) überschreiben `~/.ssh/authorized_keys`.
Put the key into a file they do not manage and make sure sshd reads it: Den Schlüssel dann in eine Datei legen, die sie nicht verwalten, und sicherstellen, dass sshd sie liest:
```bash ```bash
grep -i '^AuthorizedKeysFile' /etc/ssh/sshd_config /etc/ssh/sshd_config.d/*.conf 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 \ bash install.sh --key-file /tmp/webui.pub --from 198.51.100.10 \
--user root --authorized-keys-file /root/.ssh/authorized_keys2 --user root --authorized-keys-file /root/.ssh/authorized_keys2
``` ```
## Configuration – /etc/f2b-webctl.conf ## Konfiguration – /etc/f2b-webctl.conf
```ini ```ini
# fail2ban-webui agent (f2b-webctl) – settings # fail2ban-webui agent (f2b-webctl) – settings
@@ -210,60 +211,60 @@ PROTECT_OWN_ADDRESSES=yes
PROTECT_CLIENT=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 ```bash
# as root, directly # direkt als root
f2b-webctl list | python3 -m json.tool | head -30 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 runuser -u f2bweb -- env SSH_ORIGINAL_COMMAND=version sudo -n /usr/local/sbin/f2b-webctl
# what the agent did # was der Agent getan hat
journalctl -t f2b-webctl --since today # or: grep f2b-webctl /var/log/auth.log 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 Den Installer der neuen Version mit denselben Optionen erneut ausführen. Konfiguration und
and the jail are kept; the script and the key entry are replaced. Jail bleiben erhalten; Skript und Schlüsseleintrag werden ersetzt.
## Uninstall ## Deinstallieren
```bash ```bash
bash uninstall.sh --dry-run bash uninstall.sh --dry-run
bash uninstall.sh bash uninstall.sh
``` ```
| Option | Meaning | | Option | Bedeutung |
|---|---| |---|---|
| `--user NAME` | agent user (default `f2bweb`) | | `--user NAME` | Agent-Benutzer (Standard `f2bweb`) |
| `--authorized-keys-file PATH` | file with the key entry | | `--authorized-keys-file PFAD` | Datei mit dem Schlüsseleintrag |
| `--remove-user` | also delete the user and its home | | `--remove-user` | auch den Benutzer samt Home löschen |
| `--remove-jail` | also delete the manual jail – **lifts all manual bans** (they are listed first) | | `--remove-jail` | auch das manuelle Jail löschen – **hebt alle manuellen Sperren auf** (sie werden vorher aufgelistet) |
| `--backup-dir DIR`, `--dry-run`, `--yes` | as for the installer | | `--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 ```bash
install -o root -g root -m 0750 f2b-webctl /usr/local/sbin/f2b-webctl 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 useradd --system --create-home --home-dir /var/lib/f2bweb --shell /bin/sh f2bweb
usermod -p '*' 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 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 chown f2bweb: /var/lib/f2bweb/.ssh/authorized_keys
chmod 600 /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 touch /var/log/fail2ban-manual-block.log
fail2ban-client reload fail2ban-client reload
``` ```
+57 -57
View File
@@ -1,19 +1,19 @@
# Authentication # Anmeldung
`AUTH_MODE` selects how users sign in. The container refuses to start without a working `AUTH_MODE` legt fest, wie sich Benutzer anmelden. Ohne funktionierende Konfiguration startet
configuration – there is no unauthenticated mode. der Container nicht – einen Modus ohne Anmeldung gibt es nicht.
| Mode | Use when | | Modus | Einsatz |
|---|---| |---|---|
| `local` | one shared admin account is enough | | `local` | ein gemeinsames Admin-Konto reicht |
| `oidc` | you have an identity provider (Authentik, Keycloak, Authelia, Zitadel, …) | | `oidc` | ein Identity Provider ist vorhanden (Authentik, Keycloak, Authelia, Zitadel, …) |
| `local,oidc` | SSO with a local fallback account | | `local,oidc` | Single Sign-on mit lokalem Notfallkonto |
| `proxy` | your reverse proxy already authenticates (forward auth, basic auth, IP allow list) | | `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 Jede Änderung (Sperren/Entsperren) braucht zusätzlich das CSRF-Token der Sitzung und einen
`Origin` header, and every action is written to the audit log with the user name. passenden `Origin`-Header; jede Aktion wird mit Benutzernamen protokolliert.
## Local user ## Lokaler Benutzer
```bash ```bash
docker compose run --rm -it fail2ban-webui hash-password docker compose run --rm -it fail2ban-webui hash-password
@@ -25,42 +25,43 @@ AUTH_USER=admin
AUTH_PASSWORD_HASH='scrypt:32768:8:1$…' AUTH_PASSWORD_HASH='scrypt:32768:8:1$…'
``` ```
- Passwords need at least 12 characters; the hash is scrypt (Werkzeug). - Passwörter brauchen mindestens 12 Zeichen; der Hash ist scrypt (Werkzeug).
- After `LOGIN_MAX_ATTEMPTS` (5) failures a client IP is locked for `LOGIN_LOCK_MINUTES` (15). - Nach `LOGIN_MAX_ATTEMPTS` (5) Fehlversuchen wird eine Client-IP für `LOGIN_LOCK_MINUTES` (15) gesperrt.
- Failed logins are logged as `LOGIN_FAILED user=… ip=…` – see - Fehlgeschlagene Anmeldungen werden als `LOGIN_FAILED user=… ip=…` protokolliert – siehe
[operations.md](operations.md#protecting-the-login-with-fail2ban) to let fail2ban ban [operations.md](operations.md#login-mit-fail2ban-absichern), um sie von fail2ban am Proxy
them at the proxy. sperren zu lassen.
- Set `TRUSTED_PROXY_HOPS=1` behind a reverse proxy, otherwise all clients appear with the - Hinter einem Reverse Proxy `TRUSTED_PROXY_HOPS=1` setzen, sonst erscheinen alle Clients
proxy's address and share one lockout counter. mit der Adresse des Proxys und teilen sich einen Sperrzähler.
## OpenID Connect ## OpenID Connect
Requirements: `PUBLIC_URL` is set to the external `https://` URL, and the provider allows Voraussetzungen: `PUBLIC_URL` ist auf die externe `https://`-URL gesetzt, und der Provider
the redirect URI **`<PUBLIC_URL>/auth/callback`**. erlaubt die Redirect-URI **`<PUBLIC_URL>/auth/callback`**.
The UI uses the authorization code flow with PKCE (`S256`) and a confidential client. Die Oberfläche nutzt den Authorization Code Flow mit PKCE (`S256`) und einen vertraulichen
User name: `OIDC_USERNAME_CLAIM` (default `preferred_username`); groups: Client. Benutzername: `OIDC_USERNAME_CLAIM` (Standard `preferred_username`); Gruppen:
`OIDC_GROUPS_CLAIM` (default `groups`). `OIDC_GROUPS_CLAIM` (Standard `groups`).
**Always restrict who may sign in** with `OIDC_ALLOWED_GROUPS` and/or **Immer einschränken, wer sich anmelden darf** – mit `OIDC_ALLOWED_GROUPS` und/oder
`OIDC_ALLOWED_USERS` – or with an access policy in the provider. Without either, every `OIDC_ALLOWED_USERS` oder mit einer Zugriffsrichtlinie im Provider. Ohne beides kann jedes
account that the provider lets use this client can ban IPs (a warning is logged at start). Konto, das der Provider für diesen Client zulässt, IPs sperren (beim Start wird eine Warnung
protokolliert).
### Authentik ### Authentik
1. **Applications → Providers → Create → OAuth2/OpenID Provider** 1. **Applications → Providers → Create → OAuth2/OpenID Provider**
- Name: `fail2ban-webui` - Name: `fail2ban-webui`
- Authorization flow: `default-provider-authorization-explicit-consent` (or implicit) - Authorization flow: `default-provider-authorization-explicit-consent` (oder implicit)
- Client type: **Confidential** – note *Client ID* and *Client Secret* - Client type: **Confidential** – *Client ID* und *Client Secret* notieren
- Redirect URIs: `strict` · `https://fail2ban.example.org/auth/callback` - 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` - 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** 2. **Applications → Applications → Create**
- Name: `fail2ban-webui`, Slug: `fail2ban-webui`, Provider: the one from step 1 - Name: `fail2ban-webui`, Slug: `fail2ban-webui`, Provider: der aus Schritt 1
3. **Directory → Groups → Create** `fail2ban-admins` and add the users. 3. **Directory → Groups → Create** `fail2ban-admins` anlegen und die Benutzer hinzufügen.
4. Optional, recommended: in the application under **Policy / Group / User Bindings** 4. Optional, empfohlen: in der Application unter **Policy / Group / User Bindings** die
bind the group `fail2ban-admins`, so nobody else even reaches the consent screen. Gruppe `fail2ban-admins` binden – dann kommt sonst niemand bis zur Zustimmungsseite.
5. `.env`: 5. `.env`:
```ini ```ini
@@ -73,27 +74,27 @@ account that the provider lets use this client can ban IPs (a warning is logged
OIDC_BUTTON_LABEL=Authentik 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/<realm>` | add a *Group Membership* mapper with claim name `groups`, "Full group path" off | | Keycloak | `https://kc.example.org/realms/<realm>` | einen *Group Membership*-Mapper mit Claim-Name `groups` anlegen, „Full group path" aus |
| Authelia | `https://auth.example.org` | `groups` scope; set `OIDC_SCOPES=openid email profile groups` | | Authelia | `https://auth.example.org` | Scope `groups`; `OIDC_SCOPES=openid email profile groups` setzen |
| Zitadel | `https://<instance>.zitadel.cloud` | roles instead of groups – use `OIDC_ALLOWED_USERS` or a project role check in Zitadel | | Zitadel | `https://<instanz>.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 „Abmelden" beendet die Sitzung in der Weboberfläche. Die Sitzung beim Provider bleibt aktiv;
the next "Sign in with …" usually logs in again without a password. 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, Der Proxy übernimmt die Anmeldung; die Oberfläche prüft nur, woher Anfragen kommen, und
optionally, which user the proxy reports. optional, welchen Benutzer der Proxy meldet.
```ini ```ini
AUTH_MODE=proxy 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_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 - `AUTH_PROXY_ALLOWED_IPS` wird gegen den **direkten** Gegenüber des Containers geprüft,
against `X-Forwarded-For`. If the proxy runs on the same Docker host and reaches the nicht gegen `X-Forwarded-For`. Läuft der Proxy auf demselben Docker-Host und erreicht den
container via the published port, that is the network gateway (`172.23.51.1` in the Container über den veröffentlichten Port, ist das das Netzwerk-Gateway (`172.23.51.1` in
example compose file); check with `docker compose logs` after a request. der Beispiel-Compose); nach einer Anfrage mit `docker compose logs` prüfen.
- With `AUTH_PROXY_HEADER` set, requests without the header are rejected (401). Make sure - Ist `AUTH_PROXY_HEADER` gesetzt, werden Anfragen ohne diesen Header abgelehnt (401). Der
the proxy **removes** that header from client requests – forward-auth integrations Proxy muss diesen Header aus Client-Anfragen **entfernen** – Forward-Auth-Integrationen
(Authentik, Authelia) do that. (Authentik, Authelia) tun das.
- Without `AUTH_PROXY_HEADER` the audit log shows the user `proxy`. - Ohne `AUTH_PROXY_HEADER` steht im Protokoll der Benutzer `proxy`.
Examples for Nginx Proxy Manager, Traefik and Caddy are in Beispiele für Nginx Proxy Manager, Traefik und Caddy stehen in [reverse-proxy.md](reverse-proxy.md).
[reverse-proxy.md](reverse-proxy.md).
+77 -77
View File
@@ -1,12 +1,12 @@
# Configuration # Konfiguration
The web UI is configured with environment variables (`.env`, loaded via `env_file:`) and Die Weboberfläche wird über Umgebungsvariablen (`.env`, geladen per `env_file:`) und die
the host list `config/hosts.yaml`. A complete, commented template is Hostliste `config/hosts.yaml` konfiguriert. Eine vollständige, kommentierte Vorlage ist
[`examples/.env.example`](../examples/.env.example). Changes need [`examples/.env.example`](../examples/.env.example). Änderungen brauchen
`docker compose up -d` (which recreates the container). `docker compose up -d` (das legt den Container neu an).
Values containing `$` (password hashes, some secrets) must be wrapped in **single quotes** Werte mit `$` (Passwort-Hashes, manche Secrets) müssen in der `.env` in **einfachen
in `.env`, otherwise Docker Compose expands them. Anführungszeichen** stehen, sonst wertet Docker Compose sie als Variablen aus.
## Hosts – config/hosts.yaml ## Hosts – config/hosts.yaml
@@ -18,7 +18,7 @@ hosts:
user: f2bweb # optional, default SSH_USER 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 ```bash
docker compose run --rm -it fail2ban-webui keyscan 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 docker compose up -d --force-recreate
``` ```
`F2B_HOSTS=name=address,name2=address2` (port 22, user `SSH_USER`) is still accepted for `F2B_HOSTS=name=adresse,name2=adresse2` (Port 22, Benutzer `SSH_USER`) wird aus
compatibility; entries from both are combined. 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 | | `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` | language until a user picks one in the dropdown (`en`, `de`, …) | | `DEFAULT_LANG` | `en` | Sprache, solange ein Benutzer im Dropdown keine gewählt hat (`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 | | `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` | session lifetime | | `SESSION_HOURS` | `12` | Gültigkeit einer Sitzung |
| `SESSION_COOKIE_SECURE` | from `PUBLIC_URL` | force the `Secure` cookie flag on/off | | `SESSION_COOKIE_SECURE` | aus `PUBLIC_URL` | `Secure`-Flag des Cookies erzwingen oder abschalten |
| `SECRET_KEY` | generated | key that signs session cookies; by default created once in `data/secret_key` | | `SECRET_KEY` | wird erzeugt | Schlüssel zum Signieren der Sitzungs-Cookies; standardmäßig einmalig in `data/secret_key` angelegt |
| `TZ` | `Europe/Vienna` (compose) | time zone of the log and audit timestamps | | `TZ` | `Europe/Vienna` (Compose) | Zeitzone für Log- und Protokoll-Zeitstempel |
| `CONFIG_FILE` | `/config/hosts.yaml` | path of the host list inside the container | | `CONFIG_FILE` | `/config/hosts.yaml` | Pfad der Hostliste im Container |
| `DATA_DIR` | `/data` | data directory inside the 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_MODE` | `local` | `local`, `oidc`, `local,oidc` oder `proxy` |
| `AUTH_USER` | `admin` | local user name | | `AUTH_USER` | `admin` | lokaler Benutzername |
| `AUTH_PASSWORD_HASH` | – | scrypt hash from `hash-password` (single quotes!) | | `AUTH_PASSWORD_HASH` | – | scrypt-Hash aus `hash-password` (einfache Anführungszeichen!) |
| `AUTH_PASSWORD_HASH_FILE` | – | alternative: file containing the hash (e.g. a Docker secret) | | `AUTH_PASSWORD_HASH_FILE` | – | Alternative: Datei mit dem Hash (z. B. ein Docker-Secret) |
| `LOGIN_MAX_ATTEMPTS` | `5` | failed logins per client IP before the lockout | | `LOGIN_MAX_ATTEMPTS` | `5` | Fehlversuche pro Client-IP bis zur Sperre |
| `LOGIN_LOCK_MINUTES` | `15` | duration of the lockout window | | `LOGIN_LOCK_MINUTES` | `15` | Dauer des Sperrfensters |
| `OIDC_ISSUER` | – | issuer URL; the discovery document is read from `<issuer>/.well-known/openid-configuration` | | `OIDC_ISSUER` | – | Issuer-URL; das Discovery-Dokument wird von `<issuer>/.well-known/openid-configuration` gelesen |
| `OIDC_DISCOVERY_URL` | – | full discovery URL instead of `OIDC_ISSUER` | | `OIDC_DISCOVERY_URL` | – | vollständige Discovery-URL statt `OIDC_ISSUER` |
| `OIDC_CLIENT_ID` / `OIDC_CLIENT_SECRET` | – | client credentials | | `OIDC_CLIENT_ID` / `OIDC_CLIENT_SECRET` | – | Client-Zugangsdaten |
| `OIDC_SCOPES` | `openid email profile` | requested scopes | | `OIDC_SCOPES` | `openid email profile` | angeforderte Scopes |
| `OIDC_USERNAME_CLAIM` | `preferred_username` | claim shown as user name (falls back to `email`, then `sub`) | | `OIDC_USERNAME_CLAIM` | `preferred_username` | Claim, der als Benutzername angezeigt wird (Rückfall auf `email`, dann `sub`) |
| `OIDC_GROUPS_CLAIM` | `groups` | claim with the group list | | `OIDC_GROUPS_CLAIM` | `groups` | Claim mit der Gruppenliste |
| `OIDC_ALLOWED_GROUPS` | – | only members of these groups may sign in (comma separated) | | `OIDC_ALLOWED_GROUPS` | – | nur Mitglieder dieser Gruppen dürfen sich anmelden (durch Komma getrennt) |
| `OIDC_ALLOWED_USERS` | – | only these user names may sign in | | `OIDC_ALLOWED_USERS` | – | nur diese Benutzernamen dürfen sich anmelden |
| `OIDC_BUTTON_LABEL` | `SSO` | text on the login button ("Sign in with …") | | `OIDC_BUTTON_LABEL` | `SSO` | Text auf dem Anmeldeknopf („Anmelden mit …") |
| `AUTH_PROXY_ALLOWED_IPS` | – | proxy mode: only accept requests from these addresses/networks | | `AUTH_PROXY_ALLOWED_IPS` | – | Proxy-Modus: nur Anfragen von diesen Adressen/Netzen annehmen |
| `AUTH_PROXY_HEADER` | – | proxy mode: header with the user name, e.g. `X-authentik-username` | | `AUTH_PROXY_HEADER` | – | Proxy-Modus: Header mit dem Benutzernamen, z. B. `X-authentik-username` |
| `AUTH_PROXY_LOGOUT_URL` | – | proxy mode: target of the "Sign out" button | | `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_USER` | `f2bweb` | Standardbenutzer für Hosts ohne `user:` |
| `SSH_KEY` | `/data/ssh/id_ed25519` | private key | | `SSH_KEY` | `/data/ssh/id_ed25519` | privater Schlüssel |
| `KNOWN_HOSTS` | `/data/ssh/known_hosts` | pinned host keys (written by `keyscan`) | | `KNOWN_HOSTS` | `/data/ssh/known_hosts` | festgelegte Host-Schlüssel (geschrieben von `keyscan`) |
| `SSH_TIMEOUT` | `40` | seconds per agent call | | `SSH_TIMEOUT` | `40` | Sekunden pro Agent-Aufruf |
| `CACHE_SECONDS` | `15` | how long the ban list is cached between page refreshes | | `CACHE_SECONDS` | `15` | wie lange die Sperrliste zwischen zwei Seitenaufrufen zwischengespeichert wird |
| `MANUAL_JAIL` | `manual-block` | jail name shown in the UI hint – the agent decides the real jail (`/etc/f2b-webctl.conf`) | | `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` | audit log (JSON lines) | | `AUDIT_LOG` | `/data/audit.log` | Protokoll (JSON-Zeilen) |
The SSH client ignores the system `ssh_config`, uses only the configured key, never Der SSH-Client ignoriert die System-`ssh_config`, verwendet nur den konfigurierten
asks questions and refuses unknown host keys. Schlüssel, stellt nie Rückfragen und lehnt unbekannte Host-Schlüssel ab.
## GeoIP ## 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_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` | path of the database – any MaxMind-format city database works (e.g. GeoLite2 City) | | `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 Mit einer Städte-Datenbank zeigt die Oberfläche auch die **Herkunftskarte** (Datenbanken
coordinates). The map is bundled with the app (Natural Earth, public domain) – no external nur mit Ländern haben keine Koordinaten). Die Karte ist Teil der App (Natural Earth, Public
tile server is contacted. Without a database the "Origin" column stays empty and the map Domain) – es wird kein externer Kartendienst abgefragt. Ohne Datenbank bleibt die Spalte
is hidden. „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 Mit Loki bekommt die Oberfläche pro IP einen **Verlauf**-Knopf mit den passenden Logzeilen
last days; with Grafana every ban gets a link to Explore. 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_URL` | – | z. B. `http://loki:3100`; leer = Verlauf ausgeschaltet |
| `LOKI_SELECTOR` | `{job=~".+"}` | LogQL stream selector for the search, e.g. `{job=~"fail2ban\|sshd\|nginx"}` | | `LOKI_SELECTOR` | `{job=~".+"}` | LogQL-Stream-Selektor für die Suche, z. B. `{job=~"fail2ban\|sshd\|nginx"}` |
| `LOKI_DAYS` | `7` | how far back to search | | `LOKI_DAYS` | `7` | wie weit zurück gesucht wird |
| `LOKI_LIMIT` | `50` | maximum lines | | `LOKI_LIMIT` | `50` | maximale Anzahl Zeilen |
| `LOKI_USER` / `LOKI_PASSWORD` | – | basic auth | | `LOKI_USER` / `LOKI_PASSWORD` | – | Basic Auth |
| `LOKI_TENANT` | – | `X-Scope-OrgID` for multi-tenant Loki | | `LOKI_TENANT` | – | `X-Scope-OrgID` für mandantenfähiges Loki |
| `GRAFANA_URL` | – | e.g. `https://grafana.example.org` | | `GRAFANA_URL` | – | z. B. `https://grafana.example.org` |
| `GRAFANA_DASHBOARD` | – | path of a dashboard for the header link, e.g. `/d/abc123/security` | | `GRAFANA_DASHBOARD` | – | Pfad eines Dashboards für den Link in der Kopfzeile, z. B. `/d/abc123/security` |
| `GRAFANA_LOKI_UID` | `loki` | UID of the Loki data source in Grafana (for Explore links) | | `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 ```ini
LOKI_SELECTOR={job=~"fail2ban|ufw|nginx|sshd", domain!="fail2ban.example.org"} 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 Geschützte Adressen und das manuelle Jail werden **auf jedem Host** in
`/etc/f2b-webctl.conf` – see [agent.md](agent.md#configuration--etcf2b-webctlconf). `/etc/f2b-webctl.conf` eingestellt – siehe [agent.md](agent.md#konfiguration--etcf2b-webctlconf).
+46 -45
View File
@@ -1,24 +1,24 @@
# Development # Entwicklung
## Layout ## Aufbau
``` ```
app/webui/ Flask application (package "webui") app/webui/ Flask-Anwendung (Paket "webui")
app.py routes, security headers, audit log app.py Routen, Sicherheits-Header, Protokoll
auth.py local / OIDC / proxy authentication, login lockout auth.py Anmeldung lokal / OIDC / Proxy, Login-Sperre
backend.py SSH to the agents, GeoIP, Loki backend.py SSH zu den Agenten, GeoIP, Loki
cli.py serve, hash-password, keygen, keyscan, check cli.py serve, hash-password, keygen, keyscan, check
config.py settings from the environment, hosts.yaml config.py Einstellungen aus der Umgebung, hosts.yaml
i18n.py, locales/ translations (one JSON file per language) i18n.py, locales/ Übersetzungen (eine JSON-Datei pro Sprache)
templates/, static/ templates/, static/ (static/world.svg: Weltkarte, Natural Earth)
agent/ f2b-webctl, install.sh, uninstall.sh agent/ f2b-webctl, install.sh, uninstall.sh
docker/Dockerfile production image docker/Dockerfile Produktions-Image
examples/ docker-compose.yaml, .env.example, hosts.yaml.example examples/ docker-compose.yaml, .env.example, hosts.yaml.example
dev/testbed/ web UI + two fail2ban hosts in Docker dev/testbed/ Weboberfläche + zwei fail2ban-Hosts in Docker
tests/smoke_test.py web UI tests without Docker/SSH tests/smoke_test.py Tests der Weboberfläche ohne Docker/SSH
``` ```
## Smoke test (no Docker needed) ## Smoke-Test (ohne Docker)
```bash ```bash
python3 -m venv .venv python3 -m venv .venv
@@ -26,20 +26,20 @@ python3 -m venv .venv
.venv/bin/python tests/smoke_test.py .venv/bin/python tests/smoke_test.py
``` ```
The agents are simulated. Covered: login, lockout, CSRF/Origin checks, translations, Die Agenten werden simuliert. Abgedeckt: Anmeldung, Login-Sperre, CSRF-/Origin-Prüfung,
ban/unban validation, audit log, proxy mode, config validation, SSH command line, log Übersetzungen, Prüfung beim Sperren/Entsperren, Protokoll, Proxy-Modus, Prüfung der
injection. Konfiguration, SSH-Befehlszeile, Log-Injection.
## Testbed (Docker) ## Testbed (Docker)
`dev/testbed` starts the web UI and two Debian hosts with sshd and fail2ban (dummy ban `dev/testbed` startet die Weboberfläche und zwei Debian-Hosts mit sshd und fail2ban
actions – nothing is blocked). The agents are installed with `agent/install.sh` at (Dummy-Banactions – es wird nichts gesperrt). Die Agenten werden beim Containerstart mit
container start: `agent/install.sh` installiert:
| Host | Agent setup | | Host | Agent-Einrichtung |
|---|---| |---|---|
| `agent1` | user `f2bweb` + sudo, `--from` the testbed network, extra protected IP | | `agent1` | Benutzer `f2bweb` + sudo, `--from` auf das Testbed-Netz, zusätzliche geschützte IP |
| `agent2` | user `root`, key in `authorized_keys2`, manual jail named `blocklist` | | `agent2` | Benutzer `root`, Schlüssel in `authorized_keys2`, manuelles Jail namens `blocklist` |
```bash ```bash
cd dev/testbed 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 keyscan --yes
docker compose run --rm webui check docker compose run --rm webui check
docker compose up -d webui # http://127.0.0.1:18080 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 `hosts.yaml` wird ins Daten-Volume geschrieben, weil Docker Desktop Netzlaufwerke nicht
network drives. After recreating an agent its host key changes – run `keyscan` again. 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`, Unter Windows mit Git Bash `docker compose exec … /pfad` mit `MSYS_NO_PATHCONV=1`
otherwise the path is rewritten to a Windows path. 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/<code>.json` (e.g. `fr.json`). 1. `app/webui/locales/en.json` nach `app/webui/locales/<code>.json` kopieren (z. B. `fr.json`).
2. Set `"_name"` to the language's own name (`"Français"`) – it appears in the dropdown. 2. `"_name"` auf den Eigennamen der Sprache setzen (`"Français"`) – so erscheint sie im Dropdown.
3. Translate the values; keep `{placeholders}` unchanged. Missing keys fall back to English. 3. Die Werte übersetzen; `{Platzhalter}` unverändert lassen. Fehlende Schlüssel fallen auf Englisch zurück.
4. Run the smoke test and check the UI. 4. Smoke-Test ausführen und die Oberfläche prüfen.
## Building the image ## Image bauen
```bash ```bash
docker build -f docker/Dockerfile --build-arg VERSION=1.0.0 -t fail2ban-webui:1.0.0 . 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 \ 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 . --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 ```bash
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock aquasec/trivy:0.58.1 \ 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 image --scanners vuln --ignore-unfixed --severity HIGH,CRITICAL fail2ban-webui:1.0.0
``` ```
## Agent protocol ## Agent-Protokoll
The web UI runs `ssh … <user>@<host> "<command>"`; the agent reads the command from Die Weboberfläche führt `ssh … <benutzer>@<host> "<befehl>"` aus; der Agent liest den Befehl
`SSH_ORIGINAL_COMMAND` and prints exactly one JSON line: aus `SSH_ORIGINAL_COMMAND` und gibt genau eine JSON-Zeile aus:
```json ```json
{"ok": true, "host": "web01", "agent_version": "1.0.0", "manual_jail": "manual-block", {"ok": true, "host": "web01", "agent_version": "1.0.0", "manual_jail": "manual-block",
@@ -95,12 +96,12 @@ The web UI runs `ssh … <user>@<host> "<command>"`; the agent reads the command
"bantime": 600, "end": "2026-10-25 10:10:00"}], "total": 42}}} "bantime": 600, "end": "2026-10-25 10:10:00"}], "total": 42}}}
``` ```
Errors: `{"ok": false, "error_key": "agent_protected", "error_args": {"ip": "…"}, "error": "English text"}`. Fehler: `{"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_`); Die Weboberfläche übersetzt `error_key` mit `locales/*.json` (Schlüssel mit Präfix `agent_`);
unknown keys show the English `error`. unbekannte Schlüssel zeigen den englischen `error`-Text.
## Versioning ## Versionierung
- Version numbers follow SemVer and are set at build time (`VERSION` build arg → - Versionsnummern folgen SemVer und werden beim Bauen gesetzt (Build-Argument `VERSION` →
`APP_VERSION`); the agent has its own `VERSION` in `agent/f2b-webctl`. `APP_VERSION`); der Agent hat eine eigene `VERSION` in `agent/f2b-webctl`.
- Every change goes into `CHANGELOG.md` under *Unreleased*. - Jede Änderung kommt in `CHANGELOG.md` unter *Unreleased*.
+57 -54
View File
@@ -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 Diese Anleitung richtet die Weboberfläche Schritt für Schritt auf einem Docker-Host ein.
fail2ban hosts are covered in [agent.md](agent.md); you need both. Die Agenten auf den fail2ban-Hosts sind in [agent.md](agent.md) beschrieben; du brauchst beides.
- Source: <https://git.retronerd.at/schiwagoa/fail2ban-webui> - Quelle: <https://git.retronerd.at/schiwagoa/fail2ban-webui>
- Image: `git.retronerd.at/schiwagoa/fail2ban-webui` (`amd64`, `arm64`) - 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`) - Docker Engine mit Compose v2 (`docker compose version`)
- Outgoing SSH (TCP 22 or your port) from the Docker host to every fail2ban host - Ausgehendes SSH (TCP 22 oder dein Port) vom Docker-Host zu jedem fail2ban-Host
- A DNS name and a reverse proxy with TLS for the UI (see [reverse-proxy.md](reverse-proxy.md)) - 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 ```bash
docker network inspect $(docker network ls -q) --format '{{.Name}} {{range .IPAM.Config}}{{.Subnet}}{{end}}' | grep 172.23.51 \ 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" || 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 ```bash
mkdir -p /opt/fail2ban-webui/config /opt/fail2ban-webui/data mkdir -p /opt/fail2ban-webui/config /opt/fail2ban-webui/data
cd /opt/fail2ban-webui 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 chown -R 10050:10050 data
chmod 700 data chmod 700 data
``` ```
| Path | Content | | Pfad | Inhalt |
|---|---| |---|---|
| `docker-compose.yaml` | the service | | `docker-compose.yaml` | der Dienst |
| `.env` | settings and secrets (password hash, OIDC secret) | | `.env` | Einstellungen und Geheimnisse (Passwort-Hash, OIDC-Secret) |
| `config/hosts.yaml` | the fail2ban hosts (mounted read-only) | | `config/hosts.yaml` | die fail2ban-Hosts (schreibgeschützt eingebunden) |
| `data/` | SSH key, `known_hosts`, session secret, audit log, GeoIP database | | `data/` | SSH-Schlüssel, `known_hosts`, Sitzungsschlüssel, Protokoll, GeoIP-Datenbank |
## 3. docker-compose.yaml ## 3. docker-compose.yaml
@@ -102,13 +102,13 @@ networks:
ip_range: 172.23.51.128/25 ip_range: 172.23.51.128/25
``` ```
If the reverse proxy runs on another machine, bind the port to the LAN address instead, Läuft der Reverse Proxy auf einer anderen Maschine, den Port stattdessen an die LAN-Adresse
e.g. `"192.168.1.10:8080:8080"` – never to `0.0.0.0` without a firewall in front. binden, z. B. `"192.168.1.10:8080:8080"` – niemals ohne Firewall davor an `0.0.0.0`.
## 4. .env ## 4. .env
Start with this minimal file; every option is described in Mit dieser minimalen Datei beginnen; alle Optionen stehen in
[configuration.md](configuration.md) and in [`examples/.env.example`](../examples/.env.example). [configuration.md](configuration.md) und in [`examples/.env.example`](../examples/.env.example).
```bash ```bash
nano .env nano .env
@@ -129,35 +129,38 @@ MANUAL_JAIL=manual-block
GEOIP_AUTO_DOWNLOAD=1 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 ```bash
docker compose run --rm -it fail2ban-webui hash-password docker compose run --rm -it fail2ban-webui hash-password
``` ```
Copy the printed line (`AUTH_PASSWORD_HASH='scrypt:…'`) into `.env` and replace the Die ausgegebene Zeile (`AUTH_PASSWORD_HASH='scrypt:…'`) in die `.env` kopieren und den
placeholder. **Keep the single quotes** – the hash contains `$` signs that Docker Compose Platzhalter ersetzen. **Die einfachen Anführungszeichen beibehalten** – der Hash enthält
would otherwise try to expand. For single sign-on instead see `$`-Zeichen, die Docker Compose sonst als Variablen auswerten würde. Für Single Sign-on
[authentication.md](authentication.md). stattdessen siehe [authentication.md](authentication.md).
## 5. SSH key ## 5. SSH-Schlüssel
```bash ```bash
docker compose run --rm fail2ban-webui keygen docker compose run --rm fail2ban-webui keygen
``` ```
The command creates `data/ssh/id_ed25519` and prints the public key Der Befehl erzeugt `data/ssh/id_ed25519` und gibt den öffentlichen Schlüssel aus
(`ssh-ed25519 AAAA… fail2ban-webui`). Copy it – you need it for the agent installer. (`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 ```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 git clone https://git.retronerd.at/schiwagoa/fail2ban-webui.git /tmp/fail2ban-webui
echo 'ssh-ed25519 AAAA… fail2ban-webui' > /tmp/webui.pub echo 'ssh-ed25519 AAAA… fail2ban-webui' > /tmp/webui.pub
bash /tmp/fail2ban-webui/agent/install.sh --key-file /tmp/webui.pub --from <public IP of the web UI host> bash /tmp/fail2ban-webui/agent/install.sh --key-file /tmp/webui.pub --from <öffentliche IP des Webui-Hosts>
``` ```
## 7. config/hosts.yaml ## 7. config/hosts.yaml
@@ -180,24 +183,24 @@ hosts:
address: host.docker.internal address: host.docker.internal
``` ```
`user` defaults to `SSH_USER` (`f2bweb`). Set `user: root` for hosts where the agent was `user` ist standardmäßig `SSH_USER` (`f2bweb`). Für Hosts, auf denen der Agent mit
installed with `--user root`. `--user root` installiert wurde, `user: root` setzen.
## 8. Host keys and connection test ## 8. Host-Schlüssel und Verbindungstest
```bash ```bash
docker compose run --rm -it fail2ban-webui keyscan docker compose run --rm -it fail2ban-webui keyscan
``` ```
`keyscan` shows the fingerprint of every host and asks before writing `keyscan` zeigt den Fingerprint jedes Hosts und fragt vor dem Schreiben von
`data/ssh/known_hosts`. Compare the fingerprints with the hosts: `data/ssh/known_hosts` nach. Die Fingerprints mit den Hosts vergleichen:
```bash ```bash
# on the fail2ban host # auf dem fail2ban-Host
ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub
``` ```
Then test every agent: Danach jeden Agenten testen:
```bash ```bash
docker compose run --rm fail2ban-webui check 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 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 ```bash
docker compose up -d docker compose up -d
docker compose ps # STATUS: Up … (healthy) 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`. `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 ```bash
curl -s http://127.0.0.1:8080/healthz # ok 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 `http://127.0.0.1:8080` (oder die LAN-Adresse aus Schritt 3) unter deinem DNS-Namen mit
TLS – examples for Nginx Proxy Manager, Caddy, Traefik and nginx are in TLS veröffentlichen – Beispiele für Nginx Proxy Manager, Caddy, Traefik und nginx stehen in
[reverse-proxy.md](reverse-proxy.md). Then open `PUBLIC_URL` and sign 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) - [Login mit fail2ban absichern](operations.md#login-mit-fail2ban-absichern)
- [Updates and backups](operations.md) - [Updates und Backups](operations.md)
- Optional: [Loki history and Grafana links](configuration.md#loki-and-grafana) - Optional: [Loki-Verlauf und Grafana-Links](configuration.md#loki-und-grafana)
+62 -61
View File
@@ -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 ```bash
docker compose ps # Up … (healthy) docker compose ps # Up … (healthy)
docker compose logs --tail 100 fail2ban-webui docker compose logs --tail 100 fail2ban-webui
docker compose logs -f fail2ban-webui | grep -E 'AUDIT|LOGIN|ERROR|WARNING' 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 | | `fail2ban-webui 1.0.0 – hosts: … – auth: … – loki: … – geoip: …` | Zusammenfassung beim Start |
| `AUDIT {…}` | ban/unban with user, client IP, hosts and result | | `AUDIT {…}` | Sperren/Entsperren mit Benutzer, Client-IP, Hosts und Ergebnis |
| `LOGIN_OK` / `LOGIN_FAILED` / `LOGIN_LOCKED` / `LOGOUT` | sign-in events | | `LOGIN_OK` / `LOGIN_FAILED` / `LOGIN_LOCKED` / `LOGOUT` | Anmeldeereignisse |
| `OIDC_FAILED` / `OIDC_DENIED` | SSO errors / account not allowed | | `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 ```bash
tail -n 20 data/audit.log | python3 -m json.tool --json-lines 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 ```bash
journalctl -t f2b-webctl --since today journalctl -t f2b-webctl --since today
@@ -35,63 +35,64 @@ journalctl -t f2b-webctl --since today
## Update ## Update
```bash ```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 pull
docker compose up -d docker compose up -d
docker compose ps docker compose ps
docker compose run --rm fail2ban-webui check docker compose run --rm fail2ban-webui check
``` ```
Read the [changelog](../CHANGELOG.md) first – if it mentions a new agent version, update Vorher das [Changelog](../CHANGELOG.md) lesen – nennt es eine neue Agent-Version, auch die
the agents as well (re-run `install.sh` with the same options, see [agent.md](agent.md#update)). Agenten aktualisieren (`install.sh` mit denselben Optionen erneut ausführen, siehe
The `1` tag only moves within the 1.x series; breaking changes get a new major version. [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 Auch ohne neue Funktionen regelmäßig neu ziehen bzw. bauen: Das Image wird mit aktuellen
Debian security updates. Debian-Sicherheitsupdates neu gebaut.
## Backup ## Backup
| What | Why | | Was | Warum |
|---|---| |---|---|
| `.env` | settings, password hash, OIDC secret | | `.env` | Einstellungen, Passwort-Hash, OIDC-Secret |
| `config/hosts.yaml` | host list | | `config/hosts.yaml` | Hostliste |
| `data/ssh/` | private key and pinned host keys – without them every agent needs the new key | | `data/ssh/` | privater Schlüssel und festgelegte Host-Schlüssel – ohne sie braucht jeder Agent den neuen Schlüssel |
| `data/audit.log` | audit trail | | `data/audit.log` | Protokoll |
| `data/secret_key` | only keeps sessions valid; losing it signs everybody out | | `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 ```bash
tar -cpzf /backups/fail2ban-webui_$(date +%F_%H-%M).tar.gz .env docker-compose.yaml config data 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 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. 1. Auf dem alten Host `docker compose stop`, Archiv wie oben erstellen.
2. Copy it, unpack with `tar -xpzf` (keeps owner 10050 and permissions). 2. Kopieren, mit `tar -xpzf` entpacken (behält Besitzer 10050 und Rechte).
3. On **every agent** add the new address to `from=` – easiest by re-running 3. Auf **jedem Agenten** die neue Adresse in `from=` aufnehmen – am einfachsten
`install.sh … --from <new address>`. `install.sh … --from <neue Adresse>` erneut ausführen.
4. `docker compose up -d`, then `check`. 4. `docker compose up -d`, danach `check`.
## Rotating the SSH key ## SSH-Schlüssel tauschen
```bash ```bash
docker compose run --rm fail2ban-webui keygen --force docker compose run --rm fail2ban-webui keygen --force
# on every host: # auf jedem Host:
bash install.sh --key-file new.pub --from … (same options as before) 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 Der Installer ersetzt nur Einträge mit demselben Schlüssel; den alten vorher mit
`uninstall.sh` first or delete its line from `authorized_keys` (it contains `uninstall.sh` entfernen oder seine Zeile aus `authorized_keys` löschen (sie enthält
`command="…f2b-webctl"`). `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 Die Oberfläche sperrt einen Client nach 5 Fehlversuchen selbst. Um hartnäckige Angreifer an
firewall, let fail2ban on the **proxy host** read the container log. der Firewall zu sperren, liest fail2ban auf dem **Proxy-Host** das Container-Log.
`/etc/fail2ban/filter.d/fail2ban-webui.conf` `/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 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` Das funktioniert nur, wenn der Container die echte Client-Adresse sieht (`TRUSTED_PROXY_HOPS`
set correctly). User names are logged with spaces and control characters replaced, so a richtig gesetzt). Benutzernamen werden mit ersetzten Leer- und Steuerzeichen protokolliert;
crafted user name cannot inject a second log line. 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 beendet sich: `… 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 beendet sich: `AUTH_MODE=local needs AUTH_PASSWORD_HASH` | `hash-password` ausführen, Zeile mit einfachen Anführungszeichen einfügen |
| Container exits: `No hosts configured` | `config/hosts.yaml` missing or empty; on Windows/Docker Desktop: the drive cannot be bind-mounted | | Container beendet sich: `No hosts configured` | `config/hosts.yaml` fehlt oder ist leer; unter Windows/Docker Desktop: das Laufwerk lässt sich nicht einbinden |
| Login always fails after pasting the hash | single quotes missing in `.env` – Compose ate the `$` parts | | Anmeldung scheitert immer, nachdem der Hash eingefügt wurde | einfache Anführungszeichen in der `.env` fehlen – Compose hat die `$`-Teile verschluckt |
| Everybody locked out together / audit shows the proxy IP | `TRUSTED_PROXY_HOPS` not set | | Alle werden gemeinsam gesperrt / Protokoll zeigt die Proxy-IP | `TRUSTED_PROXY_HOPS` nicht gesetzt |
| `Host key verification failed` | host not in `known_hosts` or its key changed → `keyscan`, compare fingerprints | | `Host key verification failed` | Host nicht in `known_hosts` oder sein Schlüssel hat sich geändert → `keyscan`, Fingerprints vergleichen |
| `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 | | `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 rule missing → re-run `install.sh` | | `sudo: a password is required` | sudo-Regel fehlt → `install.sh` erneut ausführen |
| `The agent does not run as root` | forced command without `sudo -n` for a non-root user → re-run `install.sh` | | `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, or the host is slow – raise `SSH_TIMEOUT` | | `Timeout talking to …` | Firewall/Port, oder der Host ist langsam – `SSH_TIMEOUT` erhöhen |
| `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` | | `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 |
| Bans have no start/end time | fail2ban older than 0.11 (no `--with-time`) | | Sperren haben keine Start-/Endzeit | fail2ban älter als 0.11 (kein `--with-time`) |
| Banned IP still reaches a Docker service | banaction blocks only `INPUT` – see [Docker hosts](agent.md#docker-hosts) | | Gesperrte IP erreicht trotzdem einen Docker-Dienst | Banaction sperrt nur `INPUT` – siehe [Docker-Hosts](agent.md#docker-hosts) |
| OIDC: `redirect_uri` mismatch | provider must allow exactly `<PUBLIC_URL>/auth/callback` | | OIDC: `redirect_uri` passt nicht | Provider muss genau `<PUBLIC_URL>/auth/callback` erlauben |
| 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: `mismatching_state` | Sitzungs-Cookie zwischen Weiterleitung und Callback verloren: Host in `PUBLIC_URL` weicht vom Browser ab, oder `https` ohne `Secure`/Proxy-Header |
| OIDC: "account … is not allowed" | user not in `OIDC_ALLOWED_GROUPS`; check that the provider sends the `groups` claim | | OIDC: „Konto … darf diese Anwendung nicht verwenden" | Benutzer nicht in `OIDC_ALLOWED_GROUPS`; prüfen, ob der Provider den Claim `groups` mitschickt |
+26 -26
View File
@@ -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 Der Container liefert reines HTTP auf Port 8080 (im Beispiel veröffentlicht als
example). Put a reverse proxy with TLS in front of it and set in `.env`: `127.0.0.1:8080`). Davor gehört ein Reverse Proxy mit TLS; in der `.env` dazu setzen:
```ini ```ini
PUBLIC_URL=https://fail2ban.example.org PUBLIC_URL=https://fail2ban.example.org
TRUSTED_PROXY_HOPS=1 TRUSTED_PROXY_HOPS=1
``` ```
The proxy must pass `Host` unchanged and set `X-Forwarded-For` and `X-Forwarded-Proto` – Der Proxy muss `Host` unverändert weitergeben und `X-Forwarded-For` sowie `X-Forwarded-Proto`
all examples below do. Restricting access to known IP addresses at the proxy is a cheap setzen – alle Beispiele unten tun das. Den Zugriff am Proxy zusätzlich auf bekannte
extra layer and strongly recommended. IP-Adressen zu beschränken kostet wenig und ist dringend empfohlen.
## Nginx Proxy Manager ## Nginx Proxy Manager
**Hosts → Proxy Hosts → Add Proxy Host** **Hosts → Proxy Hosts → Add Proxy Host**
| Field | Value | | Feld | Wert |
|---|---| |---|---|
| Domain Names | `fail2ban.example.org` | | 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` | | 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 | on | | Block Common Exploits | an |
| SSL | request a certificate, *Force SSL*, *HTTP/2*, *HSTS* | | SSL | Zertifikat anfordern, *Force SSL*, *HTTP/2*, *HSTS* |
| Access List | optional: an access list with your IP addresses (*Satisfy any* off) | | 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 Läuft NPM auf demselben Host in einem anderen Docker-Netz, den Port der Oberfläche an die
LAN address (e.g. `"192.168.1.10:8080:8080"`) or attach the UI to NPM's network and use LAN-Adresse des Hosts binden (z. B. `"192.168.1.10:8080:8080"`) oder die Oberfläche an das
`fail2ban-webui:8080` as target. Netz von NPM hängen und `fail2ban-webui:8080` als Ziel verwenden.
Response headers belong into **Advanced → Custom Nginx Configuration**; NPM's image Antwort-Header gehören nach **Advanced → Custom Nginx Configuration**; das NPM-Image enthält
includes the *headers-more* module, which also applies them to proxied responses: das Modul *headers-more*, das sie auch auf weitergeleitete Antworten anwendet:
```nginx ```nginx
more_set_headers "Strict-Transport-Security: max-age=31536000; includeSubDomains"; more_set_headers "Strict-Transport-Security: max-age=31536000; includeSubDomains";
``` ```
(The app sets CSP, `X-Frame-Options`, `X-Content-Type-Options`, `Referrer-Policy` and (CSP, `X-Frame-Options`, `X-Content-Type-Options`, `Referrer-Policy` und `Cache-Control`
`Cache-Control` itself.) setzt die App selbst.)
## Caddy ## 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 ```yaml
services: services:
@@ -73,7 +73,7 @@ services:
- traefik - traefik
``` ```
### Traefik with Authentik forward auth (proxy mode) ### Traefik mit Authentik Forward Auth (Proxy-Modus)
```yaml ```yaml
- traefik.http.routers.f2bwebui.middlewares=authentik@docker - traefik.http.routers.f2bwebui.middlewares=authentik@docker
@@ -110,12 +110,12 @@ server {
} }
``` ```
## Check ## Prüfen
```bash ```bash
curl -sI https://fail2ban.example.org/login | grep -iE '^(HTTP|content-security|set-cookie)' 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 Erwartet werden `HTTP/2 200`, der Header `Content-Security-Policy` und ein Sitzungs-Cookie
`Secure; HttpOnly; SameSite=Lax`. A cookie without `Secure` means `PUBLIC_URL` does not mit `Secure; HttpOnly; SameSite=Lax`. Fehlt `Secure` am Cookie, beginnt `PUBLIC_URL` nicht
start with `https://`. mit `https://`.