Move Fail2Ban exceptions into central app.exceptions module
This commit is contained in:
@@ -27,6 +27,24 @@ class ServerOperationError(Exception):
|
||||
"""Raised when a server control command (e.g. refresh) fails."""
|
||||
|
||||
|
||||
class Fail2BanConnectionError(Exception):
|
||||
"""Raised when the fail2ban socket is unreachable or returns an error."""
|
||||
|
||||
def __init__(self, message: str, socket_path: str) -> None:
|
||||
"""Initialize with a human-readable message and the socket path.
|
||||
|
||||
Args:
|
||||
message: Description of the connection problem.
|
||||
socket_path: The fail2ban socket path that was targeted.
|
||||
"""
|
||||
self.socket_path: str = socket_path
|
||||
super().__init__(f"{message} (socket: {socket_path})")
|
||||
|
||||
|
||||
class Fail2BanProtocolError(Exception):
|
||||
"""Raised when the response from fail2ban cannot be parsed."""
|
||||
|
||||
|
||||
class FilterInvalidRegexError(Exception):
|
||||
"""Raised when a regex pattern fails to compile."""
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ from app.routers import (
|
||||
setup,
|
||||
)
|
||||
from app.startup import startup_shared_resources
|
||||
from app.utils.fail2ban_client import Fail2BanConnectionError, Fail2BanProtocolError
|
||||
from app.exceptions import Fail2BanConnectionError, Fail2BanProtocolError
|
||||
from app.utils.runtime_state import ApplicationState, RuntimeState
|
||||
from app.utils.session_cache import InMemorySessionCache, NoOpSessionCache
|
||||
from app.utils.setup_state import is_setup_complete_cached, set_setup_complete_cache
|
||||
@@ -162,7 +162,7 @@ async def _fail2ban_connection_handler(
|
||||
|
||||
Args:
|
||||
request: The incoming FastAPI request.
|
||||
exc: The :class:`~app.utils.fail2ban_client.Fail2BanConnectionError`.
|
||||
exc: The :class:`~app.exceptions.Fail2BanConnectionError`.
|
||||
|
||||
Returns:
|
||||
A :class:`fastapi.responses.JSONResponse` with status 502.
|
||||
@@ -187,7 +187,7 @@ async def _fail2ban_protocol_handler(
|
||||
|
||||
Args:
|
||||
request: The incoming FastAPI request.
|
||||
exc: The :class:`~app.utils.fail2ban_client.Fail2BanProtocolError`.
|
||||
exc: The :class:`~app.exceptions.Fail2BanProtocolError`.
|
||||
|
||||
Returns:
|
||||
A :class:`fastapi.responses.JSONResponse` with status 502.
|
||||
|
||||
@@ -23,7 +23,7 @@ from app.exceptions import JailNotFoundError, JailOperationError
|
||||
from app.models.ban import ActiveBanListResponse, BanRequest, UnbanAllResponse, UnbanRequest
|
||||
from app.models.jail import JailCommandResponse
|
||||
from app.services import jail_service
|
||||
from app.utils.fail2ban_client import Fail2BanConnectionError
|
||||
from app.exceptions import Fail2BanConnectionError
|
||||
|
||||
router: APIRouter = APIRouter(prefix="/api/bans", tags=["Bans"])
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ from app.models.config import (
|
||||
ServiceStatusResponse,
|
||||
)
|
||||
from app.services import config_file_service, config_service, jail_service, log_service, setup_service
|
||||
from app.utils.fail2ban_client import Fail2BanConnectionError
|
||||
from app.exceptions import Fail2BanConnectionError
|
||||
|
||||
log: structlog.stdlib.BoundLogger = structlog.get_logger()
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ from app.dependencies import (
|
||||
)
|
||||
from app.models.geo import GeoCacheStatsResponse, GeoReResolveResponse, IpLookupResponse
|
||||
from app.services import geo_service, jail_service
|
||||
from app.utils.fail2ban_client import Fail2BanConnectionError
|
||||
from app.exceptions import Fail2BanConnectionError
|
||||
|
||||
router: APIRouter = APIRouter(prefix="/api/geo", tags=["Geo"])
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ from app.services import (
|
||||
filter_config_service,
|
||||
jail_config_service,
|
||||
)
|
||||
from app.utils.fail2ban_client import Fail2BanConnectionError
|
||||
from app.exceptions import Fail2BanConnectionError
|
||||
from app.utils.runtime_state import (
|
||||
clear_activation_record,
|
||||
clear_pending_recovery,
|
||||
|
||||
@@ -40,7 +40,7 @@ from app.models.jail import (
|
||||
JailListResponse,
|
||||
)
|
||||
from app.services import jail_service
|
||||
from app.utils.fail2ban_client import Fail2BanConnectionError
|
||||
from app.exceptions import Fail2BanConnectionError
|
||||
|
||||
router: APIRouter = APIRouter(prefix="/api/jails", tags=["Jails"])
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ from app.dependencies import AuthDep, Fail2BanSocketDep
|
||||
from app.models.server import ServerSettingsResponse, ServerSettingsUpdate
|
||||
from app.services import server_service
|
||||
from app.exceptions import ServerOperationError
|
||||
from app.utils.fail2ban_client import Fail2BanConnectionError
|
||||
from app.exceptions import Fail2BanConnectionError
|
||||
|
||||
router: APIRouter = APIRouter(prefix="/api/server", tags=["Server"])
|
||||
|
||||
|
||||
@@ -27,6 +27,8 @@ from typing import TYPE_CHECKING, Protocol
|
||||
|
||||
import structlog
|
||||
|
||||
from app.exceptions import Fail2BanConnectionError, Fail2BanProtocolError
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Types
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -125,23 +127,6 @@ _RETRY_INITIAL_BACKOFF: float = 0.15 # seconds; doubles on each attempt
|
||||
_COMMAND_SEMAPHORE_CONCURRENCY: int = 10
|
||||
|
||||
|
||||
class Fail2BanConnectionError(Exception):
|
||||
"""Raised when the fail2ban socket is unreachable or returns an error."""
|
||||
|
||||
def __init__(self, message: str, socket_path: str) -> None:
|
||||
"""Initialise with a human-readable message and the socket path.
|
||||
|
||||
Args:
|
||||
message: Description of the connection problem.
|
||||
socket_path: The fail2ban socket path that was targeted.
|
||||
"""
|
||||
self.socket_path: str = socket_path
|
||||
super().__init__(f"{message} (socket: {socket_path})")
|
||||
|
||||
|
||||
class Fail2BanProtocolError(Exception):
|
||||
"""Raised when the response from fail2ban cannot be parsed."""
|
||||
|
||||
|
||||
def _send_command_sync(
|
||||
socket_path: str,
|
||||
|
||||
@@ -13,7 +13,7 @@ from app.config import Settings
|
||||
from app.db import init_db
|
||||
from app.main import create_app
|
||||
from app.models.ban import ActiveBan, ActiveBanListResponse
|
||||
from app.utils.fail2ban_client import Fail2BanConnectionError
|
||||
from app.exceptions import Fail2BanConnectionError
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Fixtures
|
||||
|
||||
@@ -121,7 +121,7 @@ class TestGetJailConfigs:
|
||||
|
||||
async def test_502_on_connection_error(self, config_client: AsyncClient) -> None:
|
||||
"""GET /api/config/jails returns 502 when fail2ban is unreachable."""
|
||||
from app.utils.fail2ban_client import Fail2BanConnectionError
|
||||
from app.exceptions import Fail2BanConnectionError
|
||||
|
||||
with patch(
|
||||
"app.routers.jail_config.config_service.list_jail_configs",
|
||||
@@ -373,7 +373,7 @@ class TestReloadFail2ban:
|
||||
|
||||
async def test_502_when_fail2ban_unreachable(self, config_client: AsyncClient) -> None:
|
||||
"""POST /api/config/reload returns 502 when fail2ban socket is unreachable."""
|
||||
from app.utils.fail2ban_client import Fail2BanConnectionError
|
||||
from app.exceptions import Fail2BanConnectionError
|
||||
|
||||
with patch(
|
||||
"app.routers.config_misc.jail_service.reload_all",
|
||||
@@ -458,7 +458,7 @@ class TestRestartFail2ban:
|
||||
|
||||
async def test_502_when_fail2ban_unreachable(self, config_client: AsyncClient) -> None:
|
||||
"""POST /api/config/restart returns 502 when fail2ban socket is unreachable."""
|
||||
from app.utils.fail2ban_client import Fail2BanConnectionError
|
||||
from app.exceptions import Fail2BanConnectionError
|
||||
|
||||
with patch(
|
||||
"app.routers.config_misc.jail_service.restart",
|
||||
@@ -1965,7 +1965,7 @@ class TestGetFail2BanLog:
|
||||
|
||||
async def test_502_when_fail2ban_unreachable(self, config_client: AsyncClient) -> None:
|
||||
"""GET /api/config/fail2ban-log returns 502 when fail2ban is down."""
|
||||
from app.utils.fail2ban_client import Fail2BanConnectionError
|
||||
from app.exceptions import Fail2BanConnectionError
|
||||
|
||||
with patch(
|
||||
"app.routers.config_misc.config_service.read_fail2ban_log",
|
||||
|
||||
@@ -423,7 +423,7 @@ class TestIgnoreIpEndpoints:
|
||||
|
||||
async def test_get_ignore_list_502_on_connection_error(self, jails_client: AsyncClient) -> None:
|
||||
"""GET /api/jails/sshd/ignoreip returns 502 when fail2ban is unreachable."""
|
||||
from app.utils.fail2ban_client import Fail2BanConnectionError
|
||||
from app.exceptions import Fail2BanConnectionError
|
||||
|
||||
with patch(
|
||||
"app.routers.jails.jail_service.get_ignore_list",
|
||||
@@ -465,7 +465,7 @@ class TestIgnoreIpEndpoints:
|
||||
|
||||
async def test_add_ignore_ip_502_on_connection_error(self, jails_client: AsyncClient) -> None:
|
||||
"""POST /api/jails/sshd/ignoreip returns 502 when fail2ban is unreachable."""
|
||||
from app.utils.fail2ban_client import Fail2BanConnectionError
|
||||
from app.exceptions import Fail2BanConnectionError
|
||||
|
||||
with patch(
|
||||
"app.routers.jails.jail_service.add_ignore_ip",
|
||||
@@ -512,7 +512,7 @@ class TestIgnoreIpEndpoints:
|
||||
|
||||
async def test_delete_ignore_ip_502_on_connection_error(self, jails_client: AsyncClient) -> None:
|
||||
"""DELETE /api/jails/sshd/ignoreip returns 502 when fail2ban is unreachable."""
|
||||
from app.utils.fail2ban_client import Fail2BanConnectionError
|
||||
from app.exceptions import Fail2BanConnectionError
|
||||
|
||||
with patch(
|
||||
"app.routers.jails.jail_service.del_ignore_ip",
|
||||
@@ -599,7 +599,7 @@ class TestToggleIgnoreSelf:
|
||||
|
||||
async def test_502_on_connection_error(self, jails_client: AsyncClient) -> None:
|
||||
"""POST /api/jails/sshd/ignoreself returns 502 when fail2ban is unreachable."""
|
||||
from app.utils.fail2ban_client import Fail2BanConnectionError
|
||||
from app.exceptions import Fail2BanConnectionError
|
||||
|
||||
with patch(
|
||||
"app.routers.jails.jail_service.set_ignore_self",
|
||||
@@ -624,7 +624,7 @@ class TestFail2BanConnectionErrors:
|
||||
|
||||
async def test_get_jails_502(self, jails_client: AsyncClient) -> None:
|
||||
"""GET /api/jails returns 502 when fail2ban socket is unreachable."""
|
||||
from app.utils.fail2ban_client import Fail2BanConnectionError
|
||||
from app.exceptions import Fail2BanConnectionError
|
||||
|
||||
with patch(
|
||||
"app.routers.jails.jail_service.list_jails",
|
||||
@@ -636,7 +636,7 @@ class TestFail2BanConnectionErrors:
|
||||
|
||||
async def test_get_jail_502(self, jails_client: AsyncClient) -> None:
|
||||
"""GET /api/jails/sshd returns 502 when fail2ban is unreachable."""
|
||||
from app.utils.fail2ban_client import Fail2BanConnectionError
|
||||
from app.exceptions import Fail2BanConnectionError
|
||||
|
||||
with patch(
|
||||
"app.routers.jails.jail_service.get_jail",
|
||||
@@ -660,7 +660,7 @@ class TestFail2BanConnectionErrors:
|
||||
|
||||
async def test_reload_all_502(self, jails_client: AsyncClient) -> None:
|
||||
"""POST /api/jails/reload-all returns 502 when fail2ban is unreachable."""
|
||||
from app.utils.fail2ban_client import Fail2BanConnectionError
|
||||
from app.exceptions import Fail2BanConnectionError
|
||||
|
||||
with patch(
|
||||
"app.routers.jails.jail_service.reload_all",
|
||||
@@ -672,7 +672,7 @@ class TestFail2BanConnectionErrors:
|
||||
|
||||
async def test_start_jail_502(self, jails_client: AsyncClient) -> None:
|
||||
"""POST /api/jails/sshd/start returns 502 when fail2ban is unreachable."""
|
||||
from app.utils.fail2ban_client import Fail2BanConnectionError
|
||||
from app.exceptions import Fail2BanConnectionError
|
||||
|
||||
with patch(
|
||||
"app.routers.jails.jail_service.start_jail",
|
||||
@@ -696,7 +696,7 @@ class TestFail2BanConnectionErrors:
|
||||
|
||||
async def test_stop_jail_502(self, jails_client: AsyncClient) -> None:
|
||||
"""POST /api/jails/sshd/stop returns 502 when fail2ban is unreachable."""
|
||||
from app.utils.fail2ban_client import Fail2BanConnectionError
|
||||
from app.exceptions import Fail2BanConnectionError
|
||||
|
||||
with patch(
|
||||
"app.routers.jails.jail_service.stop_jail",
|
||||
@@ -740,7 +740,7 @@ class TestFail2BanConnectionErrors:
|
||||
|
||||
async def test_toggle_idle_502(self, jails_client: AsyncClient) -> None:
|
||||
"""POST /api/jails/sshd/idle returns 502 when fail2ban is unreachable."""
|
||||
from app.utils.fail2ban_client import Fail2BanConnectionError
|
||||
from app.exceptions import Fail2BanConnectionError
|
||||
|
||||
with patch(
|
||||
"app.routers.jails.jail_service.set_idle",
|
||||
@@ -780,7 +780,7 @@ class TestFail2BanConnectionErrors:
|
||||
|
||||
async def test_reload_jail_502(self, jails_client: AsyncClient) -> None:
|
||||
"""POST /api/jails/sshd/reload returns 502 when fail2ban is unreachable."""
|
||||
from app.utils.fail2ban_client import Fail2BanConnectionError
|
||||
from app.exceptions import Fail2BanConnectionError
|
||||
|
||||
with patch(
|
||||
"app.routers.jails.jail_service.reload_jail",
|
||||
@@ -894,7 +894,7 @@ class TestGetJailBannedIps:
|
||||
|
||||
async def test_502_when_fail2ban_unreachable(self, jails_client: AsyncClient) -> None:
|
||||
"""GET /api/jails/sshd/banned returns 502 when fail2ban is unreachable."""
|
||||
from app.utils.fail2ban_client import Fail2BanConnectionError
|
||||
from app.exceptions import Fail2BanConnectionError
|
||||
|
||||
with patch(
|
||||
"app.routers.jails.jail_service.get_jail_banned_ips",
|
||||
|
||||
@@ -106,7 +106,7 @@ class TestGetServerSettings:
|
||||
|
||||
async def test_502_on_connection_error(self, server_client: AsyncClient) -> None:
|
||||
"""GET /api/server/settings returns 502 when fail2ban is unreachable."""
|
||||
from app.utils.fail2ban_client import Fail2BanConnectionError
|
||||
from app.exceptions import Fail2BanConnectionError
|
||||
|
||||
with patch(
|
||||
"app.routers.server.server_service.get_settings",
|
||||
@@ -163,7 +163,7 @@ class TestUpdateServerSettings:
|
||||
|
||||
async def test_502_on_connection_error(self, server_client: AsyncClient) -> None:
|
||||
"""PUT /api/server/settings returns 502 when fail2ban is unreachable."""
|
||||
from app.utils.fail2ban_client import Fail2BanConnectionError
|
||||
from app.exceptions import Fail2BanConnectionError
|
||||
|
||||
with patch(
|
||||
"app.routers.server.server_service.update_settings",
|
||||
@@ -218,7 +218,7 @@ class TestFlushLogs:
|
||||
|
||||
async def test_502_on_connection_error(self, server_client: AsyncClient) -> None:
|
||||
"""POST /api/server/flush-logs returns 502 when fail2ban is unreachable."""
|
||||
from app.utils.fail2ban_client import Fail2BanConnectionError
|
||||
from app.exceptions import Fail2BanConnectionError
|
||||
|
||||
with patch(
|
||||
"app.routers.server.server_service.flush_logs",
|
||||
|
||||
@@ -5,7 +5,7 @@ from __future__ import annotations
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from app.services.fail2ban_metadata_service import Fail2BanMetadataService
|
||||
from app.utils.fail2ban_client import Fail2BanConnectionError
|
||||
from app.exceptions import Fail2BanConnectionError
|
||||
|
||||
|
||||
async def test_get_db_path_caches_result() -> None:
|
||||
|
||||
@@ -9,7 +9,7 @@ import pytest
|
||||
|
||||
from app.models.server import ServerStatus
|
||||
from app.services import health_service
|
||||
from app.utils.fail2ban_client import Fail2BanConnectionError, Fail2BanProtocolError
|
||||
from app.exceptions import Fail2BanConnectionError, Fail2BanProtocolError
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers
|
||||
|
||||
@@ -13,7 +13,7 @@ from app.models.geo import GeoDetail, GeoInfo
|
||||
from app.models.jail import JailDetailResponse, JailListResponse
|
||||
from app.services import jail_service
|
||||
from app.services.jail_service import JailNotFoundError, JailOperationError
|
||||
from app.utils.fail2ban_client import Fail2BanConnectionError
|
||||
from app.exceptions import Fail2BanConnectionError
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers
|
||||
|
||||
Reference in New Issue
Block a user