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.
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.
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.
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.
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.
- 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
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.