feat: marketplace modules and isolated container management

This commit is contained in:
ayde64
2026-10-07 23:18:02 +02:00
parent 564f7def1c
commit 7b121fa908
68 changed files with 2560 additions and 699 deletions

View File

@@ -23,7 +23,7 @@ Die Plattform darf **niemals** von einem einzelnen Modul abhängig sein. Wird ei
Internet
│
▼ HTTPS (Produktion; lokal :8080)
Docker Container "mpm-platform" (Benutzer: app, kein Root)
Docker Container "mpm-platform" (Supervisor mit engen Capabilities)
┌──────────────────────────────────────────────┐
│ Supervisor (Prozessmanager, Crash-Recovery) │
│ │ │
@@ -40,10 +40,13 @@ persistentes Volume "postgres-data")
Grundsätze:
- **Ein** Applikationscontainer; Module laufen als interne Prozesse darin (kein Docker-in-Docker, kein Docker-Socket).
- MPM bleibt der Managementcontainer. Neue Module laufen in eigenen Compose-Stacks mit optionalen Datenbankservices.
- Nur der MPM-Backendprozess erhält Zugriff auf den Docker-Socket; Modulcontainer erhalten ihn nie.
- Docker-Socket-Zugriff entspricht weitreichender Kontrolle über den Docker-Host. Installationsrechte müssen deshalb vertrauenswürdigen Administratoren vorbehalten bleiben.
- Interne Ports (z. B. 3000, 41001+) werden **nie** nach außen veröffentlicht; nur Nginx lauscht auf 8080.
- PostgreSQL liegt außerhalb des Applikationscontainers → Container-Neustarts/Updates zerstören keine Nutzdaten.
- Alle Prozesse laufen als unprivilegierter Benutzer `app`.
- Supervisor und Nginx-Master erhalten nur die Container-Capabilities `SETUID`, `SETGID` und `KILL`.
- Backend und Nginx-Worker laufen als `app`; Modulservices werden mit `no-new-privileges` gestartet und intern über ein eigenes Gateway-Netz geroutet.
## 3. Prozessmodell
@@ -63,7 +66,7 @@ Stürzt ein Modulprozess ab, startet Supervisor ihn automatisch neu – die Mana
```
Browser ──POST /api/v1/auth/login──▶ Nginx ──▶ NestJS
│ Validierung (Zod) + Rate Limit + Lockout + Argon2id
│ Validierung (Zod) + IP-Rate-Limit + Argon2id
│ Session in PostgreSQL anlegen (Token nur gehasht gespeichert)
◀── Set-Cookie: mpm_session (HttpOnly, SameSite=Lax)
Set-Cookie: mpm_csrf (lesbar für CSRF-Doppel-Submit)
@@ -97,7 +100,7 @@ Alle Plattform-Tabellen liegen im Standard-Schema (ab Phase 3 Auslagerung in ein
| Tabelle | Zweck |
|---|---|
| `roles` | Globale Rollen: `ADMIN`, `USER` |
| `users` | Benutzer mit Argon2id-Hash, Rollen-FK, Lockout-Feldern |
| `users` | Benutzer mit Argon2id-Hash, Rollen-FK und Fehlversuchs-Zähler |
| `sessions` | Serverseitige Sessions (Token SHA-256-gehasht, CSRF-Token, Gleitende Verlängerung) |
| `audit_logs` | Zentrales Audit (LOGIN_SUCCESS, LOGIN_FAILED, LOGOUT, …) |
| `schema_migrations` | Angewandte Migrationen (eigener Runner mit Advisory-Lock) |
@@ -110,7 +113,7 @@ Geplant (Phase 3+): `modules`, `user_module_permissions`, `module_settings`, `sy
- **Serverseitige Sessions**: 256-Bit-Zufalls-Token im `HttpOnly`-Cookie; in der DB wird nur der SHA-256-Hash gespeichert. `Secure` (produktiv), `SameSite=Lax`, kurze Laufzeit (`SESSION_TTL_MINUTES`), gleitende Verlängerung.
- **CSRF-Schutz**: Doppel-Submit – Server speichert pro Session ein CSRF-Token; bei jedem zustandsändernden Request muss der Header `X-CSRF-Token` (konstantzeitvergleich) übereinstimmen.
- **Rate Limiting**: In-Memory-Fenster pro IP für Login (Standard: 10 Versuche / 5 Minuten).
- **Account Lockout**: Nach `LOGIN_MAX_ATTEMPTS` Fehlversuchen wird das Konto für `LOGIN_LOCKOUT_MINUTES` gesperrt.
- **Kein Account Lockout**: Fehlversuche sperren das Zielkonto nicht, damit Dritte keine gezielte Konto-DoS auslösen können.
- **Keine User-Enumeration**: Unbekannter Benutzer, inaktiver Benutzer und falsches Passwort liefern dieselbe generische 401-Meldung.
- **Audit-Log**: Jeder Login-Versuch (Erfolg/Misserfolg mit Grund), Logout.
@@ -210,10 +213,10 @@ Minimal-API je Modul (Phase 6): `GET /health`, `GET /api/manifest`, `GET /api/me
| # | Entscheidung | Begründung |
|---|---|---|
| 1 | Serverseitige Sessions statt reiner JWTs | Widerrufbar (Logout, Deaktivierung), kein Token-Diebstahl-Risiko, einfache Lockout-Logik; JWT/Access-Tokens für die Modul-Kommunikation ab Phase 6 |
| 1 | Serverseitige Sessions statt reiner JWTs | Widerrufbar (Logout, Deaktivierung); Modulidentität wird über kurzlebige, signierte Gateway-Assertions übergeben |
| 2 | Opaque 256-Bit-Token, DB speichert nur SHA-256-Hash | DB-Leck kompromittiert keine Sessions |
| 3 | PostgreSQL außerhalb des Applikationscontainers | Persistenz bei Container-Updates, klare Trennung von Zustand und Compute |
| 4 | Supervisor statt systemd im Container | Container-Standard, verwaltet mehrere Prozesse ohne Root, Crash-Recovery |
| 4 | Supervisor statt systemd im Container | Restricted-capability supervisor starts least-privilege services and monitors crashes |
| 5 | Nginx im Container als einziger öffentlicher Endpunkt | Zentrales Routing, Security-Header, interne Ports bleiben verborgen |
| 6 | `pg` + eigener Migration-Runner statt ORM | Wenig Abhängigkeiten, volle SQL-Kontrolle, Migrationen transaktionssicher mit Advisory-Lock |
| 7 | Zod statt class-validator | Ein Validierungs-Framework für Frontend und Backend, TypeScript-Inferenz |
@@ -224,4 +227,4 @@ Minimal-API je Modul (Phase 6): `GET /health`, `GET /api/manifest`, `GET /api/me
- Schichten: `components/ui` (Design-System) → `components/layout` (App-Shell) → `features/*` (Fachseiten) → `pages` (Fehlerseiten).
- Datenzugriff ausschließlich über `lib/api-client` (CSRF-Header, 401-Behandlung) und TanStack Query.
- Routing: öffentliche Login-Seite, geschützter Bereich (`RequireAuth`), Admin-Bereich (`RequireAdmin`, RBAC im Frontend nur UX – erzwungen wird serverseitig).
- Responsiv: Sidebar wird auf Mobilgeräten zur Overlay-Navigation.
- Responsiv: Sidebar wird auf Mobilgeräten zur Overlay-Navigation.