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

+104 -103
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
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
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
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
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
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
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://`.