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:
1 parent
2e3f79236c
commit
019c74a418
8 files changed
+469
-463
No files matched your search
+104
-103
@@ -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 <ip>` | ban permanently in the manual jail (`manual-block`) |
|
||||
| `unban <jail> <ip>` | unban in one jail |
|
||||
| `unban-all <ip>` | unban in all jails |
|
||||
| `version` | agent version |
|
||||
| `list` | alle Jails mit ihren gesperrten IPs (JSON) |
|
||||
| `ban <ip>` | dauerhaft im manuellen Jail (`manual-block`) sperren |
|
||||
| `unban <jail> <ip>` | in einem Jail entsperren |
|
||||
| `unban-all <ip>` | 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
|
||||
```
|
||||
+57
-57
@@ -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 **`<PUBLIC_URL>/auth/callback`**.
|
||||
Voraussetzungen: `PUBLIC_URL` ist auf die externe `https://`-URL gesetzt, und der Provider
|
||||
erlaubt die Redirect-URI **`<PUBLIC_URL>/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/<realm>` | 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://<instance>.zitadel.cloud` | roles instead of groups – use `OIDC_ALLOWED_USERS` or a project role check in Zitadel |
|
||||
| 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` | Scope `groups`; `OIDC_SCOPES=openid email profile groups` setzen |
|
||||
| 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
|
||||
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).
|
||||
+77
-77
@@ -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 `<issuer>/.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 `<issuer>/.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).
|
||||
+46
-45
@@ -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/<code>.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/<code>.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 … <user>@<host> "<command>"`; the agent reads the command from
|
||||
`SSH_ORIGINAL_COMMAND` and prints exactly one JSON line:
|
||||
Die Weboberfläche führt `ssh … <benutzer>@<host> "<befehl>"` 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 … <user>@<host> "<command>"`; 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*.
|
||||
+57
-54
@@ -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: <https://git.retronerd.at/schiwagoa/fail2ban-webui>
|
||||
- Quelle: <https://git.retronerd.at/schiwagoa/fail2ban-webui>
|
||||
- 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 <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
|
||||
@@ -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)
|
||||
+62
-61
@@ -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 <new address>`.
|
||||
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 <neue Adresse>` 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 `<PUBLIC_URL>/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 `<PUBLIC_URL>/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 |
|
||||
+26
-26
@@ -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://`.
|
||||
Reference in new issue
Block a user