Update docs and write troubleshooting steps to debug no reciving ban/unban events

This commit is contained in:
2026-02-21 22:31:59 +01:00
parent 3f6f356923
commit 48bcd403ac
5 changed files with 338 additions and 50 deletions

View File

@@ -4,8 +4,12 @@ This is a short index for operators. The UI primarily uses these endpoints. Path
## Authentication
- When OIDC is enabled, most `/api/*` endpoints require an authenticated session.
- Callback endpoints are authenticated using `X-Callback-Secret`.
- When OIDC is enabled, all `/api/*` endpoints (including WebSocket) require an authenticated session, except the callback endpoints.
- 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
@@ -14,49 +18,79 @@ This is a short index for operators. The UI primarily uses these endpoints. Path
## Endpoints
Server management
- `GET /api/servers`
- `POST /api/servers`
- `DELETE /api/servers/:id`
- `POST /api/servers/:id/test`
### Server management
- `GET /api/servers` -> List configured servers
- `POST /api/servers` -> Create or update a server
- `DELETE /api/servers/:id` -> Delete a server
- `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
- `GET /api/summary`
- `GET /api/jails/manage`
- `POST /api/jails/manage`
- `GET /api/jails/:jail/config`
- `POST /api/jails/:jail/config`
- `POST /api/jails/:jail/unban/:ip`
- `POST /api/jails/:jail/ban/:ip`
### Jails and configuration
- `GET /api/summary` -> Dashboard summary (jails, banned IPs per server)
- `GET /api/jails/manage` -> List jails with enabled/disabled status
- `POST /api/jails/manage` -> Update jail enabled/disabled state
- `POST /api/jails` -> Create a new jail
- `DELETE /api/jails/:jail` -> Delete a jail
- `GET /api/jails/:jail/config` -> Get jail/filter configuration
- `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
- `GET /api/events/bans`
- `GET /api/events/bans/stats`
- `GET /api/events/bans/insights`
### Events and analytics
- `GET /api/events/bans` -> List ban/unban events (paginated, filterable)
- `DELETE /api/events/bans` -> Delete all stored ban events
- `GET /api/events/bans/stats` -> Ban statistics (counts, timeseries)
- `GET /api/events/bans/insights` -> Ban insights (countries, top IPs, top jails)
Settings
- `GET /api/settings`
- `POST /api/settings`
- `POST /api/settings/test-email`
### Advanced actions
- `GET /api/advanced-actions/blocks` -> List permanent block records
- `DELETE /api/advanced-actions/blocks` -> Delete all permanent block records
- `POST /api/advanced-actions/test` -> Manually test block/unblock on configured integration
Filter debugging
- `GET /api/filters`
- `POST /api/filters/test`
### Settings
- `GET /api/settings` -> Get current application settings
- `POST /api/settings` -> Update application settings
- `POST /api/settings/test-email` -> Send a test email
Service control
- `POST /api/fail2ban/restart`
### Filter management
- `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)
- `POST /api/ban`
- `POST /api/unban`
### Service control
- `POST /api/fail2ban/restart` -> Restart / Reloads the Fail2Ban service
### 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:
- Header: `X-Callback-Secret: <secret>`
- JSON body fields (typical): `serverId`, `ip`, `jail`, `hostname`, `failures`, `logs`
Authentication routes (OIDC)
- `GET /auth/login`
- `GET /auth/callback`
- `GET /auth/logout`
- `GET /auth/status`
- `GET /auth/user`
All IPs in callback payloads are validated before processing.
### Authentication routes (OIDC)
- `GET /auth/login` -> Initiate OIDC login flow
- `GET /auth/callback` -> OIDC provider callback
- `GET /auth/logout` -> Logout and clear session
- `GET /auth/status` -> Check authentication status
- `GET /auth/user` -> Get current user info

View File

@@ -23,9 +23,11 @@ Fail2Ban UI consists of :
## Components (high level)
- REST API: server management, jail/filter config read/write, ban/unban actions, settings
- WebSocket hub: streams ban/unban events and (optional) debug console logs
- REST API: server management, jail/filter config read/write, ban/unban actions, settings, data management (clear events/blocks)
- 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
- 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:
- Container deployment guide: `deployment/container/README.md`
@@ -62,11 +64,11 @@ Additional resources:
│ │ • /auth/login | /auth/callback | /auth/logout │ │ │ e
│ │ • /auth/status | /auth/user │ │ │ t
│ │ • POST /api/ban | POST /api/unban ← Fail2ban callbacks (a valid Callback │ │ │
│ │ • GET /api/ws (WebSocket) Secret is needed) │ │
│ │ • /static/* | /locales/* │ │----┘
│ │ • /static/* | /locales/* Secret is needed) │ │----┘
│ └───────────────────────────────────────────────────────────────────────────┘ │
│ ┌───────────────────────────────────────────────────────────────────────────┐ │
│ │ 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 │ │
│ │ • GET /settings • POST /settings │ │
│ │ • GET /events/bans • GET /events/bans/stats | /insights │ │
│ │ • DELETE /events/bans • DELETE /advanced-actions/blocks │ │
│ │ • GET /version (optional GitHub request if UPDATE_CHECK) │ │
│ │ • GET /servers | POST/DELETE /servers | POST /servers/:id/test │ │
│ │ • GET /filters/* • POST /filters/test | POST/DELETE /filters │ │
│ │ • POST /fail2ban/restart • GET/POST /advanced-actions/* │ │
│ │ • 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 │ │
│ │ • Origin header validated against Host (rejects cross-site connections) │ │
│ │ • broadcast to all clients: │ │
│ │ - type: "heartbeat" (every ~30s) │ │
│ │ - type: "console_log" (debug console lines) │ │
@@ -110,8 +115,10 @@ Additional resources:
│ │ Connector Manager │ │ Integrations + Email │ │
│ │ • Local (fail2ban.sock) │ │ • Mikrotik / pfSense / │ │
│ │ • SSH (exec on remote) │ │ OPNsense (block/unblock)│ │
│ │ • Agent (HTTP to agent) │ │ • SMTP alert emails │ │
│ │ • New server init: ensure │ └────────────────────────────┘
│ │ • Agent (HTTP to agent) │ │ • Input validated (IP + │ │
│ │ • New server init: ensure │ │ identifiers sanitized) │
│ │ │ │ • SMTP alert emails │ │
│ │ │ └────────────────────────────┘ │
│ │ action.d (ui-custom- │ │
│ │ action.conf) │ │
│ └────────────────────────────┘ │

View File

@@ -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.
## 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
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`).
- 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
Local connector deployments typically require access to:

View File

@@ -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
```
## 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)
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
```
## 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
Check: