diff --git a/LehrerApp.Api/Program.cs b/LehrerApp.Api/Program.cs index 09494e7..a718ddc 100644 --- a/LehrerApp.Api/Program.cs +++ b/LehrerApp.Api/Program.cs @@ -2,6 +2,7 @@ using System.Text; using System.Threading.RateLimiting; using LehrerApp.Api; using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.RateLimiting; using Microsoft.IdentityModel.Tokens; @@ -36,6 +37,19 @@ builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) }); builder.Services.AddAuthorization(); +// Läuft hinter einem Reverse Proxy (Dokploy/Traefik) - ohne diese Weiterleitung sähe Kestrel für +// JEDE Anfrage dieselbe interne Proxy-IP als Connection.RemoteIpAddress, wodurch das unten +// definierte Pro-IP-Rate-Limit faktisch zu einem einzigen globalen Limit für die gesamte +// Bereitstellung würde (Bug, siehe TODO.md 10.2.2 Nachtrag). KnownNetworks/KnownProxies bewusst +// geleert: der Container ist nur über den Reverse Proxy erreichbar, nie direkt aus dem Internet, +// daher ist der unmittelbare Absender von X-Forwarded-For hier immer vertrauenswürdig. +builder.Services.Configure(options => +{ + options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; + options.KnownIPNetworks.Clear(); + options.KnownProxies.Clear(); +}); + // Rate Limiting (10.2.2): striktes Limit gezielt gegen Brute-Force auf /api/auth/login, plus ein // grobes globales Limit pro IP als einfacher Schutz vor Überlastung der übrigen Endpunkte. builder.Services.AddRateLimiter(options => @@ -69,6 +83,7 @@ builder.Services.AddSingleton(sp => new PlainEventStore(sp.GetRequiredService())); var app = builder.Build(); +app.UseForwardedHeaders(); app.UseRateLimiter(); app.UseAuthentication(); app.UseAuthorization(); diff --git a/TODO.md b/TODO.md index 8b6e90e..52fba76 100644 --- a/TODO.md +++ b/TODO.md @@ -1574,6 +1574,29 @@ die Docker-Verifikation unter 10.2.4 (kein Docker im Entwicklungsstand verfügba (Brute-Force-Schutz), alle Endpunkte zusätzlich global auf 120 Anfragen/Minute je IP. Kestrel `MaxRequestBodySize` auf 15 MB gedeckelt (Anhänge sind clientseitig ohnehin auf 10 MB begrenzt, siehe `IAttachmentStorage.MaxSizeBytes`). + + **Nachtrag (Bugfix — Pro-IP-Limit wirkte wie ein globales Limit für alle Geräte zusammen):** + Nutzer-Bug-Report — der zweite Gerätesync scheiterte wiederholt mit HTTP 429 (Too Many + Requests), obwohl der Nutzer den Endpunkt nach eigener Aussage nicht annähernd 120-mal pro + Minute manuell aufgerufen hatte. Ursache: das Pro-IP-Limit partitioniert nach + `HttpContext.Connection.RemoteIpAddress` — hinter dem Dokploy/Traefik-Reverse-Proxy ist das + für JEDE Anfrage dieselbe interne Proxy-Adresse, nicht die echte Client-IP, ohne + `ForwardedHeaders`-Middleware. Aus "120 Anfragen/Minute je Gerät" wurde dadurch faktisch "120 + Anfragen/Minute für die gesamte Bereitstellung" — Auto-Sync (alle 5 Min.) auf beiden Geräten + plus manuelle Syncs plus die vorangegangenen Pairing-/Verbindungstests derselben Sitzung + teilten sich alle denselben Zähler und erschöpften ihn gemeinsam. + + Behoben: `ForwardedHeadersOptions` (`XForwardedFor`/`XForwardedProto`) registriert und + `app.UseForwardedHeaders()` ganz am Anfang der Middleware-Pipeline eingehängt, `KnownIPNetworks`/ + `KnownProxies` bewusst geleert — der Container ist nur über den Reverse Proxy erreichbar, der + unmittelbare Absender von `X-Forwarded-For` ist hier also vertrauenswürdig. + **Bekannte Einschränkung/Risiko:** `docker-compose.yml` published Port 5000 direkt + (`ports: ["5000:5000"]`). Ist dieser Port vom Internet aus erreichbar (abhängig von der + Docker-/Host-Firewall, nicht aus dem Compose-File allein ersichtlich), könnte ein Angreifer + Kestrel unter Umgehung von Traefik direkt ansprechen und `X-Forwarded-For` frei gefälscht + mitschicken, um das Rate-Limit vollständig zu umgehen. Nicht behoben, da die tatsächliche + Netzwerktopologie (Firewall-Regeln des Hosts) von hier aus nicht einsehbar ist — sollte vor + Ort geprüft werden. - [x] **10.2.3** Serverseitiges Backup der Event-/Snapshot-Dateien. **Umsetzung:** `docker/backup.sh` — Tar-Archiv von `./data` (Ereignis-Logs, Snapshots,