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