From 22dfe1300b52e85268913af182f2f5473615588b Mon Sep 17 00:00:00 2001 From: sysops Date: Tue, 17 Mar 2026 14:10:48 +0100 Subject: [PATCH] docs(PROJ-16/21): LDAP und Multi-Tenancy aufeinander abgestimmt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PROJ-16: Abhängigkeit zu PROJ-21 dokumentiert, Phase A (config.yml) / Phase B (pro-Mandant DB) getrennt, Rolle "admin" → "domain_admin" in group_mappings, UpsertLDAPUser erhält tenant_id in Phase B, neue /api/tenant/ldap Endpunkte - PROJ-21: tenant_ldap-Tabelle in Phase 1 ergänzt, Phasen-Abhängigkeit zu PROJ-16 explizit, LDAP in Zugriffsmatrix aufgenommen Co-Authored-By: Claude Sonnet 4.6 --- features/PROJ-16-ldap-active-directory.md | 175 ++++++++++++++++++++++ features/PROJ-21-multi-tenancy.md | 35 ++++- 2 files changed, 206 insertions(+), 4 deletions(-) create mode 100644 features/PROJ-16-ldap-active-directory.md diff --git a/features/PROJ-16-ldap-active-directory.md b/features/PROJ-16-ldap-active-directory.md new file mode 100644 index 0000000..30067ba --- /dev/null +++ b/features/PROJ-16-ldap-active-directory.md @@ -0,0 +1,175 @@ +--- +id: PROJ-16 +title: LDAP / Active Directory Anbindung +status: In Progress +priority: P1 +created: 2026-03-13 +--- + +# PROJ-16 — LDAP / Active Directory Anbindung + +## Ziel +Authentifizierung gegen einen LDAP-Server (OpenLDAP, Microsoft Active Directory, Samba AD). +Lokale Accounts bleiben weiterhin nutzbar. LDAP-User werden beim ersten Login automatisch +in der Datenbank angelegt (`source: ldap`) und bei jedem Login synchronisiert. + +## Abhängigkeit zu PROJ-21 (Multi-Tenancy) + +> **Wichtig:** PROJ-16 muss mit PROJ-21 abgestimmt werden. Im Multi-Tenant-Betrieb hat +> jeder Mandant seinen eigenen LDAP-Server. Die Umsetzung berücksichtigt daher zwei Modi: +> +> - **Single-Tenant (Kompatibilitätsmodus):** LDAP global in `config.yml` — wie unten beschrieben +> - **Multi-Tenant:** LDAP-Konfiguration pro Mandant in der Datenbank (`tenant_ldap`-Tabelle) +> +> PROJ-16 wird in zwei Phasen umgesetzt: +> - **Phase A** (unabhängig): Single-Tenant-LDAP via `config.yml` — implementierbar vor PROJ-21 +> - **Phase B** (nach PROJ-21 Phase 2): Pro-Mandant-LDAP via DB, `UpsertLDAPUser` mit `tenant_id` + +## User Stories + +- **Als Admin** möchte ich LDAP in `config.yml` konfigurieren, damit Mitarbeiter ihre + bestehenden Windows/AD-Zugangsdaten nutzen können. +- **Als Endnutzer** möchte ich mich mit meinem AD-Passwort anmelden, ohne einen separaten + archivmail-Account zu benötigen. +- **Als Domain-Admin** (Multi-Tenant) möchte ich den LDAP-Server meines Mandanten selbst + konfigurieren, ohne Zugriff auf `config.yml` zu benötigen. +- **Als Admin** möchte ich LDAP-User einer Rolle (user/auditor/domain_admin) zuweisen können, + entweder per fester Zuordnung oder über AD-Gruppen. +- **Als Admin** möchte ich LDAP deaktivieren können, ohne den restlichen Betrieb zu stören. + +## Akzeptanzkriterien + +- [ ] Login mit LDAP-Credentials funktioniert wenn `ldap.enabled: true` +- [ ] Lokale Accounts funktionieren weiterhin (Fallback wenn LDAP fehlschlägt oder deaktiviert) +- [ ] LDAP-User werden beim Login automatisch via `UpsertLDAPUser` angelegt/aktualisiert +- [ ] Rollen-Mapping via AD-Gruppen konfigurierbar (optional, Fallback: default_role); Rollen: `user`, `auditor`, `domain_admin` (nicht `superadmin`) +- [ ] STARTTLS und LDAPS (Port 636) werden unterstützt +- [ ] Bind-User (Service Account) für AD-Suche konfigurierbar +- [ ] Fehlermeldung bei falschem Passwort ist identisch zu lokalem Login (kein Info-Leak) +- [ ] LDAP-Fehler landen im Audit-Log +- [ ] Konfigurierbar per `config.yml` Abschnitt `ldap:` + +## Konfigurationsformat (`config.yml`) + +```yaml +ldap: + enabled: true + url: "ldap://192.168.1.10:389" # oder ldaps://... + bind_dn: "CN=archivmail-svc,OU=ServiceAccounts,DC=corp,DC=local" + bind_password: "geheim" + base_dn: "OU=Users,DC=corp,DC=local" + user_filter: "(sAMAccountName=%s)" # %s wird durch eingegebenen Username ersetzt + tls: false # STARTTLS + tls_skip_verify: false + default_role: "user" # Rolle für neue LDAP-User + group_mappings: # optional: AD-Gruppe → archivmail-Rolle + - group_dn: "CN=archivmail-admins,OU=Groups,DC=corp,DC=local" + role: "domain_admin" # PROJ-21: "admin" → "domain_admin" + - group_dn: "CN=archivmail-auditors,OU=Groups,DC=corp,DC=local" + role: "auditor" + # Multi-Tenant (PROJ-21 Phase B): default_tenant_id weist LDAP-User einem Mandanten zu. + # Im Single-Tenant-Betrieb weglassen oder auf den Default-Mandanten setzen. + # default_tenant_id: 1 +``` + +### Pro-Mandant-LDAP (Multi-Tenant, Phase B) + +Nach PROJ-21 Phase 2 wird LDAP zusätzlich pro Mandant in der DB konfigurierbar: + +```sql +CREATE TABLE tenant_ldap ( + tenant_id BIGINT PRIMARY KEY REFERENCES tenants(id) ON DELETE CASCADE, + enabled BOOLEAN NOT NULL DEFAULT false, + url TEXT NOT NULL, + bind_dn TEXT NOT NULL, + bind_password TEXT NOT NULL, -- verschlüsselt (AES-256-GCM wie IMAP-Passwörter) + base_dn TEXT NOT NULL, + user_filter TEXT NOT NULL DEFAULT '(sAMAccountName=%s)', + tls BOOLEAN NOT NULL DEFAULT false, + tls_skip_verify BOOLEAN NOT NULL DEFAULT false, + default_role VARCHAR(20) NOT NULL DEFAULT 'user', + group_mappings JSONB +); +``` + +API-Endpunkte für Domain-Admins (nach PROJ-21): +``` +GET /api/tenant/ldap Aktuelle LDAP-Konfiguration des eigenen Mandanten +PUT /api/tenant/ldap LDAP konfigurieren / aktualisieren +DELETE /api/tenant/ldap LDAP deaktivieren +POST /api/tenant/ldap/test Verbindung testen +``` + +## Technische Umsetzung + +### Neues Paket: `internal/ldapauth` + +``` +internal/ldapauth/ + ldap.go — Client, Bind, Search, Authenticate + ldap_test.go — Tests mit Mock-LDAP +``` + +**Abhängigkeit:** `github.com/go-ldap/ldap/v3` + +### Ablauf Login mit LDAP (Phase A — Single-Tenant) + +1. `auth.Manager.Login(username, password)` prüft zuerst lokale DB +2. Wenn lokaler User nicht gefunden UND LDAP aktiviert → LDAP-Auth versuchen +3. LDAP-Bind mit Service Account → User-DN per `user_filter` suchen +4. User-Bind mit gefundener DN + eingegebenem Passwort +5. Optional: Gruppen-Mitgliedschaft abfragen → Rolle bestimmen +6. `userstore.UpsertLDAPUser(username, email, role)` aufrufen +7. JWT-Token wie bei lokalem Login ausstellen + +### Ablauf Login mit LDAP (Phase B — Multi-Tenant, nach PROJ-21) + +1. Username enthält Domain-Hinweis (`user@mustermann.de`) **oder** Mandant wird im Login-Formular gewählt +2. Domain → `tenant_domains`-Tabelle → `tenant_id` ermitteln +3. `tenant_ldap`-Konfiguration für diesen Mandanten laden (Fallback: globale `config.yml`) +4. LDAP-Auth wie Phase A, aber mit mandantenspezifischem Server +5. `userstore.UpsertLDAPUser(username, email, role, tenantID)` aufrufen — **`tenant_id` wird mitgegeben** +6. JWT enthält `tenant_id` (PROJ-21) + +### Felder aus LDAP lesen + +| LDAP-Attribut | archivmail-Feld | +|--------------|-----------------| +| `sAMAccountName` / `uid` | username | +| `mail` | email | +| `memberOf` | → Gruppen-Mapping → role | +| `displayName` | (für spätere Anzeige) | + +### API-Endpunkte + +``` +# Phase A (Single-Tenant, superadmin/admin): +GET /api/admin/ldap/test Globale LDAP-Verbindung testen + +# Phase B (Multi-Tenant, domain_admin — nach PROJ-21): +GET /api/tenant/ldap Eigene LDAP-Konfiguration abrufen +PUT /api/tenant/ldap Eigene LDAP-Konfiguration setzen +DELETE /api/tenant/ldap LDAP deaktivieren +POST /api/tenant/ldap/test Verbindung testen (ohne Speichern) +``` + +Antwort `/test`: +```json +{"ok": true, "message": "LDAP-Verbindung erfolgreich", "users_found": 42} +``` + +## Nicht in diesem Feature + +- Automatische User-Synchronisation (Bulk-Import aller AD-User) — separates Feature +- LDAP-Gruppen als Postfach-Zuweisungen +- Kerberos / SAML / OAuth2 (separate Features) +- Pro-Mandant-LDAP-UI im Frontend — wird in PROJ-21 (Phase 7) integriert + +## Dateien + +- `internal/ldapauth/ldap.go` (neu) +- `internal/auth/auth.go` (erweitert: LDAP-Fallback) +- `config/config.go` (erweitert: `LDAPConfig`) +- `cmd/archivmail/main.go` (erweitert: LDAP-Client initialisieren) +- `internal/api/server.go` (erweitert: `/api/admin/ldap/test`) +- `install.sh` (erweitert: LDAP-Kommentar in config.yml) diff --git a/features/PROJ-21-multi-tenancy.md b/features/PROJ-21-multi-tenancy.md index d02ba33..a263115 100644 --- a/features/PROJ-21-multi-tenancy.md +++ b/features/PROJ-21-multi-tenancy.md @@ -64,6 +64,27 @@ CREATE INDEX idx_emails_tenant ON emails(tenant_id); ALTER TABLE audit_log ADD COLUMN tenant_id BIGINT REFERENCES tenants(id); ``` +### Neue Tabelle: `tenant_ldap` (PROJ-16 Phase B) + +Ermöglicht pro-Mandant-LDAP-Konfiguration durch Domain-Admins ohne `config.yml`-Zugriff. +Wird in Phase 1 als leere Tabelle angelegt; befüllt durch PROJ-16 Phase B. + +```sql +CREATE TABLE tenant_ldap ( + tenant_id BIGINT PRIMARY KEY REFERENCES tenants(id) ON DELETE CASCADE, + enabled BOOLEAN NOT NULL DEFAULT false, + url TEXT NOT NULL DEFAULT '', + bind_dn TEXT NOT NULL DEFAULT '', + bind_password BYTEA, -- AES-256-GCM verschlüsselt + base_dn TEXT NOT NULL DEFAULT '', + user_filter TEXT NOT NULL DEFAULT '(sAMAccountName=%s)', + tls BOOLEAN NOT NULL DEFAULT false, + tls_skip_verify BOOLEAN NOT NULL DEFAULT false, + default_role VARCHAR(20) NOT NULL DEFAULT 'user', + group_mappings JSONB -- [{group_dn, role}, ...] +); +``` + ### Xapian-Index Jeder Mandant bekommt ein **eigenes Xapian-Verzeichnis**: @@ -164,6 +185,8 @@ JWT-Claims erweitern: | Mandanten verwalten | ✓ | — | — | — | | Eigene Nutzer verwalten | ✓ | ✓ | — | — | | Eigene Domains verwalten | ✓ | ✓ | — | — | +| LDAP konfigurieren (eigener Mandant) | ✓ | ✓ | — | — | +| LDAP konfigurieren (alle Mandanten) | ✓ | — | — | — | | E-Mails lesen (eigener Mandant) | ✓ | ✓ | ✓ | ✓ | | E-Mails anderer Mandanten | ✓ | — | — | — | | Audit-Log (eigener Mandant) | ✓ | ✓ | ✓ | — | @@ -230,15 +253,19 @@ Domain-Lookup: SELECT tenant_id FROM tenant_domains WHERE domain = 'mustermann.d | Phase | Inhalt | Abhängigkeit | |---|---|---| -| **Phase 1** | DB-Schema: `tenants`, `tenant_domains`, Migration | — | -| **Phase 2** | `userstore` + `storage` tenant-aware, JWT erweitern | Phase 1 | +| **Phase 1** | DB-Schema: `tenants`, `tenant_domains`, `tenant_ldap` (leer), Migration | — | +| **Phase 2** | `userstore` + `storage` tenant-aware, JWT erweitern, `UpsertLDAPUser` + `tenant_id` | Phase 1 | | **Phase 3** | API-Middleware + alle bestehenden Handler tenant-gefiltert | Phase 2 | | **Phase 4** | Xapian: pro-Tenant-Index, Index-Manager | Phase 2 | | **Phase 5** | SMTP: Domain → Tenant-Routing | Phase 1 | -| **Phase 6** | Neue API-Routen (Tenant-Management) | Phase 3 | -| **Phase 7** | Frontend: superadmin-UI, domain_admin-Beschränkungen | Phase 6 | +| **Phase 6** | Neue API-Routen (Tenant-Management + `/api/tenant/ldap`) | Phase 3 | +| **Phase 7** | Frontend: superadmin-UI, domain_admin-Beschränkungen, LDAP-Konfig-Tab | Phase 6 | | **Phase 8** | Migrations-Script + CLI-Befehl `archivmail migrate-tenants` | alle | +> **PROJ-16 Abhängigkeit:** +> - PROJ-16 Phase A (Single-Tenant-LDAP via `config.yml`) ist unabhängig von PROJ-21 umsetzbar. +> - PROJ-16 Phase B (Pro-Mandant-LDAP, `tenant_ldap`-Tabelle, Domain-Admin-UI) **erfordert PROJ-21 Phase 2+6** als Voraussetzung. + --- ## Offene Entscheidungen (vor Implementierung klären)