Commit Graph

152 Commits

Author SHA1 Message Date
308cf680a7 TASK-014: Add log path validation to prevent arbitrary file access
Restrict monitored log paths to a configurable allowlist of safe directories
to prevent authenticated users from instructing fail2ban to monitor arbitrary
files on the system, which could leak contents via fail2ban logging.

Changes:
- Add 'allowed_log_dirs' setting to Settings (defaults to /var/log, /config/log)
- Add @field_validator to AddLogPathRequest to validate log paths at request time
- Validator resolves paths to canonical form and checks against allowed prefixes
- Use Path.is_relative_to() to prevent prefix bypass attacks like /var/log_evil
- Add comprehensive tests for valid/invalid paths and symlink handling
- Update Features.md and Backend-Development.md with security documentation

Security improvements:
- Blocks access to sensitive files (/etc/shadow, /etc/passwd, etc.)
- Resolves symlinks before validation to prevent escape routes
- Uses proper path comparison instead of string prefix matching
- Configurable via BANGUI_ALLOWED_LOG_DIRS environment variable

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-26 13:49:04 +02:00
8698b89f6a TASK-010: Replace .split() with shlex.split() for fail2ban_start_command
- Add @field_validator for fail2ban_start_command to validate with shlex.split()
  at startup, catching misconfigured commands with mismatched quotes
- Replace .split() with shlex.split() in jail_config.py line 450
- Replace .split() with shlex.split() in config_misc.py line 154
- Update Backend-Development.md with configuration documentation explaining
  quoted path handling and common pitfalls
- Add comprehensive test suite (8 tests) covering valid commands, quoted paths,
  and mismatched quote errors

This fix ensures commands like '/opt/my tools/fail2ban-client' start are
correctly parsed as two tokens instead of three, preventing execution failures
when the path contains spaces.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-26 13:04:14 +02:00
4ab767e3d4 TASK-009: Mitigate SSRF vulnerability in blocklist URL validation
- Change BlocklistSourceCreate.url from str to AnyHttpUrl (Pydantic type)
  - Rejects non-http schemes (file://, ftp://, etc.) at model boundary

- Add is_private_ip() utility to detect RFC 1918 private ranges:
  - 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 (RFC 1918)
  - 127.0.0.0/8, ::1/128 (loopback)
  - 169.254.0.0/16, fe80::/10 (link-local)
  - IPv6 site-local, multicast, and reserved ranges

- Add async validate_blocklist_url() function:
  - Resolves hostname via DNS using loop.run_in_executor()
  - Rejects if hostname resolves to private/reserved IP
  - Raises ValueError on validation failure

- Integrate validation into service layer:
  - create_source() calls validate_blocklist_url() before persist
  - update_source() conditionally validates if url provided
  - Both raise ValueError on failure

- Update router endpoints with error handling:
  - create_blocklist() and update_blocklist() catch ValueError
  - Return HTTP 400 Bad Request with descriptive error message

- Add comprehensive test coverage (9 new SSRF tests):
  - file://, ftp://, localhost, 127.0.0.1, 192.168.x.x
  - 10.x.x.x, 172.16.x.x, 169.254.x.x (link-local)
  - Valid public URLs (passes validation)
  - All 36 service tests passing

- Update documentation:
  - Features.md: Document URL validation constraints
  - Backend-Development.md: Add SSRF prevention pattern section

Fixes SSRF vulnerability where authenticated users could supply
file://, ftp://, or private IP URLs and the backend would fetch them.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-26 12:57:23 +02:00
a5b55d1248 Add session cleanup task and update documentation
- Implement session_cleanup task for removing expired sessions
- Add comprehensive tests for session cleanup functionality
- Update architecture and task documentation
- Integrate cleanup task into application startup

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-26 12:49:13 +02:00
ea4c7c2f85 Implement login endpoint rate limiting (TASK-007)
- Add in-memory rate limiter with per-IP deque tracking of attempt timestamps
- Limit login attempts to 5 per 60 seconds per IP, return 429 on excess
- Add Retry-After header to rate limit responses
- Implement IP extraction utility with proxy trust validation (prevent X-Forwarded-For spoofing)
- Integrate rate limiter into auth router and dependencies
- Add 10-second asyncio.sleep on failed login attempts to further slow brute-force
- Add comprehensive tests for rate limiting (9 new tests, all passing)
- Update Features.md to document login rate limiting
- Update Backend-Development.md with rate limiting conventions and design patterns
- Fix test infrastructure issues: update password to meet complexity requirements
- Fix TestValidateSession tests to use Bearer token authentication
- All tests passing: 23 auth tests + full test suite coverage

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-26 12:40:52 +02:00
29daaa9906 TASK-004: Bootstrap frontend auth state from backend session check
Validates session on app mount by calling GET /api/auth/session instead of relying
solely on cached sessionStorage. This ensures the UI state always reflects server
reality — expired or revoked sessions are detected immediately.

Changes:
- Backend: Add GET /api/auth/session endpoint (requires valid session, returns 200/401)
- Frontend: Add useSessionValidation hook for mount-time validation
- Frontend: Add SessionValidationLoading component for validation spinner
- Frontend: Update AuthProvider to call validation on mount with loading state
- Frontend: Add validateSession API function
- Docs: Update Features.md with session validation behavior
- Docs: Update Web-Development.md with session validation pattern

Handles three outcomes:
1. Valid session (200): Proceed with cached state
2. Invalid session (401): Clear sessionStorage and redirect to login
3. Network error: Don't logout (backend may be temporarily unreachable)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-26 12:00:21 +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
ac2028e1c2 Fix: Consolidate divergent _since_unix implementations (T-09)
Consolidate the two divergent implementations of _since_unix from ban_service.py
and history_service.py into a single shared utility function in time_utils.py.

Changes:
- Move _since_unix to app/utils/time_utils.py with consistent time.time() approach
- Move TIME_RANGE_SLACK_SECONDS constant to app/utils/constants.py
- Update ban_service.py to import since_unix from time_utils
- Update history_service.py to import since_unix from time_utils
- Both services now use the same window boundary calculation with 60-second slack
- Add comprehensive tests for the shared since_unix function
- Document timestamp handling rationale in Backend-Development.md

This ensures dashboard and history queries return consistent row counts for the
same time range by using the same timestamp calculation and slack window across
all services.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-25 18:44:59 +02:00
83452ffc23 Refactor backend services and jail configuration
- Refactor action_config_service, filter_config_service, jail_config_service, and jail_service
- Add jail_socket utility module for socket communication
- Update test_jail_service with new test cases
- Update architecture and task documentation

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-25 18:34:03 +02:00
d467190eb1 Refactor geo caching and service layer tests
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-25 18:15:31 +02:00
654dbdb000 T-04: Encapsulate geo_service module-level mutable state in GeoCache class
Create GeoCache class with all mutable state as instance attributes:
- _cache, _neg_cache, _dirty, _geoip_reader, _geoip_initialized, _cache_lock
- All public methods: lookup(), lookup_batch(), lookup_cached_only(), flush_dirty(), load_from_db(), clear(), etc.

Initialization & Dependency Injection:
- Instantiate GeoCache in startup.py and store on app.state.geo_cache
- Add get_geo_cache() dependency function in dependencies.py
- Inject into routes and tasks via FastAPI's dependency system

Backward Compatibility:
- Maintain module-level functions in geo_service.py as deprecated wrappers
- All old callers continue to work through _default_geo_cache instance
- Remove test-escape-hatch functions (clear_cache, clear_neg_cache moved to methods)

Background Tasks:
- Update geo_cache_flush.py and geo_re_resolve.py to receive GeoCache instance
- Tasks now operate on injected instance rather than module globals

Tests:
- Refactor test_geo_service.py with geo_cache fixture providing fresh instances
- Update patch paths to target GeoCache methods correctly
- Fix internal state assertions to access instance attributes

Documentation:
- Update Architekture.md to document GeoCache as managed stateful service
- Describe cache lifecycle (load on startup, flush periodically, re-resolve stale)
- Note process-local limitations for multi-worker deployments

Fixes violation of Single Responsibility Principle: module no longer owns both
lookup logic and cache lifecycle management. Cache is now a first-class
injectable service with transparent lifecycle.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-23 16:18:09 +02:00
e593498de5 Strengthen setup password validation
- Add backend Pydantic password complexity validation for setup
- Update frontend setup page with password rule feedback and strength indicator
- Add/adjust setup API tests for password validation
- Document setup password requirements
- Fix frontend test type annotation issue
2026-04-20 19:23:12 +02:00
01f2e07921 Extract shared raw config file helpers and simplify raw_config_io_service 2026-04-18 20:18:54 +02:00
c1f188643c Move geo cache commit handling into repository layer 2026-04-18 20:10:05 +02:00
52e08e17a4 Guard geo_service.init_geoip against repeated initialization 2026-04-18 19:54:05 +02:00
db5b4cb77e Add settings and history archive repository protocols and DI support 2026-04-17 20:54:08 +02:00
7055971163 Harden preview_log path validation and add regression test 2026-04-17 20:37:14 +02:00
5e5d7c34b2 Document task DB access and unify background task DB handling 2026-04-17 17:18:49 +02:00
4754f1407e Mark task 19 done and centralize map-color thresholds in settings_service 2026-04-17 17:04:09 +02:00
7a1cb0c46c Extract health-check crash-detection logic into runtime state helper 2026-04-17 16:58:24 +02:00
1e2850a34e Add async lock protection to geo service cache and mark Task 16 done 2026-04-17 16:51:05 +02:00
04b2e2f700 Add global domain exception handlers in main.py
Register consistent HTTP error mappings for common domain exceptions and add regression tests for 404/400/500 handler behavior.
2026-04-17 16:42:18 +02:00
900d111a5d Refactor geo enrichment into jail_service and mark Task 14 done 2026-04-17 16:36:22 +02:00
487f252a4d Move history geo enrichment into history service 2026-04-17 16:28:53 +02:00
8c6950afc1 Task 13: move ban_ip, unban_ip, and get_active_bans from jail_service to ban_service and update routers/tests 2026-04-17 16:22:20 +02:00
58112fb191 Move auth session signing into auth_service.login 2026-04-17 15:33:09 +02:00
33643880ed Extract fail2ban restart orchestration into jail_service 2026-04-17 15:23:54 +02:00
c21cf82e9e Refactor map color threshold storage into dedicated settings service 2026-04-17 15:13:07 +02:00
13b3fde274 Fix stale activation record on failed jail activation
Record activation only after a successful jail activate request and add regression coverage to prevent stale last_activation state.
2026-04-17 14:53:57 +02:00
73cc212e28 Invert blocklist scheduler dependency to task callback 2026-04-15 21:31:08 +02:00
56f03f39c7 Move history archive max timestamp query into repository 2026-04-15 21:18:44 +02:00
cdb0c3681e Task 3: remove config_file_service facade, update direct imports and tests 2026-04-15 21:16:00 +02:00
0e22d1c425 Move config file exceptions into app.exceptions
Move ConfigDirError, ConfigFileNotFoundError, ConfigFileExistsError, ConfigFileWriteError, and ConfigFileNameError from raw_config_io_service into the shared domain exception module. Update router and tests to import the exceptions from app.exceptions.
2026-04-15 10:28:27 +02:00
328f3575e2 Move Fail2Ban exceptions into central app.exceptions module 2026-04-15 10:22:48 +02:00
a79f5339bc Refactor fail2ban DB path lookup and simplify geo router response 2026-04-15 09:15:50 +02:00
56c511d905 Fix module-level asyncio locks in jail_service
Initialize jail_service locks lazily to avoid import-time event loop binding and add regression tests for lock creation.
2026-04-15 09:10:38 +02:00
a8f2d2d7b9 Refactor geo re-resolve endpoint into geo_service and add typed response 2026-04-15 08:56:37 +02:00
b70dc6fa7a Refactor blocklist schedule management into service 2026-04-14 15:25:36 +02:00
58bb769a35 Refactor history sync into history_service and update docs/tests 2026-04-14 15:09:58 +02:00
86fa271c40 Remove FastAPI dependency from jail config service signatures 2026-04-14 15:01:05 +02:00
6b436dc354 Fix undefined names and config router imports / task status update 2026-04-14 13:53:39 +02:00
0e5e08374f Use shared SESSION_COOKIE_NAME in auth router tests 2026-04-14 10:33:32 +02:00
5e4d3fcf12 Remove Mock fallback from runtime_state and add runtime settings regression tests 2026-04-14 10:30:25 +02:00
cee5372690 Add backend capability cache reset helper for jail service tests 2026-04-14 10:24:14 +02:00
a564830abb Fix blocklist service injection and centralize session cookie name 2026-04-14 09:21:38 +02:00
8e43ef9ad2 Fix setup_service to mark setup_complete only after successful runtime DB init 2026-04-12 20:30:22 +02:00
e6df045e5e Fix startup runtime settings ordering and use effective database path for request DB connections 2026-04-12 20:02:40 +02:00
ee880e6296 Introduce explicit ApplicationContext and remove raw request.app.state usage 2026-04-12 19:56:01 +02:00
72488b14b2 Centralize fail2ban metadata resolution and cache DB path discovery 2026-04-12 19:48:33 +02:00
e271207795 Refactor fail2ban client to use vendored adapter 2026-04-12 19:25:56 +02:00