Login-Rate-Limiter hinter Traefik wirkungslos - ForwardedHeaders-Trust fehlt #155
Labels
No labels
priority/could
priority/must
priority/should
priority/wont
status/blocked
status/claimed
status/done-migrated
type/bug
type/feature
type/infra
type/tech-debt
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
robert/todo#155
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Story: Login-Rate-Limiter hinter Traefik wirkungslos - ForwardedHeaders-Trust fehlt
As a Betreiber der App,
I want to dass der bereits vorhandene IP+Username-Rate-Limiter fuer LoginUserCommand die echte
Client-IP hinter dem Traefik-Reverse-Proxy kennt,
so that Brute-Force-Versuche aus verschiedenen echten Quell-IPs gegen denselben Account weiterhin
wirksam gedrosselt werden, statt durch die konstante Proxy-Container-IP im IP-Anteil des Partition-Keys
unbemerkt zu bleiben.
Kontext:
LoginRateLimitKeyMiddlewarepartitioniert bereits korrekt nach IP+Username (siehe #59).In der echten Produktion (
docker-compose.yml) sitzt die App aber hinter Traefik, ohne dassUseForwardedHeaders/ForwardedHeadersOptionskonfiguriert ist -HttpContext.Connection.RemoteIpAddressist deshalb immer die Container-IP von Traefik, nie die echte Client-IP. Der Username-Anteil des Keys
verhindert zwar weiterhin, dass unterschiedliche Accounts sich einen Bucket teilen, aber derselbe Account,
von vielen echten Quell-IPs angegriffen, zaehlt weiterhin als eine einzige Partition.
Vollstaendig dokumentiert in
docs/SECURITY_NOTES.mdunter "Known open risks" ->"[PARTIALLY FIXED] Login rate limiter is IP-keyed but the app has no ForwardedHeaders trust config".
Acceptance criteria:
ForwardedHeadersOptionsist mit einem explizitenKnownProxies/KnownNetworks-Trust-Boundaryfuer den tatsaechlichen Traefik-Hop konfiguriert (Deployment-Topologie vorher bestaetigen, nicht
raten - falsch konfiguriert wird
X-Forwarded-Forsonst zu einem spoofbaren Bypass fuer Rate-Limitund Audit-Log).
RemoteIpAddressin einer echten Anfrage hinter Traefik die tatsaechlicheClient-IP, nicht die Traefik-Container-IP.
X-Forwarded-For-Header uebernommen wird und ein nicht vertrauenswuerdiger Absender ihn nichtfaelschen kann.
Out of scope for this story:
Open questions: (escalate to human if unanswered)
Proxy zu konfigurieren) - noetigenfalls beim Menschen erfragen, bevor
KnownProxiesgesetzt wird.Marking blocked rather than picking this up this cycle: the acceptance criteria correctly require confirming the real Traefik network topology (which subnet/hop is the trusted proxy) before setting
KnownProxies/KnownNetworks- this issue's own "Open questions" section flags exactly this for human escalation, anddocs/SECURITY_NOTES.mdsays the same ("needs real infrastructure verification rather than a guess from the sandbox... confirmation from whoever manages the Traefik config").Guessing at the trust boundary is worse than not fixing it yet: a wrong
KnownNetworksconfig would letX-Forwarded-Forbe spoofed by anyone, turning this into a rate-limit and audit-log bypass rather than a fix. I don't have access to the production deployment's actual network layout (docker-compose.yml only shows service-level network names, not the real subnet Traefik sits on) to confirm this safely from this environment.Picking up #156 (step-up password rate limiting) instead this cycle, which has no open questions and doesn't depend on this.
Entscheidung (Mensch, 2026-09-08): Bleibt bewusst blockiert. Weder die genaue Docker-Netzwerk-Subnetz-Range noch ein pauschaler "vertraue allen privaten Ranges"-Fallback wurden freigegeben.
status/blockedbleibt gesetzt - der autonome Loop soll dieses Issue weiterhin ueberspringen, bis die tatsaechliche Netzwerktopologie bekannt ist (z.B. perdocker network inspect traefik_defaultauf dem Produktiv-Host).Entscheidung (2026-09-12, mit dem Menschen geklärt): Die exakte Netzwerktopologie muss nicht mehr einzeln bestätigt werden - stattdessen wird
ForwardedHeadersOptions.KnownNetworksauf die privaten RFC1918-Bereiche (10.0.0.0/8,172.16.0.0/12,192.168.0.0/16) gesetzt, unabhängig von der exakten Traefik-Container-IP. Das deckt Traefik zuverlässig ab (egal welche IP es im Docker-Netz gerade hat), schließt aber jeden Absender aus dem echten Internet aus.Voraussetzung, die der Mensch/Administrator im Hinterkopf behalten sollte: Traefik muss der einzige Hop zwischen dem echten Internet und der App bleiben. Sollte künftig ein weiterer, nicht vertrauenswürdiger Proxy im selben privaten Netz dazwischengeschaltet werden, müsste die Konfiguration enger gefasst werden (nur Traefiks exakte Adresse statt des gesamten privaten Bereichs).
Damit ist die offene Frage aus der Story geklärt - Issue wird entsperrt und ist bereit für die Umsetzung.
Claimed - Umsetzung gestartet gemaess Entscheidung vom 2026-09-12 (KnownNetworks = RFC1918-Bereiche).
Umgesetzt und auf master (
4452d7ed), CI komplett gruen (Backend, Frontend, E2E, Docker).Scope:
app.UseForwardedHeaders()laeuft jetzt als erstes Middleware, konfiguriert inCheckly.WebApi/ForwardedHeaders/ForwardedHeadersSetup.cs:X-Forwarded-Forwird ausgewertet (X-Forwarded-Proto/-Hostbleiben unvertraut).ForwardLimit= 1: nur der von Traefik selbst angehaengte, rechteste Eintrag zaehlt; vom Client vorbefuellte Eintraege werden ignoriert.RemoteIpAddress-Nutzer: Login-, Feedback- und Recipe-Integration-Rate-Limit-Partitionen.Tests: 11 neue Tests in
Checkly.WebApi.Tests/ForwardedHeaders/gegen die echte ASP.NET-Core-Middleware: vertrauenswuerdiger privater Proxy uebernimmt die Client-IP (inkl. IPv4-mapped IPv6), oeffentlicher Absender kann nicht spoofen (inkl. 172.32.0.1 knapp ausserhalb), vorbefuellte Eintraege werden ignoriert, Login-Partition-Key unterscheidet echte Clients hinter demselben Proxy. Bestehende Rate-Limit-Tests weiter gruen.Verbleibende Annahmen (in docs/SECURITY_NOTES.md dokumentiert): Traefik muss der einzige Hop zwischen Internet und App bleiben, und der
app-Service in docker-compose.yml darf keineports:veroeffentlichen, sonst koennte ueber das Docker-NAT-Gateway (private IP) gespooft werden. Aktuell ist das erfuellt.Security-Review: keine Findings.