mirror of
https://github.com/swissmakers/fail2ban-ui.git
synced 2026-04-05 09:27:00 +02:00
Update docs and write troubleshooting steps to debug no reciving ban/unban events
This commit is contained in:
14
README.md
14
README.md
@@ -6,7 +6,7 @@
|
|||||||
**Enterprise-Grade Intrusion Detection System Management Platform**
|
**Enterprise-Grade Intrusion Detection System Management Platform**
|
||||||
|
|
||||||
[](https://www.gnu.org/licenses/gpl-3.0)
|
[](https://www.gnu.org/licenses/gpl-3.0)
|
||||||
[](https://golang.org/)
|
[](https://golang.org/)
|
||||||
[](https://www.linux.org/)
|
[](https://www.linux.org/)
|
||||||
|
|
||||||
*Swissmade open-source solution for centralized Fail2Ban management across distributed infrastructure*
|
*Swissmade open-source solution for centralized Fail2Ban management across distributed infrastructure*
|
||||||
@@ -23,13 +23,15 @@ The project is maintained by Swissmakers GmbH and released under GPL-3.0.
|
|||||||
|
|
||||||
Fail2Ban UI does not replace Fail2Ban. It connects to existing Fail2Ban instances and adds:
|
Fail2Ban UI does not replace Fail2Ban. It connects to existing Fail2Ban instances and adds:
|
||||||
|
|
||||||
- A Dashboard for active jails and recent ban/unban activity
|
- A Dashboard for active jails and recent ban/unban activity with real-time WebSocket updates
|
||||||
- Server Manager for adding new fail2ban servers to Fail2ban-UI
|
- Server Manager for adding new fail2ban servers to Fail2ban-UI
|
||||||
- Central search and unban across jails and servers
|
- Central search and unban / ban across jails and servers
|
||||||
- Remote editing / creating, of jail/filter configuration (depending on connector)
|
- Remote editing / creating, of jail/filter configuration (depending on connector)
|
||||||
- Filter debug integration and live log-pattern testing
|
- Filter debug integration and live log-pattern testing
|
||||||
- Advanced ban actions for recurring offenders e.g. automatically ban on pfSense and Mikrotik, when threshold is reached.
|
- Ban Insights with an interactive 3D threat globe showing blocks per country
|
||||||
- Optional email alerts with GeoIP/Whois enrichment for selected "alert countries" only.
|
- Advanced ban actions for recurring offenders e.g. automatically ban on pfSense, Mikrotik, or OPNsense when threshold is reached
|
||||||
|
- Data management possibility for permanent block logs and stored ban events
|
||||||
|
- Optional email alerts with GeoIP/Whois enrichment for selected "alert countries" only
|
||||||
- Optional OIDC login (Keycloak, Authentik, Pocket-ID)
|
- Optional OIDC login (Keycloak, Authentik, Pocket-ID)
|
||||||
- Least-privilege, SELinux-aware container deployment (policies provided)
|
- Least-privilege, SELinux-aware container deployment (policies provided)
|
||||||
- .. and much more to come.
|
- .. and much more to come.
|
||||||
@@ -170,6 +172,8 @@ Global Fail2Ban settings including default bantime, findtime, maxretry, banactio
|
|||||||
|
|
||||||
* Do not expose the UI directly to the public Internet. Put it behind a reverse proxy, VPN, firewall rules, and/or OIDC.
|
* Do not expose the UI directly to the public Internet. Put it behind a reverse proxy, VPN, firewall rules, and/or OIDC.
|
||||||
* SSH connector should use a dedicated service account with minimal sudo permissions and ACLs.
|
* SSH connector should use a dedicated service account with minimal sudo permissions and ACLs.
|
||||||
|
* All IP addresses are validated (strict IPv4/IPv6/CIDR parsing) before being passed to any integration or command, preventing command injection.
|
||||||
|
* WebSocket connections are protected by origin validation (same-origin only) and require authentication when OIDC is enabled.
|
||||||
|
|
||||||
See [`docs/security.md`](https://github.com/swissmakers/fail2ban-ui/blob/main/docs/security.md) for details.
|
See [`docs/security.md`](https://github.com/swissmakers/fail2ban-ui/blob/main/docs/security.md) for details.
|
||||||
|
|
||||||
|
|||||||
108
docs/api.md
108
docs/api.md
@@ -4,8 +4,12 @@ This is a short index for operators. The UI primarily uses these endpoints. Path
|
|||||||
|
|
||||||
## Authentication
|
## Authentication
|
||||||
|
|
||||||
- When OIDC is enabled, most `/api/*` endpoints require an authenticated session.
|
- When OIDC is enabled, all `/api/*` endpoints (including WebSocket) require an authenticated session, except the callback endpoints.
|
||||||
- Callback endpoints are authenticated using `X-Callback-Secret`.
|
- Callback endpoints (`/api/ban`, `/api/unban`) are authenticated using `X-Callback-Secret`.
|
||||||
|
|
||||||
|
## Input validation
|
||||||
|
|
||||||
|
All endpoints that accept IP addresses validate them server-side using Go's `net.ParseIP` / `net.ParseCIDR`. Requests with invalid IPs receive a `400 Bad Request` response. This applies to ban/unban callbacks, manual ban/unban from the dashboard, and the advanced actions test endpoint.
|
||||||
|
|
||||||
## Common headers
|
## Common headers
|
||||||
|
|
||||||
@@ -14,49 +18,79 @@ This is a short index for operators. The UI primarily uses these endpoints. Path
|
|||||||
|
|
||||||
## Endpoints
|
## Endpoints
|
||||||
|
|
||||||
Server management
|
### Server management
|
||||||
- `GET /api/servers`
|
- `GET /api/servers` -> List configured servers
|
||||||
- `POST /api/servers`
|
- `POST /api/servers` -> Create or update a server
|
||||||
- `DELETE /api/servers/:id`
|
- `DELETE /api/servers/:id` -> Delete a server
|
||||||
- `POST /api/servers/:id/test`
|
- `POST /api/servers/:id/default` -> Set server as default
|
||||||
|
- `POST /api/servers/:id/test` -> Test server connectivity
|
||||||
|
- `GET /api/ssh/keys` -> List available SSH keys
|
||||||
|
|
||||||
Jails and configuration
|
### Jails and configuration
|
||||||
- `GET /api/summary`
|
- `GET /api/summary` -> Dashboard summary (jails, banned IPs per server)
|
||||||
- `GET /api/jails/manage`
|
- `GET /api/jails/manage` -> List jails with enabled/disabled status
|
||||||
- `POST /api/jails/manage`
|
- `POST /api/jails/manage` -> Update jail enabled/disabled state
|
||||||
- `GET /api/jails/:jail/config`
|
- `POST /api/jails` -> Create a new jail
|
||||||
- `POST /api/jails/:jail/config`
|
- `DELETE /api/jails/:jail` -> Delete a jail
|
||||||
- `POST /api/jails/:jail/unban/:ip`
|
- `GET /api/jails/:jail/config` -> Get jail/filter configuration
|
||||||
- `POST /api/jails/:jail/ban/:ip`
|
- `POST /api/jails/:jail/config` -> Update jail/filter configuration
|
||||||
|
- `POST /api/jails/:jail/logpath/test` -> Test log path accessibility
|
||||||
|
- `POST /api/jails/:jail/unban/:ip` -> Unban an IP from a jail
|
||||||
|
- `POST /api/jails/:jail/ban/:ip` -> Ban an IP in a jail
|
||||||
|
|
||||||
Events and analytics
|
### Events and analytics
|
||||||
- `GET /api/events/bans`
|
- `GET /api/events/bans` -> List ban/unban events (paginated, filterable)
|
||||||
- `GET /api/events/bans/stats`
|
- `DELETE /api/events/bans` -> Delete all stored ban events
|
||||||
- `GET /api/events/bans/insights`
|
- `GET /api/events/bans/stats` -> Ban statistics (counts, timeseries)
|
||||||
|
- `GET /api/events/bans/insights` -> Ban insights (countries, top IPs, top jails)
|
||||||
|
|
||||||
Settings
|
### Advanced actions
|
||||||
- `GET /api/settings`
|
- `GET /api/advanced-actions/blocks` -> List permanent block records
|
||||||
- `POST /api/settings`
|
- `DELETE /api/advanced-actions/blocks` -> Delete all permanent block records
|
||||||
- `POST /api/settings/test-email`
|
- `POST /api/advanced-actions/test` -> Manually test block/unblock on configured integration
|
||||||
|
|
||||||
Filter debugging
|
### Settings
|
||||||
- `GET /api/filters`
|
- `GET /api/settings` -> Get current application settings
|
||||||
- `POST /api/filters/test`
|
- `POST /api/settings` -> Update application settings
|
||||||
|
- `POST /api/settings/test-email` -> Send a test email
|
||||||
|
|
||||||
Service control
|
### Filter management
|
||||||
- `POST /api/fail2ban/restart`
|
- `GET /api/filters` -> List available filters
|
||||||
|
- `GET /api/filters/:filter/content` -> Get filter file content
|
||||||
|
- `POST /api/filters` -> Create a new filter
|
||||||
|
- `POST /api/filters/test` -> Test filter regex against log lines
|
||||||
|
- `DELETE /api/filters/:filter` -> Delete a filter
|
||||||
|
|
||||||
Callbacks (Fail2Ban actions)
|
### Service control
|
||||||
- `POST /api/ban`
|
- `POST /api/fail2ban/restart` -> Restart / Reloads the Fail2Ban service
|
||||||
- `POST /api/unban`
|
|
||||||
|
### Version
|
||||||
|
- `GET /api/version` -> Get running version and optional update check
|
||||||
|
|
||||||
|
### WebSocket
|
||||||
|
- `GET /api/ws` -> WebSocket endpoint (upgrade)
|
||||||
|
|
||||||
|
The WebSocket connection streams, real-time events to the frontend:
|
||||||
|
- `heartbeat` -> periodic health check (~30s)
|
||||||
|
- `console_log` -> debug console log lines (when debug mode is enabled)
|
||||||
|
- `ban_event` -> real-time ban event broadcast
|
||||||
|
- `unban_event` -> real-time unban event broadcast
|
||||||
|
|
||||||
|
The WebSocket enforces same-origin policy via the `Origin` header and requires authentication when OIDC is enabled.
|
||||||
|
|
||||||
|
### Callbacks (Fail2Ban actions)
|
||||||
|
- `POST /api/ban` -> Receive ban notification from Fail2Ban
|
||||||
|
- `POST /api/unban` -> Receive unban notification from Fail2Ban
|
||||||
|
|
||||||
Callbacks require:
|
Callbacks require:
|
||||||
- Header: `X-Callback-Secret: <secret>`
|
- Header: `X-Callback-Secret: <secret>`
|
||||||
- JSON body fields (typical): `serverId`, `ip`, `jail`, `hostname`, `failures`, `logs`
|
- JSON body fields (typical): `serverId`, `ip`, `jail`, `hostname`, `failures`, `logs`
|
||||||
|
|
||||||
Authentication routes (OIDC)
|
All IPs in callback payloads are validated before processing.
|
||||||
- `GET /auth/login`
|
|
||||||
- `GET /auth/callback`
|
### Authentication routes (OIDC)
|
||||||
- `GET /auth/logout`
|
- `GET /auth/login` -> Initiate OIDC login flow
|
||||||
- `GET /auth/status`
|
- `GET /auth/callback` -> OIDC provider callback
|
||||||
- `GET /auth/user`
|
- `GET /auth/logout` -> Logout and clear session
|
||||||
|
- `GET /auth/status` -> Check authentication status
|
||||||
|
- `GET /auth/user` -> Get current user info
|
||||||
@@ -23,9 +23,11 @@ Fail2Ban UI consists of :
|
|||||||
|
|
||||||
## Components (high level)
|
## Components (high level)
|
||||||
|
|
||||||
- REST API: server management, jail/filter config read/write, ban/unban actions, settings
|
- REST API: server management, jail/filter config read/write, ban/unban actions, settings, data management (clear events/blocks)
|
||||||
- WebSocket hub: streams ban/unban events and (optional) debug console logs
|
- WebSocket hub: streams real-time ban/unban events and (optional) debug console logs, protected by origin validation and session auth
|
||||||
- Storage: server definitions, settings, ban history, permanent block records
|
- Storage: server definitions, settings, ban history, permanent block records
|
||||||
|
- Integrations: MikroTik (SSH), pfSense (REST API), OPNsense (REST API) with input validation on all parameters
|
||||||
|
- Ban Insights: country-level analytics with interactive 3D threat globe visualization
|
||||||
|
|
||||||
Additional resources:
|
Additional resources:
|
||||||
- Container deployment guide: `deployment/container/README.md`
|
- Container deployment guide: `deployment/container/README.md`
|
||||||
@@ -62,11 +64,11 @@ Additional resources:
|
|||||||
│ │ • /auth/login | /auth/callback | /auth/logout │ │ │ e
|
│ │ • /auth/login | /auth/callback | /auth/logout │ │ │ e
|
||||||
│ │ • /auth/status | /auth/user │ │ │ t
|
│ │ • /auth/status | /auth/user │ │ │ t
|
||||||
│ │ • POST /api/ban | POST /api/unban ← Fail2ban callbacks (a valid Callback │ │ │
|
│ │ • POST /api/ban | POST /api/unban ← Fail2ban callbacks (a valid Callback │ │ │
|
||||||
│ │ • GET /api/ws (WebSocket) Secret is needed) │ │ │
|
│ │ • /static/* | /locales/* Secret is needed) │ │----┘
|
||||||
│ │ • /static/* | /locales/* │ │----┘
|
|
||||||
│ └───────────────────────────────────────────────────────────────────────────┘ │
|
│ └───────────────────────────────────────────────────────────────────────────┘ │
|
||||||
│ ┌───────────────────────────────────────────────────────────────────────────┐ │
|
│ ┌───────────────────────────────────────────────────────────────────────────┐ │
|
||||||
│ │ PROTECTED (when OIDC enabled): GET / | GET and POST to all other /api/* │ │
|
│ │ PROTECTED (when OIDC enabled): │ │
|
||||||
|
│ │ GET / | all other /api/* | GET /api/ws (WebSocket, same-origin only) │ │
|
||||||
│ └───────────────────────────────────────────────────────────────────────────┘ │
|
│ └───────────────────────────────────────────────────────────────────────────┘ │
|
||||||
└─────────────────────────────────────────────────────────────────────────────────┘
|
└─────────────────────────────────────────────────────────────────────────────────┘
|
||||||
```
|
```
|
||||||
@@ -84,16 +86,19 @@ Additional resources:
|
|||||||
│ │ • POST /jails/:jail/unban/:ip • POST /jails/:jail/ban/:ip │ │
|
│ │ • POST /jails/:jail/unban/:ip • POST /jails/:jail/ban/:ip │ │
|
||||||
│ │ • GET /settings • POST /settings │ │
|
│ │ • GET /settings • POST /settings │ │
|
||||||
│ │ • GET /events/bans • GET /events/bans/stats | /insights │ │
|
│ │ • GET /events/bans • GET /events/bans/stats | /insights │ │
|
||||||
|
│ │ • DELETE /events/bans • DELETE /advanced-actions/blocks │ │
|
||||||
│ │ • GET /version (optional GitHub request if UPDATE_CHECK) │ │
|
│ │ • GET /version (optional GitHub request if UPDATE_CHECK) │ │
|
||||||
│ │ • GET /servers | POST/DELETE /servers | POST /servers/:id/test │ │
|
│ │ • GET /servers | POST/DELETE /servers | POST /servers/:id/test │ │
|
||||||
│ │ • GET /filters/* • POST /filters/test | POST/DELETE /filters │ │
|
│ │ • GET /filters/* • POST /filters/test | POST/DELETE /filters │ │
|
||||||
│ │ • POST /fail2ban/restart • GET/POST /advanced-actions/* │ │
|
│ │ • POST /fail2ban/restart • GET/POST /advanced-actions/* │ │
|
||||||
│ │ • POST /ban (callback) • POST /unban (callback) │ │
|
│ │ • POST /ban (callback) • POST /unban (callback) │ │
|
||||||
|
│ │ All IP inputs validated via net.ParseIP / net.ParseCIDR │ │
|
||||||
│ └────────────────────────────────────────────────────────────────────────────┘ │
|
│ └────────────────────────────────────────────────────────────────────────────┘ │
|
||||||
│ │ │
|
│ │ │
|
||||||
│ ┌─────────────────────────────────┴──────────────────────────────────────────┐ │
|
│ ┌─────────────────────────────────┴──────────────────────────────────────────┐ │
|
||||||
│ │ WebSocket Hub (GET /api/ws) │ │
|
│ │ WebSocket Hub (GET /api/ws — same-origin, auth required with OIDC) │ │
|
||||||
│ │ • register / unregister clients │ │
|
│ │ • register / unregister clients │ │
|
||||||
|
│ │ • Origin header validated against Host (rejects cross-site connections) │ │
|
||||||
│ │ • broadcast to all clients: │ │
|
│ │ • broadcast to all clients: │ │
|
||||||
│ │ - type: "heartbeat" (every ~30s) │ │
|
│ │ - type: "heartbeat" (every ~30s) │ │
|
||||||
│ │ - type: "console_log" (debug console lines) │ │
|
│ │ - type: "console_log" (debug console lines) │ │
|
||||||
@@ -110,8 +115,10 @@ Additional resources:
|
|||||||
│ │ Connector Manager │ │ Integrations + Email │ │
|
│ │ Connector Manager │ │ Integrations + Email │ │
|
||||||
│ │ • Local (fail2ban.sock) │ │ • Mikrotik / pfSense / │ │
|
│ │ • Local (fail2ban.sock) │ │ • Mikrotik / pfSense / │ │
|
||||||
│ │ • SSH (exec on remote) │ │ OPNsense (block/unblock)│ │
|
│ │ • SSH (exec on remote) │ │ OPNsense (block/unblock)│ │
|
||||||
│ │ • Agent (HTTP to agent) │ │ • SMTP alert emails │ │
|
│ │ • Agent (HTTP to agent) │ │ • Input validated (IP + │ │
|
||||||
│ │ • New server init: ensure │ └────────────────────────────┘ │
|
│ │ • New server init: ensure │ │ identifiers sanitized) │ │
|
||||||
|
│ │ │ │ • SMTP alert emails │ │
|
||||||
|
│ │ │ └────────────────────────────┘ │
|
||||||
│ │ action.d (ui-custom- │ │
|
│ │ action.d (ui-custom- │ │
|
||||||
│ │ action.conf) │ │
|
│ │ action.conf) │ │
|
||||||
│ └────────────────────────────┘ │
|
│ └────────────────────────────┘ │
|
||||||
|
|||||||
@@ -12,6 +12,24 @@ This project can perform security-sensitive operations (bans, configuration chan
|
|||||||
|
|
||||||
If you must publish it, put it behind TLS and an authentication layer, and restrict source IPs.
|
If you must publish it, put it behind TLS and an authentication layer, and restrict source IPs.
|
||||||
|
|
||||||
|
## Input validation
|
||||||
|
|
||||||
|
All user-supplied IP addresses are validated using Go's `net.ParseIP` and `net.ParseCIDR` before they are passed to any integration, command, or database query. This applies to:
|
||||||
|
|
||||||
|
- Ban/Unban callbacks (`/api/ban`, `/api/unban`)
|
||||||
|
- Manual ban/unban actions from the dashboard
|
||||||
|
- Advanced action test endpoint (`/api/advanced-actions/test`)
|
||||||
|
- All integration connectors (MikroTik, pfSense, OPNsense)
|
||||||
|
|
||||||
|
Integration-specific identifiers (address list names, alias names) are validated against a strict alphanumeric pattern (`[a-zA-Z0-9._-]`) to prevent injection in both SSH commands and API payloads.
|
||||||
|
|
||||||
|
## WebSocket security
|
||||||
|
|
||||||
|
The WebSocket endpoint (`/api/ws`) is protected by:
|
||||||
|
|
||||||
|
- **Origin validation**: The upgrade handshake verifies that the `Origin` header matches the request's `Host` header (same-origin policy). Cross-origin WebSocket connections are rejected. This prevents cross-site WebSocket hijacking attacks.
|
||||||
|
- **Authentication**: When OIDC is enabled, the WebSocket endpoint requires a valid session.
|
||||||
|
|
||||||
## Callback endpoint protection
|
## Callback endpoint protection
|
||||||
|
|
||||||
The fail2ban callback endpoints (`/api/ban`, `/api/unban`) are only reachable with a correct `CALLBACK_SECRET`. This secret must be atleast 20 characters long. If not specified a secure secret, will be automatically genereated on first start. It can be further protected by:
|
The fail2ban callback endpoints (`/api/ban`, `/api/unban`) are only reachable with a correct `CALLBACK_SECRET`. This secret must be atleast 20 characters long. If not specified a secure secret, will be automatically genereated on first start. It can be further protected by:
|
||||||
@@ -30,6 +48,14 @@ For SSH-managed hosts:
|
|||||||
- Restrict sudo to the minimum set of commands required to operate Fail2Ban (typically `fail2ban-client` and optionally `systemctl restart fail2ban`).
|
- Restrict sudo to the minimum set of commands required to operate Fail2Ban (typically `fail2ban-client` and optionally `systemctl restart fail2ban`).
|
||||||
- Use filesystem ACLs for `/etc/fail2ban` rather than broad permissions to allow full modification capabilities for the specific user.
|
- Use filesystem ACLs for `/etc/fail2ban` rather than broad permissions to allow full modification capabilities for the specific user.
|
||||||
|
|
||||||
|
## Integration connector hardening
|
||||||
|
|
||||||
|
When using external firewall integrations (MikroTik, pfSense, OPNsense):
|
||||||
|
|
||||||
|
- Use a dedicated service account on the firewall device with the minimum permissions needed (address-list management only on MikroTik; alias management only on pfSense/OPNsense).
|
||||||
|
- For pfSense/OPNsense: use a dedicated API token with limited scope.
|
||||||
|
- Restrict network access so the Fail2ban-UI host is the only source allowed to reach the firewall management interface.
|
||||||
|
|
||||||
## Least privilege and file access
|
## Least privilege and file access
|
||||||
|
|
||||||
Local connector deployments typically require access to:
|
Local connector deployments typically require access to:
|
||||||
|
|||||||
@@ -65,6 +65,195 @@ getfacl /etc/fail2ban
|
|||||||
sudo podman exec -it fail2ban-ui ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o BatchMode=yes -i /config/.ssh/id_rsa -p 2222 testuser@127.0.0.1
|
sudo podman exec -it fail2ban-ui ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o BatchMode=yes -i /config/.ssh/id_rsa -p 2222 testuser@127.0.0.1
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Ban/unban notifications not showing up in the UI
|
||||||
|
|
||||||
|
This is one of the most common issues. The UI receives ban/unban events from Fail2Ban via HTTP callbacks. If nothing appears in the dashboard or "Recent stored events", the callback chain is broken somewhere. Follow these steps systematically.
|
||||||
|
|
||||||
|
### Step 1: Verify the action file exists and is correct
|
||||||
|
|
||||||
|
Fail2ban-UI creates a custom action file at `/etc/fail2ban/action.d/ui-custom-action.conf` on each managed host. This file contains `curl` commands that notify the UI when bans/unbans happen.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check if the action file exists:
|
||||||
|
cat /etc/fail2ban/action.d/ui-custom-action.conf
|
||||||
|
|
||||||
|
# You should see actionban and actionunban sections with curl commands pointing
|
||||||
|
# to your Fail2ban-UI callback URL (e.g. http://10.88.0.1:8080/api/ban)
|
||||||
|
```
|
||||||
|
|
||||||
|
If the file does not exist or looks wrong, go to Settings → Manage Servers in the UI, select the server, and click "Test connection". The UI will re-deploy the action file automatically for local connectors.
|
||||||
|
|
||||||
|
### Step 2: Verify jail.local references the action
|
||||||
|
|
||||||
|
Fail2ban-UI writes a `jail.local` that uses the custom action. Check that it is in place:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cat /etc/fail2ban/jail.local | head -30
|
||||||
|
|
||||||
|
# Look for the lines like:
|
||||||
|
# action = %(action_mwlg)s
|
||||||
|
# and a definition of action_mwlg that references ui-custom-action
|
||||||
|
```
|
||||||
|
|
||||||
|
If your `jail.local` was created manually or by another tool, the `ui-custom-action` might not be referenced. The easiest fix: let the UI manage `jail.local` by removing your manual version and restarting from the UI.
|
||||||
|
|
||||||
|
### Step 3: Check network connectivity from Fail2Ban host to the UI
|
||||||
|
|
||||||
|
The `curl` command in the action file must be able to reach the UI's callback URL. Test this from the Fail2Ban host (or from inside the container if Fail2Ban runs in one):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Replace with your actual Fail2ban-UI address:
|
||||||
|
curl -s -o /dev/null -w "%{http_code}" http://10.88.0.1:8080/api/version
|
||||||
|
|
||||||
|
# Expected: 200
|
||||||
|
# If you get connection refused, timeout, or another error,
|
||||||
|
# fix network/firewall rules first.
|
||||||
|
```
|
||||||
|
|
||||||
|
Common issues:
|
||||||
|
- Container using bridge networking but callback URL points to `127.0.0.1` (use the host IP or `--network=host`)
|
||||||
|
- Firewall on the UI host blocks the port
|
||||||
|
|
||||||
|
### Step 4: Verify the callback secret
|
||||||
|
|
||||||
|
Every callback must include the header `X-Callback-Secret`. The value must match what the UI expects. You can find the current secret in Settings → General Settings → Callback Secret (or check the container environment).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check what secret the action file uses:
|
||||||
|
grep "X-Callback-Secret" /etc/fail2ban/action.d/ui-custom-action.conf
|
||||||
|
|
||||||
|
# Compare with the UI's expected secret (from the settings page or env var)
|
||||||
|
```
|
||||||
|
|
||||||
|
If they do not match, re-deploy the action file via "Test connection" from the UI, or manually update the secret in the action file and restart Fail2Ban.
|
||||||
|
|
||||||
|
### Step 5: Simulate a ban notification with curl
|
||||||
|
|
||||||
|
This is the most direct way to test the full callback chain. Run this from any host that can reach the UI:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
FAIL2BAN_UI_HOST="your_fail2ban_host"
|
||||||
|
SECRET="your_secret"
|
||||||
|
|
||||||
|
curl -v -X POST http://$FAIL2BAN_UI_HOST:8080/api/ban \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "X-Callback-Secret: $SECRET" \
|
||||||
|
-d '{
|
||||||
|
"serverId": "local",
|
||||||
|
"ip": "203.0.113.42",
|
||||||
|
"jail": "sshd",
|
||||||
|
"hostname": "testhost",
|
||||||
|
"failures": "5",
|
||||||
|
"logs": "Jun 15 12:00:00 testhost sshd: Failed password for root from 203.0.113.42"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected response:
|
||||||
|
```json
|
||||||
|
{"message":"Ban notification processed successfully"}
|
||||||
|
```
|
||||||
|
|
||||||
|
If it works, you should immediately see:
|
||||||
|
- A new entry in "Recent stored events" on the dashboard
|
||||||
|
- A real-time WebSocket update (the entry appears without refreshing)
|
||||||
|
|
||||||
|
Common error responses:
|
||||||
|
- `401 Unauthorized` with `"Callback secret not configured"` → Secret not set in UI settings
|
||||||
|
- `401 Unauthorized` with `"Invalid callback secret"` → Secret mismatch
|
||||||
|
- `400 Bad Request` with `"invalid IP"` → The IP address in the payload is malformed
|
||||||
|
- `400 Bad Request` with `"Invalid request"` → JSON parsing failed (check `ip` and `jail` fields are present)
|
||||||
|
|
||||||
|
To simulate an unban:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -v -X POST http://$FAIL2BAN_UI_HOST:8080/api/unban \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "X-Callback-Secret: $SECRET" \
|
||||||
|
-d '{
|
||||||
|
"serverId": "local",
|
||||||
|
"ip": "203.0.113.42",
|
||||||
|
"jail": "sshd",
|
||||||
|
"hostname": "testhost"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 6: Check what Fail2Ban is actually sending
|
||||||
|
|
||||||
|
If the curl test above works but real bans still don't show up, Fail2Ban itself might not be executing the action correctly. Check:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Trigger a real ban (use a test jail or ban a test IP):
|
||||||
|
fail2ban-client set sshd banip 198.51.100.1
|
||||||
|
|
||||||
|
# Watch the Fail2Ban log for errors:
|
||||||
|
tail -f /var/log/fail2ban.log
|
||||||
|
|
||||||
|
# Look for lines like:
|
||||||
|
# ERROR ... Action ... failed
|
||||||
|
# WARNING ... Command ... failed
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also manually run the exact `curl` command from the action file to see what happens. Extract it from the action file and run it in your shell (replace the Fail2Ban variables like `<ip>`, `<name>`, etc. with real values):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Extract and run the actionban command manually:
|
||||||
|
grep -A5 "actionban" /etc/fail2ban/action.d/ui-custom-action.conf
|
||||||
|
|
||||||
|
# Then execute the curl command with real values substituted.
|
||||||
|
# This reveals whether jq is missing, curl has TLS issues, etc.
|
||||||
|
```
|
||||||
|
|
||||||
|
Common issues at this stage:
|
||||||
|
- **`jq` not installed**: The action file uses `jq` to build JSON. Install it: `dnf install jq` or `apt install jq`
|
||||||
|
- **TLS certificate issues**: If the callback URL uses HTTPS with a self-signed cert, the action file needs the `-k` flag (Fail2ban-UI adds this automatically when the callback URL starts with `https://`)
|
||||||
|
- **Fail2Ban not restarted**: After the action file is deployed, Fail2Ban must be restarted to pick up changes: `systemctl restart fail2ban`
|
||||||
|
|
||||||
|
### Step 7: Check the Fail2ban-UI logs
|
||||||
|
|
||||||
|
The UI logs every incoming callback with details. Check the container or service logs:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Container:
|
||||||
|
podman logs -f fail2ban-ui
|
||||||
|
|
||||||
|
# systemd:
|
||||||
|
journalctl -u fail2ban-ui -f
|
||||||
|
|
||||||
|
# Look for lines like:
|
||||||
|
# ✅ Parsed ban request - IP: ..., Jail: ...
|
||||||
|
# ⚠️ Invalid callback secret ...
|
||||||
|
# ❌ JSON parsing error ...
|
||||||
|
```
|
||||||
|
|
||||||
|
If you enabled debug mode in the UI settings, you will also see the raw JSON body of every incoming callback.
|
||||||
|
|
||||||
|
### Step 8: Verify the `serverId` resolves
|
||||||
|
|
||||||
|
The callback payload includes a `serverId`. The UI uses this to match the event to a configured server. If neither matches any known server, the UI will reject the callback.
|
||||||
|
|
||||||
|
Check that the `serverId` in the action file matches the server ID shown in Settings → Manage Servers. You can see the configured server IDs via:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s http://$FAIL2BAN_UI_HOST:8080/api/servers \
|
||||||
|
-H "X-F2B-Server: default" | jq '.servers[] | {id, name, hostname}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Quick reference: end-to-end callback flow
|
||||||
|
|
||||||
|
```
|
||||||
|
Fail2Ban detects intrusion
|
||||||
|
→ triggers actionban in ui-custom-action.conf
|
||||||
|
→ curl POST /api/ban with JSON payload + X-Callback-Secret header
|
||||||
|
→ Fail2ban-UI validates secret
|
||||||
|
→ Fail2ban-UI validates IP format
|
||||||
|
→ Fail2ban-UI resolves server (by serverId)
|
||||||
|
→ Stores event in SQLite (ban_events table)
|
||||||
|
→ Broadcasts via WebSocket to all connected browsers
|
||||||
|
→ Optional: sends email alert, evaluates advanced actions
|
||||||
|
```
|
||||||
|
|
||||||
|
If any step fails, the chain stops and the event will not appear in the UI.
|
||||||
|
|
||||||
## Bans fail due to firewall backend (nftables / firewalld)
|
## Bans fail due to firewall backend (nftables / firewalld)
|
||||||
|
|
||||||
Symptoms often mention `iptables (nf_tables)` or action startup errors.
|
Symptoms often mention `iptables (nf_tables)` or action startup errors.
|
||||||
@@ -92,6 +281,34 @@ podman logs fail2ban-ui
|
|||||||
# Also enable debug logging over env or over the webUI
|
# Also enable debug logging over env or over the webUI
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## WebSocket not connecting
|
||||||
|
|
||||||
|
If the real-time dashboard updates (ban/unban events appearing without page refresh) are not working:
|
||||||
|
|
||||||
|
Check:
|
||||||
|
|
||||||
|
* Browser console for WebSocket errors (F12 → Console tab)
|
||||||
|
* The WebSocket status indicator in the UI footer
|
||||||
|
* If using a reverse proxy, ensure it supports WebSocket upgrades
|
||||||
|
|
||||||
|
Common issues:
|
||||||
|
|
||||||
|
* **Reverse proxy not forwarding WebSocket**: Nginx requires explicit WebSocket upgrade configuration:
|
||||||
|
```nginx
|
||||||
|
location /api/ws {
|
||||||
|
proxy_pass http://127.0.0.1:8080;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
* **Origin mismatch**: The WebSocket endpoint validates that the `Origin` header matches the `Host` header. If your reverse proxy rewrites the `Host` header but not the `Origin`, the connection will be rejected. Ensure both headers are consistent.
|
||||||
|
|
||||||
|
* **OIDC session expired**: When OIDC is enabled, the WebSocket requires a valid session. If the session expires, the WebSocket connection will fail with a 302 redirect or 401 error. Re-login to the UI to fix this.
|
||||||
|
|
||||||
## Database issues
|
## Database issues
|
||||||
|
|
||||||
Check:
|
Check:
|
||||||
|
|||||||
Reference in New Issue
Block a user