Files
tessera-ctl/.planning/todos/completed/2026-08-11-ldap-bindpasswort-unverschluesselt.md
T
schalli e1f799513e
Tessera CI/CD / Lint & Type Check (push) Successful in 52s
Tessera CI/CD / Tests (push) Successful in 51s
Tessera CI/CD / Build & Publish Images (push) Successful in 25s
docs: close the LDAP bind password backlog item
Notes what was deliberately left out: extracting and renaming the crypto
service out of calendar/ touches five modules and belongs in its own change,
so the existing provider is reused as-is and the naming smell is recorded in
LdapModule instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 14:11:11 +02:00

68 lines
3.2 KiB
Markdown

---
created: 2026-08-11
resolved: 2026-08-11
resolution: |
Behoben in Commit 4f687ea. Spalte heisst jetzt encryptedBindPassword,
Verschluesselung ueber den bestehenden CalendarCryptoService, Entschluesselung
zentral in getConfig()/getAllActiveConfigs(), Bootstrap-Backfill fuer
Altbestand, 9 neue Tests. Punkt 1 der Loesung (Dienst aus calendar/
herausheben und umbenennen) bewusst NICHT mitgemacht — beruehrt fuenf Module
und gehoert in eine eigene Aenderung; im LdapModule als Notiz vermerkt.
title: LDAP-Bind-Passwort liegt im Klartext in der DB, obwohl der Verschluesselungsdienst schon existiert
area: ldap
severity: major
trigger: vor dem Verkauf an externe Kunden; unabhaengig davon jederzeit machbar, da klein und ohne Produktfragen
files:
- apps/api/prisma/schema.prisma:72
- apps/api/src/ldap/ldap-config.service.ts:38
- apps/api/src/calendar/crypto.service.ts
---
## Problem
`LdapConfig.bindPassword` wird so gespeichert, wie es aus dem Formular kommt —
`ldap-config.service.ts:38` und `:76` schreiben den Wert unveraendert in die
Datenbank. Wer Lesezugriff auf die Datenbank hat, hat damit die Zugangsdaten des
AD-Dienstkontos.
Hashen ist hier KEIN gangbarer Weg: Tessera muss sich mit diesem Passwort am
Domain Controller anmelden, braucht es also im Original. Der richtige Weg ist
symmetrische Verschluesselung mit einem Schluessel ausserhalb der Datenbank.
Genau das macht das Projekt an drei anderen Stellen bereits, mit AES-256-GCM:
| Feld | Modell |
|---|---|
| `encryptedPassword` | `CalendarSource` (schema.prisma:333) |
| `encryptedInboxCreds` | `DkvModuleConfig` (:264) |
| `encryptedInboxCreds` | `TenderEmailConfig` (:289) |
| `encryptedPassword` | `SmtpConfig`-naher Bereich (:237) |
Der Dienst dafuer existiert: `calendar/crypto.service.ts`, AES-256-GCM, Schluessel
aus der Umgebungsvariable `CALENDAR_ENCRYPTION_KEY`. Die LDAP-Konfiguration nutzt
ihn schlicht nicht — LDAP war frueher dran als der Verschluesselungsdienst und
wurde nie nachgezogen.
Was bereits richtig ist: die API gibt das Passwort nie heraus, `ldap.controller.ts`
maskiert es an allen vier Stellen zu `********`.
Aufgefallen am 2026-08-11 waehrend der Phase-16-Tests, als die LDAP-Konfiguration
fuer eine read-only-Pruefung aus der Datenbank gelesen wurde.
## Solution
1. Den bestehenden Verschluesselungsdienst aus `calendar/` heraushebeln, damit er
nicht laenger nach einem Modul benannt ist, das ihn zufaellig zuerst brauchte —
samt Schluesselvariable. Alternativ zunaechst unveraendert wiederverwenden und
die Umbenennung als eigenen Schritt fuehren; das ist die kleinere Aenderung.
2. `LdapConfigService.create/update` verschluesselt schreiben, alle Lesestellen
entschluesseln. Lesestellen sind `ldap.controller.ts` (Zeilen 136, 177, 210,
244, 277, 313) und der Sync selbst.
3. Migration, die den vorhandenen Klartext-Eintrag einmalig verschluesselt.
Muster vorhanden — dieselbe Form wie die bestehenden Backfill-Migrationen.
4. Feld umbenennen (`bindPassword` → `encryptedBindPassword`), damit am Schema
ablesbar ist, was drinsteht, wie bei den anderen vier Feldern.
Keine offenen Produktfragen — reine Umsetzung, entlang eines im Projekt bereits
etablierten Musters.