My-Weekly-ToDo-List/docs/secret-rotation.md
mARTin-B78 f1eeca6ab6
Some checks are pending
Playwright Tests / test (push) Waiting to run
Test and SonarQube / quality (push) Waiting to run
docs: Schritt 2 der Secret-Rotation auf selbst gehostetes Supabase korrigiert
Die Anleitung beschrieb Supabase Cloud mit Dashboard-Knopf. Tatsaechlich laeuft
Supabase per Docker auf einem eigenen LXC (scripts/supabase-setup.sh), ohne
Cloud-Dashboard. Der Weg geht ueber POSTGRES_PASSWORD in supabase/docker/.env
plus ALTER USER, mit pg_dumpall als Rueckfahrkarte -- das Passwort steckt an
mehreren Stellen, ein halber Wechsel legt die Datenbank lahm.

Ergaenzt um die Ports und eine ehrliche Einordnung: Die Datenbank hoert auf
einer privaten LAN-Adresse, dieser Schritt hat von den dreien die niedrigste
Prioritaet.

v1.117.1

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 23:15:14 +02:00

260 lines
8.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Secrets rotieren
Anleitung zum Austausch der Zugangsdaten, die versehentlich in `.env.production.save`
im Git gelandet sind.
**Grundregel:** Kein Befehl in dieser Anleitung gibt einen Geheimwert auf dem Bildschirm
aus. Die neuen Werte werden direkt in die Datei geschrieben. Du siehst sie nie, ich sehe
sie nie, und sie landen nicht in der Shell-History (dort steht nur der Befehl, nicht sein
Ergebnis).
---
## Vorab: Was passiert mit den Daten?
Kurz: **Es geht nichts verloren.** Im Einzelnen:
| Was du änderst | Folge für die Daten | Folge für die Nutzer |
|---|---|---|
| `NEXTAUTH_SECRET` | keine | Alle werden ausgeloggt, Login danach normal möglich |
| Datenbank-Passwort | keine | keine |
| `SMTP_PASSWORD` | keine | keine |
Wichtig zu verstehen: **Die Passwörter der Nutzer hängen nicht am `NEXTAUTH_SECRET`.**
Sie liegen als bcrypt-Hashes in der Datenbank und bleiben davon völlig unberührt.
`NEXTAUTH_SECRET` signiert nur die Sitzungs-Cookies. Wird es getauscht, sind die
ausgestellten Cookies ungültig — jeder muss sich **einmal neu anmelden**, mit dem
gewohnten Passwort. Danach ist alles wie vorher.
Beim Datenbank-Passwort änderst du nur die *Zugangsdaten*, nicht die Datenbank.
Tabellen und Inhalte bleiben unangetastet.
---
## Schritt 0 — Sicherheitsnetz
Auf dem Container:
```bash
cd /root/My-Weekly-ToDo-List
cp .env .env.backup-$(date +%F)
chmod 600 .env.backup-*
ls -l .env.backup-*
```
Falls etwas schiefgeht, kommst du damit zurück. Die Sicherung liegt außerhalb von Git
(`.env*` ist ignoriert) — bitte trotzdem später aufräumen.
Zusätzlich ein Datenbank-Backup, bevor du am DB-Zugang drehst:
```bash
bash /root/My-Weekly-ToDo-List/scripts/backup-db.sh
```
---
## Schritt 1 — `NEXTAUTH_SECRET` neu erzeugen
Ein Befehl, der den neuen Wert erzeugt und **direkt** in die Datei schreibt:
```bash
cd /root/My-Weekly-ToDo-List
sed -i "s|^NEXTAUTH_SECRET=.*|NEXTAUTH_SECRET=\"$(openssl rand -base64 32)\"|" .env
```
Kontrolle, ohne den Wert zu zeigen — es wird nur die Länge ausgegeben:
```bash
awk -F= '/^NEXTAUTH_SECRET=/{v=substr($0,index($0,"=")+1); gsub(/"/,"",v); print "Länge:", length(v), "Zeichen"}' .env
```
Erwartet: **44 Zeichen** (32 Byte in Base64). Der alte Wert hatte 20 — also deutlich zu kurz.
---
## Schritt 2 — Datenbank-Passwort rotieren
> **Korrektur:** Eine frühere Fassung dieser Anleitung ging von *Supabase Cloud* aus, mit
> einem Dashboard und einem "Reset database password"-Knopf. Das trifft hier **nicht** zu.
> Deine Supabase-Instanz läuft **selbst gehostet per Docker auf einem eigenen LXC**
> (siehe `scripts/supabase-setup.sh`). Es gibt kein Cloud-Dashboard.
### Wo läuft die Datenbank?
Der Host steht in deiner `.env` auf dem App-Container. So bekommst du ihn heraus, ohne
das Passwort zu zeigen:
```bash
cd /root/My-Weekly-ToDo-List
grep '^DATABASE_URL=' .env | sed -E 's|.*@([^:/]+):([0-9]+).*|Host: \1 Port: \2|'
grep '^DIRECT_URL=' .env | sed -E 's|.*@([^:/]+):([0-9]+).*|Host: \1 Port: \2|'
```
Die Ports sind aus der Konfiguration bekannt:
| Port | Wofür |
|---|---|
| **6543** | Verbindungspooler (Supavisor/pgbouncer) — das ist `DATABASE_URL` |
| **5432** | Postgres direkt — das ist `DIRECT_URL`, für Migrationen |
| **8000** | Supabase API-Gateway |
| **8001** | Supabase Studio, das Web-Dashboard |
Das Studio erreichst du also unter `http://<Host aus dem Befehl oben>:8001`.
**Aber:** Studio hat keinen Knopf zum Ändern des Postgres-Passworts — das geht nur
auf dem Supabase-LXC selbst.
### Lohnt sich das überhaupt?
**Ehrliche Einschätzung: Dieser Schritt hat die niedrigste Priorität von allen dreien.**
- Die Datenbank hört auf einer **privaten LAN-Adresse**, nicht am offenen Internet.
- Wer das Passwort aus dem Git ziehen will, braucht ohnehin schon Zugriff auf dein
privates Repository — und wäre damit bereits in deinem Netz.
- Das Rotieren ist bei selbst gehostetem Supabase **fehleranfällig**, weil dasselbe
Passwort an mehreren Stellen steckt (siehe unten). Ein halb durchgeführter Wechsel
legt die Datenbank lahm.
`NEXTAUTH_SECRET` (Schritt 1) und `SMTP_PASSWORD` (Schritt 3) sind die wichtigen —
das eine schützt die Sitzungen, das andere ist von außen missbrauchbar. Beide sind
erledigt. **Schritt 2 kannst du in Ruhe planen oder ganz auslassen.**
### Falls du es doch machst
Das Postgres-Passwort steckt bei einem Supabase-Docker-Stack an **mehreren** Stellen.
Es reicht nicht, es in Postgres zu ändern — die Supabase-Container kämen dann nicht
mehr an ihre eigene Datenbank.
Auf dem **Supabase-LXC**:
```bash
cd ~/supabase/docker # Pfad ggf. anpassen
# 1. Sicherung, bevor irgendetwas angefasst wird
docker compose exec -T db pg_dumpall -U postgres > ~/supabase-backup-$(date +%F).sql
ls -lh ~/supabase-backup-*.sql
# 2. Neues Passwort erzeugen und direkt in die Supabase-Konfiguration schreiben
sed -i "s|^POSTGRES_PASSWORD=.*|POSTGRES_PASSWORD=$(openssl rand -base64 24 | tr -d '/+=')|" .env
# 3. Dasselbe Passwort in Postgres setzen
docker compose exec -T db psql -U postgres -c \
"ALTER USER postgres WITH PASSWORD '$(grep '^POSTGRES_PASSWORD=' .env | cut -d= -f2-)';"
# 4. Stack neu starten, damit alle Dienste das neue Passwort bekommen
docker compose down && docker compose up -d
docker compose ps
```
Danach auf dem **App-Container** die beiden URLs anpassen — das neue Passwort aus
`supabase/docker/.env` einsetzen:
```bash
cd /root/My-Weekly-ToDo-List
read -rsp "Neue DATABASE_URL einfügen: " V && printf '\n' && \
sed -i "s|^DATABASE_URL=.*|DATABASE_URL=\"$V\"|" .env && unset V
read -rsp "Neue DIRECT_URL einfügen: " V && printf '\n' && \
sed -i "s|^DIRECT_URL=.*|DIRECT_URL=\"$V\"|" .env && unset V
```
Verbindung prüfen:
```bash
npx prisma db execute --stdin <<< "SELECT 1;" && echo "Datenbank erreichbar"
```
Wenn etwas schiefgeht: Der Dump aus Schritt 1 ist die Rückfahrkarte.
---
## Schritt 3 — SMTP-Passwort rotieren
1. Beim Mail-Anbieter ein neues Passwort setzen (idealerweise ein App-Passwort)
2. Auf dem Container eintragen:
```bash
cd /root/My-Weekly-ToDo-List
read -rsp "Neues SMTP-Passwort: " V && printf '\n' && \
sed -i "s|^SMTP_PASSWORD=.*|SMTP_PASSWORD=\"$V\"|" .env && unset V
```
---
## Schritt 4 — Übernehmen
```bash
cd /root/My-Weekly-ToDo-List
cp .env .next/standalone/.env
pm2 restart my-weekly-todo --update-env
pm2 save
```
Der Kopierschritt ist nötig, weil der Standalone-Build seine eigene `.env` neben
`server.js` liest.
Danach: einmal im Browser neu anmelden. Dass du ausgeloggt wurdest, ist der **Beweis**,
dass das neue `NEXTAUTH_SECRET` greift.
---
## Schritt 5 — Die Datei aus dem Git entfernen
```bash
cd /root/My-Weekly-ToDo-List # oder im Arbeitsrepo
git rm --cached .env.production.save
rm .env.production.save
```
`.gitignore` ergänzen:
```
.env*
!.env.example
```
Committen, pushen, fertig.
---
## Schritt 6 — Historie: umschreiben oder nicht?
Nach Schritt 5 ist die Datei aus dem *aktuellen* Stand verschwunden — in der **Historie**
steht sie weiterhin.
**Meine Empfehlung: Historie so lassen.** Begründung:
- Das Repository ist **privat** (geprüft).
- Nach den Schritten 1–3 sind die enthaltenen Werte **wertlos**, weil überall ersetzt.
- Historie umschreiben ändert jeden Commit-Hash. Alle Klone (dein Rechner, der Container,
Gitea) müssten neu geholt werden, alte Tags zeigen ins Leere. Das Risiko übersteigt
den Nutzen deutlich.
Rotieren schlägt Umschreiben: Ein rotiertes Geheimnis in der Historie ist harmlos, ein
nicht rotiertes bleibt auch nach dem Umschreiben in jedem existierenden Klon.
Falls du es trotzdem willst — nur bei Bedarf, mit vorherigem Voll-Backup:
```bash
git filter-repo --path .env.production.save --invert-paths
```
Danach `git push --force` auf **beide** Remotes, und jeder Klon muss neu erstellt werden.
---
## Kontrolle zum Schluss
```bash
cd /root/My-Weekly-ToDo-List
git ls-files | grep -E "^\.env" || echo "OK: keine .env-Datei mehr im Git"
grep -cE "^(NEXTAUTH_SECRET|DATABASE_URL|DIRECT_URL|SMTP_PASSWORD)=" .env
```
Die zweite Zeile muss `4` ergeben — vier gesetzte Schlüssel, ohne einen davon zu zeigen.
Zum Schluss die Sicherungen aufräumen:
```bash
shred -u .env.backup-* 2>/dev/null || rm -f .env.backup-*
```