Erste Abarbeitung des Repository-Audits (siehe TODO.md). Sicherheit: - Rate-Limiting für alle vier Auth-Endpunkte. Sie waren unbegrenzt aufrufbar, also beliebig viele Mails über den eigenen SMTP-Zugang und beliebig viele Rateversuche auf Verifizierungscodes. - Passwort-Reset-Token nur noch als SHA-256-Hash in der Datenbank. Bisher Klartext: Lesezugriff auf die DB genuegte zur Kontouebernahme. - Tote Mock-Login-Route entfernt. Sie verglich gegen ein hartcodiertes Fake-Passwort und war ueber die Middleware-Ausnahme fuer /api/auth/* ohne Anmeldung erreichbar. - Passwort-Reset auf bcrypt-Kosten 12, wie beim Signup. Reparaturen: - npm test rief jest auf, obwohl es weder Jest-Config noch Jest-Tests gibt. Zeigt jetzt auf Playwright; jest/ts-jest/@types/jest entfernt. - Falscher sonar.projectKey im Workflow. Verifiziert per tsc gegen die geaenderten Bereiche: keine neuen Typfehler. v1.117.0 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
197 lines
5.6 KiB
Markdown
197 lines
5.6 KiB
Markdown
# 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
|
||
|
||
Das passiert bei Supabase, nicht auf dem Server:
|
||
|
||
1. Supabase-Projekt öffnen → **Settings** → **Database**
|
||
2. **Reset database password** — neues Passwort erzeugen lassen
|
||
3. Passwort in deinen Passwort-Manager, **nicht** in eine Datei kopieren
|
||
4. Supabase zeigt dir die fertigen Connection-Strings für `DATABASE_URL` (Pooler, Port 6543)
|
||
und `DIRECT_URL` (direkt, Port 5432)
|
||
|
||
Auf dem Container einsetzen — dieser Weg zeigt die Eingabe nicht an:
|
||
|
||
```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
|
||
```
|
||
|
||
`read -rs` unterdrückt die Anzeige beim Tippen, `unset` räumt die Variable weg.
|
||
|
||
Verbindung prüfen, ohne Zugangsdaten auszugeben:
|
||
|
||
```bash
|
||
npx prisma db execute --stdin <<< "SELECT 1;" && echo "Datenbank erreichbar"
|
||
```
|
||
|
||
---
|
||
|
||
## 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-*
|
||
```
|