diff --git a/ansible/haproxy/haproxy.cfg.j2 b/ansible/haproxy/haproxy.cfg.j2 index d1ab685..3607002 100644 --- a/ansible/haproxy/haproxy.cfg.j2 +++ b/ansible/haproxy/haproxy.cfg.j2 @@ -87,7 +87,20 @@ frontend https_frontend # Deny if auth endpoint rate exceeded http-request deny deny_status 429 if host_id is_auth_endpoint { sc_http_req_rate(1,st_casdoor_auth) gt 20 } - + + # ------------------------------------------------------------------------- + # Internal observability + probe endpoints + # ------------------------------------------------------------------------- + # These must never be served through the public proxy. Real scrapes/probes + # reach app hosts directly on the LAN; anything arriving here is external. + # Defense-in-depth — app nginx also enforces this via a real-IP allowlist. + # 404 (not 403) so the edge doesn't advertise the path exists. Exact paths + # + trailing-slash forms only; never path_beg /mcp, which would break the + # real MCP endpoint. App-host-agnostic by design. + acl is_internal_obs path /metrics /nginx_status /mcp/live /mcp/ready /mcp/health + acl is_internal_obs path_beg /nginx_status/ /mcp/live/ /mcp/ready/ /mcp/health/ + http-request deny deny_status 404 if is_internal_obs !{ src 10.10.0.0/16 } + {% for backend in haproxy_backends %} {% if backend.subdomain %} # ACL for {{ backend.subdomain }}.{{ haproxy_domain }} (matches with or without port)