From 402c48b4a04e63054660003e9775f7a0ee27b641 Mon Sep 17 00:00:00 2001 From: jknapp Date: Sat, 30 Aug 2025 08:54:55 -0700 Subject: [PATCH] Remove 40X rate limiting from HAProxy to prevent false positives MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Removed all 40X error tracking and rate limiting from HAProxy templates - Preserved critical IP forwarding headers (X-CLIENT-IP, X-Real-IP, X-Forwarded-For) - Kept stick table and IP blocking infrastructure for potential future use - Rate limiting can now be implemented at container level with proper context This change prevents legitimate developers from being rate-limited during normal development activities while maintaining proper client IP forwarding for container-level security and logging. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- templates/hap_backend.tpl | 31 ++--------------- templates/hap_backend_basic.tpl | 29 ++-------------- templates/hap_backend_http_check.tpl | 29 ++-------------- templates/hap_listener.tpl | 52 +++------------------------- 4 files changed, 10 insertions(+), 131 deletions(-) diff --git a/templates/hap_backend.tpl b/templates/hap_backend.tpl index b004dc8..3b38212 100644 --- a/templates/hap_backend.tpl +++ b/templates/hap_backend.tpl @@ -2,39 +2,12 @@ backend {{ name }}-backend option forwardfor # Pass the real client IP to backend (from proxy headers or direct connection) + # This is crucial for container-level logging and security tools http-request add-header X-CLIENT-IP %[var(txn.real_ip)] http-request set-header X-Real-IP %[var(txn.real_ip)] + http-request set-header X-Forwarded-For %[var(txn.real_ip)] {% if ssl_enabled %}http-request set-header X-Forwarded-Proto https if { ssl_fc }{% endif %} - # Define error status codes - acl is_404_error status 404 - acl is_403_error status 403 - acl is_401_error status 401 - acl is_400_error status 400 - - # Define suspicious scan patterns - only these count as scan attempts - # Script/config files that shouldn't exist on most sites - acl scan_scripts path_reg -i \.(php|asp|aspx|jsp|cgi|pl|py|rb|sh|bash)$ - acl scan_admin path_reg -i /(wp-admin|wp-login|phpmyadmin|adminer|manager|admin-console) - acl scan_configs path_reg -i \.(env|git|svn|htaccess|htpasswd|ini|conf|config|yml|yaml|toml) - acl scan_backups path_reg -i \.(backup|bak|old|orig|save|swp|sql|db|dump|tar|zip|rar|7z) - acl scan_vulns path_reg -i /(cgi-bin|fckeditor|tiny_mce|ckfinder|userfiles|filemanager) - - # Define legitimate static assets that should NOT count as scan attempts - acl legitimate_assets path_reg -i \.(css|js|jpg|jpeg|png|gif|svg|ico|woff|woff2|ttf|eot|otf|map|webp|mp4|webm|pdf)$ - acl legitimate_paths path_beg /static/ /assets/ /media/ /images/ /fonts/ /css/ /js/ - - # Track scan attempts in the frontend stick table - # Only count suspicious 404s and auth failures - # Multiple ACL conditions on same line = AND, multiple lines = OR - http-response sc-inc-gpc0(0) if scan_scripts is_404_error !legitimate_assets !legitimate_paths - http-response sc-inc-gpc0(0) if scan_admin is_404_error !legitimate_assets !legitimate_paths - http-response sc-inc-gpc0(0) if scan_configs is_404_error !legitimate_assets !legitimate_paths - http-response sc-inc-gpc0(0) if scan_backups is_404_error !legitimate_assets !legitimate_paths - http-response sc-inc-gpc0(0) if scan_vulns is_404_error !legitimate_assets !legitimate_paths - http-response sc-inc-gpc0(0) if is_403_error !legitimate_assets !legitimate_paths - http-response sc-inc-gpc0(0) if is_401_error - {% for server in servers %} server {{ server.server_name }} {{ server.server_address }}:{{ server.server_port }} {{ server.server_options }} diff --git a/templates/hap_backend_basic.tpl b/templates/hap_backend_basic.tpl index e84d111..a594b53 100644 --- a/templates/hap_backend_basic.tpl +++ b/templates/hap_backend_basic.tpl @@ -2,35 +2,10 @@ backend {{ name }}-backend option forwardfor # Pass the real client IP to backend (from proxy headers or direct connection) + # This is crucial for container-level logging and security tools http-request add-header X-CLIENT-IP %[var(txn.real_ip)] http-request set-header X-Real-IP %[var(txn.real_ip)] - - # Define error status codes - acl is_404_error status 404 - acl is_403_error status 403 - acl is_401_error status 401 - acl is_400_error status 400 - - # Define suspicious scan patterns - only these count as scan attempts - acl scan_scripts path_reg -i \.(php|asp|aspx|jsp|cgi|pl|py|rb|sh|bash)$ - acl scan_admin path_reg -i /(wp-admin|wp-login|phpmyadmin|adminer|manager|admin-console) - acl scan_configs path_reg -i \.(env|git|svn|htaccess|htpasswd|ini|conf|config|yml|yaml|toml) - acl scan_backups path_reg -i \.(backup|bak|old|orig|save|swp|sql|db|dump|tar|zip|rar|7z) - acl scan_vulns path_reg -i /(cgi-bin|fckeditor|tiny_mce|ckfinder|userfiles|filemanager) - - # Define legitimate static assets that should NOT count - acl legitimate_assets path_reg -i \.(css|js|jpg|jpeg|png|gif|svg|ico|woff|woff2|ttf|eot|otf|map|webp|mp4|webm|pdf)$ - acl legitimate_paths path_beg /static/ /assets/ /media/ /images/ /fonts/ /css/ /js/ - - # Track scan attempts in the frontend stick table - # Only count suspicious 404s and auth failures - http-response sc-inc-gpc0(0) if scan_scripts is_404_error !legitimate_assets !legitimate_paths - http-response sc-inc-gpc0(0) if scan_admin is_404_error !legitimate_assets !legitimate_paths - http-response sc-inc-gpc0(0) if scan_configs is_404_error !legitimate_assets !legitimate_paths - http-response sc-inc-gpc0(0) if scan_backups is_404_error !legitimate_assets !legitimate_paths - http-response sc-inc-gpc0(0) if scan_vulns is_404_error !legitimate_assets !legitimate_paths - http-response sc-inc-gpc0(0) if is_403_error !legitimate_assets !legitimate_paths - http-response sc-inc-gpc0(0) if is_401_error + http-request set-header X-Forwarded-For %[var(txn.real_ip)] {% for server in servers %} server {{ server.server_name }} {{ server.server_address }}:{{ server.server_port }} {{ server.server_options }} diff --git a/templates/hap_backend_http_check.tpl b/templates/hap_backend_http_check.tpl index 1c865b4..fee7b5c 100644 --- a/templates/hap_backend_http_check.tpl +++ b/templates/hap_backend_http_check.tpl @@ -3,37 +3,12 @@ backend {{ name }}-backend option forwardfor option httpchk # Pass the real client IP to backend (from proxy headers or direct connection) + # This is crucial for container-level logging and security tools http-request add-header X-CLIENT-IP %[var(txn.real_ip)] http-request set-header X-Real-IP %[var(txn.real_ip)] + http-request set-header X-Forwarded-For %[var(txn.real_ip)] {% if ssl_enabled %}http-request set-header X-Forwarded-Proto https if { ssl_fc }{% endif %} - # Define error status codes - acl is_404_error status 404 - acl is_403_error status 403 - acl is_401_error status 401 - acl is_400_error status 400 - - # Define suspicious scan patterns - only these count as scan attempts - acl scan_scripts path_reg -i \.(php|asp|aspx|jsp|cgi|pl|py|rb|sh|bash)$ - acl scan_admin path_reg -i /(wp-admin|wp-login|phpmyadmin|adminer|manager|admin-console) - acl scan_configs path_reg -i \.(env|git|svn|htaccess|htpasswd|ini|conf|config|yml|yaml|toml) - acl scan_backups path_reg -i \.(backup|bak|old|orig|save|swp|sql|db|dump|tar|zip|rar|7z) - acl scan_vulns path_reg -i /(cgi-bin|fckeditor|tiny_mce|ckfinder|userfiles|filemanager) - - # Define legitimate static assets that should NOT count - acl legitimate_assets path_reg -i \.(css|js|jpg|jpeg|png|gif|svg|ico|woff|woff2|ttf|eot|otf|map|webp|mp4|webm|pdf)$ - acl legitimate_paths path_beg /static/ /assets/ /media/ /images/ /fonts/ /css/ /js/ - - # Track scan attempts in the frontend stick table - # Only count suspicious 404s and auth failures - http-response sc-inc-gpc0(0) if scan_scripts is_404_error !legitimate_assets !legitimate_paths - http-response sc-inc-gpc0(0) if scan_admin is_404_error !legitimate_assets !legitimate_paths - http-response sc-inc-gpc0(0) if scan_configs is_404_error !legitimate_assets !legitimate_paths - http-response sc-inc-gpc0(0) if scan_backups is_404_error !legitimate_assets !legitimate_paths - http-response sc-inc-gpc0(0) if scan_vulns is_404_error !legitimate_assets !legitimate_paths - http-response sc-inc-gpc0(0) if is_403_error !legitimate_assets !legitimate_paths - http-response sc-inc-gpc0(0) if is_401_error - {% for server in servers %} server {{ server.server_name }} {{ server.server_address }}:{{ server.server_port }} {{ server.server_options }} {% endfor %} diff --git a/templates/hap_listener.tpl b/templates/hap_listener.tpl index 18966fe..c0bdf65 100644 --- a/templates/hap_listener.tpl +++ b/templates/hap_listener.tpl @@ -4,9 +4,8 @@ frontend web # crt can now be a path, so it will load all .pem files in the path bind 0.0.0.0:443 ssl crt {{ crt_path }} alpn h2,http/1.1 - # Stick table for tracking attacks with escalating timeouts - # gpc0 = total scan attempts - # gpc1 = escalation level (0=none, 1=level1, 2=level2, 3=level3) + # Stick table kept for potential future use or custom implementations + # Can be used by containers to track IPs if needed stick-table type ip size 200k expire 1h store gpc0,gpc1,http_err_rate(10s) # Whitelist trusted networks and monitoring systems @@ -33,55 +32,12 @@ frontend web http-request set-var(txn.real_ip) src if !has_cf_connecting_ip !has_x_real_ip !has_x_forwarded_for # Track the real client IP in stick table (not the proxy IP) + # Kept for potential container-level tracking integrations http-request track-sc0 var(txn.real_ip) # IP blocking using map file (no word limit, runtime updates supported) # Map file: /etc/haproxy/blocked_ips.map # Runtime updates: echo "add map #0 IP_ADDRESS" | socat stdio /var/run/haproxy.sock - # Now checks the real client IP (from headers if present, otherwise src) + # Checks the real client IP (from headers if present, otherwise src) http-request set-path /blocked-ip if { var(txn.real_ip) -m ip -f /etc/haproxy/blocked_ips.map } use_backend default-backend if { var(txn.real_ip) -m ip -f /etc/haproxy/blocked_ips.map } - - # Define threat levels based on accumulated error responses from backends - # These will be checked on subsequent requests after errors are tracked - acl scanner_low sc0_get_gpc0 ge 25 # 25+ errors = potential scanner - acl scanner_medium sc0_get_gpc0 ge 40 # 40+ errors = likely scanner - acl scanner_high sc0_get_gpc0 ge 60 # 60+ errors = confirmed scanner - acl scanner_critical sc0_get_gpc0 ge 100 # 100+ errors = aggressive scanner - - # Rate-based detection (burst of errors) - acl burst_scanner sc0_http_err_rate gt 5 # >5 errors in 10 seconds - - # Escalation levels (tracks how many times we've escalated this IP) - acl escalation_level_0 sc0_get_gpc1 eq 0 # First offense - acl escalation_level_1 sc0_get_gpc1 eq 1 # Second offense - acl escalation_level_2 sc0_get_gpc1 eq 2 # Third offense - acl escalation_level_3 sc0_get_gpc1 ge 3 # Repeat offender - - # BLOCKING RULES - Progressive response based on threat level - - # Level 4: Complete block for critical threats (50+ errors) - http-request deny deny_status 429 if scanner_critical - - # Level 3: Silent drop for obvious scanners and burst attacks - # This immediately closes the connection without any response - http-request silent-drop if scanner_high # 35+ errors - http-request silent-drop if scanner_medium burst_scanner # 20+ errors with burst - http-request silent-drop if scanner_medium escalation_level_2 # Repeat medium scanner - http-request silent-drop if burst_scanner escalation_level_1 # Repeat burst scanner - - # Level 2: Tarpit for medium scanners (first offense) - # 10 second delay before closing connection - http-request tarpit deny_status 429 if scanner_medium escalation_level_0 - http-request tarpit deny_status 429 if scanner_medium escalation_level_1 - - # Level 1: Tarpit for low-level scanners - # 10 second delay to slow them down - http-request tarpit deny_status 429 if scanner_low - http-request tarpit deny_status 429 if burst_scanner escalation_level_0 - - # Increment escalation level when we apply any protection - # This tracks how many times this IP has been actioned - http-request sc-inc-gpc1(0) if scanner_low or scanner_medium or scanner_high or burst_scanner - - # Note: The backend will increment sc0_get_gpc0 when it sees 400/401/403/404 responses