Files
archivmail/features/PROJ-16-ldap-active-directory.md
T
sysops 22dfe1300b docs(PROJ-16/21): LDAP und Multi-Tenancy aufeinander abgestimmt
- 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 <noreply@anthropic.com>
2026-03-17 14:10:48 +01:00

7.1 KiB

id, title, status, priority, created
id title status priority created
PROJ-16 LDAP / Active Directory Anbindung In Progress P1 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)

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:

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:

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