--- 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)