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>
260 lines
8.1 KiB
Markdown
260 lines
8.1 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
|
||
|
||
> **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-*
|
||
```
|