fix: Rate-Limit hinter Reverse Proxy wirkte wie ein globales statt Pro-IP-Limit
RemoteIpAddress zeigte hinter Dokploy/Traefik ohne ForwardedHeaders-Middleware für jede Anfrage auf dieselbe interne Proxy-IP - das Pro-IP-Limit (120/Minute) wurde dadurch faktisch zu einem einzigen globalen Limit für die gesamte Bereitstellung, geteilt von allen Geräten und Endpunkten zusammen. ForwardedHeadersOptions (X-Forwarded-For/X-Forwarded-Proto) registriert, KnownIPNetworks/KnownProxies geleert (Container nur über den Reverse Proxy erreichbar). API-only-Fix, Server-Redeploy nötig. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@ using System.Text;
|
|||||||
using System.Threading.RateLimiting;
|
using System.Threading.RateLimiting;
|
||||||
using LehrerApp.Api;
|
using LehrerApp.Api;
|
||||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||||
|
using Microsoft.AspNetCore.HttpOverrides;
|
||||||
using Microsoft.AspNetCore.RateLimiting;
|
using Microsoft.AspNetCore.RateLimiting;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
|
||||||
@@ -36,6 +37,19 @@ builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
|||||||
});
|
});
|
||||||
builder.Services.AddAuthorization();
|
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<ForwardedHeadersOptions>(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
|
// 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.
|
// grobes globales Limit pro IP als einfacher Schutz vor Überlastung der übrigen Endpunkte.
|
||||||
builder.Services.AddRateLimiter(options =>
|
builder.Services.AddRateLimiter(options =>
|
||||||
@@ -69,6 +83,7 @@ builder.Services.AddSingleton<PlainEventStore>(sp =>
|
|||||||
new PlainEventStore(sp.GetRequiredService<EventStore>()));
|
new PlainEventStore(sp.GetRequiredService<EventStore>()));
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
app.UseForwardedHeaders();
|
||||||
app.UseRateLimiter();
|
app.UseRateLimiter();
|
||||||
app.UseAuthentication();
|
app.UseAuthentication();
|
||||||
app.UseAuthorization();
|
app.UseAuthorization();
|
||||||
|
|||||||
@@ -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
|
(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
|
`MaxRequestBodySize` auf 15 MB gedeckelt (Anhänge sind clientseitig ohnehin auf 10 MB
|
||||||
begrenzt, siehe `IAttachmentStorage.MaxSizeBytes`).
|
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.
|
- [x] **10.2.3** Serverseitiges Backup der Event-/Snapshot-Dateien.
|
||||||
|
|
||||||
**Umsetzung:** `docker/backup.sh` — Tar-Archiv von `./data` (Ereignis-Logs, Snapshots,
|
**Umsetzung:** `docker/backup.sh` — Tar-Archiv von `./data` (Ereignis-Logs, Snapshots,
|
||||||
|
|||||||
Reference in New Issue
Block a user