# MPM – Architekturdokumentation ## 1. Leitentscheidung Die Management-Plattform ist ein **Application Host und Gateway für Module** – nicht selbst die Fachanwendung. ``` Management-Plattform Module (ab Phase 3) ├── Identität & Login ├── eigene Oberfläche ├── Benutzer & Rollen ├── eigene Business-Logik ├── Rechte (plattformweit) ├── eigene API ├── Modul-Verwaltung ├── eigene Daten (eigenes Schema) ├── Routing / Gateway ├── eigene Migrationen ├── API & Administration └── optional eigene Modulrechte └── Audit & Monitoring ``` Die Plattform darf **niemals** von einem einzelnen Modul abhängig sein. Wird ein Modul entfernt, bleiben Login, Benutzerverwaltung, Administration, Rechteverwaltung und alle anderen Module voll funktionsfähig. ## 2. Komponenten & Container-Layout ``` Internet │ ▼ HTTPS (Produktion; lokal :8080) Docker Container "mpm-platform" (Supervisor mit engen Capabilities) ┌──────────────────────────────────────────────┐ │ Supervisor (Prozessmanager, Crash-Recovery) │ │ │ │ │ ├── nginx Reverse Proxy :8080 │ │ ├── platform-backend NestJS API 127.0.0.1:3000│ │ └── [ab Phase 3: Modul-Prozesse, │ │ z. B. Kalender 127.0.0.1:41001] │ └──────────────────────────────────────────────┘ │ ▼ PostgreSQL "mpm-postgres" (eigener Container, persistentes Volume "postgres-data") ``` Grundsätze: - 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. - 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 Supervisor verwaltet Start/Stop/Restart, Crash-Recovery und Logging aller Prozesse: | Prozess | Adresse | Aufgabe | |---|---|---| | nginx | 0.0.0.0:8080 | Reverse Proxy, statisches Frontend, Security-Header | | platform-backend | 127.0.0.1:3000 | Management-API `/api/v1` | | Modul-Prozesse (ab Phase 3) | 127.0.0.1:41001+ | Fachanwendungen | Stürzt ein Modulprozess ab, startet Supervisor ihn automatisch neu – die Management-Plattform läuft weiter. ## 4. Request-Flow ### Login ``` Browser ──POST /api/v1/auth/login──▶ Nginx ──▶ NestJS │ 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) ``` ### Authentifizierter Request ``` Browser ──▶ Nginx ──▶ SessionGuard (Session gültig? User aktiv?) ├── CsrfGuard (bei POST/PATCH/DELETE: X-CSRF-Token) ├── RolesGuard (RBAC: ADMIN/USER) └── Controller/Service (Business-Logik) ``` ### Modul-Routing (ab Phase 4, geplant) ``` Browser ──▶ Nginx (/slug) ──▶ Management-Gateway ├── User identifizieren (Session) ├── Permission Check (user_module_permissions) ├── DENIED → 403 └── ALLOWED → Modul-Gateway → Modulprozess ``` Ein Modul vertraut **niemals** allein auf die URL; die Plattform übergibt die Identität sicher an das Modul (Modul-API-Vertrag, Phase 6). ## 5. Datenmodell (Phase 1) Alle Plattform-Tabellen liegen im Standard-Schema (ab Phase 3 Auslagerung in ein `management`-Schema und pro Modul ein eigenes Schema). | Tabelle | Zweck | |---|---| | `roles` | Globale Rollen: `ADMIN`, `USER` | | `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) | Geplant (Phase 3+): `modules`, `user_module_permissions`, `module_settings`, `system_settings`, `api_clients`. ## 6. Authentifizierung & Sessions - **Argon2id** (19 MiB, t=2, p=1 – OWASP-Empfehlung) für Passwort-Hashing; niemals Klartextpasswörter. - **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). - **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. ## 7. Sicherheitsmaßnahmen (Phase 1) - Security-Header via Helmet (Backend) und Nginx (Frontend): CSP, `X-Frame-Options: DENY`, `X-Content-Type-Options`, `Referrer-Policy`. - Zod-Validierung **aller** Eingaben (serverseitig, clientseitige Validierung ist nur UX). - SQL nur parametrisiert (`pg`), keine String-Konkatenation. - Strukturierte Fehlermeldungen ohne Stack-Traces nach außen. - Secrets ausschließlich über Umgebungsvariablen (`.env` ist gitignored). - Interne Ports nicht veröffentlicht; PostgreSQL nur auf Loopback des Hosts gemappt. - Unprivilegierter Container-Benutzer; kein Root in der Laufzeit. Security Hardening (Penetrationstests, Dependency/Container-Scans, HSTS, Backup/Restore) ist gemäß Arbeitsplan Phase 10. ## 8. API-Übersicht (Phase 1) | Methode | Pfad | Auth | Beschreibung | |---|---|---|---| | POST | `/api/v1/auth/login` | Public | Anmeldung, setzt Session-Cookies | | POST | `/api/v1/auth/logout` | Session | Beendet die aktuelle Session | | GET | `/api/v1/auth/me` | Session | Angemeldeter Benutzer (Id, Rolle, …) | | GET | `/api/v1/health` | Public | Systemstatus (Backend, DB-Latenz, Version, Uptime) | | GET | `/api/v1/users` | Admin | Alle Benutzer | | POST | `/api/v1/users` | Admin | Benutzer anlegen (409 bei Duplikat) | | GET | `/api/v1/users/:id` | Admin | Einzelner Benutzer | | PATCH | `/api/v1/users/:id` | Admin | Bearbeiten / Aktivieren / Deaktivieren | | PATCH | `/api/v1/users/:id/password` | Admin | Passwort zurücksetzen | | DELETE | `/api/v1/users/:id` | Admin | Benutzer löschen | | GET | `/api/v1/profile` | Session | Eigenes Profil | | PATCH | `/api/v1/profile/password` | Session | Eigenes Passwort ändern | | GET | `/api/v1/modules` | Admin | Alle Module | | POST | `/api/v1/modules/install` | Admin | ZIP-Paket installieren (multipart, Feld `package`) | | GET | `/api/v1/modules/:id` | Admin | Einzelnes Modul | | POST | `/api/v1/modules/:id/start` | Admin | Modul starten | | POST | `/api/v1/modules/:id/stop` | Admin | Modul stoppen | | POST | `/api/v1/modules/:id/restart` | Admin | Modul neu starten | | PATCH | `/api/v1/modules/:id/enabled` | Admin | Modul aktivieren/deaktivieren | | GET | `/api/v1/modules/:id/health` | Admin | Healthcheck ausführen | | DELETE | `/api/v1/modules/:id` | Admin | Modul entfernen | OpenAPI/Swagger UI: `/api/docs` · Ab Phase 4: dynamisches Routing `/slug`. ### Schutzregeln der Benutzerverwaltung - Keine Selbst-Deaktivierung und keine Selbst-Löschung (400) - Der letzte aktive Administrator kann nicht herabgestuft, deaktiviert oder gelöscht werden (400) - Deaktivierte Benutzer werden sofort von allen Sessions abgemeldet - Passwort-Reset (Admin) meldet den Benutzer von allen Sessions ab - Eigenes Passwort ändern meldet alle übrigen Sessions ab (aktuelle bleibt aktiv) ## 9. Modul-System (Phase 3) ### Manifest-Vertrag Jedes Modul-Paket (ZIP, max. 10 MB) enthält ein `module.json`: ```json { "id": "calendar", "name": "Kalender", "version": "1.0.0", "slug": "kalender-tool", "description": "Kalenderverwaltung", "author": "…", "runtime": "node", "entrypoint": "server.js", "port": 41001, "healthcheck": "/health", "apiVersion": "v1" } ``` Validierung per Zod: ID/Slug-Muster, SemVer, Port 41000–41999, kein Pfad-Traversal im Entrypoint, Runtime `node`, apiVersion `v1`. ### Lifecycle ``` INSTALLED → STARTING → RUNNING → STOPPING → STOPPED ↓ ↓ ERROR ERROR DISABLED (via Enable/Disable) ``` ### Sicherheitsmaßnahmen - **Zip-Slip-Schutz**: Alle ZIP-Einträge müssen im Zielverzeichnis bleiben - **Minimale Prozess-ENV**: Module erhalten nur `PATH`, `NODE_ENV`, `PORT` – keine Plattform-Secrets - **Spawn ohne Shell**: keine Command-Injection möglich - **Eigene Log-Dateien** pro Modul (`/app/data/logs/module-.log`) - **Startup-Grace**: Healthcheck mit 10 Retries, bevor ein Modul als ERROR gilt - **Isolation**: Ein abgestürztes Modul (ERROR) beeinträchtigt weder Plattform noch andere Module - Persistente Volumes: Modul-Dateien und Logs überleben Container-Updates Minimal-API je Modul (Phase 6): `GET /health`, `GET /api/manifest`, `GET /api/me`. Details zum Paketformat: [`modules/README.md`](../modules/README.md). ## 10. Architektur-Entscheidungen (ADR-Kurzform) | # | Entscheidung | Begründung | |---|---|---| | 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 | 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 | | 8 | Monorepo mit unabhängigen Apps (kein Workspace-Tooling) | Einfache, cache-freundliche Docker-Builds, keine Tool-Lock-in | ## 11. Frontend-Architektur - 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.