385 lines
13 KiB
Markdown
385 lines
13 KiB
Markdown
# fail2ban-webui
|
||
|
||
Eine schlanke, selbst gehostete Weboberfläche, um **fail2ban-Sperren auf mehreren Hosts
|
||
anzuzeigen, zu setzen und aufzuheben** – Bare Metal, VMs, LXC-Container oder Docker-Hosts.
|
||
|
||
- Eine Seite für alle Hosts: aktuelle Sperren pro Jail, Herkunft (GeoIP) mit Weltkarte, Suche und Filter
|
||
- IP dauerhaft auf beliebigen Hosts sperren, pro Jail oder überall entsperren
|
||
- Protokoll jeder Aktion (wer, wann, von wo)
|
||
- Optional: Log-Verlauf pro IP aus **Loki**, Links nach **Grafana**
|
||
- Anmeldung mit lokalem Benutzer, **OpenID Connect** (z. B. Authentik) oder über den Reverse Proxy
|
||
- Oberfläche auf Englisch und Deutsch, weitere Sprachen über eine JSON-Datei
|
||
- Läuft als gehärteter Container (schreibgeschützt, ohne Capabilities, nicht als root), `amd64` und `arm64`
|
||
|
||

|
||
|
||
```
|
||
Browser ──HTTPS──▶ Reverse Proxy ──▶ fail2ban-webui (Docker)
|
||
│ SSH, ein eingeschränkter Schlüssel
|
||
┌─────────────────────┼─────────────────────┐
|
||
▼ ▼ ▼
|
||
Host A (Bare Metal) LXC-Container Docker-Host
|
||
f2b-webctl f2b-webctl f2b-webctl
|
||
fail2ban fail2ban fail2ban
|
||
```
|
||
|
||
Die Weboberfläche bekommt nie eine Shell auf deinen Hosts. Auf jedem Host läuft ein kleiner
|
||
Agent, [`f2b-webctl`](agent/f2b-webctl), als SSH-*Forced-Command*. Er kennt nur
|
||
`list`, `ban <ip>`, `unban <jail> <ip>`, `unban-all <ip>` und `version`, sperrt niemals
|
||
private Netze, die eigenen Adressen des Hosts oder Einträge seiner Schutzliste und
|
||
protokolliert jede Aktion im Syslog. Zugriff auf den fail2ban-Socket wäre gleichbedeutend
|
||
mit root – die Sicherheitsgrenze ist der Agent, nicht die Weboberfläche.
|
||
|
||
## Voraussetzungen
|
||
|
||
Getestet auf Ubuntu 24.04/26.04 und Debian 12/13 – Bare Metal, VM oder LXC.
|
||
Alle Befehle als root.
|
||
|
||
### Grundpakete (alle Hosts)
|
||
|
||
```bash
|
||
apt update
|
||
apt install -y curl git
|
||
apt install -y mc # optional: Midnight Commander als Dateimanager
|
||
```
|
||
|
||
### Docker-Host (Weboberfläche)
|
||
|
||
Gebraucht wird eine vollständige Docker-Umgebung – Docker Engine **und** das Compose-Plugin
|
||
(Befehl `docker compose` mit Leerzeichen), `amd64` oder `arm64`.
|
||
|
||
Auf Ubuntu 24.04/26.04 z. B. mit dem
|
||
[Docker-Installer](https://git.retronerd.at/schiwagoa/public-docker-installer-26.04-v1):
|
||
|
||
```bash
|
||
git clone https://git.retronerd.at/schiwagoa/public-docker-installer-26.04-v1.git
|
||
cd public-docker-installer-26.04-v1
|
||
chmod +x *.sh
|
||
./docker-installer-2604-v1.sh
|
||
```
|
||
|
||
Diese Anleitung verwendet `docker compose` (Plugin). Der Installer richtet zusätzlich das
|
||
eigenständige `docker-compose` ein; das Plugin kommt normalerweise mit `docker-ce` mit.
|
||
Prüfen – beide Befehle müssen eine Version ausgeben:
|
||
|
||
```bash
|
||
docker version --format '{{.Server.Version}}'
|
||
docker compose version
|
||
```
|
||
|
||
Meldet `docker compose version` den Fehler `'compose' is not a docker command`, das Plugin
|
||
nachinstallieren: `apt install -y docker-compose-plugin`.
|
||
|
||
In einem **LXC-Container** braucht Docker auf dem Proxmox-Host die Features
|
||
`nesting=1,keyctl=1` (unprivilegierter Container: `pct set <id> --features nesting=1,keyctl=1`,
|
||
danach den Container neu starten).
|
||
|
||
### fail2ban-Hosts (Agent)
|
||
|
||
- fail2ban ≥ 0.11 (Start-/Endzeiten der Sperren brauchen `--with-time`)
|
||
- `sudo` für den Standardbenutzer `f2bweb`; mit einem root-Schlüssel nicht nötig
|
||
- `bash`, `ssh-keygen` (Teil von OpenSSH) für den Installer
|
||
|
||
## Installation auf einem Host
|
||
|
||
Weboberfläche und Agent auf demselben Docker-Host – so getestet auf Ubuntu 26.04 (LXC).
|
||
Weitere fail2ban-Hosts kommen später nur mit dem Agenten dazu ([docs/agent.md](docs/agent.md)).
|
||
Im Beispiel hat der Host die LAN-Adresse `192.168.1.10` – überall durch deine ersetzen.
|
||
|
||
### fail2ban vorbereiten
|
||
|
||
```bash
|
||
apt install -y fail2ban python3 sudo openssh-server
|
||
```
|
||
|
||
```bash
|
||
systemctl enable --now fail2ban
|
||
```
|
||
|
||
```bash
|
||
fail2ban-client ping # Server replied: pong
|
||
```
|
||
|
||
### Quellen holen
|
||
|
||
```bash
|
||
git clone https://git.retronerd.at/schiwagoa/fail2ban-webui.git /opt/fail2ban-webui-src
|
||
```
|
||
|
||
### Weboberfläche einrichten
|
||
|
||
```bash
|
||
mkdir -p /opt/fail2ban-webui/{config,data} && cd /opt/fail2ban-webui
|
||
```
|
||
|
||
```bash
|
||
curl -fsSLo docker-compose.yaml https://git.retronerd.at/schiwagoa/fail2ban-webui/raw/branch/main/examples/docker-compose.yaml
|
||
```
|
||
|
||
```bash
|
||
curl -fsSLo .env https://git.retronerd.at/schiwagoa/fail2ban-webui/raw/branch/main/examples/.env.example
|
||
```
|
||
|
||
Der Container läuft als UID 10050 und muss in `data/` schreiben:
|
||
|
||
```bash
|
||
chown -R 10050:10050 data && chmod 700 data
|
||
```
|
||
|
||
Für den Zugriff direkt im LAN (ohne Reverse Proxy) den Port an die LAN-Adresse binden –
|
||
hinter einem Nginx Proxy Manager diesen Schritt [weglassen](#hinter-einem-vorhandenen-nginx-proxy-manager-docker):
|
||
|
||
```bash
|
||
sed -i 's/"127.0.0.1:8080:8080"/"192.168.1.10:8080:8080"/' docker-compose.yaml
|
||
```
|
||
|
||
Adresse, Sprache und Proxy-Einstellung setzen. Ohne TLS muss `PUBLIC_URL` mit `http://`
|
||
beginnen, sonst schickt der Browser das Sitzungs-Cookie nicht mit; ohne Proxy gehört
|
||
`TRUSTED_PROXY_HOPS` auf `0`:
|
||
|
||
```bash
|
||
sed -i -e 's|^PUBLIC_URL=.*|PUBLIC_URL=http://192.168.1.10:8080|' -e 's|^DEFAULT_LANG=.*|DEFAULT_LANG=de|' -e 's|^TRUSTED_PROXY_HOPS=.*|TRUSTED_PROXY_HOPS=0|' .env
|
||
```
|
||
|
||
Passwort-Hash erzeugen (mindestens 12 Zeichen, zweimal eingeben):
|
||
|
||
```bash
|
||
docker compose run --rm -it fail2ban-webui hash-password
|
||
```
|
||
|
||
Die ausgegebene Zeile `AUTH_PASSWORD_HASH='scrypt:…'` kopieren und in der `.env` die Zeile
|
||
`AUTH_PASSWORD_HASH='scrypt:...'` damit ersetzen – die einfachen Anführungszeichen bleiben:
|
||
|
||
```bash
|
||
nano .env
|
||
```
|
||
|
||
SSH-Schlüssel der Weboberfläche erzeugen:
|
||
|
||
```bash
|
||
docker compose run --rm fail2ban-webui keygen
|
||
```
|
||
|
||
### Agent installieren
|
||
|
||
Probelauf – zeigt nur, was passieren würde. `172.23.51.10` ist die feste Adresse der
|
||
Weboberfläche in ihrem Docker-Netz:
|
||
|
||
```bash
|
||
bash /opt/fail2ban-webui-src/agent/install.sh --key-file /opt/fail2ban-webui/data/ssh/id_ed25519.pub --from 172.23.51.10 --dry-run
|
||
```
|
||
|
||
Installieren und mit `y` bestätigen – am Ende meldet der Selbsttest
|
||
`2 jails, 0 bans: manual-block, sshd`. Laufen auf dem Host weitere Docker-Dienste mit
|
||
veröffentlichten Ports (z. B. ein Nginx Proxy Manager), zusätzlich `--docker` anhängen,
|
||
damit manuelle Sperren auch dort greifen:
|
||
|
||
```bash
|
||
bash /opt/fail2ban-webui-src/agent/install.sh --key-file /opt/fail2ban-webui/data/ssh/id_ed25519.pub --from 172.23.51.10
|
||
```
|
||
|
||
### Host eintragen und testen
|
||
|
||
```bash
|
||
cat > config/hosts.yaml <<'EOF'
|
||
hosts:
|
||
- name: srv200
|
||
address: host.docker.internal
|
||
EOF
|
||
```
|
||
|
||
Host-Schlüssel holen – die Fingerprints werden angezeigt, mit `y` bestätigen:
|
||
|
||
```bash
|
||
docker compose run --rm -it fail2ban-webui keyscan
|
||
```
|
||
|
||
Verbindung testen – erwartet: `OK srv200 agent 1.0.0, 2 jails, 0 bans`:
|
||
|
||
```bash
|
||
docker compose run --rm fail2ban-webui check
|
||
```
|
||
|
||
### Starten
|
||
|
||
```bash
|
||
docker compose up -d
|
||
```
|
||
|
||
```bash
|
||
docker compose ps # Up … (healthy) nach etwa 30 Sekunden
|
||
```
|
||
|
||
```bash
|
||
docker compose logs fail2ban-webui # hosts: srv200 – auth: local – geoip: on, danach "GeoIP database updated"
|
||
```
|
||
|
||
Im Browser `http://192.168.1.10:8080` öffnen und mit `admin` und dem Passwort von oben anmelden.
|
||
|
||
> `docker compose run` funktioniert nur, solange der Dienst **nicht** läuft – die feste
|
||
> IP-Adresse wäre sonst doppelt vergeben (`Address already in use`). Im laufenden Betrieb
|
||
> die Hilfsbefehle mit `exec` aufrufen, z. B. `docker compose exec fail2ban-webui python -m webui check`.
|
||
|
||
## Hinter einem vorhandenen Nginx Proxy Manager (Docker)
|
||
|
||
Ausgangslage: Nginx Proxy Manager (NPM) läuft auf demselben Host als Docker-Container,
|
||
fail2ban ist auf dem Host installiert. Die Weboberfläche bekommt dann **keinen**
|
||
veröffentlichten Port, sondern hängt zusätzlich im Docker-Netz von NPM; erreichbar ist sie
|
||
nur über NPM mit TLS.
|
||
|
||
Voraussetzung: Docker Engine ≥ 28 und Compose ≥ 2.33 (für `gw_priority`, siehe Schritt 3).
|
||
|
||
### 1. Manuelle Sperren auch für Docker-Ports
|
||
|
||
Verkehr zu veröffentlichten Docker-Ports – also auch zu NPM – läuft nicht über die Kette
|
||
`INPUT`, sondern über `DOCKER-USER`. Den Agenten deshalb mit `--docker` installieren:
|
||
|
||
```bash
|
||
bash /opt/fail2ban-webui-src/agent/install.sh --key-file /opt/fail2ban-webui/data/ssh/id_ed25519.pub --from 172.23.51.10 --docker
|
||
```
|
||
|
||
War der Agent schon ohne `--docker` installiert, lässt der Installer das vorhandene Jail
|
||
unverändert. Dann die Datei sichern, die zwei `action`-Zeilen ergänzen und fail2ban neu laden:
|
||
|
||
```bash
|
||
mkdir -p /backups/config_backups && cp -a /etc/fail2ban/jail.d/manual-block.local /backups/config_backups/manual-block.local_$(date +%F_%H-%M)
|
||
```
|
||
|
||
```bash
|
||
nano /etc/fail2ban/jail.d/manual-block.local
|
||
```
|
||
|
||
```ini
|
||
action = iptables-allports[name=manual-block]
|
||
iptables-allports[name=manual-block-docker, chain=DOCKER-USER]
|
||
```
|
||
|
||
```bash
|
||
fail2ban-client reload && fail2ban-client status manual-block
|
||
```
|
||
|
||
### 2. Docker-Netz von NPM ermitteln
|
||
|
||
```bash
|
||
docker ps --format '{{.Names}} {{.Image}}' | grep -iE 'npm|proxy-manager'
|
||
```
|
||
|
||
```bash
|
||
docker inspect <NPM-Container> --format '{{range $n, $c := .NetworkSettings.Networks}}{{$n}}{{"\n"}}{{end}}'
|
||
```
|
||
|
||
### 3. Weboberfläche ins NPM-Netz hängen
|
||
|
||
Den veröffentlichten Port entfernen – erreichbar ist die Oberfläche danach nur noch über NPM:
|
||
|
||
```bash
|
||
cd /opt/fail2ban-webui && sed -i '/^ ports:$/,/8080:8080"$/d' docker-compose.yaml
|
||
```
|
||
|
||
Das NPM-Netz kommt in eine eigene Datei `docker-compose.override.yaml`, die Docker Compose
|
||
automatisch dazulädt. Den Netznamen aus Schritt 2 in `NPM_NET` setzen:
|
||
|
||
```bash
|
||
NPM_NET=npm_default
|
||
```
|
||
|
||
```bash
|
||
cat > docker-compose.override.yaml <<EOF
|
||
# fail2ban-webui hinter Nginx Proxy Manager: zusätzlich im Netz von NPM.
|
||
# gw_priority hält die Standardroute im eigenen Netz – sonst kommt die
|
||
# SSH-Verbindung zum Agenten mit der Adresse aus dem NPM-Netz an und
|
||
# --from 172.23.51.10 lehnt sie ab.
|
||
services:
|
||
fail2ban-webui:
|
||
networks:
|
||
fail2ban-webui.dockernetwork.local:
|
||
gw_priority: 1
|
||
npm: {}
|
||
|
||
networks:
|
||
npm:
|
||
name: ${NPM_NET}
|
||
external: true
|
||
EOF
|
||
```
|
||
|
||
```bash
|
||
docker compose config --quiet && echo "Konfiguration ok"
|
||
```
|
||
|
||
### 4. Adresse und Proxy-Einstellung
|
||
|
||
```bash
|
||
sed -i -e 's|^PUBLIC_URL=.*|PUBLIC_URL=https://fail2ban.example.org|' -e 's|^TRUSTED_PROXY_HOPS=.*|TRUSTED_PROXY_HOPS=1|' .env
|
||
```
|
||
|
||
### 5. Neu starten und prüfen
|
||
|
||
```bash
|
||
docker compose up -d
|
||
```
|
||
|
||
```bash
|
||
docker compose exec fail2ban-webui python -m webui check
|
||
```
|
||
|
||
### 6. DNS und Proxy-Host in NPM
|
||
|
||
- DNS: `fail2ban.example.org` auf die Adresse des Hosts zeigen lassen.
|
||
- NPM → **Hosts → Proxy Hosts → Add Proxy Host**:
|
||
|
||
| Feld | Wert |
|
||
|---|---|
|
||
| Domain Names | `fail2ban.example.org` |
|
||
| Scheme / Forward Hostname / Port | `http` · `fail2ban-webui` · `8080` |
|
||
| Block Common Exploits | an |
|
||
| SSL | Zertifikat anfordern (für interne Namen wie `*.int`: eigenes Zertifikat oder DNS-Challenge), *Force SSL*, *HTTP/2*, *HSTS* |
|
||
| Access List | empfohlen: nur deine IP-Adressen |
|
||
|
||
### 7. Testen
|
||
|
||
```bash
|
||
curl -sI https://fail2ban.example.org/login | grep -iE '^(HTTP|set-cookie)'
|
||
```
|
||
|
||
Erwartet: `HTTP/2 200` und ein Cookie mit `Secure; HttpOnly; SameSite=Lax`. Im Protokoll der
|
||
Weboberfläche steht nach einer Aktion die echte Client-Adresse, nicht die von NPM.
|
||
|
||
Optional: fehlgeschlagene Anmeldungen von fail2ban sperren lassen – siehe
|
||
[Login mit fail2ban absichern](docs/operations.md#login-mit-fail2ban-absichern)
|
||
(Banaction dort ebenfalls mit `chain=DOCKER-USER`).
|
||
|
||
## Dokumentation
|
||
|
||
| | |
|
||
|---|---|
|
||
| [Installation](docs/installation.md) | Weboberfläche Schritt für Schritt, von `mkdir` bis zur ersten Anmeldung |
|
||
| [Agent](docs/agent.md) | `f2b-webctl` auf Bare Metal, LXC und Docker-Hosts installieren |
|
||
| [Konfiguration](docs/configuration.md) | alle Einstellungen in `.env` und `hosts.yaml` |
|
||
| [Anmeldung](docs/authentication.md) | lokaler Login, OpenID Connect (Authentik), Anmeldung über den Reverse Proxy |
|
||
| [Reverse Proxy](docs/reverse-proxy.md) | Nginx Proxy Manager, Caddy, Traefik, nginx |
|
||
| [Betrieb](docs/operations.md) | Logs, Updates, Backups, Fehlersuche |
|
||
| [Entwicklung](docs/development.md) | lokales Testbed, Tests, neue Sprache hinzufügen |
|
||
| [Sicherheit](SECURITY.md) | Sicherheitsmodell und Melden von Schwachstellen |
|
||
|
||
## Lizenz
|
||
|
||
[MIT](LICENSE). IP-Geolokalisierung durch [DB-IP Lite](https://db-ip.com) (CC BY 4.0), wird
|
||
nur bei `GEOIP_AUTO_DOWNLOAD=1` heruntergeladen. Weltkarte von [Natural Earth](https://www.naturalearthdata.com)
|
||
(Public Domain), in der App enthalten.
|
||
|
||
---
|
||
|
||
<p align="center">
|
||
🏰 <a href="https://retronerd.at">Website</a> ·
|
||
🐘 <a href="https://social.retronerd.at/@schiwagoa">Mastodon</a> ·
|
||
🔒 <a href="https://retronerd.at/datenschutzerklaerung/">Datenschutz</a> ·
|
||
💬 <a href="https://helpdesk.retronerd.at/open.php">Support</a>
|
||
</p>
|
||
|
||
<p align="center">
|
||
<img src="https://dl.retronerd.at/Bilder/cat-footer.png" alt="" width="100%">
|
||
</p>
|
||
|
||
<p align="center">© Thomas Werner Schwaighofer · retronerd.AT · <a href="LICENSE">MIT Lizenz</a></p>
|