Fix config sub-router prefixes and router tags
This commit is contained in:
@@ -11,19 +11,16 @@ from app.exceptions import (
|
|||||||
ActionNotFoundError,
|
ActionNotFoundError,
|
||||||
ActionReadonlyError,
|
ActionReadonlyError,
|
||||||
ConfigWriteError,
|
ConfigWriteError,
|
||||||
JailNameError,
|
|
||||||
JailNotFoundInConfigError,
|
|
||||||
)
|
)
|
||||||
from app.models.config import (
|
from app.models.config import (
|
||||||
ActionConfig,
|
ActionConfig,
|
||||||
ActionCreateRequest,
|
ActionCreateRequest,
|
||||||
ActionListResponse,
|
ActionListResponse,
|
||||||
ActionUpdateRequest,
|
ActionUpdateRequest,
|
||||||
AssignActionRequest,
|
|
||||||
)
|
)
|
||||||
from app.services import action_config_service
|
from app.services import action_config_service
|
||||||
|
|
||||||
router: APIRouter = APIRouter()
|
router: APIRouter = APIRouter(prefix="/actions", tags=["Action Config"])
|
||||||
|
|
||||||
_ActionNamePath = Annotated[
|
_ActionNamePath = Annotated[
|
||||||
str,
|
str,
|
||||||
@@ -54,7 +51,7 @@ def _not_found(name: str) -> HTTPException:
|
|||||||
)
|
)
|
||||||
|
|
||||||
@router.get(
|
@router.get(
|
||||||
"/actions",
|
"/",
|
||||||
response_model=ActionListResponse,
|
response_model=ActionListResponse,
|
||||||
summary="List all available actions with active/inactive status",
|
summary="List all available actions with active/inactive status",
|
||||||
)
|
)
|
||||||
@@ -91,7 +88,7 @@ async def list_actions(
|
|||||||
|
|
||||||
|
|
||||||
@router.get(
|
@router.get(
|
||||||
"/actions/{name}",
|
"/{name}",
|
||||||
response_model=ActionConfig,
|
response_model=ActionConfig,
|
||||||
summary="Return full parsed detail for a single action",
|
summary="Return full parsed detail for a single action",
|
||||||
)
|
)
|
||||||
@@ -133,7 +130,7 @@ async def get_action(
|
|||||||
|
|
||||||
|
|
||||||
@router.put(
|
@router.put(
|
||||||
"/actions/{name}",
|
"/{name}",
|
||||||
response_model=ActionConfig,
|
response_model=ActionConfig,
|
||||||
summary="Update an action's .local override with new lifecycle command values",
|
summary="Update an action's .local override with new lifecycle command values",
|
||||||
)
|
)
|
||||||
@@ -182,7 +179,7 @@ async def update_action(
|
|||||||
|
|
||||||
|
|
||||||
@router.post(
|
@router.post(
|
||||||
"/actions",
|
"/",
|
||||||
response_model=ActionConfig,
|
response_model=ActionConfig,
|
||||||
status_code=status.HTTP_201_CREATED,
|
status_code=status.HTTP_201_CREATED,
|
||||||
summary="Create a new user-defined action",
|
summary="Create a new user-defined action",
|
||||||
@@ -233,7 +230,7 @@ async def create_action(
|
|||||||
|
|
||||||
|
|
||||||
@router.delete(
|
@router.delete(
|
||||||
"/actions/{name}",
|
"/{name}",
|
||||||
status_code=status.HTTP_204_NO_CONTENT,
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
summary="Delete a user-created action's .local file",
|
summary="Delete a user-created action's .local file",
|
||||||
)
|
)
|
||||||
@@ -279,108 +276,6 @@ async def delete_action(
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
@router.post(
|
|
||||||
"/jails/{name}/action",
|
|
||||||
status_code=status.HTTP_204_NO_CONTENT,
|
|
||||||
summary="Add an action to a jail",
|
|
||||||
)
|
|
||||||
async def assign_action_to_jail(
|
|
||||||
request: Request,
|
|
||||||
_auth: AuthDep,
|
|
||||||
config_dir: Fail2BanConfigDirDep,
|
|
||||||
socket_path: Fail2BanSocketDep,
|
|
||||||
name: _NamePath,
|
|
||||||
body: AssignActionRequest,
|
|
||||||
reload: bool = Query(default=False, description="Reload fail2ban after assigning."),
|
|
||||||
) -> None:
|
|
||||||
"""Append an action entry to the jail's ``.local`` config.
|
|
||||||
|
|
||||||
Existing keys in the jail's ``.local`` file are preserved. If the file
|
|
||||||
does not exist it is created. The action is not duplicated if it is
|
|
||||||
already present.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
request: FastAPI request object.
|
|
||||||
_auth: Validated session.
|
|
||||||
name: Jail name.
|
|
||||||
body: Action to add plus optional per-jail parameters.
|
|
||||||
reload: When ``true``, trigger a fail2ban reload after writing.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
HTTPException: 400 if *name* or *action_name* contain invalid characters.
|
|
||||||
HTTPException: 404 if the jail or action does not exist.
|
|
||||||
HTTPException: 500 if writing fails.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
await action_config_service.assign_action_to_jail(config_dir, socket_path, name, body, do_reload=reload)
|
|
||||||
except (JailNameError, ActionNameError) as exc:
|
|
||||||
raise _bad_request(str(exc)) from exc
|
|
||||||
except JailNotFoundInConfigError:
|
|
||||||
raise _not_found(name) from None
|
|
||||||
except ActionNotFoundError as exc:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_404_NOT_FOUND,
|
|
||||||
detail=f"Action not found: {exc.name!r}",
|
|
||||||
) from exc
|
|
||||||
except ConfigWriteError as exc:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
||||||
detail=f"Failed to write jail override: {exc}",
|
|
||||||
) from exc
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@router.delete(
|
|
||||||
"/jails/{name}/action/{action_name}",
|
|
||||||
status_code=status.HTTP_204_NO_CONTENT,
|
|
||||||
summary="Remove an action from a jail",
|
|
||||||
)
|
|
||||||
async def remove_action_from_jail(
|
|
||||||
request: Request,
|
|
||||||
_auth: AuthDep,
|
|
||||||
config_dir: Fail2BanConfigDirDep,
|
|
||||||
socket_path: Fail2BanSocketDep,
|
|
||||||
name: _NamePath,
|
|
||||||
action_name: Annotated[str, Path(description="Action base name to remove.")],
|
|
||||||
reload: bool = Query(default=False, description="Reload fail2ban after removing."),
|
|
||||||
) -> None:
|
|
||||||
"""Remove an action from the jail's ``.local`` config.
|
|
||||||
|
|
||||||
If the jail has no ``.local`` file or the action is not listed there,
|
|
||||||
the call is silently idempotent.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
request: FastAPI request object.
|
|
||||||
_auth: Validated session.
|
|
||||||
name: Jail name.
|
|
||||||
action_name: Base name of the action to remove.
|
|
||||||
reload: When ``true``, trigger a fail2ban reload after writing.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
HTTPException: 400 if *name* or *action_name* contain invalid characters.
|
|
||||||
HTTPException: 404 if the jail is not found in config files.
|
|
||||||
HTTPException: 500 if writing fails.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
await action_config_service.remove_action_from_jail(
|
|
||||||
config_dir,
|
|
||||||
socket_path,
|
|
||||||
name,
|
|
||||||
action_name,
|
|
||||||
do_reload=reload,
|
|
||||||
)
|
|
||||||
except (JailNameError, ActionNameError) as exc:
|
|
||||||
raise _bad_request(str(exc)) from exc
|
|
||||||
except JailNotFoundInConfigError:
|
|
||||||
raise _not_found(name) from None
|
|
||||||
except ConfigWriteError as exc:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
||||||
detail=f"Failed to write jail override: {exc}",
|
|
||||||
) from exc
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# fail2ban log viewer endpoints
|
# fail2ban log viewer endpoints
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ from app.models.config import (
|
|||||||
from app.services import config_file_service, config_service, jail_service, log_service, setup_service
|
from app.services import config_file_service, config_service, jail_service, log_service, setup_service
|
||||||
from app.utils.fail2ban_client import Fail2BanConnectionError
|
from app.utils.fail2ban_client import Fail2BanConnectionError
|
||||||
|
|
||||||
router: APIRouter = APIRouter()
|
router: APIRouter = APIRouter(tags=["Config Misc"])
|
||||||
|
|
||||||
|
|
||||||
def _bad_gateway(exc: Exception) -> HTTPException:
|
def _bad_gateway(exc: Exception) -> HTTPException:
|
||||||
|
|||||||
@@ -12,11 +12,8 @@ from app.exceptions import (
|
|||||||
FilterNameError,
|
FilterNameError,
|
||||||
FilterNotFoundError,
|
FilterNotFoundError,
|
||||||
FilterReadonlyError,
|
FilterReadonlyError,
|
||||||
JailNameError,
|
|
||||||
JailNotFoundInConfigError,
|
|
||||||
)
|
)
|
||||||
from app.models.config import (
|
from app.models.config import (
|
||||||
AssignFilterRequest,
|
|
||||||
FilterConfig,
|
FilterConfig,
|
||||||
FilterCreateRequest,
|
FilterCreateRequest,
|
||||||
FilterListResponse,
|
FilterListResponse,
|
||||||
@@ -24,7 +21,7 @@ from app.models.config import (
|
|||||||
)
|
)
|
||||||
from app.services import filter_config_service
|
from app.services import filter_config_service
|
||||||
|
|
||||||
router: APIRouter = APIRouter()
|
router: APIRouter = APIRouter(prefix="/filters", tags=["Filter Config"])
|
||||||
|
|
||||||
_NamePath = Annotated[
|
_NamePath = Annotated[
|
||||||
str,
|
str,
|
||||||
@@ -59,7 +56,7 @@ def _not_found(name: str) -> HTTPException:
|
|||||||
)
|
)
|
||||||
|
|
||||||
@router.get(
|
@router.get(
|
||||||
"/filters",
|
"/",
|
||||||
response_model=FilterListResponse,
|
response_model=FilterListResponse,
|
||||||
summary="List all available filters with active/inactive status",
|
summary="List all available filters with active/inactive status",
|
||||||
)
|
)
|
||||||
@@ -97,7 +94,7 @@ async def list_filters(
|
|||||||
|
|
||||||
|
|
||||||
@router.get(
|
@router.get(
|
||||||
"/filters/{name}",
|
"/{name}",
|
||||||
response_model=FilterConfig,
|
response_model=FilterConfig,
|
||||||
summary="Return full parsed detail for a single filter",
|
summary="Return full parsed detail for a single filter",
|
||||||
)
|
)
|
||||||
@@ -156,7 +153,7 @@ def _filter_not_found(name: str) -> HTTPException:
|
|||||||
|
|
||||||
|
|
||||||
@router.put(
|
@router.put(
|
||||||
"/filters/{name}",
|
"/{name}",
|
||||||
response_model=FilterConfig,
|
response_model=FilterConfig,
|
||||||
summary="Update a filter's .local override with new regex/pattern values",
|
summary="Update a filter's .local override with new regex/pattern values",
|
||||||
)
|
)
|
||||||
@@ -210,7 +207,7 @@ async def update_filter(
|
|||||||
|
|
||||||
|
|
||||||
@router.post(
|
@router.post(
|
||||||
"/filters",
|
"/",
|
||||||
response_model=FilterConfig,
|
response_model=FilterConfig,
|
||||||
status_code=status.HTTP_201_CREATED,
|
status_code=status.HTTP_201_CREATED,
|
||||||
summary="Create a new user-defined filter",
|
summary="Create a new user-defined filter",
|
||||||
@@ -265,7 +262,7 @@ async def create_filter(
|
|||||||
|
|
||||||
|
|
||||||
@router.delete(
|
@router.delete(
|
||||||
"/filters/{name}",
|
"/{name}",
|
||||||
status_code=status.HTTP_204_NO_CONTENT,
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
summary="Delete a user-created filter's .local file",
|
summary="Delete a user-created filter's .local file",
|
||||||
)
|
)
|
||||||
@@ -313,55 +310,6 @@ async def delete_filter(
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
@router.post(
|
|
||||||
"/jails/{name}/filter",
|
|
||||||
status_code=status.HTTP_204_NO_CONTENT,
|
|
||||||
summary="Assign a filter to a jail",
|
|
||||||
)
|
|
||||||
async def assign_filter_to_jail(
|
|
||||||
request: Request,
|
|
||||||
_auth: AuthDep,
|
|
||||||
config_dir: Fail2BanConfigDirDep,
|
|
||||||
socket_path: Fail2BanSocketDep,
|
|
||||||
name: _NamePath,
|
|
||||||
body: AssignFilterRequest,
|
|
||||||
reload: bool = Query(default=False, description="Reload fail2ban after assigning."),
|
|
||||||
) -> None:
|
|
||||||
"""Write ``filter = {filter_name}`` to the jail's ``.local`` config.
|
|
||||||
|
|
||||||
Existing keys in the jail's ``.local`` file are preserved. If the file
|
|
||||||
does not exist it is created.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
request: FastAPI request object.
|
|
||||||
_auth: Validated session.
|
|
||||||
name: Jail name.
|
|
||||||
body: Filter to assign.
|
|
||||||
reload: When ``true``, trigger a fail2ban reload after writing.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
HTTPException: 400 if *name* or *filter_name* contain invalid characters.
|
|
||||||
HTTPException: 404 if the jail or filter does not exist.
|
|
||||||
HTTPException: 500 if writing fails.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
await filter_config_service.assign_filter_to_jail(config_dir, socket_path, name, body, do_reload=reload)
|
|
||||||
except (JailNameError, FilterNameError) as exc:
|
|
||||||
raise _bad_request(str(exc)) from exc
|
|
||||||
except JailNotFoundInConfigError:
|
|
||||||
raise _not_found(name) from None
|
|
||||||
except FilterNotFoundError as exc:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_404_NOT_FOUND,
|
|
||||||
detail=f"Filter not found: {exc.name!r}",
|
|
||||||
) from exc
|
|
||||||
except ConfigWriteError as exc:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
||||||
detail=f"Failed to write jail override: {exc}",
|
|
||||||
) from exc
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Action discovery endpoints (Task 3.1)
|
# Action discovery endpoints (Task 3.1)
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -13,9 +13,13 @@ from app.dependencies import (
|
|||||||
PendingRecoveryDep,
|
PendingRecoveryDep,
|
||||||
)
|
)
|
||||||
from app.exceptions import (
|
from app.exceptions import (
|
||||||
|
ActionNameError,
|
||||||
|
ActionNotFoundError,
|
||||||
ConfigOperationError,
|
ConfigOperationError,
|
||||||
ConfigValidationError,
|
ConfigValidationError,
|
||||||
ConfigWriteError,
|
ConfigWriteError,
|
||||||
|
FilterNameError,
|
||||||
|
FilterNotFoundError,
|
||||||
JailAlreadyActiveError,
|
JailAlreadyActiveError,
|
||||||
JailAlreadyInactiveError,
|
JailAlreadyInactiveError,
|
||||||
JailNameError,
|
JailNameError,
|
||||||
@@ -25,6 +29,8 @@ from app.exceptions import (
|
|||||||
from app.models.config import (
|
from app.models.config import (
|
||||||
ActivateJailRequest,
|
ActivateJailRequest,
|
||||||
AddLogPathRequest,
|
AddLogPathRequest,
|
||||||
|
AssignActionRequest,
|
||||||
|
AssignFilterRequest,
|
||||||
InactiveJailListResponse,
|
InactiveJailListResponse,
|
||||||
JailActivationResponse,
|
JailActivationResponse,
|
||||||
JailConfigListResponse,
|
JailConfigListResponse,
|
||||||
@@ -34,10 +40,15 @@ from app.models.config import (
|
|||||||
PendingRecovery,
|
PendingRecovery,
|
||||||
RollbackResponse,
|
RollbackResponse,
|
||||||
)
|
)
|
||||||
from app.services import config_service, jail_config_service, jail_service
|
from app.services import (
|
||||||
|
action_config_service,
|
||||||
|
config_service,
|
||||||
|
filter_config_service,
|
||||||
|
jail_config_service,
|
||||||
|
)
|
||||||
from app.utils.fail2ban_client import Fail2BanConnectionError
|
from app.utils.fail2ban_client import Fail2BanConnectionError
|
||||||
|
|
||||||
router: APIRouter = APIRouter()
|
router: APIRouter = APIRouter(prefix="/jails", tags=["Jail Config"])
|
||||||
|
|
||||||
_NamePath = Annotated[str, Path(description='Jail name as configured in fail2ban.')]
|
_NamePath = Annotated[str, Path(description='Jail name as configured in fail2ban.')]
|
||||||
|
|
||||||
@@ -69,8 +80,22 @@ def _bad_request(message: str) -> HTTPException:
|
|||||||
detail=message,
|
detail=message,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _filter_not_found(name: str) -> HTTPException:
|
||||||
|
return HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail=f"Filter not found: {name!r}",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _action_not_found(name: str) -> HTTPException:
|
||||||
|
return HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail=f"Action not found: {name!r}",
|
||||||
|
)
|
||||||
|
|
||||||
@router.get(
|
@router.get(
|
||||||
"/jails",
|
"/",
|
||||||
response_model=JailConfigListResponse,
|
response_model=JailConfigListResponse,
|
||||||
summary="List configuration for all active jails",
|
summary="List configuration for all active jails",
|
||||||
)
|
)
|
||||||
@@ -100,7 +125,7 @@ async def get_jail_configs(
|
|||||||
|
|
||||||
|
|
||||||
@router.get(
|
@router.get(
|
||||||
"/jails/inactive",
|
"/inactive",
|
||||||
response_model=InactiveJailListResponse,
|
response_model=InactiveJailListResponse,
|
||||||
summary="List all inactive jails discovered in config files",
|
summary="List all inactive jails discovered in config files",
|
||||||
)
|
)
|
||||||
@@ -129,7 +154,7 @@ async def get_inactive_jails(
|
|||||||
|
|
||||||
|
|
||||||
@router.get(
|
@router.get(
|
||||||
"/jails/{name}",
|
"/{name}",
|
||||||
response_model=JailConfigResponse,
|
response_model=JailConfigResponse,
|
||||||
summary="Return configuration for a single jail",
|
summary="Return configuration for a single jail",
|
||||||
)
|
)
|
||||||
@@ -164,7 +189,7 @@ async def get_jail_config(
|
|||||||
|
|
||||||
|
|
||||||
@router.put(
|
@router.put(
|
||||||
"/jails/{name}",
|
"/{name}",
|
||||||
status_code=status.HTTP_204_NO_CONTENT,
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
summary="Update jail configuration",
|
summary="Update jail configuration",
|
||||||
)
|
)
|
||||||
@@ -212,7 +237,7 @@ async def update_jail_config(
|
|||||||
|
|
||||||
|
|
||||||
@router.post(
|
@router.post(
|
||||||
"/jails/{name}/logpath",
|
"/{name}/logpath",
|
||||||
status_code=status.HTTP_204_NO_CONTENT,
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
summary="Add a log file path to an existing jail",
|
summary="Add a log file path to an existing jail",
|
||||||
)
|
)
|
||||||
@@ -252,7 +277,7 @@ async def add_log_path(
|
|||||||
|
|
||||||
|
|
||||||
@router.delete(
|
@router.delete(
|
||||||
"/jails/{name}/logpath",
|
"/{name}/logpath",
|
||||||
status_code=status.HTTP_204_NO_CONTENT,
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
summary="Remove a monitored log path from a jail",
|
summary="Remove a monitored log path from a jail",
|
||||||
)
|
)
|
||||||
@@ -292,7 +317,7 @@ async def delete_log_path(
|
|||||||
|
|
||||||
|
|
||||||
@router.post(
|
@router.post(
|
||||||
"/jails/{name}/activate",
|
"/{name}/activate",
|
||||||
response_model=JailActivationResponse,
|
response_model=JailActivationResponse,
|
||||||
summary="Activate an inactive jail",
|
summary="Activate an inactive jail",
|
||||||
)
|
)
|
||||||
@@ -353,7 +378,7 @@ async def activate_jail(
|
|||||||
|
|
||||||
|
|
||||||
@router.post(
|
@router.post(
|
||||||
"/jails/{name}/deactivate",
|
"/{name}/deactivate",
|
||||||
response_model=JailActivationResponse,
|
response_model=JailActivationResponse,
|
||||||
summary="Deactivate an active jail",
|
summary="Deactivate an active jail",
|
||||||
)
|
)
|
||||||
@@ -409,7 +434,7 @@ async def deactivate_jail(
|
|||||||
|
|
||||||
|
|
||||||
@router.delete(
|
@router.delete(
|
||||||
"/jails/{name}/local",
|
"/{name}/local",
|
||||||
status_code=status.HTTP_204_NO_CONTENT,
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
summary="Delete the jail.d override file for an inactive jail",
|
summary="Delete the jail.d override file for an inactive jail",
|
||||||
)
|
)
|
||||||
@@ -468,7 +493,7 @@ async def delete_jail_local_override(
|
|||||||
|
|
||||||
|
|
||||||
@router.post(
|
@router.post(
|
||||||
"/jails/{name}/validate",
|
"/{name}/validate",
|
||||||
response_model=JailValidationResult,
|
response_model=JailValidationResult,
|
||||||
summary="Validate jail configuration before activation",
|
summary="Validate jail configuration before activation",
|
||||||
)
|
)
|
||||||
@@ -531,7 +556,7 @@ async def get_pending_recovery(
|
|||||||
|
|
||||||
|
|
||||||
@router.post(
|
@router.post(
|
||||||
"/jails/{name}/rollback",
|
"/{name}/rollback",
|
||||||
response_model=RollbackResponse,
|
response_model=RollbackResponse,
|
||||||
summary="Disable a bad jail config and restart fail2ban",
|
summary="Disable a bad jail config and restart fail2ban",
|
||||||
)
|
)
|
||||||
@@ -578,6 +603,153 @@ async def rollback_jail(
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@router.post(
|
||||||
|
"/{name}/filter",
|
||||||
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
|
summary="Assign a filter to a jail",
|
||||||
|
)
|
||||||
|
async def assign_filter_to_jail(
|
||||||
|
request: Request,
|
||||||
|
_auth: AuthDep,
|
||||||
|
config_dir: Fail2BanConfigDirDep,
|
||||||
|
socket_path: Fail2BanSocketDep,
|
||||||
|
name: _NamePath,
|
||||||
|
body: AssignFilterRequest,
|
||||||
|
reload: bool = Query(default=False, description="Reload fail2ban after assigning."),
|
||||||
|
) -> None:
|
||||||
|
"""Write ``filter = {filter_name}`` to the jail's ``.local`` config.
|
||||||
|
|
||||||
|
Existing keys in the jail's ``.local`` file are preserved. If the file
|
||||||
|
does not exist it is created.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request: FastAPI request object.
|
||||||
|
_auth: Validated session.
|
||||||
|
name: Jail name.
|
||||||
|
body: Filter to assign.
|
||||||
|
reload: When ``true``, trigger a fail2ban reload after writing.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
HTTPException: 400 if *name* or *filter_name* contain invalid characters.
|
||||||
|
HTTPException: 404 if the jail or filter does not exist.
|
||||||
|
HTTPException: 500 if writing fails.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
await filter_config_service.assign_filter_to_jail(config_dir, socket_path, name, body, do_reload=reload)
|
||||||
|
except (JailNameError, FilterNameError) as exc:
|
||||||
|
raise _bad_request(str(exc)) from exc
|
||||||
|
except JailNotFoundInConfigError:
|
||||||
|
raise _not_found(name) from None
|
||||||
|
except FilterNotFoundError as exc:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail=f"Filter not found: {exc.name!r}",
|
||||||
|
) from exc
|
||||||
|
except ConfigWriteError as exc:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
|
detail=f"Failed to write jail override: {exc}",
|
||||||
|
) from exc
|
||||||
|
|
||||||
|
|
||||||
|
@router.post(
|
||||||
|
"/{name}/action",
|
||||||
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
|
summary="Add an action to a jail",
|
||||||
|
)
|
||||||
|
async def assign_action_to_jail(
|
||||||
|
request: Request,
|
||||||
|
_auth: AuthDep,
|
||||||
|
config_dir: Fail2BanConfigDirDep,
|
||||||
|
socket_path: Fail2BanSocketDep,
|
||||||
|
name: _NamePath,
|
||||||
|
body: AssignActionRequest,
|
||||||
|
reload: bool = Query(default=False, description="Reload fail2ban after assigning."),
|
||||||
|
) -> None:
|
||||||
|
"""Append an action entry to the jail's ``.local`` config.
|
||||||
|
|
||||||
|
Existing keys in the jail's ``.local`` file are preserved. If the file
|
||||||
|
does not exist it is created. The action is not duplicated if it is
|
||||||
|
already present.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request: FastAPI request object.
|
||||||
|
_auth: Validated session.
|
||||||
|
name: Jail name.
|
||||||
|
body: Action to add plus optional per-jail parameters.
|
||||||
|
reload: When ``true``, trigger a fail2ban reload after writing.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
HTTPException: 400 if *name* or *action_name* contain invalid characters.
|
||||||
|
HTTPException: 404 if the jail or action does not exist.
|
||||||
|
HTTPException: 500 if writing fails.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
await action_config_service.assign_action_to_jail(config_dir, socket_path, name, body, do_reload=reload)
|
||||||
|
except (JailNameError, ActionNameError) as exc:
|
||||||
|
raise _bad_request(str(exc)) from exc
|
||||||
|
except JailNotFoundInConfigError:
|
||||||
|
raise _not_found(name) from None
|
||||||
|
except ActionNotFoundError as exc:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail=f"Action not found: {exc.name!r}",
|
||||||
|
) from exc
|
||||||
|
except ConfigWriteError as exc:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
|
detail=f"Failed to write jail override: {exc}",
|
||||||
|
) from exc
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete(
|
||||||
|
"/{name}/action/{action_name}",
|
||||||
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
|
summary="Remove an action from a jail",
|
||||||
|
)
|
||||||
|
async def remove_action_from_jail(
|
||||||
|
request: Request,
|
||||||
|
_auth: AuthDep,
|
||||||
|
config_dir: Fail2BanConfigDirDep,
|
||||||
|
socket_path: Fail2BanSocketDep,
|
||||||
|
name: _NamePath,
|
||||||
|
action_name: Annotated[str, Path(description="Action base name to remove.")],
|
||||||
|
reload: bool = Query(default=False, description="Reload fail2ban after removing."),
|
||||||
|
) -> None:
|
||||||
|
"""Remove an action from the jail's ``.local`` config.
|
||||||
|
|
||||||
|
If the jail has no ``.local`` file or the action is not listed there,
|
||||||
|
the call is silently idempotent.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request: FastAPI request object.
|
||||||
|
_auth: Validated session.
|
||||||
|
name: Jail name.
|
||||||
|
action_name: Base name of the action to remove.
|
||||||
|
reload: When ``true``, trigger a fail2ban reload after writing.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
HTTPException: 400 if *name* or *action_name* contain invalid characters.
|
||||||
|
HTTPException: 404 if the jail is not found in config files.
|
||||||
|
HTTPException: 500 if writing fails.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
await action_config_service.remove_action_from_jail(
|
||||||
|
config_dir,
|
||||||
|
socket_path,
|
||||||
|
name,
|
||||||
|
action_name,
|
||||||
|
do_reload=reload,
|
||||||
|
)
|
||||||
|
except (JailNameError, ActionNameError) as exc:
|
||||||
|
raise _bad_request(str(exc)) from exc
|
||||||
|
except JailNotFoundInConfigError:
|
||||||
|
raise _not_found(name) from None
|
||||||
|
except ConfigWriteError as exc:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
|
detail=f"Failed to write jail override: {exc}",
|
||||||
|
) from exc
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Filter discovery endpoints (Task 2.1)
|
# Filter discovery endpoints (Task 2.1)
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user