Refactor backend services and utilities
- Update service layer implementations - Improve configuration handling utilities - Update documentation tasks Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -65,8 +65,6 @@ async def _get_active_jail_names(socket_path: str) -> set[str]:
|
||||
# Constants
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_SOCKET_TIMEOUT: float = 10.0
|
||||
|
||||
# Allowlist pattern for action names used in path construction.
|
||||
_SAFE_ACTION_NAME_RE: re.Pattern[str] = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$")
|
||||
|
||||
|
||||
@@ -43,7 +43,11 @@ from app.models.ban import (
|
||||
from app.repositories import fail2ban_db_repo
|
||||
from app.repositories import history_archive_repo as default_history_archive_repo
|
||||
from app.services.fail2ban_metadata_service import default_fail2ban_metadata_service
|
||||
from app.utils.constants import DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE
|
||||
from app.utils.constants import (
|
||||
DEFAULT_PAGE_SIZE,
|
||||
FAIL2BAN_SOCKET_TIMEOUT,
|
||||
MAX_PAGE_SIZE,
|
||||
)
|
||||
from app.utils.fail2ban_client import (
|
||||
Fail2BanClient,
|
||||
)
|
||||
@@ -73,10 +77,6 @@ async def get_fail2ban_db_path(socket_path: str) -> str:
|
||||
# Constants
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_SOCKET_TIMEOUT: float = 5.0
|
||||
|
||||
|
||||
|
||||
|
||||
async def ban_ip(socket_path: str, jail: str, ip: str) -> None:
|
||||
"""Ban an IP address in the specified jail."""
|
||||
@@ -85,7 +85,7 @@ async def ban_ip(socket_path: str, jail: str, ip: str) -> None:
|
||||
except ValueError as exc:
|
||||
raise ValueError(f"Invalid IP address: {ip!r}") from exc
|
||||
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=_SOCKET_TIMEOUT)
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=FAIL2BAN_SOCKET_TIMEOUT)
|
||||
|
||||
try:
|
||||
ok(await client.send(["set", jail, "banip", ip]))
|
||||
@@ -102,7 +102,7 @@ async def unban_ip(socket_path: str, ip: str, jail: str | None = None) -> None:
|
||||
except ValueError as exc:
|
||||
raise ValueError(f"Invalid IP address: {ip!r}") from exc
|
||||
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=_SOCKET_TIMEOUT)
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=FAIL2BAN_SOCKET_TIMEOUT)
|
||||
|
||||
if jail is None:
|
||||
ok(await client.send(["unban", ip]))
|
||||
@@ -254,7 +254,7 @@ async def get_active_bans(
|
||||
cannot be reached.
|
||||
"""
|
||||
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=_SOCKET_TIMEOUT)
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=FAIL2BAN_SOCKET_TIMEOUT)
|
||||
|
||||
global_status = to_dict(ok(await client.send(["status"])))
|
||||
jail_list_raw: str = str(global_status.get("Jail list", "") or "").strip()
|
||||
|
||||
@@ -51,6 +51,7 @@ from app.services.settings_service import (
|
||||
from app.services.settings_service import (
|
||||
set_map_color_thresholds as util_set_map_color_thresholds,
|
||||
)
|
||||
from app.utils.constants import FAIL2BAN_SOCKET_TIMEOUT
|
||||
from app.utils.fail2ban_client import Fail2BanClient
|
||||
from app.utils.fail2ban_response import (
|
||||
ensure_list,
|
||||
@@ -61,8 +62,6 @@ from app.utils.fail2ban_response import (
|
||||
|
||||
log: structlog.stdlib.BoundLogger = structlog.get_logger()
|
||||
|
||||
_SOCKET_TIMEOUT: float = 10.0
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Custom exceptions
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -134,7 +133,7 @@ async def get_jail_config(socket_path: str, name: str) -> JailConfigResponse:
|
||||
JailNotFoundError: If *name* is not a known jail.
|
||||
~app.utils.fail2ban_client.Fail2BanConnectionError: Socket unreachable.
|
||||
"""
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=_SOCKET_TIMEOUT)
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=FAIL2BAN_SOCKET_TIMEOUT)
|
||||
|
||||
# Verify existence.
|
||||
try:
|
||||
@@ -207,7 +206,7 @@ async def list_jail_configs(socket_path: str) -> JailConfigListResponse:
|
||||
Raises:
|
||||
~app.utils.fail2ban_client.Fail2BanConnectionError: Socket unreachable.
|
||||
"""
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=_SOCKET_TIMEOUT)
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=FAIL2BAN_SOCKET_TIMEOUT)
|
||||
|
||||
global_status = to_dict(ok(await client.send(["status"])))
|
||||
jail_list_raw: str = str(global_status.get("Jail list", "") or "").strip()
|
||||
@@ -272,7 +271,7 @@ async def update_jail_config(
|
||||
if err:
|
||||
raise ConfigValidationError(f"Invalid regex in 'prefregex': {err!r} (pattern: {update.prefregex!r})")
|
||||
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=_SOCKET_TIMEOUT)
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=FAIL2BAN_SOCKET_TIMEOUT)
|
||||
|
||||
# Verify existence.
|
||||
try:
|
||||
@@ -391,7 +390,7 @@ async def get_global_config(socket_path: str) -> GlobalConfigResponse:
|
||||
Raises:
|
||||
~app.utils.fail2ban_client.Fail2BanConnectionError: Socket unreachable.
|
||||
"""
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=_SOCKET_TIMEOUT)
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=FAIL2BAN_SOCKET_TIMEOUT)
|
||||
|
||||
(
|
||||
log_level_raw,
|
||||
@@ -424,7 +423,7 @@ async def update_global_config(socket_path: str, update: GlobalConfigUpdate) ->
|
||||
ConfigOperationError: If a ``set`` command is rejected.
|
||||
~app.utils.fail2ban_client.Fail2BanConnectionError: Socket unreachable.
|
||||
"""
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=_SOCKET_TIMEOUT)
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=FAIL2BAN_SOCKET_TIMEOUT)
|
||||
|
||||
async def _set_global(key: str, value: Fail2BanToken) -> None:
|
||||
try:
|
||||
@@ -476,7 +475,7 @@ async def add_log_path(
|
||||
ConfigOperationError: If the command is rejected by fail2ban.
|
||||
~app.utils.fail2ban_client.Fail2BanConnectionError: Socket unreachable.
|
||||
"""
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=_SOCKET_TIMEOUT)
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=FAIL2BAN_SOCKET_TIMEOUT)
|
||||
|
||||
try:
|
||||
ok(await client.send(["status", jail, "short"]))
|
||||
@@ -513,7 +512,7 @@ async def delete_log_path(
|
||||
ConfigOperationError: If the command is rejected by fail2ban.
|
||||
~app.utils.fail2ban_client.Fail2BanConnectionError: Socket unreachable.
|
||||
"""
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=_SOCKET_TIMEOUT)
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=FAIL2BAN_SOCKET_TIMEOUT)
|
||||
|
||||
try:
|
||||
ok(await client.send(["status", jail, "short"]))
|
||||
|
||||
@@ -6,6 +6,7 @@ import asyncio
|
||||
|
||||
import structlog
|
||||
|
||||
from app.utils.constants import FAIL2BAN_SOCKET_TIMEOUT_FAST
|
||||
from app.utils.fail2ban_client import (
|
||||
Fail2BanClient,
|
||||
Fail2BanConnectionError,
|
||||
@@ -64,8 +65,7 @@ class Fail2BanMetadataService:
|
||||
|
||||
async def _resolve_db_path(self, socket_path: str) -> str:
|
||||
"""Query fail2ban for the configured database path."""
|
||||
socket_timeout: float = 5.0
|
||||
async with Fail2BanClient(socket_path, timeout=socket_timeout) as client:
|
||||
async with Fail2BanClient(socket_path, timeout=FAIL2BAN_SOCKET_TIMEOUT_FAST) as client:
|
||||
response = await client.send(["get", "dbfile"])
|
||||
|
||||
if not isinstance(response, tuple) or len(response) != 2:
|
||||
|
||||
@@ -18,6 +18,7 @@ import structlog
|
||||
from app import __version__
|
||||
from app.models.config import ServiceStatusResponse
|
||||
from app.models.server import ServerStatus
|
||||
from app.utils.constants import FAIL2BAN_SOCKET_TIMEOUT_FAST
|
||||
from app.utils.fail2ban_client import (
|
||||
Fail2BanClient,
|
||||
Fail2BanCommand,
|
||||
@@ -35,8 +36,6 @@ log: structlog.stdlib.BoundLogger = structlog.get_logger()
|
||||
# Internal helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_SOCKET_TIMEOUT: float = 5.0
|
||||
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
@@ -93,7 +92,7 @@ async def get_service_status(
|
||||
if server_status.online:
|
||||
client = Fail2BanClient(
|
||||
socket_path=socket_path,
|
||||
timeout=_SOCKET_TIMEOUT,
|
||||
timeout=FAIL2BAN_SOCKET_TIMEOUT_FAST,
|
||||
)
|
||||
log_level_raw, log_target_raw = await asyncio.gather(
|
||||
_safe_get_typed(client, ["get", "loglevel"], "INFO"),
|
||||
@@ -129,7 +128,7 @@ async def get_service_status(
|
||||
|
||||
async def probe(
|
||||
socket_path: str,
|
||||
timeout: float = _SOCKET_TIMEOUT,
|
||||
timeout: float = FAIL2BAN_SOCKET_TIMEOUT_FAST,
|
||||
) -> ServerStatus:
|
||||
"""Probe the fail2ban daemon and return a
|
||||
:class:`~app.models.server.ServerStatus`.
|
||||
|
||||
@@ -70,8 +70,6 @@ async def _get_active_jail_names(socket_path: str) -> set[str]:
|
||||
# Constants
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_SOCKET_TIMEOUT: float = 10.0
|
||||
|
||||
# Sections that are not jail definitions.
|
||||
_META_SECTIONS: frozenset[str] = frozenset({"INCLUDES", "DEFAULT"})
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ from app.models.jail import (
|
||||
)
|
||||
from app.services import geo_service
|
||||
from app.utils.config_file_utils import start_daemon, wait_for_fail2ban
|
||||
from app.utils.constants import FAIL2BAN_SOCKET_TIMEOUT
|
||||
from app.utils.fail2ban_client import (
|
||||
Fail2BanClient,
|
||||
Fail2BanCommand,
|
||||
@@ -73,8 +74,6 @@ class IpLookupResult(TypedDict):
|
||||
# Constants
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_SOCKET_TIMEOUT: float = 10.0
|
||||
|
||||
# Capability detection for optional fail2ban transmitter commands (backend, idle).
|
||||
# These commands are not supported in all fail2ban versions. Caching the result
|
||||
# avoids sending unsupported commands every polling cycle and spamming the
|
||||
@@ -223,7 +222,7 @@ async def list_jails(socket_path: str) -> JailListResponse:
|
||||
~app.utils.fail2ban_client.Fail2BanConnectionError: If the socket
|
||||
cannot be reached.
|
||||
"""
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=_SOCKET_TIMEOUT)
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=FAIL2BAN_SOCKET_TIMEOUT)
|
||||
|
||||
# 1. Fetch global status to get jail names.
|
||||
global_status = to_dict(ok(await client.send(["status"])))
|
||||
@@ -376,7 +375,7 @@ async def get_jail(socket_path: str, name: str) -> JailDetailResponse:
|
||||
~app.utils.fail2ban_client.Fail2BanConnectionError: If the socket
|
||||
cannot be reached.
|
||||
"""
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=_SOCKET_TIMEOUT)
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=FAIL2BAN_SOCKET_TIMEOUT)
|
||||
|
||||
# Verify the jail exists by sending a status command first.
|
||||
try:
|
||||
@@ -493,7 +492,7 @@ async def start_jail(socket_path: str, name: str) -> None:
|
||||
~app.utils.fail2ban_client.Fail2BanConnectionError: If the socket
|
||||
cannot be reached.
|
||||
"""
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=_SOCKET_TIMEOUT)
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=FAIL2BAN_SOCKET_TIMEOUT)
|
||||
try:
|
||||
ok(await client.send(["start", name]))
|
||||
log.info("jail_started", jail=name)
|
||||
@@ -518,7 +517,7 @@ async def stop_jail(socket_path: str, name: str) -> None:
|
||||
~app.utils.fail2ban_client.Fail2BanConnectionError: If the socket
|
||||
cannot be reached.
|
||||
"""
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=_SOCKET_TIMEOUT)
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=FAIL2BAN_SOCKET_TIMEOUT)
|
||||
try:
|
||||
ok(await client.send(["stop", name]))
|
||||
log.info("jail_stopped", jail=name)
|
||||
@@ -548,7 +547,7 @@ async def set_idle(socket_path: str, name: str, *, on: bool) -> None:
|
||||
cannot be reached.
|
||||
"""
|
||||
state = "on" if on else "off"
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=_SOCKET_TIMEOUT)
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=FAIL2BAN_SOCKET_TIMEOUT)
|
||||
try:
|
||||
ok(await client.send(["set", name, "idle", state]))
|
||||
log.info("jail_idle_toggled", jail=name, idle=on)
|
||||
@@ -578,7 +577,7 @@ async def reload_jail(socket_path: str, name: str) -> None:
|
||||
~app.utils.fail2ban_client.Fail2BanConnectionError: If the socket
|
||||
cannot be reached.
|
||||
"""
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=_SOCKET_TIMEOUT)
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=FAIL2BAN_SOCKET_TIMEOUT)
|
||||
try:
|
||||
ok(await client.send(["reload", name, [], [["start", name]]]))
|
||||
log.info("jail_reloaded", jail=name)
|
||||
@@ -608,7 +607,7 @@ async def restart(socket_path: str) -> None:
|
||||
~app.utils.fail2ban_client.Fail2BanConnectionError: If the socket
|
||||
cannot be reached.
|
||||
"""
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=_SOCKET_TIMEOUT)
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=FAIL2BAN_SOCKET_TIMEOUT)
|
||||
try:
|
||||
ok(await client.send(["stop"]))
|
||||
log.info("fail2ban_stopped_for_restart")
|
||||
@@ -619,7 +618,7 @@ async def restart(socket_path: str) -> None:
|
||||
async def restart_daemon(
|
||||
socket_path: str,
|
||||
start_cmd_parts: list[str],
|
||||
max_wait_seconds: float = _SOCKET_TIMEOUT,
|
||||
max_wait_seconds: float = FAIL2BAN_SOCKET_TIMEOUT,
|
||||
) -> bool:
|
||||
"""Restart the fail2ban daemon and verify it comes back online.
|
||||
|
||||
@@ -781,7 +780,7 @@ async def get_jail_banned_ips(
|
||||
# Clamp page_size to the allowed maximum.
|
||||
page_size = min(page_size, _MAX_PAGE_SIZE)
|
||||
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=_SOCKET_TIMEOUT)
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=FAIL2BAN_SOCKET_TIMEOUT)
|
||||
|
||||
# Verify the jail exists.
|
||||
try:
|
||||
@@ -896,7 +895,7 @@ async def get_ignore_list(socket_path: str, name: str) -> list[str]:
|
||||
~app.utils.fail2ban_client.Fail2BanConnectionError: If the socket
|
||||
cannot be reached.
|
||||
"""
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=_SOCKET_TIMEOUT)
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=FAIL2BAN_SOCKET_TIMEOUT)
|
||||
try:
|
||||
raw = ok(await client.send(["get", name, "ignoreip"]))
|
||||
return ensure_list(raw)
|
||||
@@ -926,7 +925,7 @@ async def add_ignore_ip(socket_path: str, name: str, ip: str) -> None:
|
||||
except ValueError as exc:
|
||||
raise ValueError(f"Invalid IP address or network: {ip!r}") from exc
|
||||
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=_SOCKET_TIMEOUT)
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=FAIL2BAN_SOCKET_TIMEOUT)
|
||||
try:
|
||||
ok(await client.send(["set", name, "addignoreip", ip]))
|
||||
log.info("ignore_ip_added", jail=name, ip=ip)
|
||||
@@ -950,7 +949,7 @@ async def del_ignore_ip(socket_path: str, name: str, ip: str) -> None:
|
||||
~app.utils.fail2ban_client.Fail2BanConnectionError: If the socket
|
||||
cannot be reached.
|
||||
"""
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=_SOCKET_TIMEOUT)
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=FAIL2BAN_SOCKET_TIMEOUT)
|
||||
try:
|
||||
ok(await client.send(["set", name, "delignoreip", ip]))
|
||||
log.info("ignore_ip_removed", jail=name, ip=ip)
|
||||
@@ -975,7 +974,7 @@ async def get_ignore_self(socket_path: str, name: str) -> bool:
|
||||
~app.utils.fail2ban_client.Fail2BanConnectionError: If the socket
|
||||
cannot be reached.
|
||||
"""
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=_SOCKET_TIMEOUT)
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=FAIL2BAN_SOCKET_TIMEOUT)
|
||||
try:
|
||||
raw = ok(await client.send(["get", name, "ignoreself"]))
|
||||
return bool(raw)
|
||||
@@ -1000,7 +999,7 @@ async def set_ignore_self(socket_path: str, name: str, *, on: bool) -> None:
|
||||
cannot be reached.
|
||||
"""
|
||||
value = "true" if on else "false"
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=_SOCKET_TIMEOUT)
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=FAIL2BAN_SOCKET_TIMEOUT)
|
||||
try:
|
||||
ok(await client.send(["set", name, "ignoreself", value]))
|
||||
log.info("ignore_self_toggled", jail=name, on=on)
|
||||
@@ -1047,7 +1046,7 @@ async def lookup_ip(
|
||||
except ValueError as exc:
|
||||
raise ValueError(f"Invalid IP address: {ip!r}") from exc
|
||||
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=_SOCKET_TIMEOUT)
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=FAIL2BAN_SOCKET_TIMEOUT)
|
||||
|
||||
with contextlib.suppress(ValueError, Fail2BanConnectionError):
|
||||
# Use fail2ban's "banned <ip>" command which checks all jails.
|
||||
@@ -1120,7 +1119,7 @@ async def unban_all_ips(socket_path: str) -> int:
|
||||
~app.utils.fail2ban_client.Fail2BanConnectionError: If the socket
|
||||
cannot be reached.
|
||||
"""
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=_SOCKET_TIMEOUT)
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=FAIL2BAN_SOCKET_TIMEOUT)
|
||||
count: int = int(str(ok(await client.send(["unban", "--all"])) or 0))
|
||||
log.info("all_ips_unbanned", count=count)
|
||||
return count
|
||||
|
||||
@@ -21,6 +21,7 @@ from app.models.config import (
|
||||
RegexTestResponse,
|
||||
)
|
||||
from app.utils.async_utils import run_blocking
|
||||
from app.utils.constants import FAIL2BAN_SOCKET_TIMEOUT
|
||||
from app.utils.fail2ban_client import (
|
||||
Fail2BanClient,
|
||||
Fail2BanConnectionError,
|
||||
@@ -30,8 +31,6 @@ from app.utils.fail2ban_response import ok
|
||||
|
||||
log: structlog.stdlib.BoundLogger = structlog.get_logger()
|
||||
|
||||
_SOCKET_TIMEOUT: float = 10.0
|
||||
|
||||
_NON_FILE_LOG_TARGETS: frozenset[str] = frozenset(
|
||||
{"STDOUT", "STDERR", "SYSLOG", "SYSTEMD-JOURNAL"}
|
||||
)
|
||||
@@ -85,7 +84,7 @@ async def read_fail2ban_log(
|
||||
validates that the target is a readable file, then returns the last
|
||||
*lines* entries optionally filtered by *filter_text*.
|
||||
"""
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=_SOCKET_TIMEOUT)
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=FAIL2BAN_SOCKET_TIMEOUT)
|
||||
|
||||
log_level_raw, log_target_raw = await asyncio.gather(
|
||||
_safe_get_typed(client, ["get", "loglevel"], "INFO"),
|
||||
|
||||
@@ -16,6 +16,7 @@ import structlog
|
||||
|
||||
from app.exceptions import ServerOperationError
|
||||
from app.models.server import ServerSettings, ServerSettingsResponse, ServerSettingsUpdate
|
||||
from app.utils.constants import FAIL2BAN_SOCKET_TIMEOUT
|
||||
from app.utils.fail2ban_client import Fail2BanClient, Fail2BanCommand, Fail2BanResponse
|
||||
from app.utils.fail2ban_response import ok
|
||||
|
||||
@@ -28,8 +29,6 @@ type Fail2BanSettingValue = str | int | bool
|
||||
|
||||
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.
|
||||
@@ -105,7 +104,7 @@ async def get_settings(socket_path: str) -> ServerSettingsResponse:
|
||||
"""
|
||||
import asyncio
|
||||
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=_SOCKET_TIMEOUT)
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=FAIL2BAN_SOCKET_TIMEOUT)
|
||||
|
||||
(
|
||||
log_level_raw,
|
||||
@@ -160,7 +159,7 @@ async def update_settings(socket_path: str, update: ServerSettingsUpdate) -> Non
|
||||
ServerOperationError: If any ``set`` command is rejected.
|
||||
~app.utils.fail2ban_client.Fail2BanConnectionError: Socket unreachable.
|
||||
"""
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=_SOCKET_TIMEOUT)
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=FAIL2BAN_SOCKET_TIMEOUT)
|
||||
|
||||
async def _set(key: str, value: Fail2BanSettingValue) -> None:
|
||||
try:
|
||||
@@ -197,7 +196,7 @@ async def flush_logs(socket_path: str) -> str:
|
||||
ServerOperationError: If the command is rejected.
|
||||
~app.utils.fail2ban_client.Fail2BanConnectionError: Socket unreachable.
|
||||
"""
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=_SOCKET_TIMEOUT)
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=FAIL2BAN_SOCKET_TIMEOUT)
|
||||
try:
|
||||
response = await client.send(["flushlogs"])
|
||||
result = ok(cast("Fail2BanResponse", response))
|
||||
|
||||
@@ -22,7 +22,7 @@ from app.models.config import (
|
||||
JailValidationIssue,
|
||||
JailValidationResult,
|
||||
)
|
||||
from app.utils.constants import FAIL2BAN_TRUTHY_VALUES
|
||||
from app.utils.constants import FAIL2BAN_SOCKET_TIMEOUT, FAIL2BAN_TRUTHY_VALUES
|
||||
from app.utils.fail2ban_client import (
|
||||
Fail2BanClient,
|
||||
Fail2BanConnectionError,
|
||||
@@ -32,8 +32,6 @@ from app.utils.fail2ban_response import ok, to_dict
|
||||
|
||||
log: structlog.stdlib.BoundLogger = structlog.get_logger()
|
||||
|
||||
_SOCKET_TIMEOUT: float = 10.0
|
||||
|
||||
# Allowlist pattern for jail names used in path construction.
|
||||
_SAFE_JAIL_NAME_RE: re.Pattern[str] = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$")
|
||||
|
||||
@@ -253,7 +251,7 @@ def _parse_jails_sync(
|
||||
async def _get_active_jail_names(socket_path: str) -> set[str]:
|
||||
"""Fetch the set of currently running jail names from fail2ban."""
|
||||
try:
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=_SOCKET_TIMEOUT)
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=FAIL2BAN_SOCKET_TIMEOUT)
|
||||
|
||||
status_raw = ok(await client.send(["status"]))
|
||||
status_dict = to_dict(status_raw)
|
||||
@@ -272,7 +270,7 @@ async def _get_active_jail_names(socket_path: str) -> set[str]:
|
||||
async def _probe_fail2ban_running(socket_path: str) -> bool:
|
||||
"""Return ``True`` when fail2ban responds successfully to a status request."""
|
||||
try:
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=_SOCKET_TIMEOUT)
|
||||
client = Fail2BanClient(socket_path=socket_path, timeout=FAIL2BAN_SOCKET_TIMEOUT)
|
||||
response = await client.send(["status"])
|
||||
code, _ = cast("Fail2BanResponse", response)
|
||||
return code == 0
|
||||
|
||||
@@ -13,8 +13,11 @@ from typing import Final
|
||||
DEFAULT_FAIL2BAN_SOCKET: Final[str] = "/var/run/fail2ban/fail2ban.sock"
|
||||
"""Default path to the fail2ban Unix domain socket."""
|
||||
|
||||
FAIL2BAN_SOCKET_TIMEOUT_SECONDS: Final[float] = 5.0
|
||||
"""Maximum seconds to wait for a response from the fail2ban socket."""
|
||||
FAIL2BAN_SOCKET_TIMEOUT_FAST: Final[float] = 5.0
|
||||
"""Maximum seconds for fast operations (health checks, metadata probes)."""
|
||||
|
||||
FAIL2BAN_SOCKET_TIMEOUT: Final[float] = 10.0
|
||||
"""Maximum seconds for command operations (config, jail management)."""
|
||||
|
||||
FAIL2BAN_TRUTHY_VALUES: Final[frozenset[str]] = frozenset({"true", "yes", "1"})
|
||||
"""String values treated as boolean true by fail2ban configuration parsers."""
|
||||
|
||||
Reference in New Issue
Block a user