Files
melesICUmover/MIGRATION.md
Dominik Dachs 88ef0d6943 Remove hardcoded credentials, harden deployment, optimize OCR
Secrets (S3 keys, PG password, DeerMapper API key) were committed in
config.yaml and .env and remain in git history. This removes them from
the tracked tree and moves all secrets to env injection.

Security:
- config.yaml: drop all credentials, keep only non-secret app tunables
- untrack .env, add .env.example template; .gitignore excludes .env
- main.py: tolerant config lookups + fail-fast validation for missing secrets
- docker-compose: env_file injection, no full-repo bind mount, debug port off
- Dockerfile: bake config into image, run as non-root user

Efficiency:
- OCR: run the second (expensive) tesseract pass only when the first
  is unparsable; identical fallback behavior

Docs:
- README with operation + security notes
- MIGRATION.md runbook: secret rotation, server cutover, decommission,
  git history purge

Note: the leaked secrets are compromised and MUST be rotated; removing
them from the tree is not sufficient. See MIGRATION.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 21:35:04 +02:00

6.5 KiB

Migration & Secret-Rotation - Runbook

Ziel: melesICUmover sicher vom Altserver auf den neuen Server umziehen, alle kompromittierten Zugangsdaten rotieren, den Altserver stilllegen und die Secrets aus der Git-History entfernen.

Kontext: Der Poller haelt keine eigenen Daten - Bilder liegen in Hetzner S3 (trapper-meles), der Zustand in PostgreSQL. "Migration" heisst also: den Worker-Container umziehen und die Credentials erneuern. Es gibt keine Datenmigration.

Warum rotieren (nicht nur entfernen)?

Folgende Secrets lagen im Klartext in config.yaml und .env und in der Git-History (Commits 400f8a5/b35394c) auf git.meles.eu. Sie gelten als kompromittiert und muessen ersetzt werden:

  • S3 Access Key + Secret Key (Hetzner Object Storage)
  • PostgreSQL-Passwort (postgres@136.243.41.58:7777)
  • DeerMapper API-Key

Das Bereinigen des Codes allein schuetzt nicht - die alten Werte bleiben gueltig, bis sie widerrufen/geaendert werden.


Phase 0 - Vorbereitung (kein Ausfall)

  1. Auf dem neuen Server Docker + Compose pruefen/installieren:
    docker --version && docker compose version || curl -fsSL https://get.docker.com | sh
    
  2. Repo auf den neuen Server holen (bereinigter Stand, ohne Secrets):
    git clone ssh://git.meles.eu/meles2/melesICUmover.git
    cd melesICUmover
    
  3. .env anlegen (noch NICHT starten):
    cp .env.example .env
    chmod 600 .env
    

Phase 1 - S3-Key rotieren (kein Ausfall, additiv)

Hetzner erlaubt mehrere S3-Credentials pro Projekt. Neuen Key zusaetzlich anlegen, alten vorerst aktiv lassen:

  1. In der Hetzner Console -> Object Storage -> neuen S3-Zugang (Access/Secret) erzeugen.
  2. In der .env auf dem neuen Server S3_ACCESS_KEY / S3_SECRET_KEY eintragen.

Phase 2 - DeerMapper API-Key rotieren

Nur relevant, wenn enable_deermapper_api: true (aktuell false). Falls genutzt: neuen Key beim DeerMapper-Betreiber anfordern und in die .env eintragen. Sonst Feld leer lassen.

Phase 3 - Cutover (kurzer, geplanter Ausfall)

Der PostgreSQL-Passwortwechsel trennt sofort den Altserver - daher hier gebuendelt:

  1. Altserver-Poller stoppen (kein Doppelbetrieb auf denselben Bucket/DB):
    # auf dem ALTSERVER
    docker compose down     # bzw. docker stop melesicumover
    
  2. PostgreSQL-Passwort aendern (Beispiel; besser: eigene DB-Rolle fuer den Dienst):
    ALTER USER postgres WITH PASSWORD '<NEUES_STARKES_PASSWORT>';
    -- Empfohlen stattdessen: dedizierte Rolle mit minimalen Rechten auf remote_cam.*
    -- CREATE ROLE icu_mover LOGIN PASSWORD '...';
    -- GRANT USAGE ON SCHEMA remote_cam TO icu_mover;
    -- GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA remote_cam TO icu_mover;
    
  3. PG_DSN in der .env des neuen Servers setzen - mit ?sslmode=require und, sofern moeglich, ueber privates Netz / SSH-Tunnel statt der oeffentlichen IP.
  4. Neuen Server starten und verifizieren:
    # auf dem NEUEN Server
    docker compose up -d --build
    docker compose logs -f      # auf [RESULT] ...-Zeilen achten
    
    Verifikation:
    • Log zeigt verarbeitete UUIDs mit result=success.
    • In S3 landen neue Objekte unter icu/processed/ und icu/thumbnails/.
    • icu/entrance/ wird geleert (Cleanup laeuft).
    • DB: SELECT status, count(*) FROM remote_cam.import_job GROUP BY 1;

Phase 4 - Altzugaenge widerrufen

  1. Alten S3-Key in der Hetzner Console loeschen (erst nachdem der neue Server nachweislich laeuft).
  2. Sicherstellen, dass der Altserver den neuen DB-Zugang nicht kennt.

Phase 5 - DB-Zugang absichern (Haertung)

  • PostgreSQL nicht auf 0.0.0.0:7777 oeffentlich anbieten. Firewall so setzen, dass nur die IP des neuen Servers (oder ein privates Netz) den DB-Port erreicht.
  • TLS erzwingen (sslmode=require), Hetzner Cloud Firewall bzw. pg_hba.conf pruefen.

Phase 6 - Altserver stilllegen / neu aufsetzen

  1. Sicherstellen: keine unersetzten Daten mehr lokal (der Poller hat keine).
  2. Container + Images + lokale .env/config.yaml mit Secrets entfernen:
    # auf dem ALTSERVER
    docker compose down --rmi all --volumes
    shred -u .env 2>/dev/null || rm -f .env
    
  3. Server neu aufsetzen bzw. deprovisionieren. Falls neu installiert: alte SSH-/Deploy-Keys, die auf git.meles.eu Zugriff hatten, am Git-Server widerrufen.

Phase 7 - Git-History bereinigen (koordiniert, destruktiv)

Erst nach erfolgreicher Rotation - danach sind die alten Werte ohnehin wertlos, aber sie sollen auch nicht mehr auffindbar sein.

Warnung: Das schreibt die History um und erfordert --force-Push. Alle Personen mit Klon muessen anschliessend neu klonen. Vorher mit dem Team abstimmen.

Mit git filter-repo (empfohlen):

# frisches Spiegel-Klon als Arbeitskopie
git clone ssh://git.meles.eu/meles2/melesICUmover.git repo-clean
cd repo-clean

# .env komplett aus der History entfernen
git filter-repo --path .env --invert-paths

# Secret-Werte auch aus historischen config.yaml-Versionen tilgen.
# Die vier ALTEN Werte NICHT hier ins Repo schreiben - die Datei liegt ausserhalb
# des Repos und wird nach dem Lauf geloescht. Werte aus der alten .env / History
# (git show 400f8a5:config.yaml) entnehmen:
cat > ../replacements.txt <<'EOF'
<ALTES_PG_PASSWORT>==>ENTFERNT
<ALTES_S3_SECRET_KEY>==>ENTFERNT
<ALTER_S3_ACCESS_KEY>==>ENTFERNT
<ALTER_DEERMAPPER_API_KEY>==>ENTFERNT
EOF
git filter-repo --replace-text ../replacements.txt

# Remote neu setzen und History ueberschreiben
git remote add origin ssh://git.meles.eu/meles2/melesICUmover.git
git push --force --all origin
git push --force --tags origin

Danach replacements.txt loeschen. Alternative: BFG Repo-Cleaner.


Rollback

Falls der neue Server Probleme macht, bevor Phase 4/6 abgeschlossen sind: alten S3-Key noch aktiv lassen, altes PG-Passwort noch nicht wegwerfen, und den Altserver-Poller wieder starten (docker compose up -d). Deshalb Altzugaenge erst in Phase 4 widerrufen.

Abschluss-Checkliste

  • Neuer Server verarbeitet Bilder (result=success, S3 + DB aktualisiert)
  • Neue S3-Keys aktiv, alte geloescht
  • Neues PG-Passwort/-Rolle aktiv, sslmode=require, DB-Port nicht oeffentlich
  • DeerMapper-Key rotiert (falls genutzt)
  • Altserver-Poller gestoppt, .env sicher geloescht, Server neu aufgesetzt
  • Alte SSH-/Deploy-Keys am Git-Server widerrufen
  • Git-History bereinigt + force-push, Team informiert
  • Repo enthaelt keine Secrets mehr (git grep auf HEAD + Stichprobe History)