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>
This commit is contained in:
@@ -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)
|
||||
Reference in New Issue
Block a user