Web interface to view, ban and unban fail2ban IPs on several hosts via SSH, generalised from the private f2b-web tool. - Web UI (Flask/Waitress): hosts.yaml, local/OIDC/proxy authentication, English/German UI, origin map, optional Loki/Grafana/GeoIP, CLI helpers - Agent f2b-webctl with install.sh/uninstall.sh (dedicated user + sudo, from= restriction, protected networks, manual jail) - Hardened Docker image, examples, documentation, smoke test, testbed Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
120 lines
5.1 KiB
Markdown
120 lines
5.1 KiB
Markdown
# Authentication
|
||
|
||
`AUTH_MODE` selects how users sign in. The container refuses to start without a working
|
||
configuration – there is no unauthenticated mode.
|
||
|
||
| Mode | Use when |
|
||
|---|---|
|
||
| `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) |
|
||
|
||
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.
|
||
|
||
## Local user
|
||
|
||
```bash
|
||
docker compose run --rm -it fail2ban-webui hash-password
|
||
```
|
||
|
||
```ini
|
||
AUTH_MODE=local
|
||
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.
|
||
|
||
## OpenID Connect
|
||
|
||
Requirements: `PUBLIC_URL` is set to the external `https://` URL, and the provider allows
|
||
the 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`).
|
||
|
||
**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).
|
||
|
||
### 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*
|
||
- Redirect URIs: `strict` · `https://fail2ban.example.org/auth/callback`
|
||
- Signing key: any certificate (e.g. `authentik Self-signed Certificate`)
|
||
- Advanced protocol settings → Scopes: `openid`, `email`, `profile`
|
||
(the default *profile* mapping contains the `groups` claim)
|
||
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.
|
||
5. `.env`:
|
||
|
||
```ini
|
||
PUBLIC_URL=https://fail2ban.example.org
|
||
AUTH_MODE=oidc
|
||
OIDC_ISSUER=https://auth.example.org/application/o/fail2ban-webui/
|
||
OIDC_CLIENT_ID=…
|
||
OIDC_CLIENT_SECRET='…'
|
||
OIDC_ALLOWED_GROUPS=fail2ban-admins
|
||
OIDC_BUTTON_LABEL=Authentik
|
||
```
|
||
|
||
6. `docker compose up -d`, open `PUBLIC_URL`, click **Sign in with Authentik**.
|
||
|
||
The issuer URL is shown in Authentik on the provider page (*OpenID Configuration Issuer*).
|
||
|
||
### Other providers
|
||
|
||
| Provider | `OIDC_ISSUER` | Groups |
|
||
|---|---|---|
|
||
| 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 |
|
||
|
||
### Sign out
|
||
|
||
"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.
|
||
|
||
## Reverse-proxy mode
|
||
|
||
The proxy does the authentication; the UI only checks where requests come from and,
|
||
optionally, which user the proxy reports.
|
||
|
||
```ini
|
||
AUTH_MODE=proxy
|
||
TRUSTED_PROXY_HOPS=1
|
||
# only accept requests from the proxy (address as seen by the container)
|
||
AUTH_PROXY_ALLOWED_IPS=172.23.51.1/32
|
||
# optional: user name for the audit log, set by the proxy
|
||
AUTH_PROXY_HEADER=X-authentik-username
|
||
# optional: logout URL of the proxy/IdP
|
||
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`.
|
||
|
||
Examples for Nginx Proxy Manager, Traefik and Caddy are in
|
||
[reverse-proxy.md](reverse-proxy.md).
|