libnginx-mod-http-lua is a dynamic module in Debian nginx; it must be
explicitly loaded with load_module or Lua directives are unrecognized.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
lua-resty-redis is an OpenResty library not packaged in Debian repos.
Download resty_redis.lua from upstream and install it to /usr/lib/lua/resty/redis.lua
at image build time. Update lua_package_path to match.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
OpenResty has no arm64 packages for Debian Bookworm; the official repo
only publishes amd64. Switch to Debian's own packages which support both
architectures and avoid any external repo setup:
nginx libnginx-mod-http-geoip2 libnginx-mod-http-lua lua-resty-redis
GeoIP2 C module returns (same nginx version → compatible). ASN/UA blocking
goes back to nginx if() blocks. blocklist.lua handles only the Redis checks
(prefix shared dict + pipelined GET for global/API block keys).
lua_package_path set to /usr/share/lua/5.1/ where Debian installs resty.*.
All paths revert to /etc/nginx/; start.sh reverts to /usr/sbin/nginx.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace nginx + libnginx-mod-http-geoip2 with OpenResty so that blocked
IPs are rejected before reaching Gunicorn, saving worker CPU on DDoS.
nginx layer (read-only, DB 1):
- blocklist.lua: UA check (nginx map var), ASN check (lua-resty-maxminddb,
replaces geoip2 C module), IP-prefix check (shared dict refreshed every
60s, 4-candidate O(1) lookup), pipelined GET for global IP block and
per-tenant API block. Parses REDIS_URL. Fail-open on Redis error.
- lua_shared_dict ip_prefix_blocked 1m: in-process prefix cache.
- init_by_lua_block: opens MaxMind ASN DB once in master process.
- init_worker_by_lua_block: refreshes prefix SET from Redis every 60s.
Django (ratelimit.py):
- _refresh_ip_prefix_blocklist: normalises entries to trailing-dot form
on load so per-request checks are O(1) set membership, not iteration.
- _is_ip_prefix_blocked: 4-candidate check (p1., p1.p2., p1.p2.p3., ip)
against the local set; same 60s refresh cadence as before.
Capacity (1,200 tenants, single Redis):
- Django pool: max_connections 6 → 3 (7,200 peak connections).
- nginx keepalive pool: 1 connection/worker (4,800 peak connections).
- Total: ~12,200 connections — 39% headroom under maxclients 20,000.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
SAPL pages fire 12-45 parallel requests; the old 30r/m nginx zone and
35/m Django threshold blocked normal navigation. Key changes:
nginx (nginx.conf / sapl.conf / start.sh):
- Split sapl_general (30r/m) into four dedicated zones:
sapl_general 90r/m burst=180 (HTML pages)
sapl_media 180r/m burst=180 (/media/ — own bucket, no longer drains general)
sapl_api 60r/m burst=120 (/api/ — quota layer is the real constraint)
sapl_heavy 10r/m burst=20 (/relatorios/ — unchanged, nodelay kept)
- /media/ and /api/ location blocks now reference their own zones
Django (settings.py):
- RATE_LIMITER_RATE: 35/m → 120/m
- RATE_LIMITER_RATE_AUTHENTICATED: 120/m → 240/m
- RATE_LIMIT_404_THRESHOLD: 10 → 20
- API_QUOTA_ANON_DAILY: 50 → 500 / weekly 350 → 3500
- API_QUOTA_AUTH_DAILY: 1000 → 5000 / weekly 7000 → 35000
Middleware (ratelimit.py):
- Authenticated users no longer receive a persistent 300s block key on
rate breach — they get 429 for the over-limit request and the window
resets naturally after 60s. A 5-minute lockout is wrong for a logged-in
user who clicked too fast.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add AnonCachePageMixin (sapl/middleware/page_cache.py) that stores full
view responses in the default Redis cache for anonymous (unauthenticated)
GET requests only. Authenticated users always bypass the cache so CSRF
tokens and user-specific UI controls are never served stale.
Applied to:
- ParlamentarCrud.ListView / DetailView — TTL 600 s (changes each term)
- AudienciaCrud.ListView — TTL 120 s (hearings added infrequently)
- ComissaoCrud.ListView — TTL 300 s (committees change rarely)
Also:
- Add PAGE_CACHE_TTL_LIST/DETAIL/STABLE settings (env-configurable)
- Add bingbot + SERankingBacklinksBot to nginx UA blocklist (were already
in BOT_UA_FRAGMENTS / robots.txt; nginx map was the only gap)
- Remove unused ratelimit/method_decorator/RATE_LIMITER_RATE imports from
audiencia/views.py that crept in during Phase 2
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove a analise de vínculos cíclicos na construção inicial
do form do filterset.
- O item anterior deve resolver o timeout causado na abertura da
anexação em lote, no entanto os timeouts do nginx e gunicorn foram
aumentados.