Harden authentication, tenant isolation and security boundaries
This commit is contained in:
+52
-39
@@ -1,55 +1,68 @@
|
||||
# Kita-Planer Deployment
|
||||
# Sicherer Betrieb und Rollout
|
||||
|
||||
Dieses Verzeichnis enthaelt die ausfuellbaren Vorlagen fuer das Production-Deployment.
|
||||
Die Anwendung startet ohne Schemaänderungen und ohne Demo-Seeding. Datenbankmigrationen laufen separat mit einer administrativen Rolle. Der App-Container verwendet ausschließlich `kita_runtime`, läuft als UID/GID 1001 und hat ein schreibgeschütztes Dateisystem mit persistenten Upload- und Cache-Volumes.
|
||||
|
||||
## Dateien
|
||||
## Konfiguration
|
||||
|
||||
- `production.env.example`: Vorlage fuer alle benoetigten Environment Variables.
|
||||
- `production-checklist.md`: Checkliste fuer Domain, Datenbank, Auth, Mail, Uploads und Rechtliches.
|
||||
- `../Dockerfile`: Container-Build fuer Coolify oder andere Docker-Hosts.
|
||||
- `../docker-compose.coolify.yml`: Compose-Vorlage fuer Coolify mit required Environment Variables.
|
||||
Werte aus `production.env.example` als Hosting-Secrets hinterlegen. `APP_DATABASE_URL` enthält die eingeschränkte Rolle; `MIGRATION_DATABASE_URL` den Datenbankeigentümer. Für lokale CLI-Befehle heißt die jeweils benötigte URL `DATABASE_URL`. `APP_DATABASE_PASSWORD` ist das separate Runtime-Passwort (24–256 Zeichen); in URLs muss es URL-kodiert werden. Keine echten Secrets im Repository speichern.
|
||||
|
||||
## Empfohlener Ablauf mit Coolify und Gitea
|
||||
`AUTH_URL`, `NEXTAUTH_URL` und `NEXT_PUBLIC_SITE_URL` müssen dieselbe kanonische HTTPS-Origin verwenden. Der Reverse Proxy terminiert TLS, routet intern auf App-Port 3000 und überschreibt weitergeleitete Host-/IP-Header. `TRUSTED_CLIENT_IP_HEADER` nur setzen, wenn der Proxy den gewählten Header (`x-real-ip` oder `cf-connecting-ip`) zuverlässig überschreibt. Ohne diese Einstellung gelten weiterhin gemeinsame globale und identitätsbezogene Datenbanklimits. Proxyseitig Body-Limit von höchstens 26 MiB, Timeouts und zusätzliche Request-Limits setzen.
|
||||
|
||||
1. Repository in deine Gitea-Instanz pushen.
|
||||
2. In Coolify eine neue Application aus diesem Gitea-Repository anlegen.
|
||||
3. Als Build Pack `Docker Compose` waehlen.
|
||||
4. Als Compose-Datei `docker-compose.coolify.yml` eintragen.
|
||||
5. Die bestehende Coolify-Postgres-Datenbank ueber ihre interne `DATABASE_URL` verbinden.
|
||||
6. Environment Variables aus `production.env.example` in Coolify eintragen.
|
||||
7. Domain auf den `app`-Service mit internem Port `3000` zeigen lassen.
|
||||
8. Deploy starten.
|
||||
Compose startet PostgreSQL standardmäßig intern. Bei einer bereits extern verwalteten Coolify-Datenbank den Stack entsprechend anpassen und deren interne URL verwenden; niemals versehentlich einen neuen leeren Datenbankdienst als Ersatz anschließen. pgAdmin ist nur im Profil `maintenance` und nur auf Loopback verfügbar; Zugriff über SSH-Tunnel.
|
||||
|
||||
Beim Container-Start fuehrt `scripts/start-production.sh` automatisch aus:
|
||||
## Bestehende Installation aktualisieren
|
||||
|
||||
```bash
|
||||
npx prisma db push
|
||||
1. Datenbank **und** Upload-Volume sichern und Wiederherstellung überprüfen. Wartungsfenster ankündigen und alte App stoppen: das neue Schema und der neue Code gehören zusammen.
|
||||
2. Den Zustand der bestehenden Datenbank mit `prisma/migrations/20261009000000_baseline/migration.sql` vergleichen. Die Baseline bildet das bisherige Schema ab. Bei Abweichungen zuerst die Unterschiede gezielt klären; eine Baseline-Markierung prüft das Schema nicht automatisch.
|
||||
3. `scripts/check-tenant-integrity.sql` mit `psql "$MIGRATION_DATABASE_URL" -v ON_ERROR_STOP=1 -f scripts/check-tenant-integrity.sql` ausführen. **Keine Ergebniszeilen** bedeutet, dass keine der geprüften widersprüchlichen Tenant-Verknüpfungen vorliegt. Ergebnisse vor der Migration fachlich korrigieren; keine Daten automatisch einem anderen Mandanten zuweisen.
|
||||
4. **Nur für eine bestehende, zur Baseline passende Datenbank** einmal die Baseline als bereits angewendet markieren. Bei einer leeren neuen Datenbank diesen Schritt auslassen.
|
||||
5. Alle weiteren Migrationen anwenden, Runtime-Rolle bereitstellen und Größenmetadaten vorhandener Anhänge nachtragen.
|
||||
6. Eigentümer bestehender Upload-/Cache-Volumes auf UID/GID 1001 vorbereiten, dann die neue App starten. Stichproben mit zwei verschiedenen Kitas und den echten E-Mail-Flows prüfen.
|
||||
|
||||
Compose-Beispiele (Environment-Secrets vorher bereitstellen):
|
||||
|
||||
```sh
|
||||
docker compose -f docker-compose.coolify.yml build app migrate
|
||||
# NUR bei bestehender, zuvor geprüfter Baseline-Datenbank:
|
||||
docker compose -f docker-compose.coolify.yml --profile maintenance run --rm migrate npx prisma migrate resolve --applied 20261009000000_baseline
|
||||
# Bei neuen und bestehenden Datenbanken:
|
||||
docker compose -f docker-compose.coolify.yml --profile maintenance run --rm migrate npm run db:migrate:deploy
|
||||
docker compose -f docker-compose.coolify.yml --profile maintenance run --rm migrate npm run db:provision-runtime
|
||||
docker compose -f docker-compose.coolify.yml --profile maintenance run --rm migrate npm run uploads:backfill
|
||||
# Nur auf dem vorgesehenen App-Upload-Volume:
|
||||
docker compose -f docker-compose.coolify.yml --profile maintenance run --rm migrate chown -R 1001:1001 /app/uploads
|
||||
# Falls ein bereits vorhandenes Cache-Volume root gehört: dessen Eigentümer separat korrigieren.
|
||||
docker compose -f docker-compose.coolify.yml up -d app
|
||||
```
|
||||
|
||||
Coolify erkennt Variablen aus `docker-compose.coolify.yml` und zeigt required Variablen
|
||||
wie `DATABASE_URL`, `AUTH_SECRET`, `AUTH_URL`, `NEXTAUTH_URL`, `NEXT_PUBLIC_SITE_URL`,
|
||||
`RESEND_API_KEY`, `EMAIL_FROM` und `ADMIN_EMAIL` in der UI an.
|
||||
Bei einem Rollback nach Migration zuerst Kompatibilität prüfen; das alte Image allein zurückzusetzen reicht wegen neuer Pflichtfelder nicht aus. Im Zweifel das geprüfte Backup von Datenbank und Dateien gemeinsam wiederherstellen.
|
||||
|
||||
## Welche Coolify-Postgres-URL?
|
||||
## Auswirkungen auf bestehende Konten
|
||||
|
||||
Wenn die App und die Postgres-DB im selben Coolify-Projekt/Netzwerk laufen, nimm
|
||||
die interne Datenbank-URL aus Coolify, nicht die oeffentliche externe URL. Das ist
|
||||
stabiler und vermeidet unnoetigen Traffic ueber den Host.
|
||||
Alle JWT-Sitzungen vor diesem Release werden ungültig; erneute Anmeldung ist erforderlich. Administrative Sperren entwerten auch bei späterer Entsperrung dauerhaft die alten Sitzungen. Gründerkonten ohne Kita und ohne echte E-Mail-Prüfung registrieren sich mit derselben Adresse erneut und bestätigen den Link. Bereits bestehende Kita-Gründer behalten ihre bisherige Verifizierung; deren historische Adressen werden dadurch nicht nachträglich als echte Mailbox-Bestätigung nachgewiesen.
|
||||
|
||||
Das Format bleibt:
|
||||
Neue Registrierungen bestätigen zuerst die E-Mail-Adresse, wählen anschließend ihr Passwort und erstellen erst danach eine Kita. Beim Ändern einer Elternadresse werden Passwort und bisherige Sitzungen entwertet; die neue Adresse erhält eine Einladung zum Festlegen eines neuen Passworts. Kita-Admins können die Einladung erneut senden. Neue Registrierungslinks gelten 30 Minuten, Einladungen sieben Tage und Notdienst-Bestätigungslinks höchstens 24 Stunden sowie nur für offene, noch relevante Einsätze. Bestehende UUID-Links bleiben innerhalb ihrer Laufzeit kompatibel. Zwei-Faktor-Anmeldung ist auf ausdrücklichen Wunsch vorerst nicht eingeführt.
|
||||
|
||||
```txt
|
||||
postgresql://USER:PASSWORD@HOST:PORT/DATABASE?schema=public
|
||||
## Dateien und Wartung
|
||||
|
||||
`UPLOAD_DIR` bezeichnet die bisherige Basis; die Anwendung ergänzt `announcements`. Compose verwendet unverändert `/app/uploads/announcements/announcements`. Backfill muss genau dieselben Dateien sehen. Fehlende Dateien werden mit Größe 0 erfasst und als Anzahl gemeldet: Ursache und Datenverlust prüfen. Solange Größenwerte `NULL` sind, blockiert die Anwendung neue Anhänge, damit das Gesamtlimit nicht umgangen werden kann.
|
||||
|
||||
Grenzen: 10 MiB pro Datei, fünf Dateien und 25 MiB pro Nachricht; standardmäßig 256 MiB pro Kita und 10 GiB insgesamt, konfigurierbar über die beiden Quota-Variablen. Erlaubt sind PDF, PNG und JPEG mit passenden Dateisignaturen und Endungen. Das ist keine Virenprüfung oder vollständige PDF-Bereinigung. Downloads erfordern Sitzung, Consent und die passende Kita; sie werden als Anhang ausgeliefert.
|
||||
|
||||
`npm run uploads:cleanup` zeigt zunächst nur verwaiste Dateien mit mindestens 24 Stunden Alter. Nach Prüfung entfernt `npm run uploads:cleanup -- --delete` diese Dateien. Backups, Speicherüberwachung und eine Aufbewahrungs-/Löschstrategie für Backups separat einrichten. Dateien gelöschter Datensätze werden auch unmittelbar bestmöglich entfernt.
|
||||
|
||||
Die SQL-Migrationen ergänzen **31 zusammengesetzte Tenant-Fremdschlüssel**, die Prisma wegen der vorhandenen optionalen Cascade-/SetNull-Beziehungen nicht vollständig abbildet. Zukünftige generierte Migrationen auf unbeabsichtigtes Entfernen dieser Constraints prüfen. Kein `prisma db push` auf verwalteten Datenbanken einsetzen. Runtime-Grants nach zukünftigen Migrationen erneut bereitstellen.
|
||||
|
||||
## Verifikation und Updates
|
||||
|
||||
```sh
|
||||
npm ci
|
||||
npm run build:prod
|
||||
npx tsc --noEmit
|
||||
npm run lint -- --quiet
|
||||
npm audit --omit=dev
|
||||
```
|
||||
|
||||
## Wichtige Hinweise
|
||||
Die Integrationstests setzen eine ausdrücklich isolierte lokale PostgreSQL-Datenbank namens `kita_security_test` voraus; `SECURITY_TEST_DATABASE_URL` darf keine Anwendungsdaten enthalten. `npm run test:security` prüft Aktionen und echte DB-Transaktionen. `npm run test:security:http` benötigt den Production-Server auf localhost:3057 mit derselben Testdatenbank, `UPLOAD_DIR` und dem im Test dokumentierten Test-Secret. Niemals dieses Test-Secret in Produktion verwenden.
|
||||
|
||||
- Das aktuelle Projekt nutzt noch `prisma db push` statt versionierter Prisma-Migrationen.
|
||||
Fuer den ersten MVP-Deploy ist das praktikabel, fuer laengerfristigen Betrieb sollten
|
||||
Migrationen eingefuehrt werden.
|
||||
- `UPLOAD_DIR` muss persistent sein. Die Coolify-Compose-Datei mountet dafuer ein
|
||||
Docker-Volume nach `/app/uploads`.
|
||||
- `NEXT_PUBLIC_SITE_URL` muss die echte Domain enthalten, damit OpenGraph-Bilder in
|
||||
WhatsApp, LinkedIn und Co. korrekt als absolute URL aufgeloest werden.
|
||||
- `NEXT_PUBLIC_SITE_URL` wird auch als Build-Arg an Docker uebergeben. In Coolify sollte
|
||||
diese Variable deshalb fuer Build und Runtime verfuegbar sein.
|
||||
Docker-Images sind auf Digests fixiert. Neue Sicherheitsreleases regelmäßig prüfen und die Digests bewusst aktualisieren. Zum Prüfzeitpunkt verbleibt eine ungepatchte `braces`-Schwachstelle in der Entwicklungs-Lint-Kette; `npm audit --force` löst sie nicht sinnvoll. Der Runtime-Audit ist ohne bekannte Findings. NextAuth bleibt eine Beta-Abhängigkeit und benötigt weiterhin Update-Beobachtung.
|
||||
|
||||
Reference in New Issue
Block a user