traefik: real client-IP visibility via X-Forwarded-For (CrowdSec prereq #1) #81

Merged
k.renner merged 1 commits from traefik-client-ip-visibility into master 2026-06-27 15:48:02 +00:00
2 changed files with 32 additions and 4 deletions
@@ -1,7 +1,11 @@
# CrowdSec bouncer middleware, attached globally to the websecure
# entrypoint (see helmrelease.yaml ports.websecure.middlewares). The
# plugin queries the CrowdSec LAPI and rejects requests from IPs with an
# active ban decision.
# CrowdSec bouncer middleware. Currently INERT — enforcement was disabled
# after the 2026-06-27 IPv6 outage (PR #80) and this Middleware is not
# attached to any router. Re-enable by re-adding the entrypoint default
# middleware in helmrelease.yaml additionalArguments
# (--entryPoints.websecure.http.middlewares=ingress-crowdsec-bouncer@kubernetescrd),
# but only after client-IP visibility (done here via forwardedHeadersTrustedIPs)
# AND an internal allowlist are in place. When attached, the plugin queries
# the CrowdSec LAPI and rejects requests from IPs with an active ban decision.
#
# Fail-OPEN by design: Traefik is the only ingress controller, so a
# CrowdSec/LAPI outage must never take down every app. updateMaxFailure=-1
@@ -28,6 +32,16 @@ spec:
streamStartupBlock: false
defaultDecisionSeconds: 60
httpTimeoutSeconds: 10
# Evaluate the real client IP from X-Forwarded-For when the request
# comes from a trusted in-cluster forwarder (the netbird-ingress
# routing peer re-originates from a pod IP). Mirrors Traefik's
# forwardedHeaders.trustedIPs so the bouncer bans real clients, not the
# shared pod IP. Direct LAN/mesh traffic with no XFF still uses the
# RemoteAddr (a pod IP) — that residual is handled by an internal
# allowlist before enforcement is re-enabled.
forwardedHeadersTrustedIPs:
- "10.244.0.0/16"
- "fd10:244::/56"
crowdsecLapiScheme: http
crowdsecLapiHost: crowdsec-service.crowdsec.svc.cluster.local:8080
# Static pre-shared bouncer key, pre-registered with the LAPI via the
@@ -90,6 +90,20 @@ spec:
scheme: https
permanent: true
websecure:
# Real client-IP visibility. The netbird-ingress routing peer
# re-originates every mesh + WAN-via-proxy connection from its own
# pod IP (rootless userspace WireGuard), so Traefik's TCP peer for
# that traffic is always a pod IP (e.g. fd10:244::2de1) — collapsing
# all external clients to one source. The NetBird reverse-proxy
# (HTTP mode, on the VPS) sets X-Forwarded-For with the real WAN
# client IP; trust the pod CIDRs so Traefik reads it instead. Without
# this, a CrowdSec bouncer bans the shared pod IP and 403s everyone
# (the 2026-06-27 IPv6 outage). Harmless no-op when no XFF is present
# (direct LAN/mesh) — those keep their RemoteAddr.
forwardedHeaders:
trustedIPs:
- "10.244.0.0/16"
- "fd10:244::/56"
# HTTP/3 is the headline reason for this migration — ingress-nginx
# cannot serve QUIC on UDP/443, so LAN clients that learned the
# `alpn=h3` hint (DNS HTTPS RR / Alt-Svc) over Cloudflare get