feat(api): cap banned IPs per jail in /api/summary response#136
Conversation
The dashboard's /api/summary handler enumerates every active jail and
returns the FULL banned-IP list for each. On hosts with large jails
(e.g. 4,500+ active bans on one jail across 17 active jails), this
single endpoint can take 3+ minutes to respond, exceeding any
reasonable HTTP gateway/proxy timeout (we hit a 60s ALB idle_timeout
default that returned 504). The dashboard renders only the first 5
IPs per jail by default (renderBannedIPs, maxVisible=5), so returning
a few hundred is more than enough for the UI's needs.
This patch caps BannedIPs per jail at 100 (configurable via the
FAIL2BAN_UI_SUMMARY_MAX_IPS env var; set to 0 to disable). The
accurate TotalBanned count is always preserved -- only the BannedIPs
slice is truncated.
Reproduction:
fail2ban-client status nginx-property-scraper # ~4500 banned IPs
curl http://localhost:8080/api/summary?serverId=local
# times out at >180s
After the patch (FAIL2BAN_UI_SUMMARY_MAX_IPS=100):
curl http://localhost:8080/api/summary?serverId=local
# responds in <1s, 100 IPs per jail, accurate TotalBanned
Trade-offs: a future user who actually wants the full IP list per jail
in /api/summary can opt out via FAIL2BAN_UI_SUMMARY_MAX_IPS=0. A
follow-up could expose the full list via a paginated /api/jails/:jail
endpoint, but that's out of scope for this fix.
|
Hi @michael-ferioli thanks a lot for your detailed write-up, the reproduction steps, and your careful analysis of A couple of things before we can merge this: 1. Please retarget the PR to 2. The hard cap of 100 has UI side effects we need to address.
So while the cap removes the timeout, it creates new problems.. 3. Preferred direction Your own follow-up proposal in the description -> a separate paginated endpoint like Would you be willing to fold that into this PR (or replace it with that PR)? Concretely, I think that would make sense:
Alternatively we could also think of an redis cache to initial fetch all the blocked IP's / Jails once inside redis and call them from there (then update only single entries on ban / unban - or the whole dataset on server enable / disable events in the background.) If that's too large to land quickly and you'd prefer to keep this PR as a stopgap, you can also only switch the destination branch to dev in a first step. We will take then the rest to our roadmap. Happy either way, your call on which path you want to take. Thanks again for the contribution! |
|
Other plan, i will merge it then move it to dev and will take a look at it. Thanks for your contribution again! 👍 |
Why
The dashboard's
/api/summaryhandler enumerates every active jail and returns the full banned-IP list for each viacollectJailInfos. On hosts with large jails this becomes a hard scaling problem.In our environment we have ~17 active jails and one of them (
nginx-property-scraper) holds 4,500+ active bans at any given moment due to a sustained MLS-scraping campaign. With those numbers,/api/summarytakes 3+ minutes to respond. Every reasonable HTTP gateway/proxy returns 504 long before that — for us it was AWS ALB's default 60sidle_timeout. Result: the dashboard's "Loading summary data…" panel never resolves and the JS console showsSyntaxError: Unexpected token '<', '<html><h'... is not valid JSON(the parser choking on the gateway's HTML 504).What this changes
Adds a small cap on
BannedIPsper jail in the JSON returned by/api/summary. The accurateTotalBannedcount is always preserved — only theBannedIPsslice is truncated.FAIL2BAN_UI_SUMMARY_MAX_IPS0to disable the cap and restore current behaviour (full list)Why 100 is enough for the dashboard
pkg/web/static/js/dashboard.js'srenderBannedIPsshows only the first 5 IPs per jail by default (maxVisible = 5); the rest are hidden behind a "show more" toggle. 100 leaves comfortable headroom for the expanded view without forcing the backend to materialise tens of thousands of strings into a single JSON response.Reproduction
After this patch (with default
FAIL2BAN_UI_SUMMARY_MAX_IPS=100):Trade-offs and follow-ups
/api/summarycan keep current behaviour viaFAIL2BAN_UI_SUMMARY_MAX_IPS=0./api/jails/:jail/banned?limit=&offset=) and have the dashboard fetch it on-demand when the user expands a jail. Happy to follow up with that as a separate PR if you'd like — keeping this one minimal so it can land quickly.Diff
+23 / -1lines, single fileinternal/fail2ban/connector_global.go.Thanks for fail2ban-ui — it's been a great fit for our infrastructure.
Made with Cursor