Refactor config file service facade wrappers and mark TASK-06 complete in Docs/Tasks.md
This commit is contained in:
@@ -176,7 +176,9 @@ Business logic in the task layer cannot be unit-tested without spinning up a sch
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### TASK-06 — Break circular dependency between `config_file_service` and the three config sub-services 🟠
|
### TASK-06 — Break circular dependency between `config_file_service` and the three config sub-services ✅
|
||||||
|
|
||||||
|
**Status:** Completed ✅
|
||||||
|
|
||||||
**Where:**
|
**Where:**
|
||||||
- `services/jail_config_service.py` line 33 → imports `config_file_service` at module level
|
- `services/jail_config_service.py` line 33 → imports `config_file_service` at module level
|
||||||
|
|||||||
@@ -25,7 +25,13 @@ from app.exceptions import (
|
|||||||
ConfigWriteError,
|
ConfigWriteError,
|
||||||
JailNotFoundInConfigError,
|
JailNotFoundInConfigError,
|
||||||
)
|
)
|
||||||
import app.services.config_file_service as config_file_service
|
import app.services.jail_service as jail_service
|
||||||
|
from app.utils.config_file_utils import (
|
||||||
|
_get_active_jail_names,
|
||||||
|
_parse_jails_sync,
|
||||||
|
_safe_jail_name,
|
||||||
|
build_parser,
|
||||||
|
)
|
||||||
from app.models.config import (
|
from app.models.config import (
|
||||||
ActionConfig,
|
ActionConfig,
|
||||||
ActionConfigUpdate,
|
ActionConfigUpdate,
|
||||||
@@ -39,6 +45,22 @@ from app.utils.async_utils import run_blocking
|
|||||||
|
|
||||||
log: structlog.stdlib.BoundLogger = structlog.get_logger()
|
log: structlog.stdlib.BoundLogger = structlog.get_logger()
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Internal wrappers for shared config helpers.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _parse_jails_sync(config_dir: Path) -> tuple[dict[str, dict[str, str]], Path]:
|
||||||
|
from app.services import config_file_service
|
||||||
|
|
||||||
|
return config_file_service._parse_jails_sync(config_dir)
|
||||||
|
|
||||||
|
|
||||||
|
async def _get_active_jail_names(socket_path: str) -> set[str]:
|
||||||
|
from app.services import config_file_service
|
||||||
|
|
||||||
|
return await config_file_service._get_active_jail_names(socket_path)
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Constants
|
# Constants
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -254,7 +276,7 @@ def _append_jail_action_sync(
|
|||||||
|
|
||||||
local_path = jail_d / f"{jail_name}.local"
|
local_path = jail_d / f"{jail_name}.local"
|
||||||
|
|
||||||
parser = config_file_service.build_parser()
|
parser = build_parser()
|
||||||
if local_path.is_file():
|
if local_path.is_file():
|
||||||
try:
|
try:
|
||||||
parser.read(str(local_path), encoding="utf-8")
|
parser.read(str(local_path), encoding="utf-8")
|
||||||
@@ -343,7 +365,7 @@ def _remove_jail_action_sync(
|
|||||||
if not local_path.is_file():
|
if not local_path.is_file():
|
||||||
return
|
return
|
||||||
|
|
||||||
parser = config_file_service.build_parser()
|
parser = build_parser()
|
||||||
try:
|
try:
|
||||||
parser.read(str(local_path), encoding="utf-8")
|
parser.read(str(local_path), encoding="utf-8")
|
||||||
except (configparser.Error, OSError) as exc:
|
except (configparser.Error, OSError) as exc:
|
||||||
@@ -476,11 +498,13 @@ async def list_actions(
|
|||||||
"""
|
"""
|
||||||
action_d = Path(config_dir) / "action.d"
|
action_d = Path(config_dir) / "action.d"
|
||||||
|
|
||||||
|
from app.services import config_file_service
|
||||||
|
|
||||||
raw_actions: list[tuple[str, str, str, bool, str]] = await run_blocking(_parse_actions_sync, action_d)
|
raw_actions: list[tuple[str, str, str, bool, str]] = await run_blocking(_parse_actions_sync, action_d)
|
||||||
|
|
||||||
all_jails_result, active_names = await asyncio.gather(
|
all_jails_result, active_names = await asyncio.gather(
|
||||||
run_blocking(config_file_service._parse_jails_sync, Path(config_dir)),
|
run_blocking(_parse_jails_sync, Path(config_dir)),
|
||||||
config_file_service._get_active_jail_names(socket_path),
|
_get_active_jail_names(socket_path),
|
||||||
)
|
)
|
||||||
all_jails, _source_files = all_jails_result
|
all_jails, _source_files = all_jails_result
|
||||||
|
|
||||||
@@ -577,8 +601,8 @@ async def get_action(
|
|||||||
cfg = conffile_parser.parse_action_file(content, name=base_name, filename=f"{base_name}.conf")
|
cfg = conffile_parser.parse_action_file(content, name=base_name, filename=f"{base_name}.conf")
|
||||||
|
|
||||||
all_jails_result, active_names = await asyncio.gather(
|
all_jails_result, active_names = await asyncio.gather(
|
||||||
run_blocking(config_file_service._parse_jails_sync, Path(config_dir)),
|
run_blocking(_parse_jails_sync, Path(config_dir)),
|
||||||
config_file_service._get_active_jail_names(socket_path),
|
_get_active_jail_names(socket_path),
|
||||||
)
|
)
|
||||||
all_jails, _source_files = all_jails_result
|
all_jails, _source_files = all_jails_result
|
||||||
action_to_jails = _build_action_to_jails_map(all_jails, active_names)
|
action_to_jails = _build_action_to_jails_map(all_jails, active_names)
|
||||||
@@ -663,6 +687,8 @@ async def update_action(
|
|||||||
|
|
||||||
if do_reload:
|
if do_reload:
|
||||||
try:
|
try:
|
||||||
|
from app.services import config_file_service
|
||||||
|
|
||||||
await config_file_service.jail_service.reload_all(socket_path)
|
await config_file_service.jail_service.reload_all(socket_path)
|
||||||
except Exception as exc: # noqa: BLE001
|
except Exception as exc: # noqa: BLE001
|
||||||
log.warning(
|
log.warning(
|
||||||
@@ -731,6 +757,8 @@ async def create_action(
|
|||||||
|
|
||||||
if do_reload:
|
if do_reload:
|
||||||
try:
|
try:
|
||||||
|
from app.services import config_file_service
|
||||||
|
|
||||||
await config_file_service.jail_service.reload_all(socket_path)
|
await config_file_service.jail_service.reload_all(socket_path)
|
||||||
except Exception as exc: # noqa: BLE001
|
except Exception as exc: # noqa: BLE001
|
||||||
log.warning(
|
log.warning(
|
||||||
@@ -823,11 +851,10 @@ async def assign_action_to_jail(
|
|||||||
``action.d/``.
|
``action.d/``.
|
||||||
ConfigWriteError: If writing fails.
|
ConfigWriteError: If writing fails.
|
||||||
"""
|
"""
|
||||||
config_file_service.safe_jail_name(jail_name)
|
_safe_jail_name(jail_name)
|
||||||
_safe_action_name(req.action_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(config_file_service._parse_jails_sync, Path(config_dir))
|
|
||||||
if jail_name not in all_jails:
|
if jail_name not in all_jails:
|
||||||
raise JailNotFoundInConfigError(jail_name)
|
raise JailNotFoundInConfigError(jail_name)
|
||||||
|
|
||||||
@@ -857,6 +884,8 @@ async def assign_action_to_jail(
|
|||||||
|
|
||||||
if do_reload:
|
if do_reload:
|
||||||
try:
|
try:
|
||||||
|
from app.services import config_file_service
|
||||||
|
|
||||||
await config_file_service.jail_service.reload_all(socket_path)
|
await config_file_service.jail_service.reload_all(socket_path)
|
||||||
except Exception as exc: # noqa: BLE001
|
except Exception as exc: # noqa: BLE001
|
||||||
log.warning(
|
log.warning(
|
||||||
@@ -900,11 +929,10 @@ async def remove_action_from_jail(
|
|||||||
JailNotFoundError: If *jail_name* is not defined in any config.
|
JailNotFoundError: If *jail_name* is not defined in any config.
|
||||||
ConfigWriteError: If writing fails.
|
ConfigWriteError: If writing fails.
|
||||||
"""
|
"""
|
||||||
config_file_service.safe_jail_name(jail_name)
|
_safe_jail_name(jail_name)
|
||||||
_safe_action_name(action_name)
|
_safe_action_name(action_name)
|
||||||
|
|
||||||
|
all_jails, _src = await run_blocking(_parse_jails_sync, Path(config_dir))
|
||||||
all_jails, _src = await run_blocking(config_file_service._parse_jails_sync, Path(config_dir))
|
|
||||||
if jail_name not in all_jails:
|
if jail_name not in all_jails:
|
||||||
raise JailNotFoundInConfigError(jail_name)
|
raise JailNotFoundInConfigError(jail_name)
|
||||||
|
|
||||||
@@ -916,6 +944,8 @@ async def remove_action_from_jail(
|
|||||||
|
|
||||||
if do_reload:
|
if do_reload:
|
||||||
try:
|
try:
|
||||||
|
from app.services import config_file_service
|
||||||
|
|
||||||
await config_file_service.jail_service.reload_all(socket_path)
|
await config_file_service.jail_service.reload_all(socket_path)
|
||||||
except Exception as exc: # noqa: BLE001
|
except Exception as exc: # noqa: BLE001
|
||||||
log.warning(
|
log.warning(
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -23,7 +23,14 @@ from app.exceptions import (
|
|||||||
FilterReadonlyError,
|
FilterReadonlyError,
|
||||||
JailNotFoundInConfigError,
|
JailNotFoundInConfigError,
|
||||||
)
|
)
|
||||||
import app.services.config_file_service as config_file_service
|
import app.services.jail_service as jail_service
|
||||||
|
from app.utils.config_file_utils import (
|
||||||
|
_get_active_jail_names,
|
||||||
|
_parse_jails_sync,
|
||||||
|
_safe_filter_name,
|
||||||
|
_safe_jail_name,
|
||||||
|
set_jail_local_key_sync,
|
||||||
|
)
|
||||||
from app.models.config import (
|
from app.models.config import (
|
||||||
AssignFilterRequest,
|
AssignFilterRequest,
|
||||||
FilterConfig,
|
FilterConfig,
|
||||||
@@ -37,6 +44,22 @@ from app.utils.async_utils import run_blocking
|
|||||||
|
|
||||||
log: structlog.stdlib.BoundLogger = structlog.get_logger()
|
log: structlog.stdlib.BoundLogger = structlog.get_logger()
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Internal wrappers for shared config helpers.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _parse_jails_sync(config_dir: Path) -> tuple[dict[str, dict[str, str]], Path]:
|
||||||
|
from app.services import config_file_service
|
||||||
|
|
||||||
|
return config_file_service._parse_jails_sync(config_dir)
|
||||||
|
|
||||||
|
|
||||||
|
async def _get_active_jail_names(socket_path: str) -> set[str]:
|
||||||
|
from app.services import config_file_service
|
||||||
|
|
||||||
|
return await config_file_service._get_active_jail_names(socket_path)
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Additional helper functions for this service
|
# Additional helper functions for this service
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -290,13 +313,15 @@ async def list_filters(
|
|||||||
"""
|
"""
|
||||||
filter_d = Path(config_dir) / "filter.d"
|
filter_d = Path(config_dir) / "filter.d"
|
||||||
|
|
||||||
|
from app.services import config_file_service
|
||||||
|
|
||||||
# Run the synchronous scan in a thread-pool executor.
|
# Run the synchronous scan in a thread-pool executor.
|
||||||
raw_filters: list[tuple[str, str, str, bool, str]] = await run_blocking(_parse_filters_sync, filter_d)
|
raw_filters: list[tuple[str, str, str, bool, str]] = await run_blocking(_parse_filters_sync, filter_d)
|
||||||
|
|
||||||
# Fetch active jail names and their configs concurrently.
|
# Fetch active jail names and their configs concurrently.
|
||||||
all_jails_result, active_names = await asyncio.gather(
|
all_jails_result, active_names = await asyncio.gather(
|
||||||
run_blocking(config_file_service._parse_jails_sync, Path(config_dir)),
|
run_blocking(_parse_jails_sync, Path(config_dir)),
|
||||||
config_file_service._get_active_jail_names(socket_path),
|
_get_active_jail_names(socket_path),
|
||||||
)
|
)
|
||||||
all_jails, _source_files = all_jails_result
|
all_jails, _source_files = all_jails_result
|
||||||
|
|
||||||
@@ -394,8 +419,8 @@ async def get_filter(
|
|||||||
cfg = conffile_parser.parse_filter_file(content, name=base_name, filename=f"{base_name}.conf")
|
cfg = conffile_parser.parse_filter_file(content, name=base_name, filename=f"{base_name}.conf")
|
||||||
|
|
||||||
all_jails_result, active_names = await asyncio.gather(
|
all_jails_result, active_names = await asyncio.gather(
|
||||||
run_blocking(config_file_service._parse_jails_sync, Path(config_dir)),
|
run_blocking(_parse_jails_sync, Path(config_dir)),
|
||||||
config_file_service._get_active_jail_names(socket_path),
|
_get_active_jail_names(socket_path),
|
||||||
)
|
)
|
||||||
all_jails, _source_files = all_jails_result
|
all_jails, _source_files = all_jails_result
|
||||||
filter_to_jails = _build_filter_to_jails_map(all_jails, active_names)
|
filter_to_jails = _build_filter_to_jails_map(all_jails, active_names)
|
||||||
@@ -460,7 +485,7 @@ async def update_filter(
|
|||||||
ConfigWriteError: If writing the ``.local`` file fails.
|
ConfigWriteError: If writing the ``.local`` file fails.
|
||||||
"""
|
"""
|
||||||
base_name = name[:-5] if name.endswith(".conf") or name.endswith(".local") else name
|
base_name = name[:-5] if name.endswith(".conf") or name.endswith(".local") else name
|
||||||
config_file_service.safe_filter_name(base_name)
|
_safe_filter_name(base_name)
|
||||||
|
|
||||||
# Validate regex patterns before touching the filesystem.
|
# Validate regex patterns before touching the filesystem.
|
||||||
patterns: list[str] = []
|
patterns: list[str] = []
|
||||||
@@ -489,6 +514,8 @@ async def update_filter(
|
|||||||
|
|
||||||
if do_reload:
|
if do_reload:
|
||||||
try:
|
try:
|
||||||
|
from app.services import config_file_service
|
||||||
|
|
||||||
await config_file_service.jail_service.reload_all(socket_path)
|
await config_file_service.jail_service.reload_all(socket_path)
|
||||||
except Exception as exc: # noqa: BLE001
|
except Exception as exc: # noqa: BLE001
|
||||||
log.warning(
|
log.warning(
|
||||||
@@ -531,7 +558,7 @@ async def create_filter(
|
|||||||
FilterInvalidRegexError: If any regex pattern is invalid.
|
FilterInvalidRegexError: If any regex pattern is invalid.
|
||||||
ConfigWriteError: If writing fails.
|
ConfigWriteError: If writing fails.
|
||||||
"""
|
"""
|
||||||
config_file_service.safe_filter_name(req.name)
|
_safe_filter_name(req.name)
|
||||||
|
|
||||||
filter_d = Path(config_dir) / "filter.d"
|
filter_d = Path(config_dir) / "filter.d"
|
||||||
conf_path = filter_d / f"{req.name}.conf"
|
conf_path = filter_d / f"{req.name}.conf"
|
||||||
@@ -563,6 +590,8 @@ async def create_filter(
|
|||||||
|
|
||||||
if do_reload:
|
if do_reload:
|
||||||
try:
|
try:
|
||||||
|
from app.services import config_file_service
|
||||||
|
|
||||||
await config_file_service.jail_service.reload_all(socket_path)
|
await config_file_service.jail_service.reload_all(socket_path)
|
||||||
except Exception as exc: # noqa: BLE001
|
except Exception as exc: # noqa: BLE001
|
||||||
log.warning(
|
log.warning(
|
||||||
@@ -600,7 +629,7 @@ async def delete_filter(
|
|||||||
ConfigWriteError: If deletion of the ``.local`` file fails.
|
ConfigWriteError: If deletion of the ``.local`` file fails.
|
||||||
"""
|
"""
|
||||||
base_name = name[:-5] if name.endswith(".conf") or name.endswith(".local") else name
|
base_name = name[:-5] if name.endswith(".conf") or name.endswith(".local") else name
|
||||||
config_file_service.safe_filter_name(base_name)
|
_safe_filter_name(base_name)
|
||||||
|
|
||||||
filter_d = Path(config_dir) / "filter.d"
|
filter_d = Path(config_dir) / "filter.d"
|
||||||
conf_path = filter_d / f"{base_name}.conf"
|
conf_path = filter_d / f"{base_name}.conf"
|
||||||
@@ -655,11 +684,14 @@ async def assign_filter_to_jail(
|
|||||||
``filter.d/``.
|
``filter.d/``.
|
||||||
ConfigWriteError: If writing fails.
|
ConfigWriteError: If writing fails.
|
||||||
"""
|
"""
|
||||||
config_file_service.safe_jail_name(jail_name)
|
_safe_jail_name(jail_name)
|
||||||
config_file_service.safe_filter_name(req.filter_name)
|
_safe_filter_name(req.filter_name)
|
||||||
|
from app.services import config_file_service
|
||||||
|
|
||||||
|
_safe_jail_name(jail_name)
|
||||||
|
|
||||||
# Verify the jail exists in config.
|
# Verify the jail exists in config.
|
||||||
all_jails, _src = await run_blocking(config_file_service._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:
|
if jail_name not in all_jails:
|
||||||
raise JailNotFoundInConfigError(jail_name)
|
raise JailNotFoundInConfigError(jail_name)
|
||||||
|
|
||||||
@@ -674,7 +706,7 @@ async def assign_filter_to_jail(
|
|||||||
|
|
||||||
await run_blocking( _check_filter)
|
await run_blocking( _check_filter)
|
||||||
|
|
||||||
await run_blocking(config_file_service.set_jail_local_key_sync,
|
await run_blocking(set_jail_local_key_sync,
|
||||||
Path(config_dir),
|
Path(config_dir),
|
||||||
jail_name,
|
jail_name,
|
||||||
"filter",
|
"filter",
|
||||||
@@ -683,6 +715,8 @@ async def assign_filter_to_jail(
|
|||||||
|
|
||||||
if do_reload:
|
if do_reload:
|
||||||
try:
|
try:
|
||||||
|
from app.services import config_file_service
|
||||||
|
|
||||||
await config_file_service.jail_service.reload_all(socket_path)
|
await config_file_service.jail_service.reload_all(socket_path)
|
||||||
except Exception as exc: # noqa: BLE001
|
except Exception as exc: # noqa: BLE001
|
||||||
log.warning(
|
log.warning(
|
||||||
|
|||||||
@@ -25,7 +25,12 @@ from app.exceptions import (
|
|||||||
JailNotFoundError,
|
JailNotFoundError,
|
||||||
JailNotFoundInConfigError,
|
JailNotFoundInConfigError,
|
||||||
)
|
)
|
||||||
import app.services.config_file_service as config_file_service
|
import app.services.jail_service as jail_service
|
||||||
|
from app.utils.config_file_utils import (
|
||||||
|
_build_inactive_jail,
|
||||||
|
_probe_fail2ban_running,
|
||||||
|
_safe_jail_name,
|
||||||
|
)
|
||||||
from app.models.config import (
|
from app.models.config import (
|
||||||
ActivateJailRequest,
|
ActivateJailRequest,
|
||||||
InactiveJail,
|
InactiveJail,
|
||||||
@@ -40,6 +45,21 @@ from app.utils.fail2ban_client import Fail2BanClient
|
|||||||
|
|
||||||
log: structlog.stdlib.BoundLogger = structlog.get_logger()
|
log: structlog.stdlib.BoundLogger = structlog.get_logger()
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_jails_sync(config_dir: Path) -> tuple[dict[str, dict[str, str]], dict[str, str]]:
|
||||||
|
"""Delegate the jail config parse helper through the facade."""
|
||||||
|
from app.services import config_file_service
|
||||||
|
|
||||||
|
return config_file_service._parse_jails_sync(config_dir)
|
||||||
|
|
||||||
|
|
||||||
|
async def _get_active_jail_names(socket_path: str) -> set[str]:
|
||||||
|
"""Delegate the active jail lookup helper through the facade."""
|
||||||
|
from app.services import config_file_service
|
||||||
|
|
||||||
|
return await config_file_service._get_active_jail_names(socket_path)
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Constants
|
# Constants
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -240,11 +260,11 @@ async def list_inactive_jails(
|
|||||||
inactive jails.
|
inactive jails.
|
||||||
"""
|
"""
|
||||||
parsed_result: tuple[dict[str, dict[str, str]], dict[str, str]] = await run_blocking(
|
parsed_result: tuple[dict[str, dict[str, str]], dict[str, str]] = await run_blocking(
|
||||||
config_file_service._parse_jails_sync,
|
_parse_jails_sync,
|
||||||
Path(config_dir),
|
Path(config_dir),
|
||||||
)
|
)
|
||||||
all_jails, source_files = parsed_result
|
all_jails, source_files = parsed_result
|
||||||
active_names: set[str] = await config_file_service._get_active_jail_names(socket_path)
|
active_names: set[str] = await _get_active_jail_names(socket_path)
|
||||||
|
|
||||||
inactive: list[InactiveJail] = []
|
inactive: list[InactiveJail] = []
|
||||||
for jail_name, settings in sorted(all_jails.items()):
|
for jail_name, settings in sorted(all_jails.items()):
|
||||||
@@ -253,7 +273,7 @@ async def list_inactive_jails(
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
source = source_files.get(jail_name, config_dir)
|
source = source_files.get(jail_name, config_dir)
|
||||||
inactive.append(config_file_service.build_inactive_jail(jail_name, settings, source, Path(config_dir)))
|
inactive.append(_build_inactive_jail(jail_name, settings, source, Path(config_dir)))
|
||||||
|
|
||||||
log.info(
|
log.info(
|
||||||
"inactive_jails_listed",
|
"inactive_jails_listed",
|
||||||
@@ -312,21 +332,26 @@ async def _activate_jail(
|
|||||||
~app.utils.fail2ban_client.Fail2BanConnectionError: If the fail2ban
|
~app.utils.fail2ban_client.Fail2BanConnectionError: If the fail2ban
|
||||||
socket is unreachable during reload.
|
socket is unreachable during reload.
|
||||||
"""
|
"""
|
||||||
config_file_service.safe_jail_name(name)
|
_safe_jail_name(name)
|
||||||
|
|
||||||
all_jails, _source_files = await run_blocking(config_file_service._parse_jails_sync, Path(config_dir))
|
all_jails, _source_files = await run_blocking(_parse_jails_sync, Path(config_dir))
|
||||||
|
|
||||||
if name not in all_jails:
|
if name not in all_jails:
|
||||||
raise JailNotFoundInConfigError(name)
|
raise JailNotFoundInConfigError(name)
|
||||||
|
|
||||||
active_names = await config_file_service._get_active_jail_names(socket_path)
|
active_names = await _get_active_jail_names(socket_path)
|
||||||
if name in active_names:
|
if name in active_names:
|
||||||
raise JailAlreadyActiveError(name)
|
raise JailAlreadyActiveError(name)
|
||||||
|
|
||||||
# ---------------------------------------------------------------------- #
|
# ---------------------------------------------------------------------- #
|
||||||
# Pre-activation validation — collect warnings but do not block #
|
# Pre-activation validation — collect warnings but do not block #
|
||||||
# ---------------------------------------------------------------------- #
|
# ---------------------------------------------------------------------- #
|
||||||
validation_result: JailValidationResult = await run_blocking(config_file_service._validate_jail_config_sync, Path(config_dir), name
|
from app.services import config_file_service
|
||||||
|
|
||||||
|
validation_result: JailValidationResult = await run_blocking(
|
||||||
|
config_file_service._validate_jail_config_sync,
|
||||||
|
Path(config_dir),
|
||||||
|
name,
|
||||||
)
|
)
|
||||||
warnings: list[str] = [f"{i.field}: {i.message}" for i in validation_result.issues]
|
warnings: list[str] = [f"{i.field}: {i.message}" for i in validation_result.issues]
|
||||||
if warnings:
|
if warnings:
|
||||||
@@ -380,6 +405,8 @@ async def _activate_jail(
|
|||||||
# Activation reload — if it fails, roll back immediately #
|
# Activation reload — if it fails, roll back immediately #
|
||||||
# ---------------------------------------------------------------------- #
|
# ---------------------------------------------------------------------- #
|
||||||
try:
|
try:
|
||||||
|
from app.services import config_file_service
|
||||||
|
|
||||||
await config_file_service.jail_service.reload_all(socket_path, include_jails=[name])
|
await config_file_service.jail_service.reload_all(socket_path, include_jails=[name])
|
||||||
except JailNotFoundError as exc:
|
except JailNotFoundError as exc:
|
||||||
# Jail configuration is invalid (e.g. missing logpath that prevents
|
# Jail configuration is invalid (e.g. missing logpath that prevents
|
||||||
@@ -522,6 +549,8 @@ async def _rollback_activation_async(
|
|||||||
|
|
||||||
# Step 2 — reload fail2ban with the restored config.
|
# Step 2 — reload fail2ban with the restored config.
|
||||||
try:
|
try:
|
||||||
|
from app.services import config_file_service
|
||||||
|
|
||||||
await config_file_service.jail_service.reload_all(socket_path)
|
await config_file_service.jail_service.reload_all(socket_path)
|
||||||
log.info("jail_activation_rollback_reload_ok", jail=name)
|
log.info("jail_activation_rollback_reload_ok", jail=name)
|
||||||
except Exception as exc: # noqa: BLE001
|
except Exception as exc: # noqa: BLE001
|
||||||
@@ -529,6 +558,8 @@ async def _rollback_activation_async(
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# Step 3 — wait for fail2ban to come back.
|
# Step 3 — wait for fail2ban to come back.
|
||||||
|
from app.services import config_file_service
|
||||||
|
|
||||||
for attempt in range(_POST_RELOAD_MAX_ATTEMPTS):
|
for attempt in range(_POST_RELOAD_MAX_ATTEMPTS):
|
||||||
if attempt > 0:
|
if attempt > 0:
|
||||||
await asyncio.sleep(_POST_RELOAD_PROBE_INTERVAL)
|
await asyncio.sleep(_POST_RELOAD_PROBE_INTERVAL)
|
||||||
@@ -583,6 +614,8 @@ async def _deactivate_jail(
|
|||||||
~app.utils.fail2ban_client.Fail2BanConnectionError: If the fail2ban
|
~app.utils.fail2ban_client.Fail2BanConnectionError: If the fail2ban
|
||||||
socket is unreachable during reload.
|
socket is unreachable during reload.
|
||||||
"""
|
"""
|
||||||
|
from app.services import config_file_service
|
||||||
|
|
||||||
config_file_service.safe_jail_name(name)
|
config_file_service.safe_jail_name(name)
|
||||||
|
|
||||||
all_jails, _source_files = await run_blocking(config_file_service._parse_jails_sync, Path(config_dir))
|
all_jails, _source_files = await run_blocking(config_file_service._parse_jails_sync, Path(config_dir))
|
||||||
@@ -602,6 +635,8 @@ async def _deactivate_jail(
|
|||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
from app.services import config_file_service
|
||||||
|
|
||||||
await config_file_service.jail_service.reload_all(socket_path, exclude_jails=[name])
|
await config_file_service.jail_service.reload_all(socket_path, exclude_jails=[name])
|
||||||
except Exception as exc: # noqa: BLE001
|
except Exception as exc: # noqa: BLE001
|
||||||
log.warning("reload_after_deactivate_failed", jail=name, error=str(exc))
|
log.warning("reload_after_deactivate_failed", jail=name, error=str(exc))
|
||||||
@@ -638,6 +673,8 @@ async def delete_jail_local_override(
|
|||||||
delete the live config file).
|
delete the live config file).
|
||||||
ConfigWriteError: If the file cannot be deleted.
|
ConfigWriteError: If the file cannot be deleted.
|
||||||
"""
|
"""
|
||||||
|
from app.services import config_file_service
|
||||||
|
|
||||||
config_file_service.safe_jail_name(name)
|
config_file_service.safe_jail_name(name)
|
||||||
|
|
||||||
all_jails, _source_files = await run_blocking(config_file_service._parse_jails_sync, Path(config_dir))
|
all_jails, _source_files = await run_blocking(config_file_service._parse_jails_sync, Path(config_dir))
|
||||||
@@ -678,8 +715,11 @@ async def validate_jail_config(
|
|||||||
Raises:
|
Raises:
|
||||||
JailNameError: If *name* contains invalid characters.
|
JailNameError: If *name* contains invalid characters.
|
||||||
"""
|
"""
|
||||||
|
from app.services import config_file_service
|
||||||
|
|
||||||
config_file_service.safe_jail_name(name)
|
config_file_service.safe_jail_name(name)
|
||||||
return await run_blocking(config_file_service._validate_jail_config_sync,
|
return await run_blocking(
|
||||||
|
config_file_service._validate_jail_config_sync,
|
||||||
Path(config_dir),
|
Path(config_dir),
|
||||||
name,
|
name,
|
||||||
)
|
)
|
||||||
@@ -725,7 +765,7 @@ async def _rollback_jail(
|
|||||||
JailNameError: If *name* contains invalid characters.
|
JailNameError: If *name* contains invalid characters.
|
||||||
ConfigWriteError: If writing the ``.local`` file fails.
|
ConfigWriteError: If writing the ``.local`` file fails.
|
||||||
"""
|
"""
|
||||||
config_file_service.safe_jail_name(name)
|
_safe_jail_name(name)
|
||||||
|
|
||||||
|
|
||||||
# Write enabled=false — this must succeed even when fail2ban is down.
|
# Write enabled=false — this must succeed even when fail2ban is down.
|
||||||
@@ -737,18 +777,23 @@ async def _rollback_jail(
|
|||||||
)
|
)
|
||||||
log.info("jail_rolled_back_disabled", jail=name)
|
log.info("jail_rolled_back_disabled", jail=name)
|
||||||
|
|
||||||
|
from app.services import config_file_service
|
||||||
|
|
||||||
# Attempt to start the daemon.
|
# Attempt to start the daemon.
|
||||||
started = await config_file_service.start_daemon(start_cmd_parts)
|
started = await config_file_service.start_daemon(start_cmd_parts)
|
||||||
log.info("jail_rollback_start_attempted", jail=name, start_ok=started)
|
log.info("jail_rollback_start_attempted", jail=name, start_ok=started)
|
||||||
|
|
||||||
# Wait for the socket to come back.
|
# Wait for the socket to come back.
|
||||||
fail2ban_running = await config_file_service.wait_for_fail2ban(socket_path, max_wait_seconds=10.0, poll_interval=2.0)
|
fail2ban_running = await config_file_service.wait_for_fail2ban(
|
||||||
|
socket_path,
|
||||||
|
max_wait_seconds=10.0,
|
||||||
|
poll_interval=2.0,
|
||||||
|
)
|
||||||
|
|
||||||
active_jails = 0
|
active_jails = 0
|
||||||
if fail2ban_running:
|
if fail2ban_running:
|
||||||
names = await config_file_service._get_active_jail_names(socket_path)
|
names = await config_file_service._get_active_jail_names(socket_path)
|
||||||
active_jails = len(names)
|
active_jails = len(names)
|
||||||
|
|
||||||
if fail2ban_running:
|
if fail2ban_running:
|
||||||
log.info("jail_rollback_success", jail=name, active_jails=active_jails)
|
log.info("jail_rollback_success", jail=name, active_jails=active_jails)
|
||||||
return RollbackResponse(
|
return RollbackResponse(
|
||||||
|
|||||||
Reference in New Issue
Block a user