feat(haproxy): simplify Gitea rate limiting and fix rule ordering
treefmt / nix fmt (pull_request) Successful in 5s
pytest / pytest (pull_request) Successful in 25s
build_systems / build-brain (pull_request) Successful in 43s
build_systems / build-bob (pull_request) Successful in 45s
build_systems / build-rhapsody-in-green (pull_request) Successful in 58s
build_systems / build-jeeves (pull_request) Successful in 2m31s
build_systems / build-rhapsody-in-green (push) Successful in 13s
build_systems / build-jeeves (push) Successful in 2m15s
treefmt / nix fmt (push) Failing after 13m42s
build_systems / build-brain (push) Failing after 18m43s
build_systems / build-bob (push) Failing after 18m43s
pytest / pytest (push) Failing after 23m43s
treefmt / nix fmt (pull_request) Successful in 5s
pytest / pytest (pull_request) Successful in 25s
build_systems / build-brain (pull_request) Successful in 43s
build_systems / build-bob (pull_request) Successful in 45s
build_systems / build-rhapsody-in-green (pull_request) Successful in 58s
build_systems / build-jeeves (pull_request) Successful in 2m31s
build_systems / build-rhapsody-in-green (push) Successful in 13s
build_systems / build-jeeves (push) Successful in 2m15s
treefmt / nix fmt (push) Failing after 13m42s
build_systems / build-brain (push) Failing after 18m43s
build_systems / build-bob (push) Failing after 18m43s
pytest / pytest (push) Failing after 23m43s
- Drop the session-cookie rate-limit bypass: Gitea hands the cookie to anonymous visitors too, so any cookie-accepting crawler could exempt itself after one request - Loosen the general Gitea limit from 10 to 50 req/10s since logged-in users are now rate-limited as well - Serve robots.txt above the rate-limit rules so throttled crawlers can always read the Crawl-delay, and capture Host/User-Agent before the deny so 429s are logged with them - Match the compare/diff cap on every repo case-insensitively instead of only /Richie/dotfiles - Remove the Jellyfin health check; it never ran (server had no check flag) and is not wanted
This commit was merged in pull request #31.
This commit is contained in:
@@ -37,6 +37,23 @@ frontend ContentSwitching
|
|||||||
acl host_gitea hdr(host) -i gitea.tmmworkshop.com
|
acl host_gitea hdr(host) -i gitea.tmmworkshop.com
|
||||||
acl host_norn_sight hdr(host) -i www.norn-sight.com
|
acl host_norn_sight hdr(host) -i www.norn-sight.com
|
||||||
|
|
||||||
|
# --- Request logging ---
|
||||||
|
# Capture the Host header and User-Agent so the httplog shows who is
|
||||||
|
# requesting what. They appear in the log's {captured|headers} field,
|
||||||
|
# in this order: {host|user-agent}. Client IP is already logged by httplog.
|
||||||
|
# Kept above the rate limiting so denied (429) requests are captured too.
|
||||||
|
http-request capture req.hdr(Host) len 100
|
||||||
|
http-request capture req.hdr(User-Agent) len 128
|
||||||
|
|
||||||
|
# --- robots.txt ---
|
||||||
|
# Serve a single global robots.txt for every vhost (asks crawlers to wait
|
||||||
|
# 10s between requests via Crawl-delay). Returned for both HTTP and HTTPS.
|
||||||
|
# File is deployed to /etc/haproxy/robots.txt by haproxy.nix.
|
||||||
|
# Kept above the rate limiting so crawlers can always read it: `return` is a
|
||||||
|
# terminating action, so robots.txt requests are never tracked or denied.
|
||||||
|
acl is_robots path /robots.txt
|
||||||
|
http-request return status 200 content-type "text/plain" file /etc/haproxy/robots.txt if is_robots
|
||||||
|
|
||||||
# --- Rate limiting (Gitea only, per source IP) ---
|
# --- Rate limiting (Gitea only, per source IP) ---
|
||||||
# Trusted devices exempt from rate limiting (add one line per IP/CIDR).
|
# Trusted devices exempt from rate limiting (add one line per IP/CIDR).
|
||||||
# Internal / reserved-for-private-use ranges:
|
# Internal / reserved-for-private-use ranges:
|
||||||
@@ -47,42 +64,25 @@ frontend ContentSwitching
|
|||||||
# Add specific public devices below as needed:
|
# Add specific public devices below as needed:
|
||||||
# acl rate_limit_allowlist src 192.0.2.50
|
# acl rate_limit_allowlist src 192.0.2.50
|
||||||
|
|
||||||
# Logged-in Gitea sessions bypass the rate limits. Gitea sets the
|
|
||||||
# `i_like_gitea` session cookie on login, and it is only sent to the Gitea
|
|
||||||
# vhost, so this only affects Gitea traffic. Note: this matches cookie
|
|
||||||
# PRESENCE, not validity, so it filters anonymous crawlers (which carry no
|
|
||||||
# cookie) rather than acting as a hard security boundary.
|
|
||||||
acl gitea_logged_in req.cook(i_like_gitea) -m found
|
|
||||||
|
|
||||||
# Track HTTP request rate per client IP over a 10s sliding window. Only Gitea
|
# Track HTTP request rate per client IP over a 10s sliding window. Only Gitea
|
||||||
# is rate-limited; all other vhosts are left alone.
|
# is rate-limited; all other vhosts are left alone.
|
||||||
# ipv6 table type also covers IPv4 (mapped), so it works for both binds.
|
# ipv6 table type also covers IPv4 (mapped), so it works for both binds.
|
||||||
stick-table type ipv6 size 100k expire 30s store http_req_rate(10s)
|
stick-table type ipv6 size 100k expire 30s store http_req_rate(10s)
|
||||||
http-request track-sc0 src if host_gitea !is_acme !rate_limit_allowlist !gitea_logged_in
|
http-request track-sc0 src if host_gitea !is_acme !rate_limit_allowlist
|
||||||
# Threshold: deny (429) when a client exceeds this many requests per 10s.
|
# Threshold: deny (429) when a client exceeds this many requests per 10s.
|
||||||
acl over_rate_limit sc_http_req_rate(0) gt 10
|
# Kept loose (50/10s) since logged-in users are rate-limited too; a page
|
||||||
http-request deny deny_status 429 if over_rate_limit host_gitea !is_acme !rate_limit_allowlist !gitea_logged_in
|
# load can burst a few dozen asset requests.
|
||||||
|
acl over_rate_limit sc_http_req_rate(0) gt 50
|
||||||
# --- Request logging ---
|
http-request deny deny_status 429 if over_rate_limit host_gitea !is_acme !rate_limit_allowlist
|
||||||
# Capture the Host header and User-Agent so the httplog shows who is
|
|
||||||
# requesting what. They appear in the log's {captured|headers} field,
|
|
||||||
# in this order: {host|user-agent}. Client IP is already logged by httplog.
|
|
||||||
http-request capture req.hdr(Host) len 100
|
|
||||||
http-request capture req.hdr(User-Agent) len 128
|
|
||||||
|
|
||||||
# --- robots.txt ---
|
|
||||||
# Serve a single global robots.txt for every vhost (asks crawlers to wait
|
|
||||||
# 10s between requests via Crawl-delay). Returned for both HTTP and HTTPS.
|
|
||||||
# File is deployed to /etc/haproxy/robots.txt by haproxy.nix.
|
|
||||||
acl is_robots path /robots.txt
|
|
||||||
http-request return status 200 content-type "text/plain" file /etc/haproxy/robots.txt if is_robots
|
|
||||||
|
|
||||||
# --- Per-endpoint limit: Gitea compare/diff is expensive; cap at 1 req / 5 min / IP ---
|
# --- Per-endpoint limit: Gitea compare/diff is expensive; cap at 1 req / 5 min / IP ---
|
||||||
# Tracked in a separate 5-minute table (st_compare) since a proxy has only one
|
# Tracked in a separate 5-minute table (st_compare) since a proxy has only one
|
||||||
# inline stick-table. Allow-listed (internal) IPs are exempt.
|
# inline stick-table. Allow-listed (internal) IPs are exempt.
|
||||||
acl is_gitea_compare path_beg /Richie/dotfiles/compare
|
# Matches /<owner>/<repo>/compare on every repo; -i because Gitea routes are
|
||||||
http-request track-sc1 src table st_compare if host_gitea is_gitea_compare !rate_limit_allowlist !gitea_logged_in
|
# case-insensitive.
|
||||||
http-request deny deny_status 429 if host_gitea is_gitea_compare !rate_limit_allowlist !gitea_logged_in { sc_http_req_rate(1,st_compare) gt 1 }
|
acl is_gitea_compare path_reg -i ^/[^/]+/[^/]+/compare
|
||||||
|
http-request track-sc1 src table st_compare if host_gitea is_gitea_compare !rate_limit_allowlist
|
||||||
|
http-request deny deny_status 429 if host_gitea is_gitea_compare !rate_limit_allowlist { sc_http_req_rate(1,st_compare) gt 1 }
|
||||||
|
|
||||||
# Hosts allowed to serve plain HTTP (add entries to skip the HTTPS redirect)
|
# Hosts allowed to serve plain HTTP (add entries to skip the HTTPS redirect)
|
||||||
acl allow_http hdr(host) -i __none__
|
acl allow_http hdr(host) -i __none__
|
||||||
@@ -117,10 +117,8 @@ backend cache_nodes
|
|||||||
server server 127.0.0.1:5000
|
server server 127.0.0.1:5000
|
||||||
|
|
||||||
backend jellyfin
|
backend jellyfin
|
||||||
option httpchk
|
mode http
|
||||||
option forwardfor
|
option forwardfor
|
||||||
http-check send meth GET uri /health
|
|
||||||
http-check expect string Healthy
|
|
||||||
server jellyfin 127.0.0.1:8096
|
server jellyfin 127.0.0.1:8096
|
||||||
|
|
||||||
backend share_nodes
|
backend share_nodes
|
||||||
|
|||||||
Reference in New Issue
Block a user