1339 lines
24 KiB
Markdown
1339 lines
24 KiB
Markdown
# BanGUI API Reference
|
||
|
||
Complete reference for the BanGUI REST API. All endpoints require authentication unless noted as public.
|
||
|
||
Base URL: `http://{host}:8000`
|
||
|
||
**Authentication** — All protected endpoints require a valid session cookie (`bangui_session`) or `Authorization: Bearer <token>` header.
|
||
|
||
---
|
||
|
||
## Public Endpoints
|
||
|
||
### `GET /api/v1/health`
|
||
|
||
Health check. No auth required.
|
||
|
||
**Response `200`**
|
||
```json
|
||
{
|
||
"status": "ok",
|
||
"fail2ban": "online",
|
||
"database": "ok",
|
||
"scheduler": "running",
|
||
"cache": "initialised",
|
||
"components": []
|
||
}
|
||
```
|
||
|
||
| Field | Description |
|
||
|---|---|
|
||
| `status` | `ok`, `degraded`, or `unavailable` |
|
||
| `fail2ban` | `online` or `offline` |
|
||
| `database` | `ok` or `error` |
|
||
| `scheduler` | `running`, `stopped`, or `unknown` |
|
||
| `cache` | `initialised` or `uninitialised` |
|
||
| `components` | List of unhealthy components (empty when `ok`) |
|
||
|
||
**Response `503**` — fail2ban offline.
|
||
|
||
---
|
||
|
||
### `GET /api/v1/setup`
|
||
|
||
Check whether initial setup has been completed.
|
||
|
||
**Response `200`**
|
||
```json
|
||
{ "completed": true }
|
||
```
|
||
|
||
---
|
||
|
||
### `POST /api/v1/setup`
|
||
|
||
Run the first-run setup wizard.
|
||
|
||
**Request**
|
||
```json
|
||
{
|
||
"master_password": "Hallo123!",
|
||
"database_path": "/var/lib/fail2ban/fail2ban.sqlite3",
|
||
"fail2ban_socket": "/var/run/fail2ban/fail2ban.sock",
|
||
"timezone": "Europe/Berlin",
|
||
"session_duration_minutes": 480
|
||
}
|
||
```
|
||
|
||
| Field | Type | Required | Description |
|
||
|---|---|---|---|
|
||
| `master_password` | string | Yes | Min 8 chars, uppercase + number + special (`!@#$%^&*()`) |
|
||
| `database_path` | string | No | Path to fail2ban DB (default: `/var/lib/fail2ban/fail2ban.sqlite3`) |
|
||
| `fail2ban_socket` | string | No | Path to fail2ban socket (default: `/var/run/fail2ban/fail2ban.sock`) |
|
||
| `timezone` | string | No | IANA timezone (default: `UTC`) |
|
||
| `session_duration_minutes` | int | No | Session TTL in minutes (default: `480`) |
|
||
|
||
**Response `201`** — Setup completed.
|
||
|
||
**Response `409`** — Setup already completed.
|
||
|
||
---
|
||
|
||
### `GET /api/v1/setup/timezone`
|
||
|
||
Returns the configured IANA timezone.
|
||
|
||
**Response `200`**
|
||
```json
|
||
{ "timezone": "Europe/Berlin" }
|
||
```
|
||
|
||
---
|
||
|
||
### `GET /metrics`
|
||
|
||
Prometheus metrics endpoint. No auth required. Returns OpenMetrics text format.
|
||
|
||
---
|
||
|
||
## Auth
|
||
|
||
### `POST /api/v1/auth/login`
|
||
|
||
Authenticate with the master password.
|
||
|
||
**Request**
|
||
```json
|
||
{ "password": "Hallo123!" }
|
||
```
|
||
|
||
> Note: The frontend SHA256-hashes the password before sending. The backend expects the already-hashed value.
|
||
|
||
**Response `200`** — Sets `bangui_session` cookie.
|
||
```json
|
||
{ "expires_at": "2024-12-25T10:00:00Z" }
|
||
```
|
||
|
||
**Response `401`** — Invalid password.
|
||
|
||
**Response `429`** — Too many login attempts (exponential backoff delay). Includes `Retry-After` header.
|
||
|
||
**Response `503`** — Setup not complete.
|
||
|
||
---
|
||
|
||
### `GET /api/v1/auth/session`
|
||
|
||
Validate the current session.
|
||
|
||
**Response `200`**
|
||
```json
|
||
{ "valid": true }
|
||
```
|
||
|
||
**Response `401`** — Session missing, expired, or invalid.
|
||
|
||
---
|
||
|
||
### `POST /api/v1/auth/logout`
|
||
|
||
Revoke the current session.
|
||
|
||
**Response `200`**
|
||
```json
|
||
{}
|
||
```
|
||
|
||
Session cookie is cleared. Idempotent — returns `200` even if no session present.
|
||
|
||
---
|
||
|
||
## Dashboard
|
||
|
||
### `GET /api/v1/dashboard/status`
|
||
|
||
Returns the cached fail2ban server health snapshot (refreshed every 30 seconds).
|
||
|
||
**Response `200`**
|
||
```json
|
||
{
|
||
"status": {
|
||
"version": "0.12.1",
|
||
"online": true,
|
||
"uptime": 86400,
|
||
"jail_count": 3
|
||
}
|
||
}
|
||
```
|
||
|
||
| Field | Type | Description |
|
||
|---|---|---|
|
||
| `version` | string | fail2ban server version |
|
||
| `online` | bool | Whether fail2ban daemon is reachable |
|
||
| `uptime` | int | Daemon uptime in seconds |
|
||
| `jail_count` | int | Number of configured jails |
|
||
|
||
**Response `401`** — Not authenticated.
|
||
|
||
**Response `502`** — fail2ban unreachable.
|
||
|
||
---
|
||
|
||
### `GET /api/v1/dashboard/bans`
|
||
|
||
Paginated list of recent bans with geo enrichment.
|
||
|
||
**Query Parameters**
|
||
|
||
| Param | Type | Default | Description |
|
||
|---|---|---|---|
|
||
| `range` | `TimeRange` | `24h` | Time window: `24h`, `7d`, `30d`, `365d` |
|
||
| `source` | string | `fail2ban` | Data source: `fail2ban` or `archive` |
|
||
| `page` | int | `1` | 1-based page number |
|
||
| `page_size` | int | `25` | Items per page (max 500) |
|
||
| `origin` | string | null | Filter: `blocklist` or `selfblock` |
|
||
|
||
**Response `200`**
|
||
```json
|
||
{
|
||
"items": [
|
||
{
|
||
"ip": "1.2.3.4",
|
||
"jail": "sshd",
|
||
"banned_at": "2024-12-25T08:00:00Z",
|
||
"expires_at": "2024-12-26T08:00:00Z",
|
||
"country": "US",
|
||
"asn": "AS15169",
|
||
"org": "Google LLC"
|
||
}
|
||
],
|
||
"total": 150,
|
||
"page": 1,
|
||
"page_size": 25
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### `GET /api/v1/dashboard/bans/by-country`
|
||
|
||
Ban counts aggregated by country.
|
||
|
||
**Query Parameters** — Same as `/bans` plus optional `country_code` filter.
|
||
|
||
**Response `200`**
|
||
```json
|
||
{
|
||
"countries": { "US": 45, "CN": 32, "BR": 18 },
|
||
"total": 150,
|
||
"items": [...]
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### `GET /api/v1/dashboard/bans/trend`
|
||
|
||
Ban counts grouped into time buckets for charts.
|
||
|
||
**Bucket sizes:**
|
||
- `24h` → 1-hour buckets (24 total)
|
||
- `7d` → 6-hour buckets (28 total)
|
||
- `30d` → 1-day buckets (30 total)
|
||
- `365d` → 7-day buckets (~53 total)
|
||
|
||
**Query Parameters** — Same as `/bans`.
|
||
|
||
**Response `200`**
|
||
```json
|
||
{
|
||
"buckets": [
|
||
{ "ts": "2024-12-24T00:00:00Z", "count": 12 },
|
||
{ "ts": "2024-12-24T01:00:00Z", "count": 8 }
|
||
],
|
||
"bucket_size": "1h",
|
||
"total": 150
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### `GET /api/v1/dashboard/bans/by-jail`
|
||
|
||
Ban counts grouped by jail.
|
||
|
||
**Query Parameters** — Same as `/bans`.
|
||
|
||
**Response `200`**
|
||
```json
|
||
{
|
||
"jails": [
|
||
{ "jail": "sshd", "count": 120 },
|
||
{ "jail": "nginx-http-auth", "count": 30 }
|
||
],
|
||
"total": 150
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Bans
|
||
|
||
### `GET /api/v1/bans/active`
|
||
|
||
List all currently banned IPs across all jails.
|
||
|
||
**Response `200`**
|
||
```json
|
||
{
|
||
"items": [
|
||
{
|
||
"ip": "1.2.3.4",
|
||
"jail": "sshd",
|
||
"banned_at": "2024-12-25T08:00:00Z",
|
||
"expires_at": "2024-12-26T08:00:00Z",
|
||
"country": "US"
|
||
}
|
||
],
|
||
"total": 42
|
||
}
|
||
```
|
||
|
||
**Response `401`** — Not authenticated.
|
||
|
||
**Response `502`** — fail2ban unreachable.
|
||
|
||
---
|
||
|
||
### `POST /api/v1/bans`
|
||
|
||
Ban an IP address in a specific jail.
|
||
|
||
**Request**
|
||
```json
|
||
{ "jail": "sshd", "ip": "5.6.7.8" }
|
||
```
|
||
|
||
**Response `201`**
|
||
```json
|
||
{ "message": "IP '5.6.7.8' banned in jail 'sshd'.", "jail": "sshd" }
|
||
```
|
||
|
||
**Response `400`** — Invalid IP address.
|
||
|
||
**Response `404`** — Jail not found.
|
||
|
||
**Response `409`** — Ban command failed in fail2ban.
|
||
|
||
**Response `429`** — Rate limit exceeded (10 ban requests/minute per IP).
|
||
|
||
**Response `502`** — fail2ban unreachable.
|
||
|
||
---
|
||
|
||
### `DELETE /api/v1/bans`
|
||
|
||
Unban an IP from one or all jails.
|
||
|
||
**Request**
|
||
```json
|
||
{ "ip": "5.6.7.8", "jail": "sshd", "unban_all": false }
|
||
```
|
||
|
||
| Field | Type | Required | Description |
|
||
|---|---|---|---|
|
||
| `ip` | string | Yes | IP address to unban |
|
||
| `jail` | string | No | Specific jail to unban from |
|
||
| `unban_all` | bool | No | `true` = unban from all jails (default: `false` if `jail` omitted) |
|
||
|
||
**Response `200`**
|
||
```json
|
||
{ "message": "IP '5.6.7.8' unbanned from jail 'sshd'.", "jail": "sshd" }
|
||
```
|
||
|
||
**Response `404`** — Jail not found.
|
||
|
||
**Response `429`** — Rate limit exceeded (10 unban requests/minute per IP).
|
||
|
||
---
|
||
|
||
### `DELETE /api/v1/bans/all`
|
||
|
||
Unban every currently banned IP across all jails.
|
||
|
||
**Response `200`**
|
||
```json
|
||
{ "message": "All bans cleared. 42 IP addresses unbanned.", "count": 42 }
|
||
```
|
||
|
||
---
|
||
|
||
## History
|
||
|
||
### `GET /api/v1/history`
|
||
|
||
Paginated historical ban records.
|
||
|
||
**Query Parameters**
|
||
|
||
| Param | Type | Default | Description |
|
||
|---|---|---|---|
|
||
| `range` | `TimeRange` | null | Time filter: `24h`, `7d`, `30d`, `365d` (null = all-time) |
|
||
| `jail` | string | null | Filter by jail name (exact match) |
|
||
| `ip` | string | null | Filter by IP prefix |
|
||
| `origin` | string | null | Filter: `blocklist` or `selfblock` |
|
||
| `source` | string | `fail2ban` | `fail2ban` or `archive` |
|
||
| `page` | int | `1` | 1-based page number |
|
||
| `page_size` | int | `25` | Items per page (max 500) |
|
||
|
||
**Response `200`**
|
||
```json
|
||
{
|
||
"items": [
|
||
{
|
||
"ip": "1.2.3.4",
|
||
"jail": "sshd",
|
||
"banned_at": "2024-12-25T08:00:00Z",
|
||
"unbanned_at": "2024-12-26T08:00:00Z",
|
||
"origin": "selfblock",
|
||
"country": "US"
|
||
}
|
||
],
|
||
"total": 500,
|
||
"page": 1,
|
||
"page_size": 25
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### `GET /api/v1/history/archive`
|
||
|
||
Same as `/history` but reads from the archive database.
|
||
|
||
**Query Parameters** — Same as `/history` (no `origin` filter).
|
||
|
||
---
|
||
|
||
### `GET /api/v1/history/{ip}`
|
||
|
||
Complete ban timeline for a single IP.
|
||
|
||
**Response `200`**
|
||
```json
|
||
{
|
||
"ip": "1.2.3.4",
|
||
"country": "US",
|
||
"total_bans": 5,
|
||
"timeline": [
|
||
{
|
||
"jail": "sshd",
|
||
"banned_at": "2024-12-25T08:00:00Z",
|
||
"unbanned_at": "2024-12-26T08:00:00Z",
|
||
"origin": "selfblock"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
**Response `404`** — No history found for this IP.
|
||
|
||
---
|
||
|
||
## Jails
|
||
|
||
### `GET /api/v1/jails`
|
||
|
||
List all active fail2ban jails.
|
||
|
||
**Response `200`**
|
||
```json
|
||
{
|
||
"items": [
|
||
{
|
||
"name": "sshd",
|
||
"enabled": true,
|
||
"currently_banned": 12,
|
||
"total_bans": 150,
|
||
"failed_attempts": 320,
|
||
"find_time": 600,
|
||
"ban_time": 86400,
|
||
"max_retries": 5,
|
||
"backend": "polling",
|
||
"idle": false
|
||
}
|
||
],
|
||
"total": 3
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### `GET /api/v1/jails/{name}`
|
||
|
||
Full detail for a single jail.
|
||
|
||
**Response `200`**
|
||
```json
|
||
{
|
||
"name": "sshd",
|
||
"enabled": true,
|
||
"log_paths": ["/var/log/auth.log"],
|
||
"fail_regex": ["^%(__prefix_line)sFailed publickey forInvalid user"],
|
||
"ignore_regex": [],
|
||
"date_pattern": null,
|
||
"log_encoding": "UTF-8",
|
||
"actions": ["iptables"],
|
||
"find_time": 600,
|
||
"ban_time": 86400,
|
||
"max_retries": 5,
|
||
"ignore_list": ["192.168.1.1"],
|
||
"ignore_self": true,
|
||
"currently_banned": 12,
|
||
"total_bans": 150,
|
||
"failed_attempts": 320
|
||
}
|
||
```
|
||
|
||
**Response `404`** — Jail not found.
|
||
|
||
---
|
||
|
||
### `POST /api/v1/jails/{name}/start`
|
||
|
||
Start a stopped jail.
|
||
|
||
**Response `200`**
|
||
```json
|
||
{ "message": "Jail 'sshd' started.", "jail": "sshd" }
|
||
```
|
||
|
||
---
|
||
|
||
### `POST /api/v1/jails/{name}/stop`
|
||
|
||
Stop a running jail.
|
||
|
||
**Response `200`**
|
||
```json
|
||
{ "message": "Jail 'sshd' stopped.", "jail": "sshd" }
|
||
```
|
||
|
||
---
|
||
|
||
### `POST /api/v1/jails/{name}/idle`
|
||
|
||
Toggle jail idle mode.
|
||
|
||
**Request body**
|
||
```json
|
||
{ "on": true }
|
||
```
|
||
|
||
**Response `200`**
|
||
```json
|
||
{ "message": "Jail 'sshd' idle mode turned on.", "jail": "sshd" }
|
||
```
|
||
|
||
---
|
||
|
||
### `POST /api/v1/jails/{name}/reload`
|
||
|
||
Reload a single jail.
|
||
|
||
**Response `200`**
|
||
```json
|
||
{ "message": "Jail 'sshd' reloaded.", "jail": "sshd" }
|
||
```
|
||
|
||
---
|
||
|
||
### `POST /api/v1/jails/reload-all`
|
||
|
||
Reload all fail2ban jails.
|
||
|
||
**Response `200`**
|
||
```json
|
||
{ "message": "All jails reloaded successfully.", "jail": "*" }
|
||
```
|
||
|
||
---
|
||
|
||
### `GET /api/v1/jails/{name}/ignoreip`
|
||
|
||
Get the ignore (whitelist) list for a jail.
|
||
|
||
**Response `200`**
|
||
```json
|
||
{ "items": ["192.168.1.0/24", "10.0.0.1"], "total": 2 }
|
||
```
|
||
|
||
---
|
||
|
||
### `POST /api/v1/jails/{name}/ignoreip`
|
||
|
||
Add an IP or CIDR to the ignore list.
|
||
|
||
**Request**
|
||
```json
|
||
{ "ip": "192.168.1.100" }
|
||
```
|
||
|
||
**Response `201`**
|
||
```json
|
||
{ "message": "IP '192.168.1.100' added to ignore list of jail 'sshd'.", "jail": "sshd" }
|
||
```
|
||
|
||
**Response `400`** — Invalid IP or network.
|
||
|
||
---
|
||
|
||
### `DELETE /api/v1/jails/{name}/ignoreip`
|
||
|
||
Remove an IP or CIDR from the ignore list.
|
||
|
||
**Request**
|
||
```json
|
||
{ "ip": "192.168.1.100" }
|
||
```
|
||
|
||
**Response `200`**
|
||
```json
|
||
{ "message": "IP '192.168.1.100' removed from ignore list of jail 'sshd'.", "jail": "sshd" }
|
||
```
|
||
|
||
---
|
||
|
||
### `POST /api/v1/jails/{name}/ignoreself`
|
||
|
||
Toggle the `ignoreself` option (ban server's own IP).
|
||
|
||
**Request**
|
||
```json
|
||
{ "on": true }
|
||
```
|
||
|
||
**Response `200`**
|
||
```json
|
||
{ "message": "ignoreself enabled for jail 'sshd'.", "jail": "sshd" }
|
||
```
|
||
|
||
---
|
||
|
||
### `GET /api/v1/jails/{name}/banned`
|
||
|
||
Paginated currently-banned IPs for a specific jail.
|
||
|
||
**Query Parameters**
|
||
|
||
| Param | Type | Default | Description |
|
||
|---|---|---|---|
|
||
| `page` | int | `1` | 1-based page number |
|
||
| `page_size` | int | `25` | Items per page (max 100) |
|
||
| `search` | string | null | Case-insensitive substring filter on IP |
|
||
|
||
**Response `200`**
|
||
```json
|
||
{
|
||
"items": [
|
||
{
|
||
"ip": "1.2.3.4",
|
||
"banned_at": "2024-12-25T08:00:00Z",
|
||
"expires_at": "2024-12-26T08:00:00Z",
|
||
"country": "US",
|
||
"asn": "AS15169",
|
||
"org": "Google LLC"
|
||
}
|
||
],
|
||
"total": 12,
|
||
"page": 1,
|
||
"page_size": 25
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Config
|
||
|
||
### `GET /api/v1/config/global`
|
||
|
||
Get global fail2ban settings.
|
||
|
||
**Response `200`**
|
||
```json
|
||
{
|
||
"loglevel": "INFO",
|
||
"logtarget": "/var/log/fail2ban.log",
|
||
"syslog_socket": "auto",
|
||
"db_file": "/var/lib/fail2ban/fail2ban.sqlite3",
|
||
"db_purge_age": 86400
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### `PUT /api/v1/config/global`
|
||
|
||
Update global fail2ban settings.
|
||
|
||
**Request** — All fields optional (only non-null fields written):
|
||
```json
|
||
{
|
||
"loglevel": "DEBUG",
|
||
"logtarget": "/var/log/fail2ban.log",
|
||
"db_purge_age": 604800
|
||
}
|
||
```
|
||
|
||
| Field | Type | Description |
|
||
|---|---|---|
|
||
| `loglevel` | string | `CRITICAL`, `ERROR`, `WARNING`, `NOTICE`, `INFO`, `DEBUG` |
|
||
| `logtarget` | string | `STDOUT`, `STDERR`, `SYSLOG`, or a file path |
|
||
| `db_purge_age` | int | Seconds before old ban records are purged |
|
||
|
||
**Response `204`** — Updated.
|
||
|
||
**Response `400`** — `logtarget` invalid (not in allowed directories).
|
||
|
||
**Response `429`** — Rate limit exceeded (10 updates/minute per IP).
|
||
|
||
---
|
||
|
||
### `POST /api/v1/config/reload`
|
||
|
||
Trigger a full fail2ban reload.
|
||
|
||
**Response `204`**
|
||
|
||
---
|
||
|
||
### `POST /api/v1/config/restart`
|
||
|
||
Restart the fail2ban service (stop + start).
|
||
|
||
**Response `204`**
|
||
|
||
**Response `503`** — fail2ban did not come back online within 10 seconds.
|
||
|
||
---
|
||
|
||
### `POST /api/v1/config/regex-test`
|
||
|
||
Test a fail regex pattern against a sample log line (stateless, no fail2ban call).
|
||
|
||
**Request**
|
||
```json
|
||
{ "pattern": "^%(__prefix_line)sFailed publickey", "sample": "Dec 25 08:00:01 server sshd[123]: Failed publickey for user admin from 1.2.3.4" }
|
||
```
|
||
|
||
**Response `200`**
|
||
```json
|
||
{ "matched": true, "groups": ["Dec 25 08:00:01", "server", "123", "admin", "1.2.3.4"] }
|
||
```
|
||
|
||
---
|
||
|
||
### `POST /api/v1/config/preview-log`
|
||
|
||
Read a log file and test a regex against each line.
|
||
|
||
**Request**
|
||
```json
|
||
{
|
||
"path": "/var/log/auth.log",
|
||
"pattern": "^Failed publickey",
|
||
"lines": 50
|
||
}
|
||
```
|
||
|
||
**Response `200`**
|
||
```json
|
||
{
|
||
"lines": [
|
||
{ "line": "Dec 25 08:00:01 server sshd[123]: Failed publickey...", "matched": true, "groups": [...] },
|
||
{ "line": "Dec 25 08:00:02 server sshd[456]: Accepted publickey...", "matched": false }
|
||
]
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### `GET /api/v1/config/map-color-thresholds`
|
||
|
||
Get map color threshold configuration.
|
||
|
||
**Response `200`**
|
||
```json
|
||
{
|
||
"thresholds": [
|
||
{ "count": 0, "color": "#4ade80" },
|
||
{ "count": 10, "color": "#facc15" },
|
||
{ "count": 50, "color": "#f97316" },
|
||
{ "count": 200, "color": "#ef4444" }
|
||
]
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### `PUT /api/v1/config/map-color-thresholds`
|
||
|
||
Update map color thresholds.
|
||
|
||
**Request**
|
||
```json
|
||
{
|
||
"thresholds": [
|
||
{ "count": 0, "color": "#4ade80" },
|
||
{ "count": 100, "color": "#facc15" }
|
||
]
|
||
}
|
||
```
|
||
|
||
> Thresholds must be strictly ascending by `count`.
|
||
|
||
**Response `200`** — Updated thresholds.
|
||
|
||
**Response `400`** — Thresholds not properly ordered.
|
||
|
||
---
|
||
|
||
### `GET /api/v1/config/fail2ban-log`
|
||
|
||
Read the tail of the fail2ban daemon log file.
|
||
|
||
**Query Parameters**
|
||
|
||
| Param | Type | Default | Description |
|
||
|---|---|---|---|
|
||
| `lines` | int | `200` | Number of tail lines (1–2000) |
|
||
| `filter` | string | null | Plain-text substring filter |
|
||
|
||
**Response `200`**
|
||
```json
|
||
{
|
||
"lines": ["2024-12-25 08:00:01,000 INFO ...", "2024-12-25 08:00:02,000 WARNING ..."],
|
||
"count": 2
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### `GET /api/v1/config/service-status`
|
||
|
||
Fail2ban service health with log configuration.
|
||
|
||
**Response `200`**
|
||
```json
|
||
{
|
||
"online": true,
|
||
"version": "0.12.1",
|
||
"loglevel": "INFO",
|
||
"logtarget": "/var/log/fail2ban.log"
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Filters
|
||
|
||
### `GET /api/v1/config/filters`
|
||
|
||
List all available filters with active/inactive status.
|
||
|
||
**Response `200`**
|
||
```json
|
||
{
|
||
"items": [
|
||
{
|
||
"name": "sshd",
|
||
"active": true,
|
||
"used_by_jails": ["sshd"],
|
||
"source_file": "/etc/fail2ban/filter.d/sshd.conf",
|
||
"has_local_override": false,
|
||
"failregex": ["^%(__prefix_line)sFailed publickey"],
|
||
"ignoreregex": [],
|
||
"date_pattern": null,
|
||
"journalmatch": null
|
||
}
|
||
],
|
||
"total": 12
|
||
}
|
||
```
|
||
|
||
Active filters (used by running jails) are listed first, sorted alphabetically. Inactive filters follow.
|
||
|
||
---
|
||
|
||
### `GET /api/v1/config/filters/{name}`
|
||
|
||
Full detail for a single filter.
|
||
|
||
**Response `200`** — FilterConfig object (same shape as list item).
|
||
|
||
**Response `404`** — Filter not found in `filter.d/`.
|
||
|
||
---
|
||
|
||
### `POST /api/v1/config/filters`
|
||
|
||
Create a new user-defined filter.
|
||
|
||
**Request**
|
||
```json
|
||
{
|
||
"name": "nginx-404",
|
||
"failregex": ["^\\s*\\S+ \\S+ \\S+ GET /nonexistent"],
|
||
"ignoreregex": null,
|
||
"date_pattern": null,
|
||
"journalmatch": null
|
||
}
|
||
```
|
||
|
||
**Response `201`** — Created FilterConfig object.
|
||
|
||
**Response `409`** — Filter with this name already exists.
|
||
|
||
**Response `422`** — Regex failed to compile.
|
||
|
||
**Response `429`** — Rate limit exceeded (5 creates/minute per IP).
|
||
|
||
---
|
||
|
||
### `PUT /api/v1/config/filters/{name}`
|
||
|
||
Update a filter's `.local` override. Only non-null fields are written.
|
||
|
||
**Request**
|
||
```json
|
||
{
|
||
"failregex": ["^new pattern here"],
|
||
"ignoreregex": null
|
||
}
|
||
```
|
||
|
||
**Query Parameter** — `reload` (bool, default `false`) — trigger fail2ban reload after writing.
|
||
|
||
**Response `200`** — Updated FilterConfig object.
|
||
|
||
**Response `422`** — Regex failed to compile.
|
||
|
||
**Response `429`** — Rate limit exceeded (10 updates/minute per IP).
|
||
|
||
---
|
||
|
||
### `DELETE /api/v1/config/filters/{name}`
|
||
|
||
Delete a user-created filter's `.local` file. Shipped `.conf`-only filters cannot be deleted.
|
||
|
||
**Response `204`**
|
||
|
||
**Response `409`** — Filter is a shipped default (conf-only).
|
||
|
||
---
|
||
|
||
## Actions
|
||
|
||
### `GET /api/v1/config/actions`
|
||
|
||
List all available actions with active/inactive status.
|
||
|
||
**Response `200`**
|
||
```json
|
||
{
|
||
"actions": [
|
||
{
|
||
"name": "iptables",
|
||
"active": true,
|
||
"used_by_jails": ["sshd", "nginx-http-auth"],
|
||
"source_file": "/etc/fail2ban/action.d/iptables.conf",
|
||
"has_local_override": false,
|
||
"start_command": "iptables -N f2b-sshd...",
|
||
"stop_command": "iptables -X f2b-sshd...",
|
||
"check_command": "iptables -L f2b-sshd -n",
|
||
"ban_action": "iptables -I f2b-sshd...",
|
||
"unban_action": "iptables -D f2b-sshd..."
|
||
}
|
||
],
|
||
"total": 8
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### `GET /api/v1/config/actions/{name}`
|
||
|
||
Full detail for a single action.
|
||
|
||
**Response `200`** — ActionConfig object (same shape as list item).
|
||
|
||
---
|
||
|
||
### `POST /api/v1/config/actions`
|
||
|
||
Create a new user-defined action.
|
||
|
||
**Request**
|
||
```json
|
||
{
|
||
"name": "my-custom-action",
|
||
"start_command": "echo 'starting'",
|
||
"stop_command": "echo 'stopping'",
|
||
"check_command": "echo 'checking'",
|
||
"ban_action": "echo 'banning'",
|
||
"unban_action": "echo 'unbanning'"
|
||
}
|
||
```
|
||
|
||
**Response `201`** — Created ActionConfig object.
|
||
|
||
**Response `409`** — Action with this name already exists.
|
||
|
||
---
|
||
|
||
### `PUT /api/v1/config/actions/{name}`
|
||
|
||
Update an action's `.local` override.
|
||
|
||
**Request** — All fields optional:
|
||
```json
|
||
{ "ban_action": "new ban command here" }
|
||
```
|
||
|
||
**Query Parameter** — `reload` (bool, default `false`).
|
||
|
||
**Response `200`** — Updated ActionConfig object.
|
||
|
||
---
|
||
|
||
### `DELETE /api/v1/config/actions/{name}`
|
||
|
||
Delete a user-created action's `.local` file.
|
||
|
||
**Response `204`**
|
||
|
||
**Response `409`** — Action is a shipped default (conf-only).
|
||
|
||
---
|
||
|
||
## Geo
|
||
|
||
### `GET /api/v1/geo/lookup/{ip}`
|
||
|
||
Ban status and geo info for an IP.
|
||
|
||
**Response `200`**
|
||
```json
|
||
{
|
||
"ip": "1.2.3.4",
|
||
"banned": true,
|
||
"jails": ["sshd", "nginx-http-auth"],
|
||
"country": "US",
|
||
"country_name": "United States",
|
||
"region": "North America",
|
||
"city": "Mountain View",
|
||
"isp": "Google LLC",
|
||
"asn": "AS15169",
|
||
"org": "Google LLC",
|
||
"last_ban": "2024-12-25T08:00:00Z",
|
||
"total_bans": 3
|
||
}
|
||
```
|
||
|
||
**Response `400`** — Invalid IP address.
|
||
|
||
---
|
||
|
||
### `GET /api/v1/geo/stats`
|
||
|
||
Geo cache diagnostic counters.
|
||
|
||
**Response `200`**
|
||
```json
|
||
{
|
||
"total": 1500,
|
||
"resolved": 1480,
|
||
"failed": 20,
|
||
"cache_size": 1480
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### `POST /api/v1/geo/re-resolve`
|
||
|
||
Re-resolve all IPs with failed geo lookups.
|
||
|
||
**Response `200`**
|
||
```json
|
||
{
|
||
"total": 20,
|
||
"resolved": 18,
|
||
"failed": 2
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Blocklists
|
||
|
||
### `GET /api/v1/blocklists`
|
||
|
||
List all blocklist sources.
|
||
|
||
**Response `200`**
|
||
```json
|
||
{
|
||
"sources": [
|
||
{
|
||
"id": 1,
|
||
"name": "Country Block List",
|
||
"url": "https://example.com/blocklist.txt",
|
||
"enabled": true,
|
||
"last_import_at": "2024-12-25T08:00:00Z",
|
||
"last_import_succeeded": true,
|
||
"last_import_ban_count": 45
|
||
}
|
||
],
|
||
"total": 1
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### `POST /api/v1/blocklists`
|
||
|
||
Add a new blocklist source.
|
||
|
||
**Request**
|
||
```json
|
||
{ "name": "Spamhaus DROP", "url": "https://www.spamhaus.org/drop/drop.txt", "enabled": true }
|
||
```
|
||
|
||
**Response `201`** — Created BlocklistSource object.
|
||
|
||
**Response `400`** — URL validation failed.
|
||
|
||
---
|
||
|
||
### `GET /api/v1/blocklists/{source_id}`
|
||
|
||
Get a single blocklist source.
|
||
|
||
---
|
||
|
||
### `PUT /api/v1/blocklists/{source_id}`
|
||
|
||
Update a blocklist source.
|
||
|
||
**Request** — All fields optional:
|
||
```json
|
||
{ "name": "New Name", "enabled": false }
|
||
```
|
||
|
||
---
|
||
|
||
### `DELETE /api/v1/blocklists/{source_id}`
|
||
|
||
Delete a blocklist source.
|
||
|
||
**Response `204`**
|
||
|
||
---
|
||
|
||
### `POST /api/v1/blocklists/import`
|
||
|
||
Trigger an immediate import of all enabled blocklist sources.
|
||
|
||
**Response `200`**
|
||
```json
|
||
{
|
||
"started_at": "2024-12-25T10:00:00Z",
|
||
"sources": [
|
||
{
|
||
"id": 1,
|
||
"name": "Spamhaus DROP",
|
||
"url": "https://www.spamhaus.org/drop/drop.txt",
|
||
"imported": 45,
|
||
"skipped": 3,
|
||
"failed": false,
|
||
"error": null
|
||
}
|
||
],
|
||
"total_imported": 45,
|
||
"total_skipped": 3,
|
||
"total_failed": 0
|
||
}
|
||
```
|
||
|
||
**Response `429`** — Rate limit exceeded (1 import/hour per IP).
|
||
|
||
---
|
||
|
||
### `GET /api/v1/blocklists/schedule`
|
||
|
||
Get the current import schedule.
|
||
|
||
**Response `200`**
|
||
```json
|
||
{
|
||
"enabled": true,
|
||
"interval_hours": 24,
|
||
"next_run_at": "2024-12-26T08:00:00Z"
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### `PUT /api/v1/blocklists/schedule`
|
||
|
||
Update the import schedule.
|
||
|
||
**Request**
|
||
```json
|
||
{ "enabled": true, "interval_hours": 12 }
|
||
```
|
||
|
||
**Response `200`** — Updated ScheduleInfo.
|
||
|
||
---
|
||
|
||
### `GET /api/v1/blocklists/log`
|
||
|
||
Paginated import log.
|
||
|
||
**Query Parameters**
|
||
|
||
| Param | Type | Default | Description |
|
||
|---|---|---|---|
|
||
| `source_id` | int | null | Filter by source |
|
||
| `page` | int | `1` | 1-based page |
|
||
| `page_size` | int | `25` | Items per page (max 500) |
|
||
|
||
**Response `200`**
|
||
```json
|
||
{
|
||
"items": [
|
||
{
|
||
"id": 1,
|
||
"source_id": 1,
|
||
"source_name": "Spamhaus DROP",
|
||
"started_at": "2024-12-25T08:00:00Z",
|
||
"completed_at": "2024-12-25T08:01:23Z",
|
||
"imported": 45,
|
||
"skipped": 3,
|
||
"failed": false,
|
||
"error": null
|
||
}
|
||
],
|
||
"total": 50,
|
||
"page": 1,
|
||
"page_size": 25
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### `GET /api/v1/blocklists/{source_id}/preview`
|
||
|
||
Preview the contents of a blocklist source (downloads and samples first ~20 lines).
|
||
|
||
**Response `200`**
|
||
```json
|
||
{
|
||
"url": "https://example.com/blocklist.txt",
|
||
"validated_lines": ["1.2.3.4", "5.6.7.8"],
|
||
"invalid_lines": ["not-an-ip"],
|
||
"total_valid": 2,
|
||
"total_invalid": 1
|
||
}
|
||
```
|
||
|
||
**Response `502`** — URL could not be reached.
|
||
|
||
---
|
||
|
||
## Server
|
||
|
||
### `GET /api/v1/server/settings`
|
||
|
||
Get fail2ban server-level settings.
|
||
|
||
**Response `200`**
|
||
```json
|
||
{
|
||
"loglevel": "INFO",
|
||
"logtarget": "/var/log/fail2ban.log",
|
||
"syslog_socket": "auto",
|
||
"db_file": "/var/lib/fail2ban/fail2ban.sqlite3",
|
||
"db_purge_age": 86400,
|
||
"max_matches": 100
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### `PUT /api/v1/server/settings`
|
||
|
||
Update fail2ban server-level settings.
|
||
|
||
**Request** — All fields optional:
|
||
```json
|
||
{ "loglevel": "DEBUG", "max_matches": 200 }
|
||
```
|
||
|
||
**Response `204`**
|
||
|
||
**Response `400`** — fail2ban rejected a setting.
|
||
|
||
---
|
||
|
||
### `POST /api/v1/server/flush-logs`
|
||
|
||
Flush and re-open fail2ban log files (after log rotation).
|
||
|
||
**Response `200`**
|
||
```json
|
||
{ "message": "Success: 1 log(s) flushed" }
|
||
```
|
||
|
||
---
|
||
|
||
## Common Types
|
||
|
||
### `TimeRange`
|
||
|
||
```
|
||
"24h" | "7d" | "30d" | "365d"
|
||
```
|
||
|
||
### `BanOrigin`
|
||
|
||
```
|
||
"blocklist" | "selfblock"
|
||
```
|
||
|
||
### `Source`
|
||
|
||
```
|
||
"fail2ban" | "archive"
|
||
```
|
||
|
||
---
|
||
|
||
## Status Codes
|
||
|
||
| Code | Meaning |
|
||
|---|---|
|
||
| `200` | OK |
|
||
| `201` | Created |
|
||
| `204` | No Content |
|
||
| `400` | Bad Request — invalid input |
|
||
| `401` | Unauthorized — session missing, expired, or invalid |
|
||
| `404` | Not Found |
|
||
| `409` | Conflict |
|
||
| `422` | Unprocessable Entity — validation failed |
|
||
| `429` | Too Many Requests — rate limit exceeded |
|
||
| `502` | Bad Gateway — fail2ban unreachable |
|
||
| `503` | Service Unavailable |
|