|
|
|
|
@@ -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,
|
|
|
|
|
|