Commit Graph

49 Commits

Author SHA1 Message Date
94d6352d1d Fix health check endpoint to return 503 when fail2ban is offline
The health check endpoint now properly indicates service unavailability:
- Returns HTTP 200 when fail2ban is online
- Returns HTTP 503 when fail2ban is offline

This allows Docker and other orchestration tools to correctly detect when
fail2ban is unreachable and automatically restart the backend container,
preventing the situation where Docker treats the container as healthy
despite fail2ban being down.

Changes:
- Update GET /api/health to return 503 on fail2ban offline
- Return appropriate JSON response bodies for each state
- Update tests to verify both online (200) and offline (503) scenarios
- Update Dockerfile HEALTHCHECK documentation
- Add Health Checks section to Deployment.md documentation

All tests pass with 100% coverage on health.py.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-30 21:56:42 +02:00
90f4c6239c Add resource limits to all Docker containers
- fail2ban: 0.5 CPU / 128M memory limit, 0.1 CPU / 64M reserved
- backend: 2.0 CPU / 512M memory limit, 1.0 CPU / 256M reserved
- frontend: 0.5 CPU / 128M memory limit, 0.25 CPU / 64M reserved

Prevents 'noisy neighbor' scenarios where one container exhausts
host resources (CPU, memory, disk). Limits are hard caps; reservations
guarantee minimum allocation to prevent OOM kills and ensure
responsive service even under load.

Fixes resource contention issue in production and staging environments.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-30 21:03:56 +02:00
c4ede71fa6 Fix: Enforce single-worker deployment for session cache cluster safety
Addresses: Backend session cache not cluster-safe (multi-worker issue)

Problem:
- Session cache is process-local (InMemorySessionCache)
- Multi-worker deployments (uvicorn --workers N) create separate processes
- Each process has its own independent session cache
- Sessions cached in Worker A are invisible to Workers B, C, D
- Users randomly logged out when requests land on different workers
- Also affects RuntimeState, rate limiter, and background jobs

Solution (Option A - Strict single-worker enforcement):
- Enhance startup validation with clearer error messages
- Update error messages to explain the problem and how to fix it
- Document single-worker requirement prominently in Docker configs
- Update module docstrings to clarify constraints

Changes:
1. app/startup.py:
   - Enhanced _check_single_worker_mode() error message with troubleshooting
   - Enhanced _stage_check_worker_mode_and_acquire_lock() error message
   - Removed unused import

2. app/utils/session_cache.py:
   - Updated module docstring to explain constraints more clearly
   - Added references to deployment documentation
   - Clarified multi-worker solution for future implementation

3. app/utils/runtime_state.py:
   - Updated module docstring with deployment constraint references
   - Aligned messaging with session_cache.py

4. Docker/Dockerfile.backend:
   - Added comprehensive comments about single-worker requirement
   - Explained impact in multi-worker deployments
   - Referenced deployment constraints documentation

5. Docker/docker-compose.yml, compose.prod.yml, compose.debug.yml:
   - Added documentation comments about BANGUI_WORKERS constraint
   - Explained why single-worker is required

6. backend/tests/test_startup_integration.py:
   - Fixed test unpacking to match function return signature (3 values, not 2)

This ensures multi-worker deployments fail loudly at startup with clear
guidance on what went wrong and how to fix it. The database-backed scheduler
lock provides defense-in-depth for container orchestration scenarios.

For future multi-worker support, implement:
- Redis or database-backed session cache
- Shared RuntimeState coordination
- Distributed APScheduler backend

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-30 20:54:24 +02:00
57eacf39ba fix(security): Remove insecure session secret fallback in compose.debug.yml
TASK-027: The compose.debug.yml file had a publicly known weak session secret as
a fallback value. This has been replaced with an explicit requirement via the :?
bash parameter expansion pattern, forcing developers to set BANGUI_SESSION_SECRET.

Changes:
- Changed BANGUI_SESSION_SECRET fallback to use :? pattern with clear error message
- Created .env.example with placeholder values and generation instructions
- Added first-run setup instructions to Instructions.md
- Verified .env is already in .gitignore

The error message provides clear guidance:
'BANGUI_SESSION_SECRET must be set — generate with: python -c "import secrets; print(secrets.token_hex(32))"'

Severity: Medium
- Prevents exposure of session secret in repositories
- Ensures each environment has unique secrets
- Aligns with production compose.prod.yml which already uses this pattern

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-26 15:12:10 +02:00
df841c21e4 TASK-026: Disable API docs in production, protect with BANGUI_ENABLE_DOCS setting
Addresses security concern where FastAPI's default behavior exposes interactive
API documentation (/docs, /redoc) without authentication, allowing attackers to
enumerate endpoints and understand API schemas.

Changes:
- Add BANGUI_ENABLE_DOCS boolean setting (default: false) to Settings
- Modify create_app() to conditionally set docs_url, redoc_url, openapi_url
- Add docs endpoints to SetupRedirectMiddleware allowlist (/api/docs, /api/redoc, /api/openapi.json)
- Set BANGUI_ENABLE_DOCS=true in Docker/compose.debug.yml for development
- Production compose files leave it unset (defaults to false, docs disabled)
- Add comprehensive tests for docs configuration
- Document the new setting in Backend-Development.md

Security Impact:
- API documentation is now disabled by default in production
- Development environments can enable docs by setting BANGUI_ENABLE_DOCS=true
- Docs endpoints are inaccessible in production without manual configuration

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-26 15:09:51 +02:00
5d9cef7760 TASK-013: Add nginx security headers (CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy)
- Added OWASP-recommended security headers to nginx server block
- CSP allows same-origin scripts and inline styles (required for Fluent UI v9)
- X-Frame-Options: DENY prevents clickjacking
- X-Content-Type-Options: nosniff prevents MIME-sniffing
- Referrer-Policy: no-referrer prevents URL leakage
- Permissions-Policy: disables geolocation, microphone, camera APIs
- HSTS commented out until HTTPS is fully configured
- All headers use 'always' directive for error responses (4xx, 5xx)
- Updated Architekture.md with security header documentation

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-26 13:35:15 +02:00
f55c317f87 Backend refactoring updates
- Update Docker compose debug configuration
- Update backend documentation
- Update tasks documentation
- Update backend config module

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-26 12:05:01 +02:00
825a67f13a Add multi-worker detection for APScheduler safety
- Add _check_single_worker_mode() to startup.py that detects and rejects
  multi-worker configurations, raising a clear RuntimeError with instructions
- Set BANGUI_WORKERS=1 as default in Dockerfile.backend
- Document single-worker requirement in compose.prod.yml
- Add 'Deployment Constraints' section to Architekture.md explaining why
  single-worker mode is required and detailing future multi-worker support
- Add '9.1 Background Tasks and Scheduler Architecture' section to
  Backend-Development.md documenting task structure and single-worker requirement
- Add comprehensive test suite (test_startup.py) covering all scenarios:
  allows single worker, rejects multi-worker, validates config format,
  and verifies informative error messages

This fix addresses TASK-002 which identified that in-process APScheduler is
unsafe in multi-worker deployments due to each worker creating independent
scheduler instances, causing duplicate background job execution.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-26 11:39:51 +02:00
96f75db75f chore: release v0.9.19 2026-04-05 22:47:42 +02:00
eb983799cd chore: release v0.9.18 2026-04-05 21:45:28 +02:00
bbd57c808b chore: release v0.9.17 2026-04-05 20:44:43 +02:00
ffaa14f864 Switch dashboard/map/history views to archive source for long-term data
Update fail2ban dbpurgeage to 648000 and history sync backfill/pagination for archive-based 7.5 day history.
2026-04-05 20:21:54 +02:00
7d09b78437 chore: release v0.9.16 2026-04-05 18:54:02 +02:00
470c29443c chore: release v0.9.15 2026-03-29 21:21:30 +02:00
2ea4a8304f backup 2026-03-24 19:46:12 +01:00
e99920e616 chore: release v0.9.14 2026-03-24 19:38:05 +01:00
670ff3e8a2 chore: release v0.9.13 2026-03-24 19:23:43 +01:00
f6672d0d16 better release script 2026-03-22 21:51:02 +01:00
d909f93efc chore: release v0.9.12 2026-03-22 21:47:40 +01:00
0663740b08 chore: release v0.9.11 2026-03-22 21:43:31 +01:00
376c13370d chore: release v0.9.10 2026-03-20 13:32:26 +01:00
fb6d0e588f chore: release v0.9.9 2026-03-19 20:13:24 +01:00
e44caccb3c chore(release): push git refs after successful container build 2026-03-19 19:52:17 +01:00
59da34dc3b chore: release v0.9.8 2026-03-17 18:22:02 +01:00
90f54cf39c chore: release v0.9.6 2026-03-17 18:21:46 +01:00
93d26e3c60 chore: release v0.9.7 2026-03-17 18:18:49 +01:00
954dcf7ea6 fix: remove invalid --security-opt flag from push.sh build commands 2026-03-17 18:18:25 +01:00
bf8144916a chore: release v0.9.6 2026-03-17 18:16:59 +01:00
481daa4e1a fix: resolve TS build errors and suppress rootless podman capability warnings
- Add Toolbar and ToolbarButton to HistoryPage imports
- Add Tooltip import and filterBar style to MapPage
- Fix JailsTab test: use new Set<string>() instead of [] for activeJails
- Add --security-opt=no-new-privileges:true to push.sh build commands
2026-03-17 18:16:16 +01:00
889976c7ee chore: release v0.9.5 2026-03-17 18:06:03 +01:00
bf82e38b6e Fix blocklist-import bantime, unify filter bar, and improve config navigation 2026-03-17 11:31:46 +01:00
e98fd1de93 Fix global version handling and unify app version across backend/frontend 2026-03-17 09:06:42 +01:00
81f99d0b50 release script 2026-03-16 20:04:36 +01:00
030bca09b7 version 2026-03-16 20:02:22 +01:00
e7834a888e Show BanGUI app version in sidebar, fix version tooltips
- Inject __APP_VERSION__ at build time via vite.config.ts define (reads
  frontend/package.json#version); declare the global in vite-env.d.ts.
- Render 'BanGUI v{__APP_VERSION__}' in the sidebar footer (MainLayout)
  when expanded; hidden when collapsed.
- Rename fail2ban version tooltip to 'fail2ban daemon version' in
  ServerStatusBar so it is visually distinct from the app version.
- Sync frontend/package.json version (0.9.0 → 0.9.3) to match
  Docker/VERSION; update release.sh to keep them in sync on every bump.
- Add vitest define stub for __APP_VERSION__ so tests compile cleanly.
- Add ServerStatusBar and MainLayout test suites (10 new test cases).
2026-03-16 19:45:55 +01:00
abb224e01b Docker: add PUID/PGID env vars, fix env format, add release script and VERSION 2026-03-16 19:22:16 +01:00
cdf73e2d65 docker files 2026-03-15 18:10:25 +01:00
00119ed68d Rename dev jail bangui-sim to manual-Jail
Rename fail2ban-dev-config jail.d/bangui-sim.conf and filter.d/bangui-sim.conf
to manual-Jail.conf. Update section header, filter reference, and comments in
both files. Update JAIL constant and header comment in check_ban_status.sh.
Update comments in simulate_failed_logins.sh. Replace all bangui-sim
occurrences in fail2ban-dev-config/README.md.
2026-03-15 14:09:49 +01:00
b81e0cdbb4 Fix raw action config endpoint shadowed by config router
Rename GET/PUT /api/config/actions/{name} to /actions/{name}/raw in
file_config.py to eliminate the route-shadowing conflict with config.py,
which registers its own GET /actions/{name} returning ActionConfig.

Add configActionRaw endpoint helper in endpoints.ts and update
fetchActionFile/updateActionFile in config.ts to use it. Add
TestGetActionFileRaw and TestUpdateActionFileRaw test classes.
2026-03-15 14:09:37 +01:00
61daa8bbc0 Fix BUG-001: resolve banaction interpolation error in fail2ban jails
The container init script (init-fail2ban-config) copies jail.conf from the
image's /defaults/ on every start, overwriting any direct edits.  The correct
fix is jail.local, which is not present in the image defaults and therefore
persists across restarts.

Changes:
- Add Docker/fail2ban-dev-config/fail2ban/jail.local with [DEFAULT] overrides
  for banaction = iptables-multiport and banaction_allports = iptables-allports.
  fail2ban loads jail.local after jail.conf so these values are available to
  all jails during %(action_)s interpolation.
- Untrack jail.local from .gitignore so it is committed to the repo.
- Fix TypeError in config_file_service: except jail_service.JailNotFoundError
  failed when jail_service was mocked in tests because MagicMock attributes are
  not BaseException subclasses.  Import JailNotFoundError directly instead.
- Mark BUG-001 as Done in Tasks.md.
2026-03-15 11:39:20 +01:00
f62785aaf2 Fix fail2ban runtime errors: jail not found, action locks, log noise
This commit implements fixes for three independent bugs in the fail2ban configuration and integration layer:

1. Task 1: Detect UnknownJailException and prevent silent failures
   - Added JailNotFoundError detection in jail_service.reload_all()
   - Enhanced error handling in config_file_service to catch JailNotFoundError
   - Added specific error message with logpath validation hints
   - Added rollback test for this scenario

2. Task 2: Fix iptables-allports exit code 4 (xtables lock contention)
   - Added global banaction setting in jail.conf with -w 5 lockingopt
   - Removed redundant per-jail banaction overrides from bangui-sim and blocklist-import
   - Added production compose documentation note

3. Task 3: Suppress log noise from unsupported backend/idle commands
   - Implemented capability detection to cache command support status
   - Double-check locking to minimize lock contention
   - Avoids sending unsupported get <jail> backend/idle commands
   - Returns default values without socket calls when unsupported

All changes include comprehensive tests and maintain backward compatibility.
2026-03-15 10:57:00 +01:00
ea35695221 Add better jail configuration: file CRUD, enable/disable, log paths
Task 4 (Better Jail Configuration) implementation:
- Add fail2ban_config_dir setting to app/config.py
- New file_config_service: list/view/edit/create jail.d, filter.d, action.d files
  with path-traversal prevention and 512 KB content size limit
- New file_config router: GET/PUT/POST endpoints for jail files, filter files,
  and action files; PUT .../enabled for toggle on/off
- Extend config_service with delete_log_path() and add_log_path()
- Add DELETE /api/config/jails/{name}/logpath and POST /api/config/jails/{name}/logpath
- Extend geo router with re-resolve endpoint; add geo_re_resolve background task
- Update blocklist_service with revised scheduling helpers
- Update Docker compose files with BANGUI_FAIL2BAN_CONFIG_DIR env var and
  rw volume mount for the fail2ban config directory
- Frontend: new Jail Files, Filters, Actions tabs in ConfigPage; file editor
  with accordion-per-file, editable textarea, save/create; add/delete log paths
- Frontend: types in types/config.ts; API calls in api/config.ts and api/endpoints.ts
- 63 new backend tests (test_file_config_service, test_file_config, test_geo_re_resolve)
- 6 new frontend tests in ConfigPageLogPath.test.tsx
- ruff, mypy --strict, tsc --noEmit, eslint: all clean; 617 backend tests pass
2026-03-12 20:08:33 +01:00
706d2e1df8 fix: add blocklist-import jail to dev fail2ban config
The blocklist import service targets a dedicated jail called
'blocklist-import' (BLOCKLIST_JAIL constant in blocklist_service.py),
but that jail was never defined in the dev fail2ban configuration.
Every import attempt immediately failed with UnknownJailException.

Add Docker/fail2ban-dev-config/fail2ban/jail.d/blocklist-import.conf:
a manual-ban jail with no log-based detection that accepts banip
commands only, using iptables-allports with a 1-week bantime.

Also track the new file in .gitignore (whitelist) and fix a
pre-existing blank-line-with-whitespace lint error in setup_service.py.
2026-03-07 19:31:36 +01:00
cbad4ea706 Add ban management features and update documentation
- Implement ban model, service, and router endpoints in backend
- Add ban table component and dashboard integration in frontend
- Update ban-related types and API endpoints
- Add comprehensive tests for ban service and dashboard router
- Update documentation (Features, Tasks, Architecture, Web-Design)
- Clean up old fail2ban configuration files
- Update Makefile with new commands
2026-03-06 20:33:42 +01:00
08b8f3872a fix: reload/stop jail 404 + access list simulator
Task 1 — fix Stop/Reload Jail returning 404
  Root cause: reload_jail and reload_all sent an empty config stream
  (["reload", name, [], []]).  In fail2ban's reload protocol the end-of-
  reload phase deletes every jail still in reload_state — i.e. every jail
  that received no configuration commands.  An empty stream means *all*
  affected jails are silently removed from the daemon's runtime, causing
  everything touching those jails afterwards (including stop) to receive
  UnknownJailException → HTTP 404.

  Fixes:
  - reload_jail: send ["start", name] in the config stream; startJail()
    removes the jail from reload_state so the end phase commits instead of
    deletes, and un-idles the jail.
  - reload_all: fetch current jail list first, build a ["start", name]
    entry for every active jail, then send reload --all with that stream.
  - stop_jail: made idempotent — if the jail is already gone (not-found
    error) the operation silently succeeds (200 OK) rather than returning
    404, matching the user expectation that stop = ensure-stopped.
  - Router: removed dead JailNotFoundError handler from stop endpoint.

  391 tests pass (2 new), ruff clean, mypy clean (pre-existing
  config.py error unchanged).

Task 2 — access list simulator
  - Docker/simulate_accesses.sh: writes fake HTTP-scan log lines in
    custom format (bangui-access: http scan from <IP> ...) to
    Docker/logs/access.log so the bangui-access jail detects them.
  - fail2ban/filter.d/bangui-access.conf: failregex matching the above.
  - fail2ban/jail.d/bangui-access.conf: polling jail on access.log,
    same settings as bangui-sim (maxretry=3, bantime=60s).
  - .gitignore: whitelist new bangui-access.conf files.
  - Docker/fail2ban-dev-config/README.md: added "Testing the Access
    List Feature" section with step-by-step instructions and updated
    Configuration Reference + Troubleshooting.
2026-03-06 19:49:31 +01:00
2eb032ecd4 Fix backend unable to open fail2ban database in dev compose
The backend container mounted fail2ban-dev-config as an anonymous named
volume, while the fail2ban container used a bind-mount of the same local
directory. The backend's /config was therefore always empty, causing
sqlite3.OperationalError when ban_service attempted to open the path
returned by 'get dbfile' (/config/fail2ban/fail2ban.sqlite3).

Change the backend volume declaration from the named volume reference
to the same bind-mount used by fail2ban:
  fail2ban-dev-config:/config:ro  →  ./fail2ban-dev-config:/config:ro

Also removes the now-unused 'fail2ban-dev-config' named-volume entry.

Affected endpoints (all returned HTTP 500, now return HTTP 200):
  GET /api/dashboard/bans
  GET /api/dashboard/accesses
  GET /api/dashboard/bans/by-country
2026-03-06 18:57:50 +01:00
1c89454197 Add fail2ban dev test environment (Stage 0)
- Add bangui-sim filter (filter.d/bangui-sim.conf) matching the
  simulated authentication failure log format
- Add bangui-sim jail (jail.d/bangui-sim.conf) with maxretry=3,
  bantime=60s, findtime=120s, ignoreip safeguard, polling backend
- Mount Docker/logs/ into fail2ban container at /remotelogs/bangui
  in compose.debug.yml
- Add simulate_failed_logins.sh to write synthetic failure lines
- Add check_ban_status.sh with optional --unban flag
- Add dev-ban-test Makefile target for one-command smoke testing
- Write Docker/fail2ban-dev-config/README.md with setup and
  troubleshooting docs
- Update .gitignore to track custom config files while still
  excluding auto-generated linuxserver fail2ban files
2026-03-03 21:00:08 +01:00
39ee1e2945 chore: add Docker config files and fix fail2ban bind mount path 2026-03-03 20:38:32 +01:00
36e1e12a8a Fix console errors: React Router v7 flags, form a11y, bans 500
- Add v7_startTransition and v7_relativeSplatPath future flags to
  BrowserRouter to silence React Router deprecation warnings
- Add hidden autocomplete='username' inputs to LoginPage and SetupPage
  so password managers and browsers stop warning about missing username
  fields in password forms
- Mount fail2ban-dev-config volume into backend container at /config:ro
  so ban_service can open the fail2ban SQLite database returned by
  'get dbfile'; this fixes the 500 on GET /api/dashboard/bans
- Track compose.debug.yml in git (was previously untracked)
2026-03-01 20:15:23 +01:00