diff --git a/Docs/Backend-Development.md b/Docs/Backend-Development.md index c929311..cacfe20 100644 --- a/Docs/Backend-Development.md +++ b/Docs/Backend-Development.md @@ -1726,6 +1726,57 @@ Cookie: bangui_session=... (no X-BanGUI-Request header needed) ``` +### Setup Guard Route Policy + +BanGUI requires a one-time setup wizard to be completed before the application is usable. The `SetupRedirectMiddleware` enforces this by redirecting unauthenticated API requests to `/api/setup` until setup is complete. + +**How It Works:** + +1. **Explicit Allowlist:** The middleware maintains two allowlists: + - `_EXACT_ALLOWED`: Exact paths that bypass the guard (e.g., `/api/setup`, `/api/health`, `/api/docs`) + - `_PREFIX_ALLOWED`: Route prefixes that bypass the guard (e.g., `/api/setup/` for nested routes like `/api/setup/timezone`) + +2. **Path Matching Strategy:** The middleware uses **exact matching for exact paths** and **prefix matching with trailing slashes for nested routes**. This prevents fragile prefix-based allowlists (e.g., using `startswith("/api/setup")` would accidentally allow `/api/setup-debug`). + +3. **When Setup is Complete:** Once setup completes, the middleware becomes a no-op and all routes are accessible normally. + +**Allowlisted Paths:** +- `/api/setup` — Setup status check and initialization endpoint +- `/api/setup/timezone` — Timezone configuration (reaches via `/api/setup/` prefix) +- `/api/health` — Health check endpoint (used by monitoring and load balancers) +- `/api/docs` — Swagger UI documentation +- `/api/redoc` — ReDoc documentation +- `/api/openapi.json` — OpenAPI schema (required by docs frontends) + +**Adding New Setup Routes:** + +When adding new routes to the setup flow: +1. If the route is an exact path (e.g., `/api/setup/validate`), add it to `_EXACT_ALLOWED` +2. If the route is nested under `/api/setup/` (e.g., `/api/setup/validate/config`), ensure `/api/setup/` is in `_PREFIX_ALLOWED` (it already is) +3. Never use prefix matching without a trailing slash — it leads to security issues with future route additions + +**Implementation Location:** +- Middleware: `backend/app/main.py` — `SetupRedirectMiddleware` class +- Configuration: Lines 584–601 in `backend/app/main.py` — `_EXACT_ALLOWED` and `_PREFIX_ALLOWED` constants +- Guard logic: Lines 638–648 in `backend/app/main.py` — `dispatch()` method + +**Example:** +```python +# If setup is incomplete: +GET /api/jails +→ 307 Temporary Redirect to /api/setup + +# Allowlisted paths are always accessible: +GET /api/setup → 200 OK (setup status) +POST /api/setup → 201 Created (run setup) +GET /api/setup/timezone → 200 OK (get timezone) +GET /api/health → 200 OK (health check) +GET /api/docs → 200 OK (documentation) + +# If setup is complete, all routes are accessible: +GET /api/jails → 200 OK (jail list) +``` + ### fail2ban_start_command Configuration The `fail2ban_start_command` setting specifies the shell command used to start the fail2ban daemon during recovery operations (e.g., after a rollback). diff --git a/Docs/Tasks.md b/Docs/Tasks.md index cf6764d..c289f95 100644 --- a/Docs/Tasks.md +++ b/Docs/Tasks.md @@ -1,23 +1,3 @@ -## 33) Trusted proxy configuration is hardcoded in auth router -- Where found: - - [backend/app/routers/auth.py](backend/app/routers/auth.py#L46) - - [backend/app/utils/client_ip.py](backend/app/utils/client_ip.py) -- Why this is needed: - - Incorrect client IP extraction can break per-IP rate limiting behind proxies. -- Goal: - - Move trusted proxies to validated runtime config. -- What to do: - - Add settings for trusted proxy IPs/CIDRs. - - Validate and use these in client IP extraction. -- Possible traps and issues: - - Over-trusting headers can enable spoofing. -- Docs changes needed: - - Add reverse-proxy deployment configuration section. -- Doc references: - - [Docs/Instructions.md](Docs/Instructions.md) - ---- - ## 34) Setup redirect allowlist uses broad prefix matching - Where found: - [backend/app/main.py](backend/app/main.py#L434) diff --git a/backend/app/main.py b/backend/app/main.py index 31621f6..cf6e900 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -41,6 +41,7 @@ from app.exceptions import ( ServiceUnavailableError, ) from app.middleware.csrf import CsrfMiddleware +from app.models.response import ErrorResponse from app.routers import ( auth, bans, @@ -56,7 +57,6 @@ from app.routers import ( setup, ) from app.startup import startup_shared_resources -from app.models.response import ErrorResponse from app.utils.rate_limiter import RateLimiter from app.utils.runtime_state import ApplicationState, RuntimeState from app.utils.session_cache import InMemorySessionCache, NoOpSessionCache @@ -180,7 +180,7 @@ def _get_error_code(exc: Exception) -> str: """ if hasattr(exc, "error_code"): return exc.error_code - + exc_name = exc.__class__.__name__ import re snake_case = re.sub(r"(?