feat(stage-1): inactive jail discovery and activation
- Backend: config_file_service.py parses jail.conf/jail.local/jail.d/*
following fail2ban merge order; discovers jails not running in fail2ban
- Backend: 3 new API endpoints (GET /jails/inactive, POST /jails/{name}/activate,
POST /jails/{name}/deactivate); moved /jails/inactive before /jails/{name}
to fix route-ordering conflict
- Frontend: ActivateJailDialog component with optional parameter overrides
- Frontend: JailsTab extended with inactive jail list and InactiveJailDetail pane
- Frontend: JailsPage JailOverviewSection shows inactive jails with toggle
- Tests: 57 service tests + 16 router tests for all new endpoints (all pass)
- Docs: Features.md, Architekture.md, Tasks.md updated; Tasks 1.1-1.5 marked done
This commit is contained in:
@@ -447,3 +447,119 @@ class JailFileConfigUpdate(BaseModel):
|
||||
default=None,
|
||||
description="Jail section updates. Only jails present in this dict are updated.",
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Inactive jail models (Stage 1)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class InactiveJail(BaseModel):
|
||||
"""A jail defined in fail2ban config files that is not currently active.
|
||||
|
||||
A jail is considered inactive when its ``enabled`` key is ``false`` (or
|
||||
absent from the config, since fail2ban defaults to disabled) **or** when it
|
||||
is explicitly enabled in config but fail2ban is not reporting it as
|
||||
running.
|
||||
"""
|
||||
|
||||
model_config = ConfigDict(strict=True)
|
||||
|
||||
name: str = Field(..., description="Jail name from the config section header.")
|
||||
filter: str = Field(
|
||||
...,
|
||||
description=(
|
||||
"Filter name used by this jail. May include fail2ban mode suffix, "
|
||||
"e.g. ``sshd[mode=normal]``."
|
||||
),
|
||||
)
|
||||
actions: list[str] = Field(
|
||||
default_factory=list,
|
||||
description="Action references listed in the config (raw strings).",
|
||||
)
|
||||
port: str | None = Field(
|
||||
default=None,
|
||||
description="Port(s) to monitor, e.g. ``ssh`` or ``22,2222``.",
|
||||
)
|
||||
logpath: list[str] = Field(
|
||||
default_factory=list,
|
||||
description="Log file paths to monitor.",
|
||||
)
|
||||
bantime: str | None = Field(
|
||||
default=None,
|
||||
description="Ban duration as a raw config string, e.g. ``10m`` or ``-1``.",
|
||||
)
|
||||
findtime: str | None = Field(
|
||||
default=None,
|
||||
description="Failure-counting window as a raw config string, e.g. ``10m``.",
|
||||
)
|
||||
maxretry: int | None = Field(
|
||||
default=None,
|
||||
description="Number of failures before a ban is issued.",
|
||||
)
|
||||
source_file: str = Field(
|
||||
...,
|
||||
description="Absolute path to the config file where this jail is defined.",
|
||||
)
|
||||
enabled: bool = Field(
|
||||
...,
|
||||
description=(
|
||||
"Effective ``enabled`` value from the merged config. ``False`` for "
|
||||
"inactive jails that appear in this list."
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class InactiveJailListResponse(BaseModel):
|
||||
"""Response for ``GET /api/config/jails/inactive``."""
|
||||
|
||||
model_config = ConfigDict(strict=True)
|
||||
|
||||
jails: list[InactiveJail] = Field(default_factory=list)
|
||||
total: int = Field(..., ge=0)
|
||||
|
||||
|
||||
class ActivateJailRequest(BaseModel):
|
||||
"""Optional override values when activating an inactive jail.
|
||||
|
||||
All fields are optional. Omitted fields are not written to the
|
||||
``.local`` override file so that fail2ban falls back to its default
|
||||
values.
|
||||
"""
|
||||
|
||||
model_config = ConfigDict(strict=True)
|
||||
|
||||
bantime: str | None = Field(
|
||||
default=None,
|
||||
description="Override ban duration, e.g. ``1h`` or ``3600``.",
|
||||
)
|
||||
findtime: str | None = Field(
|
||||
default=None,
|
||||
description="Override failure-counting window, e.g. ``10m``.",
|
||||
)
|
||||
maxretry: int | None = Field(
|
||||
default=None,
|
||||
ge=1,
|
||||
description="Override maximum failures before a ban.",
|
||||
)
|
||||
port: str | None = Field(
|
||||
default=None,
|
||||
description="Override port(s) to monitor.",
|
||||
)
|
||||
logpath: list[str] | None = Field(
|
||||
default=None,
|
||||
description="Override log file paths.",
|
||||
)
|
||||
|
||||
|
||||
class JailActivationResponse(BaseModel):
|
||||
"""Response for jail activation and deactivation endpoints."""
|
||||
|
||||
model_config = ConfigDict(strict=True)
|
||||
|
||||
name: str = Field(..., description="Name of the affected jail.")
|
||||
active: bool = Field(
|
||||
...,
|
||||
description="New activation state: ``True`` after activate, ``False`` after deactivate.",
|
||||
)
|
||||
message: str = Field(..., description="Human-readable result message.")
|
||||
|
||||
Reference in New Issue
Block a user