feat(backend): add raw file write endpoints for jail, filter, and action configs
Add PUT endpoints for overwriting raw content of jail.d, filter.d, and action.d config files. Mirrors the existing GET endpoints so the frontend can show an editable raw-text view of each config file.
This commit is contained in:
@@ -6,6 +6,7 @@ files directly on the filesystem (``jail.d/``, ``filter.d/``, ``action.d/``).
|
||||
Endpoints:
|
||||
* ``GET /api/config/jail-files`` — list all jail config files
|
||||
* ``GET /api/config/jail-files/{filename}`` — get one jail config file (with content)
|
||||
* ``PUT /api/config/jail-files/{filename}`` — overwrite a jail config file
|
||||
* ``PUT /api/config/jail-files/{filename}/enabled`` — enable/disable a jail config
|
||||
* ``GET /api/config/filters`` — list all filter files
|
||||
* ``GET /api/config/filters/{name}`` — get one filter file (with content)
|
||||
@@ -169,6 +170,46 @@ async def get_jail_config_file(
|
||||
raise _service_unavailable(str(exc)) from exc
|
||||
|
||||
|
||||
@router.put(
|
||||
"/jail-files/{filename}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
summary="Overwrite a jail.d config file with new raw content",
|
||||
)
|
||||
async def write_jail_config_file(
|
||||
request: Request,
|
||||
_auth: AuthDep,
|
||||
filename: _FilenamePath,
|
||||
body: ConfFileUpdateRequest,
|
||||
) -> None:
|
||||
"""Overwrite the raw content of an existing jail.d config file.
|
||||
|
||||
The change is written directly to disk. You must reload fail2ban
|
||||
(``POST /api/config/reload``) separately for the change to take effect.
|
||||
|
||||
Args:
|
||||
request: Incoming request.
|
||||
_auth: Validated session.
|
||||
filename: Filename of the jail config file (e.g. ``sshd.conf``).
|
||||
body: New raw file content.
|
||||
|
||||
Raises:
|
||||
HTTPException: 400 if *filename* is unsafe or content is invalid.
|
||||
HTTPException: 404 if the file does not exist.
|
||||
HTTPException: 503 if the config directory is unavailable.
|
||||
"""
|
||||
config_dir: str = request.app.state.settings.fail2ban_config_dir
|
||||
try:
|
||||
await file_config_service.write_jail_config_file(config_dir, filename, body)
|
||||
except ConfigFileNameError as exc:
|
||||
raise _bad_request(str(exc)) from exc
|
||||
except ConfigFileNotFoundError:
|
||||
raise _not_found(filename) from None
|
||||
except ConfigFileWriteError as exc:
|
||||
raise _bad_request(str(exc)) from exc
|
||||
except ConfigDirError as exc:
|
||||
raise _service_unavailable(str(exc)) from exc
|
||||
|
||||
|
||||
@router.put(
|
||||
"/jail-files/{filename}/enabled",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
|
||||
@@ -418,6 +418,49 @@ async def create_jail_config_file(
|
||||
return await asyncio.get_event_loop().run_in_executor(None, _do)
|
||||
|
||||
|
||||
async def write_jail_config_file(
|
||||
config_dir: str,
|
||||
filename: str,
|
||||
req: ConfFileUpdateRequest,
|
||||
) -> None:
|
||||
"""Overwrite an existing jail.d config file with new raw content.
|
||||
|
||||
Args:
|
||||
config_dir: Path to the fail2ban configuration directory.
|
||||
filename: Filename including extension (e.g. ``sshd.conf``).
|
||||
req: :class:`~app.models.file_config.ConfFileUpdateRequest` with new
|
||||
content.
|
||||
|
||||
Raises:
|
||||
ConfigFileNotFoundError: If the file does not exist.
|
||||
ConfigFileNameError: If *filename* is unsafe or has a bad extension.
|
||||
ConfigFileWriteError: If the file cannot be written.
|
||||
ConfigDirError: If *config_dir* does not exist.
|
||||
"""
|
||||
|
||||
def _do() -> None:
|
||||
jail_d = _resolve_subdir(config_dir, "jail.d").resolve()
|
||||
if not jail_d.is_dir():
|
||||
raise ConfigFileNotFoundError(filename)
|
||||
path = (jail_d / filename).resolve()
|
||||
_assert_within(jail_d, path)
|
||||
if path.suffix not in _CONF_EXTENSIONS:
|
||||
raise ConfigFileNameError(
|
||||
f"Only .conf and .local files are supported, got {filename!r}."
|
||||
)
|
||||
if not path.is_file():
|
||||
raise ConfigFileNotFoundError(filename)
|
||||
try:
|
||||
path.write_text(req.content, encoding="utf-8")
|
||||
except OSError as exc:
|
||||
raise ConfigFileWriteError(
|
||||
f"Cannot write {filename!r}: {exc}"
|
||||
) from exc
|
||||
log.info("jail_config_file_written", filename=filename)
|
||||
|
||||
await asyncio.get_event_loop().run_in_executor(None, _do)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Internal helpers — generic conf file listing / reading / writing
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user