From ea7e8615127c17263081db70cde3f62844f6de1c Mon Sep 17 00:00:00 2001 From: Konstantin Renner Date: Sat, 27 Jun 2026 16:42:07 +0200 Subject: [PATCH] traefik: real client-IP visibility via X-Forwarded-For (CrowdSec prereq #1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The netbird-ingress rootless routing peer re-originates every mesh + WAN-via-proxy connection from its own pod IP (e.g. fd10:244::2de1 = netbird-ingress-qxxc5), so Traefik's TCP peer for all that traffic is one shared pod IP — which is what let a single CrowdSec ban 403 every IPv6 client (2026-06-27 outage). The NetBird reverse-proxy (HTTP mode) already sets X-Forwarded-For with the real WAN client IP; Traefik just wasn't reading it. Set ports.websecure.forwardedHeaders.trustedIPs to the pod CIDRs (10.244.0.0/16, fd10:244::/56 — the re-originators), so Traefik resolves the real client IP from XFF. Mirror it in the (still-inert) crowdsec-bouncer Middleware via forwardedHeadersTrustedIPs so it's ready when enforcement is re-enabled. Safe no-op where no XFF exists (direct LAN/mesh keep their RemoteAddr). Enforcement stays OFF — re-enabling still needs the internal allowlist (prereq #2). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../traefik/crowdsec-middleware.yaml | 22 +++++++++++++++---- .../infrastructure/traefik/helmrelease.yaml | 14 ++++++++++++ 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/kubernetes/infrastructure/traefik/crowdsec-middleware.yaml b/kubernetes/infrastructure/traefik/crowdsec-middleware.yaml index b49192cb..b2159550 100644 --- a/kubernetes/infrastructure/traefik/crowdsec-middleware.yaml +++ b/kubernetes/infrastructure/traefik/crowdsec-middleware.yaml @@ -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 diff --git a/kubernetes/infrastructure/traefik/helmrelease.yaml b/kubernetes/infrastructure/traefik/helmrelease.yaml index be73fcc1..829ba989 100644 --- a/kubernetes/infrastructure/traefik/helmrelease.yaml +++ b/kubernetes/infrastructure/traefik/helmrelease.yaml @@ -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