C1: deploy adelaide-baby-shower-app to ringtail k3s #349

Merged
eblume merged 20 commits from shower-app-deploy into main 2026-05-11 13:47:20 -07:00
Showing only changes of commit fb6067b620 - Show all commits

C1: shower-specific rate-limit zone for venue-wifi NAT

Default `general` zone (10r/s burst=20) is tuned for internet drive-by
traffic. At the party, 30 guests scanning the splash QR from one
venue-wifi NAT'd public IP would each fetch HTML + ~5 static assets
within a few seconds — easily clearing burst=20, and the second-wave
guests would see 503 with no auto-retry.

New shower_general zone (50r/s burst=200) absorbs that simultaneous-
load spike. Exploit scanners still trip it: the 45.88.138.44 burst
we already saw in Loki fired ~30 req in 2s, well above the new
sustained 50r/s when extrapolated, and burst=200 is still a hard cap
on instantaneous spikes.

Self-healing: `limit_req` is a token bucket — no persistent ban,
nothing to manually flush. A guest who trips it auto-recovers within
~1s; tuning here is about not tripping it on legit traffic in the
first place.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Erich Blume 2026-05-11 13:44:22 -07:00

View file

@ -34,6 +34,15 @@ http {
# bucket. $http_fly_client_ip has the actual client IP. # bucket. $http_fly_client_ip has the actual client IP.
limit_req_zone $http_fly_client_ip zone=forge_auth:10m rate=3r/s; limit_req_zone $http_fly_client_ip zone=forge_auth:10m rate=3r/s;
# Shower-specific zone: loose enough that ~30 guests sharing a single
# venue-wifi NAT'd public IP can all scan the QR and load the splash
# (HTML + a handful of static asset hits each) without anyone tripping
# the limit. 50r/s + burst=200 covers the simultaneous-load spike;
# exploit scanners still trip it (e.g. the .env-sweeping bot we saw
# fired ~30 req in 2s that pattern stays caught). See the
# shower.eblu.me server block for the matching `limit_req`.
limit_req_zone $http_fly_client_ip zone=shower_general:10m rate=50r/s;
# fail2ban deny list banned IPs are written here by fail2ban and # fail2ban deny list banned IPs are written here by fail2ban and
# checked via the $forge_banned variable. The file is touched at # checked via the $forge_banned variable. The file is touched at
# container start to ensure it exists. # container start to ensure it exists.
@ -318,8 +327,13 @@ http {
listen 8080; listen 8080;
server_name shower.eblu.me; server_name shower.eblu.me;
# General per-IP rate limit (cushion for the splash page + form posts) # Per-IP rate limit. shower_general (50r/s, burst=200) instead of
limit_req zone=general burst=20 nodelay; # the global `general` zone because at the party, guests on the
# venue's wifi all NAT through a single Fly-Client-IP 30 guests
# scanning the QR at once would each fetch HTML + a few static
# assets, easily clearing 20 burst on `general`. Exploit scanners
# still trip it (sustained 50r/s patterns).
limit_req zone=shower_general burst=200 nodelay;
# Image uploads from /host/'s prize cropper are ~150-300 KiB JPEGs. # Image uploads from /host/'s prize cropper are ~150-300 KiB JPEGs.
# The host page itself isn't reachable here, but /media/ reads can # The host page itself isn't reachable here, but /media/ reads can