feat: Add CrowdSec logging, rate limiting, and fix MCP parameter defaults

- Add real client IP detection (CF-Connecting-IP / src fallback) to both frontends
- Add per-IP rate limiting (429) using real IP for Cloudflare compatibility
- Add CrowdSec syslog forwarding with custom log format
- Add httplog option for detailed HTTP logging
- Fix Python-level defaults on MCP tool parameters to match Field(default=X)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
kaffa
2026-02-07 00:22:39 +09:00
parent 4a411202d3
commit da533f407a
6 changed files with 53 additions and 14 deletions

View File

@@ -31,6 +31,7 @@ global
defaults
log global
mode http
option httplog
option dontlognull
option http-keep-alive
option forwardfor
@@ -53,10 +54,27 @@ frontend stats
# HTTP Frontend - forward to backend (same as HTTPS)
frontend http_front
bind *:80
# ACME challenge for certbot (unused - using DNS-01)
# acl is_acme path_beg /.well-known/acme-challenge/
# use_backend acme_backend if is_acme
# http-request redirect scheme https unless is_acme
# -- Shared security config (keep in sync with http_front/https_front) --
# Send HTTP logs to CrowdSec
log 10.253.100.240:514 local0
# Set real client IP (CF-Connecting-IP if via Cloudflare, otherwise direct client IP)
http-request set-var(txn.real_ip) req.hdr(CF-Connecting-IP) if { req.hdr(CF-Connecting-IP) -m found }
http-request set-var(txn.real_ip) src unless { var(txn.real_ip) -m found }
http-request set-header X-Real-IP %[var(txn.real_ip)]
log-format "%[var(txn.real_ip)]:%cp [%tr] %ft %b/%s %TR/%Tw/%Tc/%Tr/%Ta %ST %B %CC %CS %tsc %ac/%fc/%bc/%sc/%rc %sq/%bq \"%r\""
# Per-IP concurrent connection limit (slowloris protection)
# Note: http_front and https_front have separate stick-tables, so the same
# IP is counted independently in each frontend (HTTP vs HTTPS).
stick-table type ip size 200k expire 5m store conn_cur
acl is_internal src 127.0.0.0/8 100.64.0.0/10
http-request track-sc0 hdr_ip(X-Real-IP) if !is_internal
http-request deny deny_status 429 if !is_internal { sc_conn_cur(0) gt 500 }
# -- End shared security config --
# 2-stage map-based routing for performance:
# Stage 1: Exact match with map_str (O(log n) - fast, uses ebtree)
@@ -72,6 +90,27 @@ frontend https_front
bind quic4@:443 ssl crt /etc/haproxy/certs/ alpn h3
http-response set-header alt-svc "h3=\":443\"; ma=86400"
# -- Shared security config (keep in sync with http_front/https_front) --
# Send HTTP logs to CrowdSec
log 10.253.100.240:514 local0
# Set real client IP (CF-Connecting-IP if via Cloudflare, otherwise direct client IP)
http-request set-var(txn.real_ip) req.hdr(CF-Connecting-IP) if { req.hdr(CF-Connecting-IP) -m found }
http-request set-var(txn.real_ip) src unless { var(txn.real_ip) -m found }
http-request set-header X-Real-IP %[var(txn.real_ip)]
log-format "%[var(txn.real_ip)]:%cp [%tr] %ft %b/%s %TR/%Tw/%Tc/%Tr/%Ta %ST %B %CC %CS %tsc %ac/%fc/%bc/%sc/%rc %sq/%bq \"%r\""
# Per-IP concurrent connection limit (slowloris protection)
# Note: http_front and https_front have separate stick-tables, so the same
# IP is counted independently in each frontend (HTTP vs HTTPS).
stick-table type ip size 200k expire 5m store conn_cur
acl is_internal src 127.0.0.0/8 100.64.0.0/10
http-request track-sc0 hdr_ip(X-Real-IP) if !is_internal
http-request deny deny_status 429 if !is_internal { sc_conn_cur(0) gt 500 }
# -- End shared security config --
# MCP authentication (Bearer Token or Tailscale)
acl is_mcp hdr(host) -i mcp.inouter.com
acl valid_token req.hdr(Authorization) -m str "Bearer dcb7963ab3ef705f6b780818f78942a100efa3b55e3d2f99c4560b65da64c426"