Fix blocklist-import bantime, unify filter bar, and improve config navigation
This commit is contained in:
@@ -23,7 +23,7 @@ if TYPE_CHECKING:
|
||||
from fastapi import APIRouter, HTTPException, Query, Request
|
||||
|
||||
from app.dependencies import AuthDep
|
||||
from app.models.ban import TimeRange
|
||||
from app.models.ban import BanOrigin, TimeRange
|
||||
from app.models.history import HistoryListResponse, IpDetailResponse
|
||||
from app.services import geo_service, history_service
|
||||
|
||||
@@ -52,6 +52,10 @@ async def get_history(
|
||||
default=None,
|
||||
description="Restrict results to IPs matching this prefix.",
|
||||
),
|
||||
origin: BanOrigin | None = Query(
|
||||
default=None,
|
||||
description="Filter by ban origin: 'blocklist' or 'selfblock'. Omit for all.",
|
||||
),
|
||||
page: int = Query(default=1, ge=1, description="1-based page number."),
|
||||
page_size: int = Query(
|
||||
default=_DEFAULT_PAGE_SIZE,
|
||||
@@ -89,6 +93,7 @@ async def get_history(
|
||||
range_=range,
|
||||
jail=jail,
|
||||
ip_filter=ip,
|
||||
origin=origin,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
geo_enricher=_enricher,
|
||||
|
||||
@@ -368,8 +368,9 @@ async def update_jail_config(
|
||||
await _set("datepattern", update.date_pattern)
|
||||
if update.dns_mode is not None:
|
||||
await _set("usedns", update.dns_mode)
|
||||
if update.backend is not None:
|
||||
await _set("backend", update.backend)
|
||||
# Fail2ban does not support changing the log monitoring backend at runtime.
|
||||
# The configuration value is retained for read/display purposes but must not
|
||||
# be applied via the socket API.
|
||||
if update.log_encoding is not None:
|
||||
await _set("logencoding", update.log_encoding)
|
||||
if update.prefregex is not None:
|
||||
|
||||
@@ -16,7 +16,7 @@ from typing import Any
|
||||
import aiosqlite
|
||||
import structlog
|
||||
|
||||
from app.models.ban import TIME_RANGE_SECONDS, TimeRange
|
||||
from app.models.ban import BLOCKLIST_JAIL, BanOrigin, TIME_RANGE_SECONDS, TimeRange
|
||||
from app.models.history import (
|
||||
HistoryBanItem,
|
||||
HistoryListResponse,
|
||||
@@ -58,6 +58,7 @@ async def list_history(
|
||||
*,
|
||||
range_: TimeRange | None = None,
|
||||
jail: str | None = None,
|
||||
origin: BanOrigin | None = None,
|
||||
ip_filter: str | None = None,
|
||||
page: int = 1,
|
||||
page_size: int = _DEFAULT_PAGE_SIZE,
|
||||
@@ -73,6 +74,8 @@ async def list_history(
|
||||
socket_path: Path to the fail2ban Unix domain socket.
|
||||
range_: Time-range preset. ``None`` means all-time (no time filter).
|
||||
jail: If given, restrict results to bans from this jail.
|
||||
origin: Optional origin filter — ``"blocklist"`` restricts results to
|
||||
the ``blocklist-import`` jail, ``"selfblock"`` excludes it.
|
||||
ip_filter: If given, restrict results to bans for this exact IP
|
||||
(or a prefix — the query uses ``LIKE ip_filter%``).
|
||||
page: 1-based page number (default: ``1``).
|
||||
@@ -99,6 +102,14 @@ async def list_history(
|
||||
wheres.append("jail = ?")
|
||||
params.append(jail)
|
||||
|
||||
if origin is not None:
|
||||
if origin == "blocklist":
|
||||
wheres.append("jail = ?")
|
||||
params.append(BLOCKLIST_JAIL)
|
||||
elif origin == "selfblock":
|
||||
wheres.append("jail != ?")
|
||||
params.append(BLOCKLIST_JAIL)
|
||||
|
||||
if ip_filter is not None:
|
||||
wheres.append("ip LIKE ?")
|
||||
params.append(f"{ip_filter}%")
|
||||
|
||||
@@ -49,7 +49,7 @@ logpath = /dev/null
|
||||
backend = auto
|
||||
maxretry = 1
|
||||
findtime = 1d
|
||||
bantime = 1w
|
||||
bantime = 86400
|
||||
ignoreip = 127.0.0.0/8 ::1 172.16.0.0/12
|
||||
"""
|
||||
|
||||
|
||||
@@ -213,6 +213,18 @@ class TestHistoryList:
|
||||
_args, kwargs = mock_fn.call_args
|
||||
assert kwargs.get("range_") == "7d"
|
||||
|
||||
async def test_forwards_origin_filter(self, history_client: AsyncClient) -> None:
|
||||
"""The ``origin`` query parameter is forwarded to the service."""
|
||||
mock_fn = AsyncMock(return_value=_make_history_list(n=0))
|
||||
with patch(
|
||||
"app.routers.history.history_service.list_history",
|
||||
new=mock_fn,
|
||||
):
|
||||
await history_client.get("/api/history?origin=blocklist")
|
||||
|
||||
_args, kwargs = mock_fn.call_args
|
||||
assert kwargs.get("origin") == "blocklist"
|
||||
|
||||
async def test_empty_result(self, history_client: AsyncClient) -> None:
|
||||
"""An empty history returns items=[] and total=0."""
|
||||
with patch(
|
||||
|
||||
@@ -256,6 +256,27 @@ class TestUpdateJailConfig:
|
||||
assert "bantime" in keys
|
||||
assert "maxretry" in keys
|
||||
|
||||
async def test_ignores_backend_field(self) -> None:
|
||||
"""update_jail_config does not send a set command for backend."""
|
||||
sent_commands: list[list[Any]] = []
|
||||
|
||||
async def _send(command: list[Any]) -> Any:
|
||||
sent_commands.append(command)
|
||||
return (0, "OK")
|
||||
|
||||
class _FakeClient:
|
||||
def __init__(self, **_kw: Any) -> None:
|
||||
self.send = AsyncMock(side_effect=_send)
|
||||
|
||||
from app.models.config import JailConfigUpdate
|
||||
|
||||
update = JailConfigUpdate(backend="polling")
|
||||
with patch("app.services.config_service.Fail2BanClient", _FakeClient):
|
||||
await config_service.update_jail_config(_SOCKET, "sshd", update)
|
||||
|
||||
keys = [cmd[2] for cmd in sent_commands if len(cmd) >= 3 and cmd[0] == "set"]
|
||||
assert "backend" not in keys
|
||||
|
||||
async def test_raises_validation_error_on_bad_regex(self) -> None:
|
||||
"""update_jail_config raises ConfigValidationError for invalid regex."""
|
||||
from app.models.config import JailConfigUpdate
|
||||
|
||||
@@ -65,6 +65,10 @@ class TestEnsureJailConfigs:
|
||||
content = _read(jail_d, conf_file)
|
||||
assert "enabled = false" in content
|
||||
|
||||
# Blocklist-import jail must have a 24-hour ban time
|
||||
blocklist_conf = _read(jail_d, _BLOCKLIST_CONF)
|
||||
assert "bantime = 86400" in blocklist_conf
|
||||
|
||||
# .local files must set enabled = true and nothing else
|
||||
for local_file in (_MANUAL_LOCAL, _BLOCKLIST_LOCAL):
|
||||
content = _read(jail_d, local_file)
|
||||
|
||||
Reference in New Issue
Block a user