Server-Side Encryption at Rest (PostgreSQL pgcrypto / TDE) #98

Closed
opened 2026-05-17 18:41:01 +00:00 by jreinemann-euris · 1 comment
jreinemann-euris commented 2026-05-17 18:41:01 +00:00 (Migrated from github.com)

Ziel

Inventardaten in der PostgreSQL-Datenbank gegen unbefugten Direktzugriff schützen (DB-Dump, Backup-Leak, Disk-Diebstahl). Dies ist eine Defense-in-Depth-Maßnahme und schützt nicht gegen volle Server-Kompromittierung (dafür braucht es E2EE).

Kontext

Aktuell liegen alle Inventardaten (Items, Kategorien, Orte, Einstellungen, Nachrichten) als Klartext in PostgreSQL. Ein Angreifer mit Zugriff auf die DB-Dateien, einen Backup-Dump oder eine SQL-Injection kann alle Daten lesen.

Entscheidung: Key-Storage

Gewählt: Option 1 — .env-Datei auf dem VPS

  • Key wird in /opt/bollwerk/.env gespeichert (chmod 600, nur root-lesbar)
  • docker-compose.yml referenziert die .env-Datei via env_file
  • .env ist bereits in .gitignore → Key landet nie im Git
  • Bei Deployment: .env-Datei manuell auf dem VPS anlegen/aktualisieren

Ansatz: pgcrypto + Column-Level Encryption

Warum pgcrypto statt TDE?

  • PostgreSQL TDE ist erst ab PG 17 experimental und erfordert ein Custom-Build des Images
  • pgcrypto ist eine offizielle PostgreSQL-Extension, in postgres:17-alpine bereits enthalten
  • Column-Level-Encryption erlaubt gezielte Verschlüsselung sensibler Felder

Zu verschlüsselnde Felder

Tabelle Feld(er) Begründung
items name, notes Inventar-Inhalte
messages body Chat-Nachrichten
settings value Persönliche Einstellungen
categories name Benutzer-definierte Kategorien
locations name Benutzer-definierte Orte

Umsetzung

  1. Extension aktivieren: CREATE EXTENSION IF NOT EXISTS pgcrypto;
  2. Encryption Key: Symmetrischer AES-256-Key, gespeichert als Env-Variable BOLLWERK_DB_ENCRYPTION_KEY
  3. Encrypt: pgp_sym_encrypt(plaintext, key) beim Schreiben (im Ktor Repository Layer)
  4. Decrypt: pgp_sym_decrypt(ciphertext, key) beim Lesen
  5. Migration: Bestehende Klartext-Daten einmalig verschlüsseln
  6. Backup: Backups enthalten nur Ciphertext (gut!)

Key-Management

  • Key wird als Env-Variable in .env-Datei auf dem VPS gespeichert
  • .env-Datei: chmod 600, nur root-lesbar
  • Key darf nicht im Git-Repository liegen
  • Key muss beim Container-Start verfügbar sein
  • Key-Generierung: openssl rand -base64 32 (einmalig bei Ersteinrichtung)

Deployment-Änderungen

  • docker-compose.yml: env_file: .env hinzufügen + BOLLWERK_DB_ENCRYPTION_KEY nutzen
  • .env-Template-Datei: .env.example im Repo (ohne echten Key)
  • DatabaseFactory.kt: Encrypt/Decrypt-Wrapper für Exposed-Queries
  • DB-Migration: Einmaliges Verschlüsseln bestehender Daten
  • Spaltentyp-Änderung: Betroffene TEXT-Spalten werden zu BYTEA
  • Backup-Container: Keine Änderung nötig (Backups enthalten automatisch Ciphertext)

Akzeptanzkriterien

  • pgcrypto Extension wird beim DB-Start aktiviert
  • Sensible Spalten werden verschlüsselt gespeichert
  • Lesen/Schreiben funktioniert transparent für die App
  • Bestehende Daten werden migriert
  • Key liegt in .env auf dem VPS, nicht im Git
  • .env.example als Template im Repo
  • Sync (Push/Pull) funktioniert weiterhin
  • Integration-Tests laufen grün

Einschränkungen

  • Kein Schutz gegen Server-Kompromittierung — der Key liegt im App-Prozess-Speicher
  • Kein Ersatz für E2EE — ergänzende Maßnahme
  • Performance: pgcrypto-Operationen kosten ~1-5ms pro Encrypt/Decrypt — bei Batch-Sync relevant
  • Suche: Verschlüsselte Spalten können nicht mit SQL LIKE durchsucht werden
## Ziel Inventardaten in der PostgreSQL-Datenbank gegen unbefugten Direktzugriff schützen (DB-Dump, Backup-Leak, Disk-Diebstahl). Dies ist eine **Defense-in-Depth-Maßnahme** und schützt **nicht** gegen volle Server-Kompromittierung (dafür braucht es E2EE). ## Kontext Aktuell liegen alle Inventardaten (Items, Kategorien, Orte, Einstellungen, Nachrichten) als **Klartext** in PostgreSQL. Ein Angreifer mit Zugriff auf die DB-Dateien, einen Backup-Dump oder eine SQL-Injection kann alle Daten lesen. ## Entscheidung: Key-Storage **Gewählt: Option 1 — `.env`-Datei auf dem VPS** - Key wird in `/opt/bollwerk/.env` gespeichert (`chmod 600`, nur root-lesbar) - `docker-compose.yml` referenziert die `.env`-Datei via `env_file` - `.env` ist bereits in `.gitignore` → Key landet nie im Git - Bei Deployment: `.env`-Datei manuell auf dem VPS anlegen/aktualisieren ## Ansatz: pgcrypto + Column-Level Encryption ### Warum pgcrypto statt TDE? - PostgreSQL TDE ist erst ab PG 17 experimental und erfordert ein Custom-Build des Images - `pgcrypto` ist eine offizielle PostgreSQL-Extension, in `postgres:17-alpine` bereits enthalten - Column-Level-Encryption erlaubt gezielte Verschlüsselung sensibler Felder ### Zu verschlüsselnde Felder | Tabelle | Feld(er) | Begründung | |---------|----------|------------| | `items` | `name`, `notes` | Inventar-Inhalte | | `messages` | `body` | Chat-Nachrichten | | `settings` | `value` | Persönliche Einstellungen | | `categories` | `name` | Benutzer-definierte Kategorien | | `locations` | `name` | Benutzer-definierte Orte | ### Umsetzung 1. **Extension aktivieren:** `CREATE EXTENSION IF NOT EXISTS pgcrypto;` 2. **Encryption Key:** Symmetrischer AES-256-Key, gespeichert als Env-Variable `BOLLWERK_DB_ENCRYPTION_KEY` 3. **Encrypt:** `pgp_sym_encrypt(plaintext, key)` beim Schreiben (im Ktor Repository Layer) 4. **Decrypt:** `pgp_sym_decrypt(ciphertext, key)` beim Lesen 5. **Migration:** Bestehende Klartext-Daten einmalig verschlüsseln 6. **Backup:** Backups enthalten nur Ciphertext (gut!) ### Key-Management - Key wird als **Env-Variable** in `.env`-Datei auf dem VPS gespeichert - `.env`-Datei: `chmod 600`, nur root-lesbar - Key darf **nicht** im Git-Repository liegen - Key muss beim Container-Start verfügbar sein - Key-Generierung: `openssl rand -base64 32` (einmalig bei Ersteinrichtung) ## Deployment-Änderungen - `docker-compose.yml`: `env_file: .env` hinzufügen + `BOLLWERK_DB_ENCRYPTION_KEY` nutzen - `.env`-Template-Datei: `.env.example` im Repo (ohne echten Key) - `DatabaseFactory.kt`: Encrypt/Decrypt-Wrapper für Exposed-Queries - DB-Migration: Einmaliges Verschlüsseln bestehender Daten - Spaltentyp-Änderung: Betroffene TEXT-Spalten werden zu BYTEA - Backup-Container: Keine Änderung nötig (Backups enthalten automatisch Ciphertext) ## Akzeptanzkriterien - [ ] pgcrypto Extension wird beim DB-Start aktiviert - [ ] Sensible Spalten werden verschlüsselt gespeichert - [ ] Lesen/Schreiben funktioniert transparent für die App - [ ] Bestehende Daten werden migriert - [ ] Key liegt in `.env` auf dem VPS, nicht im Git - [ ] `.env.example` als Template im Repo - [ ] Sync (Push/Pull) funktioniert weiterhin - [ ] Integration-Tests laufen grün ## Einschränkungen - **Kein Schutz gegen Server-Kompromittierung** — der Key liegt im App-Prozess-Speicher - **Kein Ersatz für E2EE** — ergänzende Maßnahme - **Performance:** pgcrypto-Operationen kosten ~1-5ms pro Encrypt/Decrypt — bei Batch-Sync relevant - **Suche:** Verschlüsselte Spalten können nicht mit SQL `LIKE` durchsucht werden
jreinemann-euris commented 2026-05-17 20:17:45 +00:00 (Migrated from github.com)

Abgeschlossen (2026-05-17)

## Abgeschlossen (2026-05-17)
Sign in to join this conversation.
No description provided.