Skip to content

feat(ops): verschlüsselter Backup-Container (age) + TimescaleDB-korrektes Restore (Wave 16 PR-B)#171

Merged
gerfru merged 2 commits into
mainfrom
feat/wave16-pr-b
Jun 9, 2026
Merged

feat(ops): verschlüsselter Backup-Container (age) + TimescaleDB-korrektes Restore (Wave 16 PR-B)#171
gerfru merged 2 commits into
mainfrom
feat/wave16-pr-b

Conversation

@gerfru

@gerfru gerfru commented Jun 9, 2026

Copy link
Copy Markdown
Owner

Was

Wave 16 PR-B — macht das DB-Backup real und self-contained: ein Backup-Container im Stack (backup/) sichert die DB täglich verschlüsselt. Reine Ops/Security-Härtung, kein App-Code.

  • Container-Scheduler (kein Host-Cron): verbindet sich übers interne Netz mit dbkein Docker-Socket, keine zusätzliche Angriffsfläche. Non-root postgres-User.
  • Kette: pg_dump -Fcage-Verschlüsselung (streamed, kein Klartext auf Platte) → Retention → optional rclone-Offsite.
  • Scheduler: schlanker non-root Shell-Loop statt cron-Binary — vermeidet per-arch supercronic-Download (Supply-Chain) bzw. busybox-crond (nicht sauber non-root).

Recherche-Funde (state-of-the-art verifiziert)

  • age statt GPG: X25519/ChaCha20-Poly1305, asymmetrisch — Public-Key am Server, Private-Key offsite → kompromittierter Server kann eigene Backups nicht entschlüsseln.
  • TimescaleDB-Korrektheit (kritisch): Restore braucht timescaledb_pre_restore()pg_restore ohne -jtimescaledb_post_restore(). Der alte restore-test machte nur nacktes pg_restore → für die 6 Hypertables unvollständig. Lokal verifiziert: Hypertable überlebt den Restore.

Alles über make

make up rollt den Container aus (Daten/Volumes bleiben — kein down). make backup (One-Off). make restore-test (Key-Pfad aus env/.env.backup).

Tests

  • CI backup-smoke (Required, in ci-ok needs): baut das Image und beweist pg_dump → age → decrypt → pre/post_restore → pg_restore gegen eine seed-Test-DB.
  • Lokal isoliert (Wegwerf-Container) end-to-end durchgespielt: SMOKE PASS, Hypertable + Row-Count verifiziert.

Doku (WS-E)

NEU-3-Konsolidierung: deployment-public.md + production-hardening.md hatten zwei divergierende Inline-Backup-Skripte (-Fc+rclone vs. gzip+b2) → beide verweisen jetzt auf backup/ (Single Source). security.md §9.6 (age- vs FERNET-Key-Rotation), plus configuration/setup/development/CLAUDE.

Bewusst vertagt

DB-Zugriff via Admin-Rolle (pg_dump braucht Voll-Read); dedizierte pg_read_all_data-Backup-Rolle (V27) als Least-Privilege-Härtung dokumentiert vertagt.

🤖 Generated with Claude Code

gerfru and others added 2 commits June 9, 2026 10:33
…ktes Restore

Neuer backup-Service im Stack (backup/: postgres:16-alpine + age + rclone,
non-root postgres-User). Verbindet sich übers interne Netz mit db (KEIN
Docker-Socket, keine zusätzliche Angriffsfläche) und sichert täglich:
pg_dump -Fc → age-Verschlüsselung (streamed) → Retention → optional rclone.

Scheduler: schlanker non-root Shell-Loop statt cron-Binary (vermeidet per-arch
supercronic-Download = Supply-Chain-Risiko; busybox-crond ist nicht sauber
non-root). entrypoint.sh exect Args (One-Off via `make backup`) oder läuft den
Tages-Loop (Service-Modus).

Restore ist TimescaleDB-korrekt (Recherche-Fund, im alten restore-test fehlend):
CREATE EXTENSION → timescaledb_pre_restore() → pg_restore OHNE -j → post_restore.
Paralleles -j würde die TSDB-Kataloge korrumpieren. Lokal verifiziert: Hypertable
überlebt den Restore.

age statt GPG: X25519/ChaCha20-Poly1305, asymmetrisch — Public-Key am Server,
Private-Key offsite → kompromittierter Server kann eigene Backups nicht lesen.

Alles über make: `make up` rollt den Container aus (Daten/Volumes bleiben),
`make backup` (One-Off), `make restore-test` (Key-Pfad aus env/.env.backup).

- Makefile: backup-Target, restore-test auf age+pre/post_restore umgebaut,
  gen-secrets um DB_APP_PASSWORD + age-keygen-Hinweis, secure-env für .env.backup
- env/.env.backup.example (AGE_RECIPIENT, BACKUP_*, RCLONE_REMOTE, AGE_IDENTITY)
- CI backup-smoke (Required, in ci-ok needs): baut Image, beweist
  pg_dump→age→decrypt→pre/post_restore→pg_restore gegen seed-Test-DB
- Doku-Konsolidierung: deployment-public + production-hardening hatten zwei
  divergierende Inline-Backup-Skripte → beide verweisen jetzt auf backup/.
  security.md §9.6 (age- vs FERNET-Key-Rotation), configuration/setup/development/CLAUDE

DB-Zugriff via Admin-Rolle (pg_dump braucht Voll-Read); dedizierte
pg_read_all_data-Rolle als Least-Privilege-Härtung dokumentiert vertagt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rdown

Zwei Fehler im neuen backup-smoke-Job behoben:
- `up --wait db-test flyway-test` wartete nicht auf den Abschluss des
  Migrations-Containers → Seed lief vor den Migrationen ("relation users does
  not exist"). Jetzt: db-test mit --wait hoch, dann `run --rm flyway-test`
  (blockiert bis migrate fertig).
- `down -v` parst die ganze docker-compose.test.yml inkl. api-test, das
  env/.env.api braucht → fehlte. Jetzt alle env-Beispiele kopiert.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@gerfru gerfru merged commit 57b29a8 into main Jun 9, 2026
12 checks passed
@gerfru gerfru deleted the feat/wave16-pr-b branch June 9, 2026 08:50
gerfru added a commit that referenced this pull request Jun 12, 2026
…ktes Restore (Wave 16 PR-B) (#171)

* feat(ops): verschlüsselter Backup-Container (age) + TimescaleDB-korrektes Restore

Neuer backup-Service im Stack (backup/: postgres:16-alpine + age + rclone,
non-root postgres-User). Verbindet sich übers interne Netz mit db (KEIN
Docker-Socket, keine zusätzliche Angriffsfläche) und sichert täglich:
pg_dump -Fc → age-Verschlüsselung (streamed) → Retention → optional rclone.

Scheduler: schlanker non-root Shell-Loop statt cron-Binary (vermeidet per-arch
supercronic-Download = Supply-Chain-Risiko; busybox-crond ist nicht sauber
non-root). entrypoint.sh exect Args (One-Off via `make backup`) oder läuft den
Tages-Loop (Service-Modus).

Restore ist TimescaleDB-korrekt (Recherche-Fund, im alten restore-test fehlend):
CREATE EXTENSION → timescaledb_pre_restore() → pg_restore OHNE -j → post_restore.
Paralleles -j würde die TSDB-Kataloge korrumpieren. Lokal verifiziert: Hypertable
überlebt den Restore.

age statt GPG: X25519/ChaCha20-Poly1305, asymmetrisch — Public-Key am Server,
Private-Key offsite → kompromittierter Server kann eigene Backups nicht lesen.

Alles über make: `make up` rollt den Container aus (Daten/Volumes bleiben),
`make backup` (One-Off), `make restore-test` (Key-Pfad aus env/.env.backup).

- Makefile: backup-Target, restore-test auf age+pre/post_restore umgebaut,
  gen-secrets um DB_APP_PASSWORD + age-keygen-Hinweis, secure-env für .env.backup
- env/.env.backup.example (AGE_RECIPIENT, BACKUP_*, RCLONE_REMOTE, AGE_IDENTITY)
- CI backup-smoke (Required, in ci-ok needs): baut Image, beweist
  pg_dump→age→decrypt→pre/post_restore→pg_restore gegen seed-Test-DB
- Doku-Konsolidierung: deployment-public + production-hardening hatten zwei
  divergierende Inline-Backup-Skripte → beide verweisen jetzt auf backup/.
  security.md §9.6 (age- vs FERNET-Key-Rotation), configuration/setup/development/CLAUDE

DB-Zugriff via Admin-Rolle (pg_dump braucht Voll-Read); dedizierte
pg_read_all_data-Rolle als Least-Privilege-Härtung dokumentiert vertagt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(ci): backup-smoke — Migrationen synchron + alle env-Files für teardown

Zwei Fehler im neuen backup-smoke-Job behoben:
- `up --wait db-test flyway-test` wartete nicht auf den Abschluss des
  Migrations-Containers → Seed lief vor den Migrationen ("relation users does
  not exist"). Jetzt: db-test mit --wait hoch, dann `run --rm flyway-test`
  (blockiert bis migrate fertig).
- `down -v` parst die ganze docker-compose.test.yml inkl. api-test, das
  env/.env.api braucht → fehlte. Jetzt alle env-Beispiele kopiert.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant