Move conffile_parser from services to utils
This commit is contained in:
@@ -10,18 +10,50 @@ HTTP/FastAPI concerns.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
from typing import cast, TypeAlias
|
||||
|
||||
import structlog
|
||||
|
||||
from app.models.server import ServerSettings, ServerSettingsResponse, ServerSettingsUpdate
|
||||
from app.utils.fail2ban_client import Fail2BanClient
|
||||
from app.utils.fail2ban_client import Fail2BanClient, Fail2BanCommand, Fail2BanResponse
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Types
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Fail2BanSettingValue: TypeAlias = str | int | bool
|
||||
"""Allowed values for server settings commands."""
|
||||
|
||||
log: structlog.stdlib.BoundLogger = structlog.get_logger()
|
||||
|
||||
_SOCKET_TIMEOUT: float = 10.0
|
||||
|
||||
|
||||
def _to_int(value: object | None, default: int) -> int:
|
||||
"""Convert a raw value to an int, falling back to a default.
|
||||
|
||||
The fail2ban control socket can return either int or str values for some
|
||||
settings, so we normalise them here in a type-safe way.
|
||||
"""
|
||||
if isinstance(value, int):
|
||||
return value
|
||||
if isinstance(value, float):
|
||||
return int(value)
|
||||
if isinstance(value, str):
|
||||
try:
|
||||
return int(value)
|
||||
except ValueError:
|
||||
return default
|
||||
return default
|
||||
|
||||
|
||||
def _to_str(value: object | None, default: str) -> str:
|
||||
"""Convert a raw value to a string, falling back to a default."""
|
||||
if value is None:
|
||||
return default
|
||||
return str(value)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Custom exceptions
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -36,7 +68,7 @@ class ServerOperationError(Exception):
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _ok(response: Any) -> Any:
|
||||
def _ok(response: Fail2BanResponse) -> object:
|
||||
"""Extract payload from a fail2ban ``(code, data)`` response.
|
||||
|
||||
Args:
|
||||
@@ -59,9 +91,9 @@ def _ok(response: Any) -> Any:
|
||||
|
||||
async def _safe_get(
|
||||
client: Fail2BanClient,
|
||||
command: list[Any],
|
||||
default: Any = None,
|
||||
) -> Any:
|
||||
command: Fail2BanCommand,
|
||||
default: object | None = None,
|
||||
) -> object | None:
|
||||
"""Send a command and silently return *default* on any error.
|
||||
|
||||
Args:
|
||||
@@ -73,7 +105,8 @@ async def _safe_get(
|
||||
The successful response, or *default*.
|
||||
"""
|
||||
try:
|
||||
return _ok(await client.send(command))
|
||||
response = await client.send(command)
|
||||
return _ok(cast(Fail2BanResponse, response))
|
||||
except Exception:
|
||||
return default
|
||||
|
||||
@@ -118,13 +151,20 @@ async def get_settings(socket_path: str) -> ServerSettingsResponse:
|
||||
_safe_get(client, ["get", "dbmaxmatches"], 10),
|
||||
)
|
||||
|
||||
log_level = _to_str(log_level_raw, "INFO").upper()
|
||||
log_target = _to_str(log_target_raw, "STDOUT")
|
||||
syslog_socket = _to_str(syslog_socket_raw, "") or None
|
||||
db_path = _to_str(db_path_raw, "/var/lib/fail2ban/fail2ban.sqlite3")
|
||||
db_purge_age = _to_int(db_purge_age_raw, 86400)
|
||||
db_max_matches = _to_int(db_max_matches_raw, 10)
|
||||
|
||||
settings = ServerSettings(
|
||||
log_level=str(log_level_raw or "INFO").upper(),
|
||||
log_target=str(log_target_raw or "STDOUT"),
|
||||
syslog_socket=str(syslog_socket_raw) if syslog_socket_raw else None,
|
||||
db_path=str(db_path_raw or "/var/lib/fail2ban/fail2ban.sqlite3"),
|
||||
db_purge_age=int(db_purge_age_raw or 86400),
|
||||
db_max_matches=int(db_max_matches_raw or 10),
|
||||
log_level=log_level,
|
||||
log_target=log_target,
|
||||
syslog_socket=syslog_socket,
|
||||
db_path=db_path,
|
||||
db_purge_age=db_purge_age,
|
||||
db_max_matches=db_max_matches,
|
||||
)
|
||||
|
||||
log.info("server_settings_fetched")
|
||||
@@ -146,9 +186,10 @@ async def update_settings(socket_path: str, update: ServerSettingsUpdate) -> Non
|
||||
"""
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=_SOCKET_TIMEOUT)
|
||||
|
||||
async def _set(key: str, value: Any) -> None:
|
||||
async def _set(key: str, value: Fail2BanSettingValue) -> None:
|
||||
try:
|
||||
_ok(await client.send(["set", key, value]))
|
||||
response = await client.send(["set", key, value])
|
||||
_ok(cast(Fail2BanResponse, response))
|
||||
except ValueError as exc:
|
||||
raise ServerOperationError(f"Failed to set {key!r} = {value!r}: {exc}") from exc
|
||||
|
||||
@@ -182,7 +223,8 @@ async def flush_logs(socket_path: str) -> str:
|
||||
"""
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=_SOCKET_TIMEOUT)
|
||||
try:
|
||||
result = _ok(await client.send(["flushlogs"]))
|
||||
response = await client.send(["flushlogs"])
|
||||
result = _ok(cast(Fail2BanResponse, response))
|
||||
log.info("logs_flushed", result=result)
|
||||
return str(result)
|
||||
except ValueError as exc:
|
||||
|
||||
Reference in New Issue
Block a user