diff --git a/Docs/Architekture.md b/Docs/Architekture.md index 829f9fb..c121465 100644 --- a/Docs/Architekture.md +++ b/Docs/Architekture.md @@ -252,7 +252,7 @@ Pure helper modules with no framework dependencies. | `config_file_utils.py` | Common file-level config utility helpers | | `fail2ban_db_utils.py` | Fail2ban DB path discovery and ban-history parsing helpers | | `setup_utils.py` | Setup wizard helper utilities | -| `constants.py` | Shared constants: default socket path, default database path, time-range presets, limits | +| `constants.py` | Shared constants: default socket path, default database path, time-range presets, parser truthy values, limits | #### Configuration (`app/config.py`) diff --git a/Docs/Tasks.md b/Docs/Tasks.md index 463eb03..45e6f68 100644 --- a/Docs/Tasks.md +++ b/Docs/Tasks.md @@ -264,6 +264,7 @@ The helpers layer hides import cycles rather than eliminating them and adds thre ### Task 9 — Consolidate _TRUE_VALUES into constants.py +**Status:** Completed **Severity:** Low (part of Task 6 cleanup) **Where:** diff --git a/backend/app/services/config_file_service.py b/backend/app/services/config_file_service.py index c9cd9d7..60cc7e3 100644 --- a/backend/app/services/config_file_service.py +++ b/backend/app/services/config_file_service.py @@ -73,6 +73,7 @@ from app.models.config import ( RollbackResponse, ) from app.utils import conffile_parser +from app.utils.constants import FAIL2BAN_TRUTHY_VALUES from app.utils.async_utils import run_blocking from app.utils.fail2ban_client import ( Fail2BanClient, @@ -82,6 +83,7 @@ from app.utils.fail2ban_client import ( log: structlog.stdlib.BoundLogger = structlog.get_logger() + # Proxy object for jail reload operations. Tests can patch # app.services.config_file_service.jail_service.reload_all as needed. class _JailServiceProxy: @@ -127,9 +129,6 @@ _SAFE_JAIL_NAME_RE: re.Pattern[str] = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]{0, # Sections that are not jail definitions. _META_SECTIONS: frozenset[str] = frozenset({"INCLUDES", "DEFAULT"}) -# True-ish values for the ``enabled`` key. -_TRUE_VALUES: frozenset[str] = frozenset({"true", "yes", "1"}) - # False-ish values for the ``enabled`` key. _FALSE_VALUES: frozenset[str] = frozenset({"false", "no", "0"}) @@ -230,7 +229,7 @@ def _is_truthy(value: str) -> bool: Returns: ``True`` when the value represents enabled. """ - return value.strip().lower() in _TRUE_VALUES + return value.strip().lower() in FAIL2BAN_TRUTHY_VALUES def _parse_int_safe(value: str) -> int | None: @@ -1026,8 +1025,10 @@ async def list_inactive_jails( ) -> InactiveJailListResponse: """Delegate to the canonical jail config service.""" from app.services.jail_config_service import list_inactive_jails as _delegate + return await _delegate(config_dir, socket_path) + async def activate_jail( config_dir: str, socket_path: str, @@ -1036,8 +1037,10 @@ async def activate_jail( ) -> JailActivationResponse: """Delegate to the canonical jail config service.""" from app.services.jail_config_service import _activate_jail as _delegate + return await _delegate(config_dir, socket_path, name, req) + async def _rollback_activation_async( config_dir: str, name: str, @@ -1069,7 +1072,7 @@ async def _rollback_activation_async( # Step 1 — restore original file (or delete it). try: - await run_blocking( _restore_local_file_sync, local_path, original_content) + await run_blocking(_restore_local_file_sync, local_path, original_content) log.info("jail_activation_rollback_file_restored", jail=name) except ConfigWriteError as exc: log.error("jail_activation_rollback_restore_failed", jail=name, error=str(exc)) @@ -1102,8 +1105,10 @@ async def deactivate_jail( ) -> JailActivationResponse: """Delegate to the canonical jail config service.""" from app.services.jail_config_service import _deactivate_jail as _delegate + return await _delegate(config_dir, socket_path, name) + async def delete_jail_local_override( config_dir: str, socket_path: str, @@ -1111,16 +1116,20 @@ async def delete_jail_local_override( ) -> None: """Delegate to the canonical jail config service.""" from app.services.jail_config_service import delete_jail_local_override as _delegate + return await _delegate(config_dir, socket_path, name) + async def validate_jail_config( config_dir: str, name: str, ) -> JailValidationResult: """Delegate to the canonical jail config service.""" from app.services.jail_config_service import validate_jail_config as _delegate + return await _delegate(config_dir, name) + async def rollback_jail( config_dir: str, socket_path: str, @@ -1129,8 +1138,10 @@ async def rollback_jail( ) -> RollbackResponse: """Delegate to the canonical jail config helper.""" from app.services.jail_config_service import _rollback_jail as _delegate + return await _delegate(config_dir, socket_path, name, start_cmd_parts) + # --------------------------------------------------------------------------- # Filter discovery helpers (Task 2.1) # --------------------------------------------------------------------------- @@ -1290,8 +1301,10 @@ async def list_filters( ) -> FilterListResponse: """Delegate to the canonical filter config service.""" from app.services.filter_config_service import list_filters as _delegate + return await _delegate(config_dir, socket_path) + async def get_filter( config_dir: str, socket_path: str, @@ -1299,8 +1312,10 @@ async def get_filter( ) -> FilterConfig: """Delegate to the canonical filter config service.""" from app.services.filter_config_service import get_filter as _delegate + return await _delegate(config_dir, socket_path, name) + # --------------------------------------------------------------------------- # Public API — filter write operations (Task 2.2) # --------------------------------------------------------------------------- @@ -1315,8 +1330,10 @@ async def update_filter( ) -> FilterConfig: """Delegate to the canonical filter config service.""" from app.services.filter_config_service import update_filter as _delegate + return await _delegate(config_dir, socket_path, name, req, do_reload=do_reload) + async def create_filter( config_dir: str, socket_path: str, @@ -1325,16 +1342,20 @@ async def create_filter( ) -> FilterConfig: """Delegate to the canonical filter config service.""" from app.services.filter_config_service import create_filter as _delegate + return await _delegate(config_dir, socket_path, req, do_reload=do_reload) + async def delete_filter( config_dir: str, name: str, ) -> None: """Delegate to the canonical filter config service.""" from app.services.filter_config_service import delete_filter as _delegate + return await _delegate(config_dir, name) + async def assign_filter_to_jail( config_dir: str, socket_path: str, @@ -1344,8 +1365,10 @@ async def assign_filter_to_jail( ) -> None: """Delegate to the canonical filter config service.""" from app.services.filter_config_service import assign_filter_to_jail as _delegate + return await _delegate(config_dir, socket_path, jail_name, req, do_reload=do_reload) + # --------------------------------------------------------------------------- # Action discovery helpers (Task 3.1) # --------------------------------------------------------------------------- @@ -1727,8 +1750,10 @@ async def list_actions( ) -> ActionListResponse: """Delegate to the canonical action config service.""" from app.services.action_config_service import list_actions as _delegate + return await _delegate(config_dir, socket_path) + async def get_action( config_dir: str, socket_path: str, @@ -1736,8 +1761,10 @@ async def get_action( ) -> ActionConfig: """Delegate to the canonical action config service.""" from app.services.action_config_service import get_action as _delegate + return await _delegate(config_dir, socket_path, name) + # --------------------------------------------------------------------------- # Public API — action write operations (Task 3.2) # --------------------------------------------------------------------------- @@ -1752,8 +1779,10 @@ async def update_action( ) -> ActionConfig: """Delegate to the canonical action config service.""" from app.services.action_config_service import update_action as _delegate + return await _delegate(config_dir, socket_path, name, req, do_reload=do_reload) + async def create_action( config_dir: str, socket_path: str, @@ -1762,16 +1791,20 @@ async def create_action( ) -> ActionConfig: """Delegate to the canonical action config service.""" from app.services.action_config_service import create_action as _delegate + return await _delegate(config_dir, socket_path, req, do_reload=do_reload) + async def delete_action( config_dir: str, name: str, ) -> None: """Delegate to the canonical action config service.""" from app.services.action_config_service import delete_action as _delegate + return await _delegate(config_dir, name) + async def assign_action_to_jail( config_dir: str, socket_path: str, @@ -1805,8 +1838,7 @@ async def assign_action_to_jail( _safe_jail_name(jail_name) _safe_action_name(req.action_name) - - all_jails, _src = await run_blocking( _parse_jails_sync, Path(config_dir)) + all_jails, _src = await run_blocking(_parse_jails_sync, Path(config_dir)) if jail_name not in all_jails: raise JailNotFoundInConfigError(jail_name) @@ -1819,7 +1851,7 @@ async def assign_action_to_jail( ): raise ActionNotFoundError(req.action_name) - await run_blocking( _check_action) + await run_blocking(_check_action) # Build the action string with optional parameters. if req.params: @@ -1828,7 +1860,8 @@ async def assign_action_to_jail( else: action_entry = req.action_name - await run_blocking(_append_jail_action_sync, + await run_blocking( + _append_jail_action_sync, Path(config_dir), jail_name, action_entry, @@ -1882,12 +1915,12 @@ async def remove_action_from_jail( _safe_jail_name(jail_name) _safe_action_name(action_name) - - all_jails, _src = await run_blocking( _parse_jails_sync, Path(config_dir)) + all_jails, _src = await run_blocking(_parse_jails_sync, Path(config_dir)) if jail_name not in all_jails: raise JailNotFoundInConfigError(jail_name) - await run_blocking(_remove_jail_action_sync, + await run_blocking( + _remove_jail_action_sync, Path(config_dir), jail_name, action_name, diff --git a/backend/app/utils/constants.py b/backend/app/utils/constants.py index 0bdff18..fa3254b 100644 --- a/backend/app/utils/constants.py +++ b/backend/app/utils/constants.py @@ -16,6 +16,9 @@ DEFAULT_FAIL2BAN_SOCKET: Final[str] = "/var/run/fail2ban/fail2ban.sock" FAIL2BAN_SOCKET_TIMEOUT_SECONDS: Final[float] = 5.0 """Maximum seconds to wait for a response from the fail2ban socket.""" +FAIL2BAN_TRUTHY_VALUES: Final[frozenset[str]] = frozenset({"true", "yes", "1"}) +"""String values treated as boolean true by fail2ban configuration parsers.""" + # --------------------------------------------------------------------------- # Database # ---------------------------------------------------------------------------