backup
This commit is contained in:
@@ -1,59 +1,3 @@
|
|||||||
### Issue #67: LOW - Default Page Size Inconsistently Applied Across Routers
|
|
||||||
|
|
||||||
**Where found**:
|
|
||||||
- `backend/app/routers/history.py:80-84` – uses `DEFAULT_PAGE_SIZE` constant
|
|
||||||
- Multiple other routers – may hardcode page size values
|
|
||||||
|
|
||||||
**Why this is needed**:
|
|
||||||
Endpoints with different default page sizes create an inconsistent API experience and make it hard to reason about server load. A client that does not pass `page_size` gets different result counts from different endpoints.
|
|
||||||
|
|
||||||
**Goal**:
|
|
||||||
All paginated endpoints use the same default page size driven by a single constant.
|
|
||||||
|
|
||||||
**What to do**:
|
|
||||||
1. Audit all `page_size` Query parameters across routers.
|
|
||||||
2. Replace all hardcoded defaults with `DEFAULT_PAGE_SIZE` from `constants.py`.
|
|
||||||
3. Add a linting check or unit test that asserts no hardcoded page size defaults exist in routers.
|
|
||||||
|
|
||||||
**Possible traps and issues**:
|
|
||||||
- Some endpoints may intentionally use a different page size for performance reasons; document exceptions explicitly.
|
|
||||||
|
|
||||||
**Docs changes needed**:
|
|
||||||
- API reference: document the default page size and how to override it.
|
|
||||||
|
|
||||||
**Doc references**:
|
|
||||||
- `backend/app/utils/constants.py` – `DEFAULT_PAGE_SIZE`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Issue #68: LOW - No Reserved Keyword Validation for Jail Names
|
|
||||||
|
|
||||||
**Where found**:
|
|
||||||
- `backend/app/models/jail.py` – jail name validated against alphanumeric regex only
|
|
||||||
- `backend/app/routers/jail_config.py`
|
|
||||||
|
|
||||||
**Why this is needed**:
|
|
||||||
Fail2ban uses reserved jail names and command keywords (e.g., `all`, `status`, `purge`). A user-created jail with a reserved name could shadow fail2ban built-in commands or produce confusing behavior when management commands are issued.
|
|
||||||
|
|
||||||
**Goal**:
|
|
||||||
Reject jail names that conflict with fail2ban reserved words at model validation time.
|
|
||||||
|
|
||||||
**What to do**:
|
|
||||||
1. Define a `FAIL2BAN_RESERVED_JAIL_NAMES` set in `constants.py`.
|
|
||||||
2. Add a Pydantic validator on the jail name field that rejects reserved words.
|
|
||||||
3. Return a 422 with a descriptive error message.
|
|
||||||
|
|
||||||
**Possible traps and issues**:
|
|
||||||
- The reserved word list may change across fail2ban versions; source it from fail2ban documentation and version-gate if necessary.
|
|
||||||
|
|
||||||
**Docs changes needed**:
|
|
||||||
- API reference: document the list of reserved jail names.
|
|
||||||
|
|
||||||
**Doc references**:
|
|
||||||
- Fail2ban documentation on reserved jail identifiers
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Issue #69: LOW - Jail Names Echoed in Error Messages Without Sanitization
|
### Issue #69: LOW - Jail Names Echoed in Error Messages Without Sanitization
|
||||||
|
|
||||||
**Where found**:
|
**Where found**:
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ Covers jail config files (``jail.d/``), filter definitions (``filter.d/``),
|
|||||||
and action definitions (``action.d/``).
|
and action definitions (``action.d/``).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from pydantic import Field
|
from pydantic import Field, field_validator
|
||||||
|
|
||||||
from app.models.response import BanGuiBaseModel
|
from app.models.response import BanGuiBaseModel
|
||||||
|
from app.utils.constants import FAIL2BAN_RESERVED_JAIL_NAMES
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Jail config file models (Task 4a)
|
# Jail config file models (Task 4a)
|
||||||
@@ -82,3 +82,15 @@ class ConfFileCreateRequest(BanGuiBaseModel):
|
|||||||
"alphanumeric characters, hyphens, underscores, and dots.",
|
"alphanumeric characters, hyphens, underscores, and dots.",
|
||||||
)
|
)
|
||||||
content: str = Field(..., description="Initial raw file content (must not exceed 512 KB).")
|
content: str = Field(..., description="Initial raw file content (must not exceed 512 KB).")
|
||||||
|
|
||||||
|
@field_validator("name", mode="after")
|
||||||
|
@classmethod
|
||||||
|
def _reject_reserved_jail_name(cls, v: str) -> str:
|
||||||
|
"""Reject fail2ban reserved jail names."""
|
||||||
|
if v in FAIL2BAN_RESERVED_JAIL_NAMES:
|
||||||
|
|
||||||
|
valid_names = ", ".join(sorted(FAIL2BAN_RESERVED_JAIL_NAMES))
|
||||||
|
raise ValueError(
|
||||||
|
f"Jail name {v!r} is reserved by fail2ban ({valid_names})."
|
||||||
|
)
|
||||||
|
return v
|
||||||
|
|||||||
@@ -156,3 +156,26 @@ RATE_LIMIT_JAIL_ACTIVATE_REQUESTS: Final[int] = 100
|
|||||||
|
|
||||||
RATE_LIMIT_JAIL_DEACTIVATE_REQUESTS: Final[int] = 100
|
RATE_LIMIT_JAIL_DEACTIVATE_REQUESTS: Final[int] = 100
|
||||||
"""Max jail deactivation requests per IP per minute."""
|
"""Max jail deactivation requests per IP per minute."""
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Jail configuration
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
FAIL2BAN_RESERVED_JAIL_NAMES: Final[frozenset[str]] = frozenset(
|
||||||
|
{
|
||||||
|
"all",
|
||||||
|
"status",
|
||||||
|
"purge",
|
||||||
|
"start",
|
||||||
|
"stop",
|
||||||
|
"reload",
|
||||||
|
"restart",
|
||||||
|
"ban",
|
||||||
|
"unban",
|
||||||
|
"add",
|
||||||
|
"del",
|
||||||
|
"set",
|
||||||
|
"get",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
"""fail2ban reserved jail names. Users cannot create jails with these names."""
|
||||||
|
|||||||
@@ -347,3 +347,38 @@ def test_ban_country_empty_string_coerced_to_none() -> None:
|
|||||||
country="",
|
country="",
|
||||||
)
|
)
|
||||||
assert ban.country is None
|
assert ban.country is None
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# ConfFileCreateRequest reserved jail name validation
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def test_conffile_create_request_rejects_reserved_jail_name() -> None:
|
||||||
|
"""ConfFileCreateRequest rejects fail2ban reserved jail names."""
|
||||||
|
from app.models.file_config import ConfFileCreateRequest
|
||||||
|
|
||||||
|
for reserved in ["all", "status", "purge", "start", "stop", "reload", "restart"]:
|
||||||
|
with pytest.raises(ValidationError) as exc_info:
|
||||||
|
ConfFileCreateRequest(name=reserved, content="[my jail]")
|
||||||
|
assert "reserved" in str(exc_info.value).lower()
|
||||||
|
|
||||||
|
|
||||||
|
def test_conffile_create_request_accepts_valid_jail_name() -> None:
|
||||||
|
"""ConfFileCreateRequest accepts valid non-reserved jail names."""
|
||||||
|
from app.models.file_config import ConfFileCreateRequest
|
||||||
|
|
||||||
|
req = ConfFileCreateRequest(name="sshd", content="[my jail]")
|
||||||
|
assert req.name == "sshd"
|
||||||
|
|
||||||
|
req = ConfFileCreateRequest(name="nginx-http-auth", content="[my jail]")
|
||||||
|
assert req.name == "nginx-http-auth"
|
||||||
|
|
||||||
|
|
||||||
|
def test_conffile_create_request_rejects_ban_and_unban() -> None:
|
||||||
|
"""ConfFileCreateRequest rejects 'ban' and 'unban' as jail names."""
|
||||||
|
from app.models.file_config import ConfFileCreateRequest
|
||||||
|
|
||||||
|
for name in ["ban", "unban", "add", "del", "set", "get"]:
|
||||||
|
with pytest.raises(ValidationError):
|
||||||
|
ConfFileCreateRequest(name=name, content="[my jail]")
|
||||||
|
|||||||
Reference in New Issue
Block a user